/******************************************************************************* * Copyright (c) 2012-2017 Codenvy, S.A. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Codenvy, S.A. - initial API and implementation *******************************************************************************/ package org.eclipse.che.ide.part.explorer.project; import com.google.common.base.Optional; import com.google.gwt.dom.client.Element; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.inject.Inject; import com.google.inject.Provider; import com.google.inject.Singleton; import org.eclipse.che.ide.CoreLocalizationConstant; import org.eclipse.che.ide.Resources; import org.eclipse.che.ide.actions.LinkWithEditorAction; import org.eclipse.che.ide.actions.RefreshPathAction; import org.eclipse.che.ide.api.action.ActionEvent; import org.eclipse.che.ide.api.action.ActionManager; import org.eclipse.che.ide.api.action.Presentation; import org.eclipse.che.ide.api.data.tree.HasAction; import org.eclipse.che.ide.api.data.tree.HasAttributes; import org.eclipse.che.ide.api.data.tree.Node; import org.eclipse.che.ide.api.data.tree.NodeInterceptor; import org.eclipse.che.ide.api.data.tree.settings.HasSettings; import org.eclipse.che.ide.api.parts.PerspectiveManager; import org.eclipse.che.ide.api.parts.base.BaseView; import org.eclipse.che.ide.api.parts.base.ToolButton; import org.eclipse.che.ide.api.resources.Project; import org.eclipse.che.ide.api.resources.Resource; import org.eclipse.che.ide.menu.ContextMenu; import org.eclipse.che.ide.project.node.SyntheticNode; import org.eclipse.che.ide.resources.tree.ResourceNode; import org.eclipse.che.ide.resources.tree.SkipHiddenNodesInterceptor; import org.eclipse.che.ide.FontAwesome; import org.eclipse.che.ide.ui.Tooltip; import org.eclipse.che.ide.ui.smartTree.NodeDescriptor; import org.eclipse.che.ide.ui.smartTree.NodeLoader; import org.eclipse.che.ide.ui.smartTree.NodeStorage; import org.eclipse.che.ide.ui.smartTree.NodeStorage.StoreSortInfo; import org.eclipse.che.ide.ui.smartTree.SortDir; import org.eclipse.che.ide.ui.smartTree.Tree; import org.eclipse.che.ide.ui.smartTree.TreeStyles; import org.eclipse.che.ide.ui.smartTree.event.GoIntoStateEvent; import org.eclipse.che.ide.ui.smartTree.event.GoIntoStateEvent.GoIntoStateHandler; import org.eclipse.che.ide.ui.smartTree.presentation.DefaultPresentationRenderer; import org.eclipse.che.ide.ui.status.StatusWidget; import org.eclipse.che.ide.ui.toolbar.PresentationFactory; import java.util.Comparator; import java.util.List; import java.util.Set; import static java.util.Collections.singletonList; import static org.eclipse.che.ide.project.node.SyntheticNode.CUSTOM_BACKGROUND_FILL; import static org.eclipse.che.ide.ui.menu.PositionController.HorizontalAlign.MIDDLE; import static org.eclipse.che.ide.ui.menu.PositionController.VerticalAlign.BOTTOM; import static org.eclipse.che.ide.ui.smartTree.event.GoIntoStateEvent.State.ACTIVATED; import static org.eclipse.che.ide.ui.smartTree.event.GoIntoStateEvent.State.DEACTIVATED; /** * Implementation of the {@link ProjectExplorerView}. * * @author Vlad Zhukovskiy */ @Singleton public class ProjectExplorerViewImpl extends BaseView<ProjectExplorerView.ActionDelegate> implements ProjectExplorerView, GoIntoStateHandler { private final Tree tree; private final SkipHiddenNodesInterceptor skipHiddenNodesInterceptor; private ToolButton goBackButton; private static final String GO_BACK_BUTTON_ID = "goBackButton"; private static final String COLLAPSE_ALL_BUTTON_ID = "collapseAllButton"; private static final String REFRESH_BUTTON_ID = "refreshSelectedPath"; private static final String LINK_WITH_EDITOR_ID = "linkWithEditor"; private static final String PROJECT_TREE_WIDGET_ID = "projectTree"; @Inject public ProjectExplorerViewImpl(final Resources resources, final ContextMenu contextMenu, final CoreLocalizationConstant coreLocalizationConstant, final Set<NodeInterceptor> nodeInterceptorSet, final SkipHiddenNodesInterceptor skipHiddenNodesInterceptor, final LinkWithEditorAction linkWithEditorAction, final RefreshPathAction refreshPathAction, final PresentationFactory presentationFactory, final Provider<PerspectiveManager> managerProvider, final ActionManager actionManager, final EmptyTreePanel emptyTreePanel) { super(resources); this.skipHiddenNodesInterceptor = skipHiddenNodesInterceptor; setTitle(coreLocalizationConstant.projectExplorerTitleBarText()); NodeStorage nodeStorage = new NodeStorage(); NodeLoader nodeLoader = new NodeLoader(nodeInterceptorSet); nodeLoader.getNodeInterceptors().add(skipHiddenNodesInterceptor); tree = new Tree(nodeStorage, nodeLoader, new StatusWidget<Tree>(emptyTreePanel)); tree.setContextMenuInvocationHandler(new Tree.ContextMenuInvocationHandler() { @Override public void onInvokeContextMenu(int x, int y) { contextMenu.show(x, y); } }); tree.getNodeStorage().addSortInfo(new StoreSortInfo(new NodeTypeComparator(), SortDir.ASC)); tree.getNodeStorage().addSortInfo(new StoreSortInfo(new Comparator<Node>() { @Override public int compare(Node o1, Node o2) { if (o1 instanceof ResourceNode && o2 instanceof ResourceNode) { return ((ResourceNode)o1).compareTo((ResourceNode)o2); } return 0; } }, SortDir.ASC)); if (tree.getGoInto() != null) { tree.getGoInto().addGoIntoHandler(this); } tree.setPresentationRenderer(new ProjectExplorerRenderer(tree.getTreeStyles())); tree.ensureDebugId(PROJECT_TREE_WIDGET_ID); tree.setAutoSelect(true); tree.getNodeLoader().setUseCaching(false); setContentWidget(tree); ToolButton collapseAllButton = new ToolButton(FontAwesome.COMPRESS); collapseAllButton.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { if (tree.getGoInto().isActive()) { Node lastNode = tree.getGoInto().getLastUsed(); tree.setExpanded(lastNode, false, true); return; } tree.collapseAll(); } }); Tooltip.create((elemental.dom.Element)collapseAllButton.getElement(), BOTTOM, MIDDLE, "Collapse All"); collapseAllButton.ensureDebugId(COLLAPSE_ALL_BUTTON_ID); collapseAllButton.setVisible(true); addToolButton(collapseAllButton); ToolButton linkedEditorButton = new ToolButton(FontAwesome.EXCHANGE); linkedEditorButton.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { linkWithEditorAction.actionPerformed(null); } }); Tooltip.create((elemental.dom.Element)linkedEditorButton.getElement(), BOTTOM, MIDDLE, "Link with editor"); linkedEditorButton.ensureDebugId(LINK_WITH_EDITOR_ID); linkedEditorButton.setVisible(true); addToolButton(linkedEditorButton); ToolButton refreshPathButton = new ToolButton(FontAwesome.REFRESH); refreshPathButton.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { final Presentation presentation = presentationFactory.getPresentation(refreshPathAction); final ActionEvent actionEvent = new ActionEvent(presentation, actionManager, managerProvider.get(), null); refreshPathAction.update(actionEvent); if (presentation.isEnabled() && presentation.isVisible()) { refreshPathAction.actionPerformed(actionEvent); } } }); Tooltip.create((elemental.dom.Element)refreshPathButton.getElement(), BOTTOM, MIDDLE, "Refresh selected path"); refreshPathButton.ensureDebugId(REFRESH_BUTTON_ID); refreshPathButton.setVisible(true); addToolButton(refreshPathButton); } @Override protected void focusView() { tree.setFocus(true); } @Override public Tree getTree() { return tree; } /** {@inheritDoc} */ @Override public void reloadChildren(Node parent) { reloadChildren(parent, false); } /** {@inheritDoc} */ @Override public void reloadChildren(Node parent, boolean deep) { //iterate on root nodes and call tree widget to reload their children for (Node node : parent == null ? tree.getRootNodes() : singletonList(parent)) { if (node.isLeaf()) { //just preventive actions continue; } if (tree.isExpanded(node)) { tree.getNodeLoader().loadChildren(node, deep); } } } /** {@inheritDoc} */ @Override public void reloadChildrenByType(Class<?> type) { List<Node> rootNodes = tree.getRootNodes(); for (Node rootNode : rootNodes) { List<Node> allChildren = tree.getNodeStorage().getAllChildren(rootNode); for (Node child : allChildren) { if (child.getClass().equals(type)) { NodeDescriptor nodeDescriptor = tree.getNodeDescriptor(child); if (nodeDescriptor.isLoaded()) { tree.getNodeLoader().loadChildren(child); } } } } } /** {@inheritDoc} */ @Override public void select(Node node, boolean keepExisting) { tree.getSelectionModel().select(node, keepExisting); } /** {@inheritDoc} */ @Override public void select(List<Node> nodes, boolean keepExisting) { tree.getSelectionModel().select(nodes, keepExisting); } /** {@inheritDoc} */ @Override public void showHiddenFilesForAllExpandedNodes(boolean show) { if (show) { tree.getNodeLoader().getNodeInterceptors().remove(skipHiddenNodesInterceptor); } else { tree.getNodeLoader().getNodeInterceptors().add(skipHiddenNodesInterceptor); } for (Node node : tree.getRootNodes()) { reloadChildren(node, true); } } /** {@inheritDoc} */ @Override public boolean setGoIntoModeOn(Node node) { return tree.getGoInto().activate(node); } /** {@inheritDoc} */ @Override public boolean isGoIntoActivated() { return tree.getGoInto().isActive(); } /** {@inheritDoc} */ @Override public void onGoIntoStateChanged(GoIntoStateEvent event) { if (event.getState() == ACTIVATED) { //lazy button initializing if (goBackButton == null) { initGoIntoBackButton(); return; } goBackButton.setVisible(true); } else if (event.getState() == DEACTIVATED) { goBackButton.setVisible(false); } } private void initGoIntoBackButton() { goBackButton = new ToolButton(FontAwesome.ARROW_CIRCLE_O_LEFT); goBackButton.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { tree.getGoInto().reset(); } }); goBackButton.ensureDebugId(GO_BACK_BUTTON_ID); Tooltip.create((elemental.dom.Element)goBackButton.getElement(), BOTTOM, MIDDLE, "Go Back"); addToolButton(goBackButton); } /** {@inheritDoc} */ @Override public void collapseAll() { tree.collapseAll(); } private class ProjectExplorerRenderer extends DefaultPresentationRenderer<Node> { ProjectExplorerRenderer(TreeStyles treeStyles) { super(treeStyles); } @Override public Element render(Node node, String domID, Tree.Joint joint, int depth) { Element element = super.render(node, domID, joint, depth); element.setAttribute("name", node.getName()); if (node instanceof ResourceNode) { final Resource resource = ((ResourceNode)node).getData(); element.setAttribute("path", resource.getLocation().toString()); final Optional<Project> project = resource.getRelatedProject(); if (project.isPresent()) { element.setAttribute("project", project.get().getLocation().toString()); } } if (node instanceof HasAction) { element.setAttribute("actionable", "true"); } if (node instanceof SyntheticNode<?>) { element.setAttribute("synthetic", "true"); element.setAttribute("project", ((SyntheticNode)node).getProject().toString()); } if (node instanceof HasAttributes && ((HasAttributes)node).getAttributes().containsKey(CUSTOM_BACKGROUND_FILL)) { element.getFirstChildElement().getStyle() .setBackgroundColor(((HasAttributes)node).getAttributes().get(CUSTOM_BACKGROUND_FILL).get(0)); } return element; } } }