| /******************************************************************************* |
| * Copyright (c) 2011, 2018 IBM Corporation and others. |
| * 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: |
| * IBM Corporation - initial API and implementation |
| * Lars Vogel (Lars.Vogel@gmail.com) - Bug 331690 |
| * Dirk Fauth (dirk.fauth@googlemail.com) - Bug 459285 |
| * Eugen Neufeld (eneufeld@eclipsesource.com) - Bug 432466, Bug 455568 |
| ******************************************************************************/ |
| |
| package org.eclipse.e4.ui.workbench.addons.minmax; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.List; |
| import javax.inject.Inject; |
| import org.eclipse.e4.core.contexts.IEclipseContext; |
| import org.eclipse.e4.core.di.annotations.Optional; |
| import org.eclipse.e4.core.services.events.IEventBroker; |
| import org.eclipse.e4.ui.di.UIEventTopic; |
| import org.eclipse.e4.ui.internal.workbench.swt.AbstractPartRenderer; |
| import org.eclipse.e4.ui.internal.workbench.swt.AnimationEngine; |
| import org.eclipse.e4.ui.internal.workbench.swt.FaderAnimationFeedback; |
| import org.eclipse.e4.ui.model.application.MAddon; |
| import org.eclipse.e4.ui.model.application.ui.MUIElement; |
| import org.eclipse.e4.ui.model.application.ui.SideValue; |
| import org.eclipse.e4.ui.model.application.ui.advanced.MArea; |
| import org.eclipse.e4.ui.model.application.ui.advanced.MPerspective; |
| import org.eclipse.e4.ui.model.application.ui.advanced.MPerspectiveStack; |
| import org.eclipse.e4.ui.model.application.ui.advanced.MPlaceholder; |
| import org.eclipse.e4.ui.model.application.ui.basic.MPartStack; |
| import org.eclipse.e4.ui.model.application.ui.basic.MTrimBar; |
| import org.eclipse.e4.ui.model.application.ui.basic.MTrimmedWindow; |
| import org.eclipse.e4.ui.model.application.ui.basic.MWindow; |
| import org.eclipse.e4.ui.model.application.ui.menu.MToolControl; |
| import org.eclipse.e4.ui.model.application.ui.menu.impl.MenuFactoryImpl; |
| import org.eclipse.e4.ui.workbench.IPresentationEngine; |
| import org.eclipse.e4.ui.workbench.UIEvents; |
| import org.eclipse.e4.ui.workbench.UIEvents.EventTags; |
| import org.eclipse.e4.ui.workbench.modeling.EModelService; |
| import org.eclipse.e4.ui.workbench.modeling.EPartService; |
| import org.eclipse.swt.custom.CTabFolder; |
| import org.eclipse.swt.custom.CTabFolder2Adapter; |
| import org.eclipse.swt.custom.CTabFolderEvent; |
| import org.eclipse.swt.events.MouseAdapter; |
| import org.eclipse.swt.events.MouseEvent; |
| import org.eclipse.swt.events.MouseListener; |
| import org.eclipse.swt.graphics.Rectangle; |
| import org.eclipse.swt.widgets.Control; |
| import org.eclipse.swt.widgets.Shell; |
| import org.osgi.service.event.Event; |
| |
| /** |
| * Workbench addon that provides methods to minimize, maximize and restore parts in the window |
| */ |
| public class MinMaxAddon { |
| |
| private static final String MIN_MAXIMIZEABLE_CHILDREN_AREA_TAG = IPresentationEngine.MIN_MAXIMIZEABLE_CHILDREN_AREA_TAG; |
| |
| /** |
| * The identifier for the shared area in the Eclipse Platform. This value should be identical to |
| * the value defined in org.eclipse.ui.IPageLayout.ID_EDITOR_AREA. |
| */ |
| private static final String ID_EDITOR_AREA = "org.eclipse.ui.editorss"; //$NON-NLS-1$ |
| |
| private static final String GLOBAL_CACHE_ID = "Global"; //$NON-NLS-1$ |
| |
| // tags representing the min/max state (h |
| private static String MINIMIZED = IPresentationEngine.MINIMIZED; |
| private static String MAXIMIZED = IPresentationEngine.MAXIMIZED; |
| private static String MINIMIZED_BY_ZOOM = IPresentationEngine.MINIMIZED_BY_ZOOM; |
| |
| @Inject |
| IEventBroker eventBroker; |
| |
| @Inject |
| EModelService modelService; |
| |
| @Inject |
| private IEclipseContext context; |
| |
| @Inject |
| private EPartService partService; |
| |
| // Allow 'local' changes to the tags |
| private boolean ignoreTagChanges = false; |
| |
| @Inject |
| MAddon minMaxAddon; |
| |
| private CTabFolder2Adapter CTFButtonListener = new CTabFolder2Adapter() { |
| private MUIElement getElementToChange(CTabFolderEvent event) { |
| CTabFolder ctf = (CTabFolder) event.widget; |
| MUIElement element = (MUIElement) ctf.getData(AbstractPartRenderer.OWNING_ME); |
| if (element instanceof MArea) { |
| return element.getCurSharedRef(); |
| } |
| |
| MUIElement parentElement = element.getParent(); |
| while (parentElement != null && !(parentElement instanceof MArea)) { |
| parentElement = parentElement.getParent(); |
| } |
| |
| if (parentElement!=null && MinMaxAddonUtil.isMinMaxChildrenAreaWithMultipleVisibleChildren(parentElement)) { |
| return element; |
| } |
| |
| return parentElement != null ? parentElement.getCurSharedRef() : element; |
| } |
| |
| @Override |
| public void maximize(CTabFolderEvent event) { |
| setState(getElementToChange(event), MAXIMIZED); |
| } |
| |
| @Override |
| public void minimize(CTabFolderEvent event) { |
| setState(getElementToChange(event), MINIMIZED); |
| } |
| |
| @Override |
| public void restore(CTabFolderEvent event) { |
| setState(getElementToChange(event), null); |
| } |
| }; |
| |
| private MouseListener CTFDblClickListener = new MouseAdapter() { |
| @Override |
| public void mouseDown(MouseEvent e) { |
| // HACK! If this is an empty stack treat it as though it was the editor area |
| // and tear down any open trim stacks (see bug 384814) |
| CTabFolder ctf = (CTabFolder) e.widget; |
| MUIElement element = (MUIElement) ctf.getData(AbstractPartRenderer.OWNING_ME); |
| if (element instanceof MPartStack && ctf.getItemCount() == 0) { |
| MWindow window = modelService.getTopLevelWindowFor(element); |
| if (window != null) { |
| List<MToolControl> tcList = modelService.findElements(window, null, |
| MToolControl.class, null); |
| for (MToolControl tc : tcList) { |
| if (tc.getObject() instanceof TrimStack) { |
| TrimStack ts = (TrimStack) tc.getObject(); |
| ts.showStack(false); |
| } |
| } |
| } |
| } |
| } |
| |
| private MUIElement getElementToChange(MouseEvent event) { |
| CTabFolder ctf = (CTabFolder) event.widget; |
| MUIElement element = (MUIElement) ctf.getData(AbstractPartRenderer.OWNING_ME); |
| if (element instanceof MArea) { |
| // set the state on the placeholder |
| return element.getCurSharedRef(); |
| } |
| |
| MUIElement parentElement = element.getParent(); |
| while (parentElement != null && !(parentElement instanceof MArea)) { |
| parentElement = parentElement.getParent(); |
| } |
| |
| if (parentElement != null |
| && MinMaxAddonUtil.isMinMaxChildrenAreaWithMultipleVisibleChildren(parentElement)) { |
| return element; |
| } |
| |
| return parentElement != null ? parentElement.getCurSharedRef() : element; |
| } |
| |
| @Override |
| public void mouseDoubleClick(MouseEvent e) { |
| // only maximize if the primary mouse button was used |
| if (e.button == 1) { |
| CTabFolder ctf = (CTabFolder) e.widget; |
| if (!ctf.getMaximizeVisible()) { |
| return; |
| } |
| |
| // Only fire if we're in the 'tab' area |
| if (e.y > ctf.getTabHeight()) { |
| return; |
| } |
| |
| MUIElement elementToChange = getElementToChange(e); |
| if (!elementToChange.getTags().contains(MAXIMIZED)) { |
| setState(elementToChange, MAXIMIZED); |
| } else { |
| setState(elementToChange, null); |
| } |
| } |
| } |
| }; |
| |
| private void setState(MUIElement element, String state) { |
| if (MINIMIZED.equals(state)) { |
| element.getTags().remove(MAXIMIZED); |
| element.getTags().add(MINIMIZED); |
| } else if (MAXIMIZED.equals(state)) { |
| element.getTags().remove(MINIMIZED); |
| element.getTags().add(MAXIMIZED); |
| } else { |
| element.getTags().remove(MINIMIZED); |
| element.getTags().remove(MAXIMIZED); |
| } |
| |
| } |
| |
| @Inject |
| @Optional |
| private void subscribeTopicWidget(@UIEventTopic(UIEvents.UIElement.TOPIC_WIDGET) Event event) { |
| final MUIElement changedElement = (MUIElement) event.getProperty(EventTags.ELEMENT); |
| if (!(changedElement instanceof MPartStack) && !(changedElement instanceof MArea)) { |
| return; |
| } |
| |
| final CTabFolder ctf = getCTFFor(changedElement); |
| if (ctf == null) { |
| return; |
| } |
| |
| MUIElement stateElement = changedElement; |
| if (changedElement instanceof MPartStack) { |
| MPartStack stack = (MPartStack) changedElement; |
| MArea area = MinMaxAddonUtil.getAreaFor(stack); |
| if (area != null && !(area.getWidget() instanceof CTabFolder)) { |
| stateElement = area.getCurSharedRef(); |
| } |
| } else if (changedElement instanceof MArea) { |
| stateElement = changedElement.getCurSharedRef(); |
| } |
| |
| adjustCTFButtons(stateElement); |
| |
| ctf.removeCTabFolder2Listener(CTFButtonListener); // Prevent multiple instances |
| ctf.addCTabFolder2Listener(CTFButtonListener); |
| |
| ctf.removeMouseListener(CTFDblClickListener); // Prevent multiple instances |
| ctf.addMouseListener(CTFDblClickListener); |
| } |
| |
| /** |
| * Handles removals from the perspective |
| * |
| * @param event |
| */ |
| |
| @Inject |
| @Optional |
| private void subscribeTopicChildren( |
| @UIEventTopic(UIEvents.ElementContainer.TOPIC_CHILDREN) Event event) { |
| final MUIElement changedElement = (MUIElement) event.getProperty(EventTags.ELEMENT); |
| MWindow window = modelService.getTopLevelWindowFor(changedElement); |
| |
| // this method is intended to update the minimized stacks in a trim |
| // if the removed element is no perspective and the top level window |
| // is not a trimmed window, we don't need to do anything here |
| if (!(changedElement instanceof MPerspectiveStack) || window == null |
| || !(window instanceof MTrimmedWindow)) { |
| return; |
| } |
| |
| if (UIEvents.isREMOVE(event)) { |
| for (Object removedElement : UIEvents.asIterable(event, UIEvents.EventTags.OLD_VALUE)) { |
| MUIElement removed = (MUIElement) removedElement; |
| String perspectiveId = removed.getElementId(); |
| |
| MTrimBar bar = modelService.getTrim((MTrimmedWindow) window, SideValue.TOP); |
| |
| // gather up any minimized stacks for this perspective... |
| List<MToolControl> toRemove = new ArrayList<>(); |
| for (MUIElement child : bar.getChildren()) { |
| String trimElementId = child.getElementId(); |
| if (child instanceof MToolControl && trimElementId.contains(perspectiveId)) { |
| toRemove.add((MToolControl) child); |
| } |
| } |
| |
| // ...and remove them |
| for (MToolControl minStack : toRemove) { |
| minStack.setToBeRendered(false); |
| bar.getChildren().remove(minStack); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Handles changes of the perspective |
| * |
| * @param event |
| */ |
| |
| @Inject |
| @Optional |
| private void subscribeTopicSelectedElement( |
| @UIEventTopic(UIEvents.ElementContainer.TOPIC_SELECTEDELEMENT) Event event) { |
| final MUIElement changedElement = (MUIElement) event.getProperty(EventTags.ELEMENT); |
| if (!(changedElement instanceof MPerspectiveStack)) { |
| return; |
| } |
| |
| MPerspectiveStack ps = (MPerspectiveStack) changedElement; |
| MWindow window = modelService.getTopLevelWindowFor(ps); |
| List<MToolControl> tcList = modelService.findElements(window, null, MToolControl.class, |
| null); |
| |
| final MPerspective curPersp = ps.getSelectedElement(); |
| if (curPersp != null) { |
| List<String> tags = new ArrayList<>(); |
| tags.add(IPresentationEngine.MINIMIZED); |
| |
| List<MUIElement> minimizedElements = modelService.findElements(curPersp, null, |
| MUIElement.class, tags); |
| // Show any minimized stack from the current perspective |
| for (MUIElement ele : minimizedElements) { |
| String fullId = TrimStackIdHelper.createTrimStackId(ele, curPersp, window); |
| |
| for (MToolControl tc : tcList) { |
| if (fullId.equals(tc.getElementId())) { |
| tc.setToBeRendered(true); |
| } |
| } |
| } |
| |
| // Find the editor 'area' |
| MUIElement eaPlaceholder = modelService.find(ID_EDITOR_AREA, curPersp); |
| adjustCTFButtons(eaPlaceholder); |
| } |
| |
| // Hide any minimized stacks from the old perspective |
| if (event.getProperty(EventTags.OLD_VALUE) instanceof MPerspective) { |
| MPerspective oldPersp = (MPerspective) event.getProperty(EventTags.OLD_VALUE); |
| String perspId = '(' + oldPersp.getElementId() + ')'; |
| for (MToolControl tc : tcList) { |
| if (tc.getObject() instanceof TrimStack && tc.getElementId().contains(perspId)) { |
| TrimStack ts = (TrimStack) tc.getObject(); |
| ts.showStack(false); |
| tc.setToBeRendered(false); |
| } |
| } |
| } |
| |
| final Shell winShell = (Shell) window.getWidget(); |
| winShell.getDisplay().asyncExec(() -> { |
| if (!winShell.isDisposed()) { |
| winShell.layout(true, true); |
| } |
| }); |
| } |
| |
| /** |
| * Handles changes in tags |
| * |
| * @param event |
| */ |
| |
| @Inject |
| @Optional |
| private void subscribeTopicTagsChanged( |
| @UIEventTopic(UIEvents.ApplicationElement.TOPIC_TAGS) Event event) { |
| if (ignoreTagChanges) { |
| return; |
| } |
| |
| Object changedObj = event.getProperty(EventTags.ELEMENT); |
| |
| if (!(changedObj instanceof MUIElement)) { |
| return; |
| } |
| |
| final MUIElement changedElement = (MUIElement) changedObj; |
| |
| if (UIEvents.isADD(event)) { |
| if (UIEvents.contains(event, UIEvents.EventTags.NEW_VALUE, MINIMIZED)) { |
| minimize(changedElement); |
| } else if (UIEvents.contains(event, UIEvents.EventTags.NEW_VALUE, MAXIMIZED)) { |
| maximize(changedElement); |
| } |
| } else if (UIEvents.isREMOVE(event)) { |
| if (UIEvents.contains(event, UIEvents.EventTags.OLD_VALUE, MINIMIZED)) { |
| restore(changedElement); |
| } else if (UIEvents.contains(event, UIEvents.EventTags.OLD_VALUE, MAXIMIZED)) { |
| unzoom(changedElement); |
| } |
| } |
| } |
| |
| /** |
| * Handles changes in the id of the element If a perspective ID changes fix any TrimStacks that |
| * reference the old id to point at the new id. |
| * |
| * This keeps trim stacks attached to the correct perspective when a perspective is saved with a |
| * new name. |
| * |
| * @param event |
| */ |
| |
| @Inject |
| @Optional |
| private void subscribeTopicElementId( |
| @UIEventTopic(UIEvents.ApplicationElement.TOPIC_ELEMENTID) Event event) { |
| Object changedObject = event.getProperty(EventTags.ELEMENT); |
| |
| // Only care about MPerspective id changes |
| if (!(changedObject instanceof MPerspective)) { |
| return; |
| } |
| |
| MPerspective perspective = (MPerspective) changedObject; |
| |
| String newID = (String) event.getProperty(UIEvents.EventTags.NEW_VALUE); |
| String oldID = (String) event.getProperty(UIEvents.EventTags.OLD_VALUE); |
| |
| // pattern is trimStackID(perspectiveID) |
| newID = '(' + newID + ')'; |
| oldID = '(' + oldID + ')'; |
| |
| // Search the trim for the window containing the perspective |
| MWindow perspWin = modelService.getTopLevelWindowFor(perspective); |
| if (perspWin == null) { |
| return; |
| } |
| |
| List<MToolControl> trimStacks = modelService.findElements(perspWin, null, |
| MToolControl.class, null); |
| for (MToolControl trimStack : trimStacks) { |
| // Only care about MToolControls that are TrimStacks |
| if (TrimStack.CONTRIBUTION_URI.equals(trimStack.getContributionURI())) { |
| trimStack.setElementId(trimStack.getElementId().replace(oldID, newID)); |
| } |
| } |
| } |
| |
| /** |
| * Handles the event that the perspective is saved |
| * |
| * @param event |
| */ |
| |
| @Inject |
| @Optional |
| private void subscribeTopicPerspSaved( |
| @UIEventTopic(UIEvents.UILifeCycle.PERSPECTIVE_SAVED) Event event) { |
| final MPerspective savedPersp = (MPerspective) event.getProperty(EventTags.ELEMENT); |
| String cache = getTrimCache(savedPersp); |
| minMaxAddon.getPersistedState().put(savedPersp.getElementId(), cache); |
| } |
| |
| private String getTrimCache(MPerspective savedPersp) { |
| MWindow topWin = modelService.getTopLevelWindowFor(savedPersp); |
| StringBuilder cache = new StringBuilder(getWinCache(topWin, savedPersp)); |
| for (MWindow dw : savedPersp.getWindows()) { |
| cache.append(getWinCache(dw, savedPersp)); |
| } |
| return cache.toString(); |
| } |
| |
| private String getWinCache(MWindow win, MPerspective perspective) { |
| StringBuilder winStrBuilder = new StringBuilder(); |
| List<MPartStack> stackList = modelService.findElements(win, null, MPartStack.class, null); |
| for (MPartStack stack : stackList) { |
| winStrBuilder.append(getStackTrimLoc(stack, perspective)); |
| } |
| return winStrBuilder.toString(); |
| } |
| |
| private String getStackTrimLoc(MPartStack stack, MPerspective perspective) { |
| MWindow stackWin = modelService.getTopLevelWindowFor(stack); |
| MUIElement tcElement = modelService.find(TrimStackIdHelper.createTrimStackId(stack, perspective, stackWin), |
| stackWin); |
| if (tcElement == null) |
| { |
| return ""; //$NON-NLS-1$ |
| } |
| |
| MTrimBar bar = (MTrimBar) ((MUIElement) tcElement.getParent()); |
| int sideVal = bar.getSide().getValue(); |
| int index = bar.getChildren().indexOf(tcElement); |
| return stack.getElementId() + ' ' + sideVal + ' ' + index + "#"; //$NON-NLS-1$ |
| } |
| |
| /** |
| * Handles the event that the perspective is reset |
| * |
| * @param event |
| */ |
| @Inject |
| @Optional |
| private void subscribeTopicPerspReset( |
| @UIEventTopic(UIEvents.UILifeCycle.PERSPECTIVE_RESET) Event event) { |
| final MPerspective resetPersp = (MPerspective) event.getProperty(EventTags.ELEMENT); |
| |
| // Find any minimized stacks and show their trim |
| List<MUIElement> minimizedElements = modelService.findElements(resetPersp, null, |
| MUIElement.class, Arrays.asList(IPresentationEngine.MINIMIZED)); |
| for (MUIElement element : minimizedElements) { |
| createTrim(element); |
| } |
| } |
| |
| /** |
| * Handles the event that the perspective is opened |
| * |
| * @param event |
| */ |
| @Inject |
| @Optional |
| private void subscribeTopicPerspOpened( |
| @UIEventTopic(UIEvents.UILifeCycle.PERSPECTIVE_OPENED) Event event) { |
| final MPerspective openedPersp = (MPerspective) event.getProperty(EventTags.ELEMENT); |
| |
| // Find any minimized stacks and show their trim |
| List<MUIElement> minimizedElements = modelService.findElements(openedPersp, null, |
| MUIElement.class, Arrays.asList(IPresentationEngine.MINIMIZED)); |
| for (MUIElement element : minimizedElements) { |
| createTrim(element); |
| } |
| } |
| |
| |
| |
| private void setCTFButtons(CTabFolder ctf, MUIElement stateElement, boolean hideButtons) { |
| if (hideButtons) { |
| ctf.setMinimizeVisible(false); |
| ctf.setMaximizeVisible(false); |
| } else { |
| if (stateElement.getTags().contains(MINIMIZED)) { |
| ctf.setMinimizeVisible(false); |
| ctf.setMaximizeVisible(true); |
| ctf.setMaximized(true); |
| } else if (stateElement.getTags().contains(MAXIMIZED)) { |
| ctf.setMinimizeVisible(true); |
| ctf.setMaximizeVisible(true); |
| ctf.setMaximized(true); |
| } else { |
| ctf.setMinimizeVisible(true); |
| ctf.setMaximizeVisible(true); |
| ctf.setMinimized(false); |
| ctf.setMaximized(false); |
| ctf.layout(); |
| } |
| } |
| } |
| |
| /** |
| * Set the state of the min / max buttons on the CTF based on the model element's state. The |
| * input is expected to be the element that contains the min/max state info which should either |
| * be an MPartStack or an MPlaceholder for the shared area. |
| * |
| * @param element |
| * The element to test |
| */ |
| void adjustCTFButtons(MUIElement element) { |
| if (!(element instanceof MPartStack) && !(element instanceof MPlaceholder)) { |
| return; |
| } |
| |
| CTabFolder ctf = getCTFFor(element); |
| if (ctf == null) { |
| return; |
| } |
| |
| if (element instanceof MPlaceholder) { |
| setCTFButtons(ctf, element, false); |
| } else { |
| MArea area = MinMaxAddonUtil.getAreaFor((MPartStack) element); |
| if (area == null) { |
| setCTFButtons(ctf, element, false); |
| } |
| else if (area.getTags().contains(MIN_MAXIMIZEABLE_CHILDREN_AREA_TAG)) { |
| setCTFButtons(ctf, element, false); |
| } |
| } |
| } |
| |
| private CTabFolder getCTFFor(MUIElement element) { |
| if (element instanceof MArea) { |
| if (element.getWidget() instanceof CTabFolder) { |
| return (CTabFolder) element.getWidget(); |
| } |
| List<MPartStack> stacks = modelService.findElements(element, null, MPartStack.class, |
| null); |
| for (MPartStack stack : stacks) { |
| if (stack.getWidget() instanceof CTabFolder) { |
| return (CTabFolder) stack.getWidget(); |
| } |
| } |
| } else if (element.getWidget() instanceof CTabFolder) { |
| return (CTabFolder) element.getWidget(); |
| } else if (element instanceof MPlaceholder) { |
| MPlaceholder ph = (MPlaceholder) element; |
| if (ph.getRef() instanceof MArea) { |
| return getCTFFor(ph.getRef()); |
| } |
| } |
| return null; |
| } |
| |
| boolean isEmptyPerspectiveStack(MUIElement element) { |
| if (!(element instanceof MPerspectiveStack)) { |
| return false; |
| } |
| MPerspectiveStack ps = (MPerspectiveStack) element; |
| return ps.getChildren().isEmpty(); |
| } |
| |
| void minimize(MUIElement element) { |
| // Can't minimize a non-rendered element |
| if (!element.isToBeRendered()) { |
| return; |
| } |
| |
| if (isEmptyPerspectiveStack(element)) { |
| element.setVisible(false); |
| return; |
| } |
| |
| createTrim(element); |
| element.setVisible(false); |
| adjustCTFButtons(element); |
| // Activate a part other than the trimStack so that if the tool item is pressed |
| // immediately it will still open the stack. |
| partService.requestActivation(); |
| } |
| |
| void restore(MUIElement element) { |
| MWindow window = modelService.getTopLevelWindowFor(element); |
| String trimId = getTrimId(element, MinMaxAddonUtil.getWindowFor(element)); |
| MToolControl trimStack = (MToolControl) modelService.find(trimId, window); |
| if (trimStack == null || trimStack.getObject() == null) { |
| // try legacy id |
| trimId = TrimStackIdHelper.createTrimStackId(element, modelService.getPerspectiveFor(element), null); |
| trimStack = (MToolControl) modelService.find(trimId, window); |
| if (trimStack == null || trimStack.getObject() == null) { |
| if (element instanceof MPerspectiveStack) { |
| element.setVisible(true); |
| } |
| return; |
| } |
| } |
| |
| TrimStack ts = (TrimStack) trimStack.getObject(); |
| ts.restoreStack(); |
| |
| adjustCTFButtons(element); |
| |
| List<String> maximizeTag = new ArrayList<>(); |
| maximizeTag.add(IPresentationEngine.MAXIMIZED); |
| List<MUIElement> curMax = modelService.findElements(window, null, MUIElement.class, |
| maximizeTag, EModelService.PRESENTATION); |
| |
| MinMaxAddonUtil.ignoreChildrenOfMinMaxChildrenArea(modelService, element, curMax); |
| |
| if (curMax.size() > 0) { |
| MUIElement maxElement = curMax.get(0); |
| List<MUIElement> elementsLeftToRestore = getElementsToRestore(maxElement); |
| |
| // Are any stacks still minimized ? |
| boolean unMax = true; |
| for (MUIElement toRestore : elementsLeftToRestore) { |
| if (!toRestore.isVisible()) { |
| unMax = false; |
| } |
| } |
| if (unMax) { |
| maxElement.getTags().remove(IPresentationEngine.MAXIMIZED); |
| } |
| } |
| MinMaxAddonUtil.restoreStacksOfMinMaxChildrenArea(this, element, maximizeTag); |
| } |
| |
| void executeWithIgnoredTagChanges(Runnable runnable) { |
| ignoreTagChanges = true; |
| try { |
| runnable.run(); |
| } finally { |
| ignoreTagChanges = false; |
| } |
| } |
| |
| void maximize(final MUIElement element) { |
| if (!element.isToBeRendered()) { |
| return; |
| } |
| |
| List<MUIElement> elementsToMinimize = getElementsToMinimize(element); |
| Shell hostShell = (Shell) modelService.getTopLevelWindowFor(element).getWidget(); |
| MWindow win = MinMaxAddonUtil.getWindowFor(element); |
| |
| if (hostShell != null) { |
| FaderAnimationFeedback fader = new FaderAnimationFeedback(hostShell); |
| AnimationEngine engine = new AnimationEngine(win.getContext(), fader, 300); |
| engine.schedule(); |
| } |
| |
| // Restore any currently maximized element |
| restoreMaximizedElement(element, win); |
| |
| for (MUIElement toMinimize : elementsToMinimize) { |
| toMinimize.getTags().add(MINIMIZED); |
| toMinimize.getTags().add(MINIMIZED_BY_ZOOM); |
| } |
| |
| adjustCTFButtons(element); |
| |
| MinMaxAddonUtil.maximizeMinMaxChildrenArea(this, element); |
| } |
| |
| /** |
| * @param element |
| * @return The list of elements that need to be minimized during a maximize |
| */ |
| private List<MUIElement> getElementsToMinimize(MUIElement element) { |
| MWindow win = MinMaxAddonUtil.getWindowFor(element); |
| MPerspective persp = modelService.getActivePerspective(win); |
| |
| List<MUIElement> elementsToMinimize = new ArrayList<>(); |
| int loc = modelService.getElementLocation(element); |
| if ((loc & EModelService.OUTSIDE_PERSPECTIVE) != 0) { |
| // Minimize all other global stacks |
| List<MPartStack> partStacksToMinimize = findValidElementsToMinimize(element, win, win, |
| null, MPartStack.class, EModelService.OUTSIDE_PERSPECTIVE, false); |
| elementsToMinimize.addAll(partStacksToMinimize); |
| |
| // Minimize the Perspective Stack |
| MUIElement perspStack = null; |
| if (persp == null) { |
| // special case for windows with no perspectives (eg bug 372614: |
| // intro part with no perspectives). We know we're outside |
| // of the perspective stack, so find it top-down |
| List<MPerspectiveStack> pStacks = modelService.findElements(win, null, |
| MPerspectiveStack.class, null); |
| perspStack = (pStacks.size() > 0) ? pStacks.get(0) : null; |
| } else { |
| perspStack = persp.getParent(); |
| } |
| if (perspStack != null) { |
| if (perspStack.getElementId() == null || perspStack.getElementId().length() == 0) |
| { |
| perspStack.setElementId("PerspectiveStack"); //$NON-NLS-1$ |
| } |
| |
| elementsToMinimize.add(perspStack); |
| } |
| |
| // Find all editor 'area' outside the perspective |
| List<MPlaceholder> placeholderToMinimize = findValidElementsToMinimize(element, win, |
| win, ID_EDITOR_AREA, MPlaceholder.class, EModelService.OUTSIDE_PERSPECTIVE, |
| true); |
| elementsToMinimize.addAll(placeholderToMinimize); |
| |
| } else { |
| List<MPartStack> partStacksToMinimize = findValidElementsToMinimize(element, win, |
| persp == null ? win : persp, null, MPartStack.class, |
| EModelService.PRESENTATION, false); |
| elementsToMinimize.addAll(partStacksToMinimize); |
| // Find any 'standalone' views *not* in a stack |
| List<String> standaloneTag = new ArrayList<>(); |
| standaloneTag.add(IPresentationEngine.STANDALONE); |
| List<MPlaceholder> standaloneViews = modelService.findElements(persp == null ? win |
| : persp, null, MPlaceholder.class, standaloneTag, EModelService.PRESENTATION); |
| for (MPlaceholder part : standaloneViews) { |
| if (!part.isToBeRendered()) { |
| continue; |
| } |
| elementsToMinimize.add(part); |
| } |
| |
| // Find the editor 'area' |
| List<MPlaceholder> placeholderToMinimize = findValidElementsToMinimize(element, win, |
| win, ID_EDITOR_AREA, MPlaceholder.class, EModelService.PRESENTATION, true); |
| elementsToMinimize.addAll(placeholderToMinimize); |
| } |
| |
| MinMaxAddonUtil.handleMinimizeOfMinMaxChildrenArea(modelService, element, win, persp, elementsToMinimize); |
| |
| return elementsToMinimize; |
| } |
| |
| /** |
| * Find all elements based on |
| * {@link EModelService#findElements(MUIElement, String, Class, List, int)} and filter them for |
| * correct window and visibility. |
| * |
| * First all possible elements based on the parameters of EModelService#findElements(MUIElement, |
| * String, Class, List, int)} are retrieved. Then they are checked to be in the correct window. |
| * Then a check for the correct location is made and in the end the elements are checked to be |
| * valid (visible, not yet minimized and have a widget). |
| * |
| * @param elementToMaximize |
| * the {@link MUIElement} being maximized |
| * @param currentWindow |
| * the window of the elementToMaximize |
| * @param searchRoot |
| * the searchRoot for possible elements |
| * @param id |
| * the id of the element to search |
| * @param clazz |
| * the Class of the elements to find |
| * @param searchFlag |
| * the search flags as defined in {@link EModelService} |
| * @param allowSharedArea |
| * whether the found element is allowed to be in a shared area |
| * @return the list of elements which should be minimized |
| */ |
| private <T extends MUIElement> List<T> findValidElementsToMinimize( |
| MUIElement elementToMaximize, MWindow currentWindow, MUIElement searchRoot, String id, |
| Class<T> clazz, int searchFlag, boolean allowSharedArea) { |
| List<T> elementsToMinimize = new ArrayList<>(); |
| List<T> elements = modelService.findElements(searchRoot, id, clazz, null, searchFlag); |
| for (T element : elements) { |
| if (element == elementToMaximize || !element.isToBeRendered()) { |
| continue; |
| } |
| |
| // Exclude stacks in DW's |
| if (MinMaxAddonUtil.getWindowFor(element) != currentWindow) { |
| continue; |
| } |
| |
| int loc = modelService.getElementLocation(element); |
| boolean inSharedArea = loc == EModelService.IN_SHARED_AREA; |
| boolean validLocation = allowSharedArea || !inSharedArea; |
| if (validLocation && element.getWidget() != null && element.isVisible() |
| && !element.getTags().contains(MINIMIZED)) { |
| elementsToMinimize.add(element); |
| } |
| } |
| return elementsToMinimize; |
| } |
| |
| /** |
| * Restore any currently maximized element (except the one we're in the process of maximizing |
| * |
| * @param element |
| * @param win |
| */ |
| private void restoreMaximizedElement(final MUIElement element, MWindow win) { |
| MPerspective elePersp = modelService.getPerspectiveFor(element); |
| List<String> maxTag = new ArrayList<>(); |
| maxTag.add(MAXIMIZED); |
| List<MUIElement> curMax = modelService.findElements(win, null, MUIElement.class, maxTag); |
| if (curMax.size() > 0) { |
| for (MUIElement maxElement : curMax) { |
| // Only unmax elements in this window |
| if (MinMaxAddonUtil.getWindowFor(maxElement) != win) { |
| continue; |
| } |
| |
| MPerspective maxPersp = modelService.getPerspectiveFor(maxElement); |
| if (maxPersp != elePersp) { |
| continue; |
| } |
| if (maxElement == element) { |
| continue; |
| } |
| if (MinMaxAddonUtil.isPartOfMinMaxChildrenArea(maxElement)) { |
| continue; |
| } |
| ignoreTagChanges = true; |
| try { |
| maxElement.getTags().remove(MAXIMIZED); |
| } finally { |
| ignoreTagChanges = false; |
| } |
| } |
| } |
| } |
| |
| |
| |
| void unzoom(final MUIElement element) { |
| MWindow win = MinMaxAddonUtil.getWindowFor(element); |
| |
| Shell hostShell = (Shell) win.getWidget(); |
| if (hostShell != null) { |
| FaderAnimationFeedback fader = new FaderAnimationFeedback(hostShell); |
| AnimationEngine engine = new AnimationEngine(win.getContext(), fader, 300); |
| engine.schedule(); |
| } |
| |
| List<MUIElement> elementsToRestore = getElementsToRestore(element); |
| for (MUIElement toRestore : elementsToRestore) { |
| toRestore.getTags().remove(IPresentationEngine.MINIMIZED_BY_ZOOM); |
| toRestore.getTags().remove(IPresentationEngine.MINIMIZED); |
| } |
| |
| adjustCTFButtons(element); |
| |
| MinMaxAddonUtil.unzoomStackOfMinMaxChildrenArea(this, element); |
| |
| // There are more views available to be active... |
| partService.requestActivation(); |
| } |
| |
| |
| /** |
| * @param element |
| * @return The list of elements that need to be restored by an unzoom |
| */ |
| private List<MUIElement> getElementsToRestore(MUIElement element) { |
| MWindow win = MinMaxAddonUtil.getWindowFor(element); |
| MPerspective persp = modelService.getActivePerspective(win); |
| |
| List<MUIElement> elementsToRestore = new ArrayList<>(); |
| |
| List<String> minTag = new ArrayList<>(); |
| minTag.add(IPresentationEngine.MINIMIZED_BY_ZOOM); |
| |
| // Restore any minimized stacks |
| boolean outsidePerspectives = (modelService.getElementLocation(element) & EModelService.OUTSIDE_PERSPECTIVE) != 0; |
| List<MPartStack> stacks = modelService.findElements(win, null, MPartStack.class, minTag, |
| EModelService.PRESENTATION); |
| for (MPartStack theStack : stacks) { |
| if (theStack.getWidget() != null) { |
| // Make sure we don't restore perspective-based stacks if we're |
| // unzoooming an element outside the perspectives |
| if (outsidePerspectives) { |
| int stackLoc = modelService.getElementLocation(theStack); |
| if ((stackLoc & EModelService.OUTSIDE_PERSPECTIVE) == 0) { |
| continue; |
| } |
| } |
| |
| // Make sure we're only working on *our* window |
| if (MinMaxAddonUtil.getWindowFor(theStack) == win) { |
| elementsToRestore.add(theStack); |
| } |
| } |
| } |
| |
| // Restore any minimized standalone views |
| List<MPlaceholder> views = modelService.findElements(win, null, MPlaceholder.class, minTag, |
| EModelService.PRESENTATION); |
| for (MPlaceholder ph : views) { |
| if (ph.getWidget() != null && MinMaxAddonUtil.getWindowFor(ph) == win) { |
| elementsToRestore.add(ph); |
| } |
| } |
| |
| // Find the editor 'area' |
| MPlaceholder eaPlaceholder = (MPlaceholder) modelService.find(ID_EDITOR_AREA, |
| persp == null ? win : persp); |
| if (element != eaPlaceholder && eaPlaceholder != null |
| && eaPlaceholder.getTags().contains(MINIMIZED_BY_ZOOM)) { |
| elementsToRestore.add(eaPlaceholder); |
| } |
| |
| // Find the Perspective Stack |
| int loc = modelService.getElementLocation(element); |
| if ((loc & EModelService.OUTSIDE_PERSPECTIVE) != 0) { |
| List<MPerspectiveStack> psList = modelService.findElements(win, null, |
| MPerspectiveStack.class, null); |
| if (psList.size() == 1) { |
| MPerspectiveStack perspStack = psList.get(0); |
| if (element != perspStack && perspStack != null |
| && perspStack.getTags().contains(MINIMIZED_BY_ZOOM)) { |
| elementsToRestore.add(perspStack); |
| } |
| } |
| } |
| |
| MinMaxAddonUtil.addChildrenOfMinMaxChildrenAreaToRestoreList(modelService, element, win, persp, elementsToRestore); |
| |
| return elementsToRestore; |
| } |
| |
| private void createTrim(MUIElement element) { |
| MWindow win = MinMaxAddonUtil.getWindowFor(element); |
| if (!(win instanceof MTrimmedWindow)) { |
| return; |
| } |
| |
| MTrimmedWindow window = (MTrimmedWindow) win; |
| Shell winShell = (Shell) window.getWidget(); |
| |
| // Is there already a TrimControl there ? |
| String trimId = getTrimId(element, window); |
| MToolControl trimStack = (MToolControl) modelService.find(trimId, window); |
| |
| if (trimStack == null) { |
| trimStack = MenuFactoryImpl.eINSTANCE.createToolControl(); |
| trimStack.setElementId(trimId); |
| trimStack.setContributionURI(TrimStack.CONTRIBUTION_URI); |
| trimStack.getTags().add("TrimStack"); //$NON-NLS-1$ |
| |
| // Check if we have a cached location |
| MTrimBar bar = getBarForElement(element, window); |
| int index = getCachedIndex(element); |
| if (index == -1 || index >= bar.getChildren().size()) { |
| bar.getChildren().add(trimStack); |
| } else { |
| bar.getChildren().add(index, trimStack); |
| } |
| |
| bar.setVisible(true); |
| |
| // get the parent trim bar, see bug 320756 |
| if (bar.getWidget() == null) { |
| // ask it to be rendered |
| bar.setToBeRendered(true); |
| |
| // create the widget |
| context.get(IPresentationEngine.class) |
| .createGui(bar, winShell, window.getContext()); |
| } |
| } else { |
| // get the parent trim bar, see bug 320756 |
| MUIElement parent = trimStack.getParent(); |
| parent.setVisible(true); |
| if (parent.getWidget() == null) { |
| // ask it to be rendered |
| parent.setToBeRendered(true); |
| // create the widget |
| context.get(IPresentationEngine.class).createGui(parent, winShell, |
| window.getContext()); |
| } |
| trimStack.setToBeRendered(true); |
| } |
| } |
| |
| private String getTrimId(MUIElement element, MWindow window) { |
| String trimId; |
| if (MinMaxAddonUtil.isPartOfMinMaxChildrenArea(element)) { |
| trimId = TrimStackIdHelper.createTrimStackId(element, null, window); |
| } else { |
| trimId = TrimStackIdHelper.createTrimStackId(element, modelService.getPerspectiveFor(element), window); |
| } |
| return trimId; |
| } |
| |
| |
| |
| private String getCachedInfo(MUIElement element) { |
| String cacheId = GLOBAL_CACHE_ID; |
| MPerspective persp = modelService.getPerspectiveFor(element); |
| if (persp != null) { |
| cacheId = persp.getElementId(); |
| } |
| String cacheInfo = minMaxAddon.getPersistedState().get(cacheId); |
| |
| return cacheInfo; |
| } |
| |
| private int getCachedIndex(MUIElement element) { |
| String cache = getCachedInfo(element); |
| if (cache == null) { |
| return -1; |
| } |
| |
| String[] stacks = cache.split("#"); //$NON-NLS-1$ |
| for (String stackInfo : stacks) { |
| String[] vals = stackInfo.split(" "); //$NON-NLS-1$ |
| if (vals[0].equals(element.getElementId())) { |
| return Integer.parseInt(vals[2]); |
| } |
| } |
| return -1; |
| } |
| |
| private SideValue getCachedBar(MUIElement element) { |
| String cache = getCachedInfo(element); |
| if (cache == null) { |
| return null; |
| } |
| |
| String[] stacks = cache.split("#"); //$NON-NLS-1$ |
| for (String stackInfo : stacks) { |
| String[] vals = stackInfo.split(" "); //$NON-NLS-1$ |
| if (vals[0].equals(element.getElementId())) { |
| int sideVal = Integer.parseInt(vals[1]); |
| return SideValue.get(sideVal); |
| } |
| } |
| return null; |
| } |
| |
| private MTrimBar getBarForElement(MUIElement element, MTrimmedWindow window) { |
| SideValue side = getCachedBar(element); |
| if (side == null) { |
| Shell winShell = (Shell) window.getWidget(); |
| Rectangle winBounds = winShell.getBounds(); |
| int winCenterX = winBounds.width / 2; |
| Control stackCtrl = (Control) element.getWidget(); |
| Rectangle stackBounds = stackCtrl.getBounds(); |
| stackBounds = winShell.getDisplay().map(stackCtrl, winShell, stackBounds); |
| int stackCenterX = stackBounds.x + (stackBounds.width / 2); |
| side = stackCenterX < winCenterX ? SideValue.LEFT : SideValue.RIGHT; |
| } |
| MTrimBar bar = modelService.getTrim(window, side); |
| |
| return bar; |
| } |
| } |