/******************************************************************************* * 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.editor; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Predicate; import com.google.gwt.core.client.Scheduler; import com.google.inject.Inject; import com.google.web.bindery.event.shared.EventBus; import com.google.web.bindery.event.shared.HandlerRegistration; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.ide.api.action.Action; 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.constraints.Constraints; import org.eclipse.che.ide.api.editor.AbstractEditorPresenter; import org.eclipse.che.ide.api.editor.EditorAgent; import org.eclipse.che.ide.api.editor.EditorPartPresenter; import org.eclipse.che.ide.api.editor.EditorWithErrors; import org.eclipse.che.ide.api.editor.EditorWithErrors.EditorState; import org.eclipse.che.ide.api.parts.EditorPartStack; import org.eclipse.che.ide.api.parts.EditorTab; import org.eclipse.che.ide.api.parts.PartPresenter; import org.eclipse.che.ide.api.parts.PartStackView.TabItem; import org.eclipse.che.ide.api.parts.PropertyListener; import org.eclipse.che.ide.api.parts.base.MaximizePartEvent; 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.VirtualFile; import org.eclipse.che.ide.menu.PartMenu; import org.eclipse.che.ide.part.PartStackPresenter; import org.eclipse.che.ide.part.PartsComparator; import org.eclipse.che.ide.part.editor.actions.CloseAllTabsPaneAction; import org.eclipse.che.ide.part.editor.actions.ClosePaneAction; import org.eclipse.che.ide.part.editor.event.CloseNonPinnedEditorsEvent; import org.eclipse.che.ide.part.editor.event.CloseNonPinnedEditorsEvent.CloseNonPinnedEditorsHandler; import org.eclipse.che.ide.part.widgets.TabItemFactory; import org.eclipse.che.ide.part.widgets.panemenu.EditorPaneMenu; import org.eclipse.che.ide.part.widgets.panemenu.EditorPaneMenuItem; import org.eclipse.che.ide.part.widgets.panemenu.EditorPaneMenuItemFactory; import org.eclipse.che.ide.resource.Path; import org.eclipse.che.ide.ui.toolbar.PresentationFactory; import javax.validation.constraints.NotNull; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.collect.Iterables.filter; import static org.eclipse.che.ide.actions.EditorActions.SPLIT_HORIZONTALLY; import static org.eclipse.che.ide.actions.EditorActions.SPLIT_VERTICALLY; import static org.eclipse.che.ide.api.editor.EditorWithErrors.EditorState.ERROR; import static org.eclipse.che.ide.api.editor.EditorWithErrors.EditorState.WARNING; import static org.eclipse.che.ide.api.resources.ResourceDelta.REMOVED; import static org.eclipse.che.ide.part.editor.actions.EditorAbstractAction.CURRENT_FILE_PROP; import static org.eclipse.che.ide.part.editor.actions.EditorAbstractAction.CURRENT_PANE_PROP; import static org.eclipse.che.ide.part.editor.actions.EditorAbstractAction.CURRENT_TAB_PROP; /** * EditorPartStackPresenter is a special PartStackPresenter that is shared among all * Perspectives and used to display Editors. * * @author Nikolay Zamosenchuk * @author Stéphane Daviet * @author Dmitry Shnurenko * @author Vlad Zhukovskyi * @author Roman Nikitenko */ public class EditorPartStackPresenter extends PartStackPresenter implements EditorPartStack, EditorTab.ActionDelegate, CloseNonPinnedEditorsHandler, ResourceChangedHandler { private final PresentationFactory presentationFactory; private final EditorPaneMenuItemFactory editorPaneMenuItemFactory; private final EventBus eventBus; private final EditorPaneMenu editorPaneMenu; private final ActionManager actionManager; private final ClosePaneAction closePaneAction; private final CloseAllTabsPaneAction closeAllTabsPaneAction; private final EditorAgent editorAgent; private final Map<EditorPaneMenuItem, TabItem> items; //this list need to save order of added parts private final LinkedList<EditorPartPresenter> partsOrder; private final LinkedList<EditorPartPresenter> closedParts; private HandlerRegistration closeNonPinnedEditorsHandler; private HandlerRegistration resourceChangeHandler; @VisibleForTesting PaneMenuActionItemHandler paneMenuActionItemHandler; @VisibleForTesting PaneMenuTabItemHandler paneMenuTabItemHandler; @Inject public EditorPartStackPresenter(EditorPartStackView view, PartMenu partMenu, PartsComparator partsComparator, EditorPaneMenuItemFactory editorPaneMenuItemFactory, PresentationFactory presentationFactory, EventBus eventBus, TabItemFactory tabItemFactory, PartStackEventHandler partStackEventHandler, EditorPaneMenu editorPaneMenu, ActionManager actionManager, ClosePaneAction closePaneAction, CloseAllTabsPaneAction closeAllTabsPaneAction, EditorAgent editorAgent) { super(eventBus, partMenu, partStackEventHandler, tabItemFactory, partsComparator, view, null); this.editorPaneMenuItemFactory = editorPaneMenuItemFactory; this.eventBus = eventBus; this.presentationFactory = presentationFactory; this.editorPaneMenu = editorPaneMenu; this.actionManager = actionManager; this.closePaneAction = closePaneAction; this.closeAllTabsPaneAction = closeAllTabsPaneAction; this.editorAgent = editorAgent; this.view.setDelegate(this); this.items = new HashMap<>(); this.partsOrder = new LinkedList<>(); this.closedParts = new LinkedList<>(); initializePaneMenu(); view.addPaneMenuButton(editorPaneMenu); } private void initializePaneMenu() { paneMenuTabItemHandler = new PaneMenuTabItemHandler(); paneMenuActionItemHandler = new PaneMenuActionItemHandler(); final EditorPaneMenuItem<Action> closePaneItemWidget = editorPaneMenuItemFactory.createMenuItem(closePaneAction); closePaneItemWidget.setDelegate(paneMenuActionItemHandler); editorPaneMenu.addItem(closePaneItemWidget); final EditorPaneMenuItem<Action> closeAllTabsItemWidget = editorPaneMenuItemFactory.createMenuItem(closeAllTabsPaneAction); closeAllTabsItemWidget.setDelegate(paneMenuActionItemHandler); editorPaneMenu.addItem(closeAllTabsItemWidget, true); final Action splitHorizontallyAction = actionManager.getAction(SPLIT_HORIZONTALLY); final EditorPaneMenuItem<Action> splitHorizontallyItemWidget = editorPaneMenuItemFactory.createMenuItem(splitHorizontallyAction); splitHorizontallyItemWidget.setDelegate(paneMenuActionItemHandler); editorPaneMenu.addItem(splitHorizontallyItemWidget); final Action splitVerticallyAction = actionManager.getAction(SPLIT_VERTICALLY); final EditorPaneMenuItem<Action> splitVerticallyItemWidget = editorPaneMenuItemFactory.createMenuItem(splitVerticallyAction); splitVerticallyItemWidget.setDelegate(paneMenuActionItemHandler); editorPaneMenu.addItem(splitVerticallyItemWidget); } @Nullable private EditorPaneMenuItem getPaneMenuItemByTab(@NotNull TabItem tabItem) { for (Entry<EditorPaneMenuItem, TabItem> entry : items.entrySet()) { if (tabItem.equals(entry.getValue())) { return entry.getKey(); } } return null; } @Override public List<EditorPartPresenter> getParts() { return new ArrayList<>(partsOrder); } /** {@inheritDoc} */ @Override public void setFocus(boolean focused) { view.setFocus(focused); } /** {@inheritDoc} */ @Override public void addPart(@NotNull PartPresenter part, Constraints constraint) { addPart(part); } /** {@inheritDoc} */ @Override public void addPart(@NotNull PartPresenter part) { checkArgument(part instanceof AbstractEditorPresenter, "Can not add part " + part.getTitle() + " to editor part stack"); EditorPartPresenter editorPart = (AbstractEditorPresenter)part; if (containsPart(editorPart)) { setActivePart(editorPart); return; } VirtualFile file = editorPart.getEditorInput().getFile(); checkArgument(file != null, "File doesn't provided"); addHandlers(); updateListClosedParts(file); editorPart.addPropertyListener(propertyListener); final EditorTab editorTab = tabItemFactory.createEditorPartButton(editorPart, this); editorPart.addPropertyListener(new PropertyListener() { @Override public void propertyChanged(PartPresenter source, int propId) { if (propId == EditorPartPresenter.PROP_INPUT && source instanceof EditorPartPresenter) { editorTab.setReadOnlyMark(((EditorPartPresenter)source).getEditorInput().getFile().isReadOnly()); } } }); editorTab.setDelegate(this); parts.put(editorTab, editorPart); partsOrder.add(editorPart); view.addTab(editorTab, editorPart); TabItem tabItem = getTabByPart(editorPart); if (tabItem != null) { final EditorPaneMenuItem<TabItem> item = editorPaneMenuItemFactory.createMenuItem(tabItem); item.setDelegate(paneMenuTabItemHandler); editorPaneMenu.addItem(item); items.put(item, tabItem); } if (editorPart instanceof EditorWithErrors) { final EditorWithErrors presenter = ((EditorWithErrors)editorPart); editorPart.addPropertyListener(new PropertyListener() { @Override public void propertyChanged(PartPresenter source, int propId) { EditorState editorState = presenter.getErrorState(); editorTab.setErrorMark(ERROR.equals(editorState)); editorTab.setWarningMark(WARNING.equals(editorState)); } }); } view.selectTab(editorPart); } private void updateListClosedParts(VirtualFile file) { if (closedParts.isEmpty()) { return; } for (EditorPartPresenter closedEditorPart : closedParts) { Path path = closedEditorPart.getEditorInput().getFile().getLocation(); if (path.equals(file.getLocation())) { closedParts.remove(closedEditorPart); return; } } } /** {@inheritDoc} */ @Override public PartPresenter getActivePart() { return activePart; } /** {@inheritDoc} */ @Override public void setActivePart(@NotNull PartPresenter part) { activePart = part; view.selectTab(part); } /** {@inheritDoc} */ @Override public void onTabClicked(@NotNull TabItem tab) { activePart = parts.get(tab); view.selectTab(parts.get(tab)); } @Override public void onTabDoubleClicked(@NotNull TabItem tab) { eventBus.fireEvent(new MaximizePartEvent(parts.get(tab))); } /** {@inheritDoc} */ @Override public void removePart(PartPresenter part) { super.removePart(part); partsOrder.remove(part); activePart = partsOrder.isEmpty() ? null : partsOrder.getLast(); if (activePart != null) { onRequestFocus(); } if (parts.isEmpty()) { removeHandlers(); } } @Override public void openPreviousActivePart() { if (activePart != null) { view.selectTab(activePart); } } /** {@inheritDoc} */ @Override public void onTabClose(@NotNull TabItem tab) { final EditorPaneMenuItem editorPaneMenuItem = getPaneMenuItemByTab(tab); editorPaneMenu.removeItem(editorPaneMenuItem); items.remove(editorPaneMenuItem); EditorPartPresenter part = ((EditorTab)tab).getRelativeEditorPart(); closedParts.add(part); } /** {@inheritDoc} */ @Override public void onCloseNonPinnedEditors(CloseNonPinnedEditorsEvent event) { EditorPartPresenter editorPart = event.getEditorTab().getRelativeEditorPart(); if (!containsPart(editorPart)) { return; } Iterable<TabItem> nonPinned = filter(parts.keySet(), new Predicate<TabItem>() { @Override public boolean apply(@Nullable TabItem input) { return input instanceof EditorTab && !((EditorTab)input).isPinned(); } }); for (final TabItem tabItem : nonPinned) { Scheduler.get().scheduleDeferred(new Scheduler.ScheduledCommand() { @Override public void execute() { editorAgent.closeEditor(((EditorTab)tabItem).getRelativeEditorPart()); } }); } } @Override public EditorPartPresenter getPartByTabId(@NotNull String tabId) { for (TabItem tab : parts.keySet()) { EditorTab currentTab = (EditorTab)tab; if (currentTab.getId().equals(tabId)) { return (EditorPartPresenter)parts.get(currentTab); } } return null; } @Nullable public EditorTab getTabByPart(@NotNull EditorPartPresenter part) { for (Map.Entry<TabItem, PartPresenter> entry : parts.entrySet()) { PartPresenter currentPart = entry.getValue(); if (part.equals(currentPart)) { return (EditorTab)entry.getKey(); } } return null; } @Nullable @Override public EditorTab getTabByPath(@NotNull Path path) { for (TabItem tab : parts.keySet()) { EditorTab editorTab = (EditorTab)tab; Path currentPath = editorTab.getFile().getLocation(); if (currentPath.equals(path)) { return editorTab; } } return null; } @Nullable public PartPresenter getPartByPath(Path path) { for (TabItem tab : parts.keySet()) { EditorTab editorTab = (EditorTab)tab; Path currentPath = editorTab.getFile().getLocation(); if (currentPath.equals(path)) { return parts.get(tab); } } return null; } @Override public EditorPartPresenter getNextFor(EditorPartPresenter editorPart) { int indexForNext = partsOrder.indexOf(editorPart) + 1; return indexForNext >= partsOrder.size() ? partsOrder.getFirst() : partsOrder.get(indexForNext); } @Override public EditorPartPresenter getPreviousFor(EditorPartPresenter editorPart) { int indexForNext = partsOrder.indexOf(editorPart) - 1; return indexForNext < 0 ? partsOrder.getLast() : partsOrder.get(indexForNext); } @Nullable @Override public EditorPartPresenter getLastClosed() { if (closedParts.isEmpty()) { return null; } return closedParts.getLast(); } @Override public void onResourceChanged(ResourceChangedEvent event) { final ResourceDelta delta = event.getDelta(); if (delta.getKind() != REMOVED) { return; } Path resourcePath = delta.getResource().getLocation(); for (EditorPartPresenter editorPart : closedParts) { Path editorPath = editorPart.getEditorInput().getFile().getLocation(); if (editorPath.equals(resourcePath)) { closedParts.remove(editorPart); return; } } } private void addHandlers() { if (closeNonPinnedEditorsHandler == null) { closeNonPinnedEditorsHandler = eventBus.addHandler(CloseNonPinnedEditorsEvent.getType(), this); } if (resourceChangeHandler == null) { resourceChangeHandler = eventBus.addHandler(ResourceChangedEvent.getType(), this); } } private void removeHandlers() { if (resourceChangeHandler != null) { resourceChangeHandler.removeHandler(); resourceChangeHandler = null; } if (closeNonPinnedEditorsHandler != null) { closeNonPinnedEditorsHandler.removeHandler(); closeNonPinnedEditorsHandler = null; } } @VisibleForTesting protected class PaneMenuActionItemHandler implements EditorPaneMenuItem.ActionDelegate<Action> { @Override public void onItemClicked(@NotNull EditorPaneMenuItem<Action> item) { editorPaneMenu.hide(); final Action action = item.getData(); final Presentation presentation = presentationFactory.getPresentation(action); presentation.putClientProperty(CURRENT_PANE_PROP, EditorPartStackPresenter.this); final PartPresenter activePart = getActivePart(); final TabItem tab = getTabByPart(activePart); if (tab != null) { final VirtualFile virtualFile = ((EditorTab)tab).getFile(); //pass into action file property and editor tab presentation.putClientProperty(CURRENT_TAB_PROP, tab); presentation.putClientProperty(CURRENT_FILE_PROP, virtualFile); } action.actionPerformed(new ActionEvent(presentation, actionManager, null)); } @Override public void onCloseButtonClicked(@NotNull EditorPaneMenuItem<Action> item) {} } @VisibleForTesting protected class PaneMenuTabItemHandler implements EditorPaneMenuItem.ActionDelegate<TabItem> { @Override public void onItemClicked(@NotNull EditorPaneMenuItem<TabItem> item) { editorPaneMenu.hide(); final TabItem tabItem = item.getData(); activePart = parts.get(tabItem); view.selectTab(activePart); } @Override public void onCloseButtonClicked(@NotNull EditorPaneMenuItem<TabItem> item) { editorPaneMenu.hide(); final TabItem tabItem = item.getData(); if (tabItem instanceof EditorTab) { EditorTab editorTab = (EditorTab)tabItem; editorAgent.closeEditor(editorTab.getRelativeEditorPart()); } } } }