blob: 644ff92e72c8b1b93e5e3180811eccb5d837a647 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2011 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
******************************************************************************/
package org.eclipse.e4.ui.workbench.addons.minmax;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.inject.Inject;
import org.eclipse.e4.core.contexts.IEclipseContext;
import org.eclipse.e4.core.services.events.IEventBroker;
import org.eclipse.e4.ui.internal.workbench.swt.AbstractPartRenderer;
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.widgets.CTabFolder;
import org.eclipse.e4.ui.widgets.CTabFolder2Adapter;
import org.eclipse.e4.ui.widgets.CTabFolderEvent;
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.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;
import org.osgi.service.event.EventHandler;
/**
* Addon supporting standard drag and drop management
*/
public class MinMaxAddon {
/**
* 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 String trimURI = "platform:/plugin/org.eclipse.e4.ui.workbench.addons.swt/org.eclipse.e4.ui.workbench.addons.minmax.TrimStack"; //$NON-NLS-1$
static String ID_SUFFIX = "(minimized)"; //$NON-NLS-1$
// tags representing the min/max state
public static String MINIMIZED = "Minimized"; //$NON-NLS-1$
public static String MAXIMIZED = "Maximized"; //$NON-NLS-1$
public static String MINIMIZED_BY_ZOOM = "MinimizedByZoom"; //$NON-NLS-1$
@Inject
IEventBroker eventBroker;
// asdf
@Inject
EModelService modelService;
@Inject
private IEclipseContext context;
// Allow 'local' changes to the tags
private boolean ignoreTagChanges = false;
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();
return parentElement != null ? parentElement.getCurSharedRef() : element;
}
public void maximize(CTabFolderEvent event) {
setState(getElementToChange(event), MAXIMIZED);
}
public void minimize(CTabFolderEvent event) {
setState(getElementToChange(event), MINIMIZED);
}
public void restore(CTabFolderEvent event) {
setState(getElementToChange(event), null);
}
};
private MouseListener CTFDblClickListener = new MouseListener() {
public void mouseUp(MouseEvent e) {
}
public void mouseDown(MouseEvent e) {
}
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();
return parentElement != null ? parentElement.getCurSharedRef() : element;
}
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;
MUIElement elementToChange = getElementToChange(e);
if (!elementToChange.getTags().contains(MAXIMIZED)) {
setState(elementToChange, MAXIMIZED);
} else {
setState(elementToChange, null);
}
}
}
};
private EventHandler widgetListener = new EventHandler() {
public void handleEvent(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 = 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);
}
};
private void setState(MUIElement element, String state) {
element.getTags().remove(MINIMIZED_BY_ZOOM);
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);
}
}
private EventHandler perspectiveChangeListener = new EventHandler() {
public void handleEvent(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) {
// Show any minimized stack from the current perspective
String perspId = '(' + curPersp.getElementId() + ')';
for (MToolControl tc : tcList) {
if (tc.getObject() instanceof TrimStack && tc.getElementId().contains(perspId)) {
tc.setVisible(true);
}
}
// Find the editor 'area'
MPlaceholder eaPlaceholder = (MPlaceholder) 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.setVisible(false);
}
}
}
final Shell winShell = (Shell) window.getWidget();
winShell.getDisplay().asyncExec(new Runnable() {
public void run() {
winShell.layout(true, true);
}
});
}
};
private EventHandler tagChangeListener = new EventHandler() {
public void handleEvent(Event event) {
if (ignoreTagChanges)
return;
Object changedObj = event.getProperty(EventTags.ELEMENT);
String eventType = (String) event.getProperty(UIEvents.EventTags.TYPE);
String tag = (String) event.getProperty(UIEvents.EventTags.NEW_VALUE);
String oldVal = (String) event.getProperty(UIEvents.EventTags.OLD_VALUE);
if (!(changedObj instanceof MUIElement))
return;
final MUIElement changedElement = (MUIElement) changedObj;
if (UIEvents.EventTypes.ADD.equals(eventType)) {
if (MINIMIZED.equals(tag)) {
minimize(changedElement);
} else if (MAXIMIZED.equals(tag)) {
maximize(changedElement);
}
} else if (UIEvents.EventTypes.REMOVE.equals(eventType)) {
if (MINIMIZED.equals(oldVal)) {
restore(changedElement);
} else if (MAXIMIZED.equals(oldVal)) {
unzoom(changedElement);
}
}
}
};
private EventHandler perspectiveRemovedListener = new EventHandler() {
public void handleEvent(Event event) {
final MUIElement changedElement = (MUIElement) event.getProperty(EventTags.ELEMENT);
if (!(changedElement instanceof MPerspectiveStack))
return;
String eventType = (String) event.getProperty(UIEvents.EventTags.TYPE);
if (UIEvents.EventTypes.REMOVE.equals(eventType)) {
MUIElement removed = (MUIElement) event.getProperty(UIEvents.EventTags.OLD_VALUE);
String perspectiveId = removed.getElementId();
MWindow window = modelService.getTopLevelWindowFor(changedElement);
MTrimBar bar = modelService.getTrim((MTrimmedWindow) window, SideValue.TOP);
// gather up any minimized stacks for this perspective...
List<MToolControl> toRemove = new ArrayList<MToolControl>();
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);
}
}
}
};
@PostConstruct
void hookListeners() {
String topic = UIEvents.buildTopic(UIEvents.UIElement.TOPIC, UIEvents.UIElement.WIDGET);
eventBroker.subscribe(topic, null, widgetListener, false);
topic = UIEvents.buildTopic(UIEvents.ElementContainer.TOPIC,
UIEvents.ElementContainer.CHILDREN);
eventBroker.subscribe(topic, null, perspectiveRemovedListener, false);
topic = UIEvents.buildTopic(UIEvents.ElementContainer.TOPIC,
UIEvents.ElementContainer.SELECTEDELEMENT);
eventBroker.subscribe(topic, null, perspectiveChangeListener, false);
topic = UIEvents.buildTopic(UIEvents.ApplicationElement.TOPIC,
UIEvents.ApplicationElement.TAGS);
eventBroker.subscribe(topic, null, tagChangeListener, false);
}
@PreDestroy
void unhookListeners() {
eventBroker.unsubscribe(widgetListener);
eventBroker.unsubscribe(perspectiveRemovedListener);
eventBroker.unsubscribe(perspectiveChangeListener);
eventBroker.unsubscribe(tagChangeListener);
}
private MArea getAreaFor(MPartStack stack) {
MUIElement parent = stack.getParent();
while (parent != null) {
if (parent instanceof MArea)
return (MArea) parent;
parent = parent.getParent();
}
return null;
}
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
*/
private 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 = getAreaFor((MPartStack) element);
if (area == null) {
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;
}
void minimize(MUIElement element) {
createTrim(element);
element.setVisible(false);
adjustCTFButtons(element);
}
void restore(MUIElement element) {
MWindow window = modelService.getTopLevelWindowFor(element);
String trimId = element.getElementId() + getMinimizedElementSuffix(element);
MToolControl trimStack = (MToolControl) modelService.find(trimId, window);
TrimStack ts = (TrimStack) trimStack.getObject();
ts.restoreStack();
adjustCTFButtons(element);
element.getTags().remove(MINIMIZED_BY_ZOOM);
}
void maximize(final MUIElement element) {
MWindow win = getWindowFor(element);
MPerspective persp = modelService.getActivePerspective(win);
List<String> maxTag = new ArrayList<String>();
maxTag.add(MAXIMIZED);
List<MUIElement> curMax = modelService.findElements(persp == null ? win : persp, null,
MUIElement.class, maxTag);
if (curMax.size() > 0) {
for (MUIElement maxElement : curMax) {
if (maxElement == element)
continue;
ignoreTagChanges = true;
try {
maxElement.getTags().remove(MAXIMIZED);
} finally {
ignoreTagChanges = false;
}
}
}
List<MPartStack> stacks = modelService.findElements(persp == null ? win : persp, null,
MPartStack.class, null, EModelService.PRESENTATION);
for (MPartStack theStack : stacks) {
if (theStack == element)
continue;
// Exclude stacks in DW's
if (getWindowFor(theStack) != win)
continue;
int loc = modelService.getElementLocation(theStack);
if (loc != EModelService.IN_SHARED_AREA && theStack.getWidget() != null
&& !theStack.getTags().contains(MINIMIZED)) {
theStack.getTags().add(MINIMIZED_BY_ZOOM);
theStack.getTags().add(MINIMIZED);
}
}
// Find the editor 'area'
if (persp != null) {
MPlaceholder eaPlaceholder = (MPlaceholder) modelService.find(ID_EDITOR_AREA, persp);
if (element != eaPlaceholder && eaPlaceholder != null) {
eaPlaceholder.getTags().add(MINIMIZED_BY_ZOOM);
eaPlaceholder.getTags().add(MINIMIZED);
}
}
adjustCTFButtons(element);
}
/**
* Return the MWindow containing this element (if any). This may either be a 'top level' window
* -or- a detached window. This allows the min.max code to only affect elements in the window
* containing the element.
*
* @param element
* The element to check
*
* @return the window containing the element.
*/
private MWindow getWindowFor(MUIElement element) {
MUIElement parent = element.getParent();
// We rely here on the fact that a DW's 'getParent' will return
// null since it's not in the 'children' hierarchy
while (parent != null && !(parent instanceof MWindow))
parent = parent.getParent();
// A detached window will end up with getParent() == null
return (MWindow) parent;
}
void unzoom(final MUIElement element) {
MWindow win = modelService.getTopLevelWindowFor(element);
MPerspective persp = modelService.getActivePerspective(win);
List<MPartStack> stacks = modelService.findElements(win, null, MPartStack.class, null,
EModelService.PRESENTATION);
for (MPartStack theStack : stacks) {
if (theStack.getWidget() != null && theStack.getTags().contains(MINIMIZED)
&& theStack.getTags().contains(MINIMIZED_BY_ZOOM)) {
theStack.getTags().remove(MINIMIZED);
}
}
// Find the editor 'area'
MPlaceholder eaPlaceholder = (MPlaceholder) modelService.find(ID_EDITOR_AREA,
persp == null ? win : persp);
if (element != eaPlaceholder && eaPlaceholder != null) {
eaPlaceholder.getTags().remove(MINIMIZED);
}
adjustCTFButtons(element);
}
private void createTrim(MUIElement element) {
MTrimmedWindow window = (MTrimmedWindow) getWindowFor(element);
Shell winShell = (Shell) window.getWidget();
// Is there already a TrimControl there ?
String trimId = element.getElementId() + getMinimizedElementSuffix(element);
MToolControl trimStack = (MToolControl) modelService.find(trimId, window);
if (trimStack == null) {
trimStack = MenuFactoryImpl.eINSTANCE.createToolControl();
trimStack.setElementId(trimId);
trimStack.setContributionURI(trimURI);
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);
SideValue side = stackCenterX < winCenterX ? SideValue.LEFT : SideValue.RIGHT;
MTrimBar bar = modelService.getTrim(window, side);
bar.getChildren().add(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 getMinimizedElementSuffix(MUIElement element) {
String id = ID_SUFFIX;
MPerspective persp = modelService.getPerspectiveFor(element);
if (persp != null) {
id = '(' + persp.getElementId() + ')';
}
return id;
}
}