/******************************************************************************* * 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; import com.google.gwt.core.client.Scheduler; import com.google.gwt.user.client.ui.AcceptsOneWidget; import com.google.inject.Inject; import com.google.inject.assistedinject.Assisted; import com.google.web.bindery.event.shared.EventBus; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.ide.api.constraints.Constraints; import org.eclipse.che.ide.api.editor.EditorPartPresenter; import org.eclipse.che.ide.api.event.EditorDirtyStateChangedEvent; import org.eclipse.che.ide.api.mvp.Presenter; import org.eclipse.che.ide.api.parts.PartPresenter; import org.eclipse.che.ide.api.parts.PartStack; import org.eclipse.che.ide.api.parts.PartStackStateChangedEvent; import org.eclipse.che.ide.api.parts.PartStackView; 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.BasePresenter; import org.eclipse.che.ide.menu.PartMenu; import org.eclipse.che.ide.part.widgets.TabItemFactory; import org.eclipse.che.ide.part.widgets.partbutton.PartButton; import org.eclipse.che.ide.workspace.WorkBenchPartController; import javax.validation.constraints.NotNull; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; /** * Implements "Tab-like" UI Component, that accepts PartPresenters as child elements. * <p/> * PartStack support "focus" (please don't mix with GWT Widget's Focus feature). Focused PartStack will highlight active Part, notifying * user what component is currently active. * * @author Nikolay Zamosenchuk * @author Stéphane Daviet * @author Dmitry Shnurenko * @author Valeriy Svydenko */ public class PartStackPresenter implements Presenter, PartStackView.ActionDelegate, PartButton.ActionDelegate, PartStack { /** The default size for the part. */ private static final double DEFAULT_PART_SIZE = 260; /** The minimum allowable size for the part. */ private static final int MIN_PART_SIZE = 100; private final WorkBenchPartController workBenchPartController; private final PartsComparator partsComparator; private final Map<PartPresenter, Constraints> constraints; private final PartStackEventHandler partStackHandler; private final EventBus eventBus; private final PartMenu partMenu; protected final Map<TabItem, PartPresenter> parts; protected final TabItemFactory tabItemFactory; protected final PartStackView view; protected final PropertyListener propertyListener; protected PartPresenter activePart; protected TabItem activeTab; protected double currentSize; private State state = State.MINIMIZED; private ActionDelegate delegate; @Inject public PartStackPresenter(final EventBus eventBus, final PartMenu partMenu, PartStackEventHandler partStackEventHandler, TabItemFactory tabItemFactory, PartsComparator partsComparator, @Assisted final PartStackView view, @Assisted @NotNull WorkBenchPartController workBenchPartController) { this.view = view; this.view.setDelegate(this); this.eventBus = eventBus; this.partMenu = partMenu; this.partStackHandler = partStackEventHandler; this.workBenchPartController = workBenchPartController; this.tabItemFactory = tabItemFactory; this.partsComparator = partsComparator; this.parts = new HashMap<>(); this.constraints = new LinkedHashMap<>(); this.propertyListener = new PropertyListener() { @Override public void propertyChanged(PartPresenter source, int propId) { if (PartPresenter.TITLE_PROPERTY == propId) { updatePartTab(source); } else if (EditorPartPresenter.PROP_DIRTY == propId) { eventBus.fireEvent(new EditorDirtyStateChangedEvent((EditorPartPresenter)source)); } } }; if (workBenchPartController != null) { this.workBenchPartController.setSize(DEFAULT_PART_SIZE); } currentSize = DEFAULT_PART_SIZE; } @Override public void setDelegate(ActionDelegate delegate) { this.delegate = delegate; } private void updatePartTab(@NotNull PartPresenter part) { if (!containsPart(part)) { return; } view.updateTabItem(part); } /** {@inheritDoc} */ @Override public void go(AcceptsOneWidget container) { container.setWidget(view); } /** {@inheritDoc} */ @Override public void addPart(@NotNull PartPresenter part) { addPart(part, null); } /** {@inheritDoc} */ @Override public void addPart(@NotNull PartPresenter part, @Nullable Constraints constraint) { if (containsPart(part)) { TabItem tab = getTabByPart(part); onTabClicked(tab); return; } if (part instanceof BasePresenter) { ((BasePresenter)part).setPartStack(this); } part.addPropertyListener(propertyListener); PartButton partButton = tabItemFactory.createPartButton(part.getTitle()) .setTooltip(part.getTitleToolTip()) .setIcon(part.getTitleImage()); partButton.setDelegate(this); parts.put(partButton, part); constraints.put(part, constraint); view.addTab(partButton, part); sortPartsOnView(); onRequestFocus(); } private void sortPartsOnView() { List<PartPresenter> sortedParts = new ArrayList<>(); sortedParts.addAll(parts.values()); partsComparator.setConstraints(constraints); Collections.sort(sortedParts, partsComparator); view.setTabPositions(sortedParts); } /** {@inheritDoc} */ @Override public boolean containsPart(PartPresenter part) { return parts.values().contains(part); } /** {@inheritDoc} */ @Override public PartPresenter getActivePart() { return activePart; } /** {@inheritDoc} */ @Override public void setActivePart(@NotNull PartPresenter part) { TabItem tab = getTabByPart(part); if (tab == null) { return; } activePart = part; activeTab = tab; if (state == State.MINIMIZED) { state = State.NORMAL; if (currentSize < MIN_PART_SIZE) { currentSize = DEFAULT_PART_SIZE; } workBenchPartController.setSize(currentSize); workBenchPartController.setHidden(false); } else if (state == State.COLLAPSED) { // Collapsed state means the other part stack is maximized. // Ask the delegate to restore part stacks. if (delegate != null) { delegate.onRestore(this); } } else if (state == State.NORMAL) { if (workBenchPartController.getSize() < MIN_PART_SIZE) { workBenchPartController.setSize(DEFAULT_PART_SIZE); } workBenchPartController.setHidden(false); } // Notify the part stack state has been changed. Scheduler.get().scheduleDeferred(new Scheduler.ScheduledCommand() { @Override public void execute() { eventBus.fireEvent(new PartStackStateChangedEvent(PartStackPresenter.this)); } }); selectActiveTab(tab); } @Nullable protected TabItem getTabByPart(@NotNull PartPresenter part) { for (Map.Entry<TabItem, PartPresenter> entry : parts.entrySet()) { if (part.equals(entry.getValue())) { return entry.getKey(); } } return null; } /** {@inheritDoc} */ @Override public void removePart(PartPresenter part) { parts.remove(getTabByPart(part)); view.removeTab(part); } /** {@inheritDoc} */ @Override public void openPreviousActivePart() { if (activePart == null) { return; } TabItem selectedTab = getTabByPart(activePart); if (selectedTab != null) { selectActiveTab(selectedTab); } } @Override public void updateStack() { for (PartPresenter partPresenter : parts.values()) { if (partPresenter instanceof BasePresenter) { ((BasePresenter)partPresenter).setPartStack(this); } } } @Override public List<? extends PartPresenter> getParts() { return new ArrayList<>(constraints.keySet()); } /** {@inheritDoc} */ @Override public void onRequestFocus() { partStackHandler.onRequestFocus(PartStackPresenter.this); } /** {@inheritDoc} */ @Override public void setFocus(boolean focused) { view.setFocus(focused); } @Override public void maximize() { // Update the view state. view.setMaximized(true); // Update dimensions of the part stack if it's already maximized. Used when resizing the view. if (state == State.MAXIMIZED) { workBenchPartController.maximize(); return; } // Part stack can be maximized only having NORMAL state. if (state != State.NORMAL) { return; } // Maximize and update the state. currentSize = workBenchPartController.getSize(); workBenchPartController.maximize(); state = State.MAXIMIZED; // Ask the delegate to maximize this part stack and collapse other. if (delegate != null) { delegate.onMaximize(this); } // Notify the part stack state has been changed. Scheduler.get().scheduleDeferred(new Scheduler.ScheduledCommand() { @Override public void execute() { eventBus.fireEvent(new PartStackStateChangedEvent(PartStackPresenter.this)); } }); } @Override public void collapse() { // Update the view state. view.setMaximized(false); // Part stack can be collapsed only having NORMAL state. if (state != State.NORMAL) { return; } // Collapse and update the state. currentSize = workBenchPartController.getSize(); workBenchPartController.setSize(0); workBenchPartController.setHidden(true); state = State.COLLAPSED; // Deselect the active tab. if (activeTab != null) { activeTab.unSelect(); } // Notify the part stack state has been changed. Scheduler.get().scheduleDeferred(new Scheduler.ScheduledCommand() { @Override public void execute() { eventBus.fireEvent(new PartStackStateChangedEvent(PartStackPresenter.this)); } }); } @Override public State getPartStackState() { return state; } @Override public void minimize() { // Update the view state. view.setMaximized(false); // Ask the delegate to restore pack stack if it's maximized. if (state == State.MAXIMIZED) { if (delegate != null) { delegate.onRestore(this); } } // Part stack can be minimized only having NORMAL state. if (state == State.NORMAL) { currentSize = workBenchPartController.getSize(); workBenchPartController.setSize(0); workBenchPartController.setHidden(true); state = State.MINIMIZED; } // Deselect active tab. if (activeTab != null) { activeTab.unSelect(); } activePart = null; // Notify the part stack state has been changed. Scheduler.get().scheduleDeferred(new Scheduler.ScheduledCommand() { @Override public void execute() { eventBus.fireEvent(new PartStackStateChangedEvent(PartStackPresenter.this)); } }); } @Override public void restore() { // Update the view state. view.setMaximized(false); // Don't restore part stack if it's in MINIMIZED or NORMAL state. if (state == State.MINIMIZED || state == State.NORMAL) { return; } // Restore and update the stack. State prevState = state; state = State.NORMAL; if (!parts.isEmpty()) { if (currentSize < MIN_PART_SIZE) { currentSize = DEFAULT_PART_SIZE; } workBenchPartController.setSize(currentSize); workBenchPartController.setHidden(false); } // Ask the delegate to restore part stacks if this part stack was maximized. if (prevState == State.MAXIMIZED) { if (delegate != null) { delegate.onRestore(this); } } // Select active tab. if (activeTab != null) { activeTab.select(); } // Notify the part stack state has been changed. Scheduler.get().scheduleDeferred(new Scheduler.ScheduledCommand() { @Override public void execute() { eventBus.fireEvent(new PartStackStateChangedEvent(PartStackPresenter.this)); } }); } /** {@inheritDoc} */ @Override public void onTabClicked(@NotNull TabItem selectedTab) { // Change the state to COLLAPSED for the following restoring. if (state == State.MINIMIZED) { state = State.COLLAPSED; } // Restore COLLAPSED part stack. if (state == State.COLLAPSED) { activeTab = selectedTab; if (delegate != null) { delegate.onRestore(this); } activePart = parts.get(selectedTab); activePart.onOpen(); selectActiveTab(activeTab); return; } // Minimize the part stack if user clicked on the active tab. if (selectedTab.equals(activeTab)) { if (state == State.NORMAL) { minimize(); activeTab.unSelect(); activeTab = null; activePart = null; } return; } // Change active tab. activeTab = selectedTab; activePart = parts.get(selectedTab); activePart.onOpen(); selectActiveTab(activeTab); } private void selectActiveTab(@NotNull TabItem selectedTab) { workBenchPartController.setHidden(false); PartPresenter selectedPart = parts.get(selectedTab); view.selectTab(selectedPart); } @Override public void showPartMenu(int mouseX, int mouseY) { partMenu.show(mouseX, mouseY); } /** Handles PartStack actions */ public interface PartStackEventHandler { /** PartStack is being clicked and requests Focus */ void onRequestFocus(PartStack partStack); } }