blob: 97e111959b6530453d1fc1ccb236812a8c4604b0 [file] [log] [blame]
/*******************************************************************************
* 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;
}
}