/******************************************************************************* * 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.collect.Sets; import com.google.gwt.core.client.Scheduler; import com.google.gwt.user.client.ui.AcceptsOneWidget; import com.google.inject.Inject; import com.google.inject.Provider; import com.google.inject.Singleton; import com.google.web.bindery.event.shared.EventBus; import org.eclipse.che.api.core.jsonrpc.commons.RequestTransmitter; import org.eclipse.che.api.project.shared.dto.event.ProjectTreeTrackingOperationDto; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.ide.CoreLocalizationConstant; import org.eclipse.che.ide.DelayedTask; import org.eclipse.che.ide.Resources; import org.eclipse.che.ide.api.app.AppContext; import org.eclipse.che.ide.api.data.tree.Node; import org.eclipse.che.ide.api.data.tree.TreeExpander; import org.eclipse.che.ide.api.data.tree.settings.NodeSettings; import org.eclipse.che.ide.api.data.tree.settings.SettingsProvider; import org.eclipse.che.ide.api.extension.ExtensionsInitializedEvent; import org.eclipse.che.ide.api.extension.ExtensionsInitializedEvent.ExtensionsInitializedHandler; import org.eclipse.che.ide.api.mvp.View; import org.eclipse.che.ide.api.parts.PartStack; import org.eclipse.che.ide.api.parts.PartStackType; import org.eclipse.che.ide.api.parts.WorkspaceAgent; import org.eclipse.che.ide.api.parts.base.BasePresenter; import org.eclipse.che.ide.api.resources.Container; import org.eclipse.che.ide.api.resources.Resource; import org.eclipse.che.ide.api.resources.ResourceChangedEvent; import org.eclipse.che.ide.api.resources.ResourceChangedEvent.ResourceChangedHandler; import org.eclipse.che.ide.api.resources.ResourceDelta; import org.eclipse.che.ide.api.resources.marker.MarkerChangedEvent; import org.eclipse.che.ide.api.resources.marker.MarkerChangedEvent.MarkerChangedHandler; import org.eclipse.che.ide.api.selection.Selection; import org.eclipse.che.ide.api.workspace.event.WorkspaceStoppedEvent; import org.eclipse.che.ide.dto.DtoFactory; import org.eclipse.che.ide.part.explorer.project.ProjectExplorerView.ActionDelegate; import org.eclipse.che.ide.project.node.SyntheticNode; import org.eclipse.che.ide.project.node.SyntheticNodeUpdateEvent; import org.eclipse.che.ide.resource.Path; import org.eclipse.che.ide.resources.reveal.RevealResourceEvent; import org.eclipse.che.ide.resources.tree.ResourceNode; import org.eclipse.che.ide.ui.smartTree.NodeDescriptor; import org.eclipse.che.ide.ui.smartTree.Tree; import org.eclipse.che.ide.ui.smartTree.event.BeforeExpandNodeEvent; import org.eclipse.che.ide.ui.smartTree.event.CollapseNodeEvent; import org.eclipse.che.ide.ui.smartTree.event.ExpandNodeEvent; import org.eclipse.che.ide.ui.smartTree.event.PostLoadEvent; import org.eclipse.che.ide.ui.smartTree.event.SelectionChangedEvent; import org.eclipse.che.ide.ui.smartTree.event.SelectionChangedEvent.SelectionChangedHandler; import org.eclipse.che.providers.DynaObject; import org.vectomatic.dom.svg.ui.SVGResource; import javax.validation.constraints.NotNull; import java.util.HashSet; import java.util.Set; import static com.google.common.base.Preconditions.checkNotNull; import static org.eclipse.che.api.project.shared.dto.event.ProjectTreeTrackingOperationDto.Type.START; import static org.eclipse.che.api.project.shared.dto.event.ProjectTreeTrackingOperationDto.Type.STOP; import static org.eclipse.che.ide.api.resources.ResourceDelta.ADDED; import static org.eclipse.che.ide.api.resources.ResourceDelta.MOVED_FROM; import static org.eclipse.che.ide.api.resources.ResourceDelta.MOVED_TO; import static org.eclipse.che.ide.api.resources.ResourceDelta.REMOVED; import static org.eclipse.che.ide.api.resources.ResourceDelta.UPDATED; /** * Project explorer presenter. Handle basic logic to control project tree display. * * @author Vlad Zhukovskiy * @author Dmitry Shnurenko */ @Singleton @DynaObject public class ProjectExplorerPresenter extends BasePresenter implements ActionDelegate, ResourceChangedHandler, MarkerChangedHandler, SyntheticNodeUpdateEvent.SyntheticNodeUpdateHandler { private static final int PART_SIZE = 500; private final ProjectExplorerView view; private final EventBus eventBus; private final ResourceNode.NodeFactory nodeFactory; private final SettingsProvider settingsProvider; private final CoreLocalizationConstant locale; private final Resources resources; private final TreeExpander treeExpander; private final RequestTransmitter requestTransmitter; private final DtoFactory dtoFactory; private UpdateTask updateTask = new UpdateTask(); private Set<Path> expandQueue = new HashSet<>(); private boolean hiddenFilesAreShown; @Inject public ProjectExplorerPresenter(final ProjectExplorerView view, final EventBus eventBus, final CoreLocalizationConstant locale, final Resources resources, final ResourceNode.NodeFactory nodeFactory, final SettingsProvider settingsProvider, final AppContext appContext, final Provider<WorkspaceAgent> workspaceAgentProvider, final RequestTransmitter requestTransmitter, final DtoFactory dtoFactory) { this.view = view; this.eventBus = eventBus; this.nodeFactory = nodeFactory; this.settingsProvider = settingsProvider; this.locale = locale; this.resources = resources; this.requestTransmitter = requestTransmitter; this.dtoFactory = dtoFactory; this.view.setDelegate(this); eventBus.addHandler(ResourceChangedEvent.getType(), this); eventBus.addHandler(MarkerChangedEvent.getType(), this); eventBus.addHandler(SyntheticNodeUpdateEvent.getType(), this); eventBus.addHandler(WorkspaceStoppedEvent.TYPE, new WorkspaceStoppedEvent.Handler() { @Override public void onWorkspaceStopped(WorkspaceStoppedEvent event) { getTree().getNodeStorage().clear(); } }); view.getTree().getSelectionModel().addSelectionChangedHandler(new SelectionChangedHandler() { @Override public void onSelectionChanged(SelectionChangedEvent event) { setSelection(new Selection<>(event.getSelection())); } }); view.getTree().addBeforeExpandHandler(new BeforeExpandNodeEvent.BeforeExpandNodeHandler() { @Override public void onBeforeExpand(BeforeExpandNodeEvent event) { final NodeDescriptor nodeDescriptor = view.getTree().getNodeDescriptor(event.getNode()); if (event.getNode() instanceof SyntheticNode && nodeDescriptor != null && nodeDescriptor.isExpandDeep()) { event.setCancelled(true); } } }); view.getTree().getNodeLoader().addPostLoadHandler(new PostLoadEvent.PostLoadHandler() { @Override public void onPostLoad(PostLoadEvent event) { for (Node node : event.getReceivedNodes()) { if (node instanceof ResourceNode && expandQueue.remove(((ResourceNode)node).getData().getLocation())) { view.getTree().setExpanded(node, true); } } } }); treeExpander = new ProjectExplorerTreeExpander(view.getTree(), appContext); registerNative(); // when ide has already initialized, then we force set focus to the current part eventBus.addHandler(ExtensionsInitializedEvent.getType(), new ExtensionsInitializedHandler() { @Override public void onExtensionsInitialized(ExtensionsInitializedEvent event) { partStack.setActivePart(ProjectExplorerPresenter.this); } }); Scheduler.get().scheduleDeferred(new Scheduler.ScheduledCommand() { @Override public void execute() { final PartStack partStack = checkNotNull(workspaceAgentProvider.get().getPartStack(PartStackType.NAVIGATION), "Navigation part stack should not be a null"); partStack.addPart(ProjectExplorerPresenter.this); partStack.setActivePart(ProjectExplorerPresenter.this); } }); } @Inject public void initFileWatchers() { final String endpointId = "ws-agent"; final String method = "track:project-tree"; getTree().addExpandHandler(new ExpandNodeEvent.ExpandNodeHandler() { @Override public void onExpand(ExpandNodeEvent event) { Node node = event.getNode(); if (node instanceof ResourceNode) { Resource data = ((ResourceNode)node).getData(); requestTransmitter.newRequest() .endpointId(endpointId) .methodName(method) .paramsAsDto(dtoFactory.createDto(ProjectTreeTrackingOperationDto.class) .withPath(data.getLocation().toString()) .withType(START)) .sendAndSkipResult(); } } }); getTree().addCollapseHandler(new CollapseNodeEvent.CollapseNodeHandler() { @Override public void onCollapse(CollapseNodeEvent event) { Node node = event.getNode(); if (node instanceof ResourceNode) { Resource data = ((ResourceNode)node).getData(); requestTransmitter.newRequest() .endpointId(endpointId) .methodName(method) .paramsAsDto(dtoFactory.createDto(ProjectTreeTrackingOperationDto.class) .withPath(data.getLocation().toString()) .withType(STOP)) .sendAndSkipResult(); } } }); } /* Expose Project Explorer's internal API to the world, to allow automated Selenium scripts expand all projects tree. */ private native void registerNative() /*-{ var that = this; var ProjectExplorer = {}; ProjectExplorer.expandAll = $entry(function () { that.@org.eclipse.che.ide.part.explorer.project.ProjectExplorerPresenter::doExpand()(); }); ProjectExplorer.collapseAll = $entry(function () { that.@org.eclipse.che.ide.part.explorer.project.ProjectExplorerPresenter::doCollapse()(); }); ProjectExplorer.reveal = $entry(function (path) { that.@org.eclipse.che.ide.part.explorer.project.ProjectExplorerPresenter::doReveal(*)(path); }) $wnd.IDE.ProjectExplorer = ProjectExplorer; }-*/; private void doExpand() { if (treeExpander.isExpandEnabled()) { treeExpander.expandTree(); } } private void doCollapse() { if (treeExpander.isCollapseEnabled()) { treeExpander.collapseTree(); } } private void doReveal(String path) { eventBus.fireEvent(new RevealResourceEvent(Path.valueOf(path))); } @Override @SuppressWarnings("unchecked") public void onResourceChanged(ResourceChangedEvent event) { final Tree tree = view.getTree(); final ResourceDelta delta = event.getDelta(); final Resource resource = delta.getResource(); final NodeSettings nodeSettings = settingsProvider.getSettings(); // process root projects, they have only one segment in path if (resource.getLocation().segmentCount() == 1) { if (delta.getKind() == ADDED) { if ((delta.getFlags() & (MOVED_FROM | MOVED_TO)) != 0) { Node node = getNode(delta.getFromPath()); if (node != null) { boolean expanded = tree.isExpanded(node); tree.getNodeStorage().remove(node); node = nodeFactory.newContainerNode((Container)resource, nodeSettings); tree.getNodeStorage().add(node); if (expanded) { tree.setExpanded(node, true); } } } else if (getNode(resource.getLocation()) == null) { tree.getNodeStorage().add(nodeFactory.newContainerNode((Container)resource, nodeSettings)); } } else if (delta.getKind() == REMOVED) { Node node = getNode(resource.getLocation()); if (node != null) { tree.getNodeStorage().remove(node); } } else if (delta.getKind() == UPDATED) { for (Node node : tree.getNodeStorage().getAll()) { if (node instanceof ResourceNode && ((ResourceNode)node).getData().getLocation().equals(delta.getResource().getLocation())) { final String oldId = tree.getNodeStorage().getKeyProvider().getKey(node); ((ResourceNode)node).setData(delta.getResource()); tree.getNodeStorage().reIndexNode(oldId, node); tree.refresh(node); updateTask.submit(delta.getResource().getLocation()); } } } } else { if ((delta.getFlags() & (MOVED_FROM | MOVED_TO)) != 0) { final Node node = getNode(delta.getFromPath()); if (node != null && tree.isExpanded(node)) { expandQueue.add(delta.getToPath()); } } updateTask.submit(resource.getLocation()); if (delta.getFromPath() != null) { updateTask.submit(delta.getFromPath()); } } } private Node getNode(Path path) { final Tree tree = view.getTree(); for (final Node node : tree.getNodeStorage().getAll()) { if (isNodeServesLocation(node, path)) { return node; } } return null; } private boolean isNodeServesLocation(Node node, Path location) { return node instanceof ResourceNode && ((ResourceNode)node).getData().getLocation().equals(location); } @Override public void onMarkerChanged(MarkerChangedEvent event) { final Tree tree = view.getTree(); for (Node node : tree.getNodeStorage().getAll()) { if (node instanceof ResourceNode && ((ResourceNode)node).getData().getLocation().equals(event.getResource().getLocation())) { tree.refresh(node); } } } @Override public void onSyntheticNodeUpdate(SyntheticNodeUpdateEvent event) { final Tree tree = getTree(); final NodeDescriptor descriptor = tree.getNodeDescriptor(event.getNode()); if (descriptor == null) { return; } if (descriptor.isLoaded()) { tree.getNodeLoader().loadChildren(event.getNode(), false); } } public Tree getTree() { return view.getTree(); } /** {@inheritDoc} */ @Override public View getView() { return view; } /** {@inheritDoc} */ @NotNull @Override public String getTitle() { return locale.projectExplorerButtonTitle(); } /** {@inheritDoc} */ @Override public SVGResource getTitleImage() { return resources.projectExplorerPartIcon(); } /** {@inheritDoc} */ @Nullable @Override public String getTitleToolTip() { return locale.projectExplorerPartTooltip(); } /** {@inheritDoc} */ @Override public int getSize() { return PART_SIZE; } /** {@inheritDoc} */ @Override public void go(AcceptsOneWidget container) { container.setWidget(view); } /** * Activate "Go Into" mode on specified node if. * Node should support this mode. See {@link Node#supportGoInto()}. * * @param node * node which should be activated in "Go Into" mode */ @Deprecated public void goInto(Node node) { view.setGoIntoModeOn(node); } /** * Get "Go Into" state on current tree. * * @return true - if "Go Into" mode has been activated. */ @Deprecated public boolean isGoIntoActivated() { return view.isGoIntoActivated(); } /** * Collapse all non-leaf nodes. */ @Deprecated public void collapseAll() { view.collapseAll(); } /** * Configure tree to show or hide files that starts with ".", e.g. hidden files. * * @param show * true - if those files should be shown, otherwise - false */ @Deprecated public void showHiddenFiles(boolean show) { hiddenFilesAreShown = show; settingsProvider.getSettings().setShowHiddenFiles(show); view.showHiddenFilesForAllExpandedNodes(show); } /** * Retrieve status of showing hidden files. * * @return true - if hidden files are shown, otherwise - false */ @Deprecated public boolean isShowHiddenFiles() { return hiddenFilesAreShown; } private class UpdateTask extends DelayedTask { private Set<Path> toRefresh = new HashSet<>(); public void submit(Path path) { toRefresh.add(path.uptoSegment(1)); delay(500); } @Override public void onExecute() { if (view.getTree().getNodeLoader().isBusy()) { delay(500); return; } final Set<Path> updateQueue = Sets.newHashSet(toRefresh); toRefresh.clear(); for (Path path : updateQueue) { final Node node = getNode(path); if (node == null) { continue; } if (getTree().isExpanded(node)) { view.getTree().getNodeLoader().loadChildren(node, true); } } } } }