blob: 9b1bc5cedcee661872e3149c12e1a55bbc80ce11 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010, 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.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.inject.Inject;
import javax.inject.Named;
import org.eclipse.e4.core.services.events.IEventBroker;
import org.eclipse.e4.ui.internal.workbench.swt.ShellActivationListener;
import org.eclipse.e4.ui.model.application.ui.MElementContainer;
import org.eclipse.e4.ui.model.application.ui.MUIElement;
import org.eclipse.e4.ui.model.application.ui.MUILabel;
import org.eclipse.e4.ui.model.application.ui.SideValue;
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.MPart;
import org.eclipse.e4.ui.model.application.ui.basic.MPartStack;
import org.eclipse.e4.ui.model.application.ui.basic.MStackElement;
import org.eclipse.e4.ui.model.application.ui.basic.MTrimBar;
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.workbench.IPresentationEngine;
import org.eclipse.e4.ui.workbench.IResourceUtilities;
import org.eclipse.e4.ui.workbench.UIEvents;
import org.eclipse.e4.ui.workbench.modeling.EModelService;
import org.eclipse.e4.ui.workbench.modeling.EPartService;
import org.eclipse.e4.ui.workbench.renderers.swt.TrimmedPartLayout;
import org.eclipse.emf.common.util.URI;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.ControlListener;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.swt.widgets.ToolItem;
import org.osgi.service.event.EventHandler;
/**
* Class for representing window trim containing minimized views and shared areas
*/
public class TrimStack {
/**
* Contribution URI for this class
*/
public static String CONTRIBUTION_URI = "bundleclass://org.eclipse.e4.ui.workbench.addons.swt/org.eclipse.e4.ui.workbench.addons.minmax.TrimStack"; //$NON-NLS-1$
private static final String LAYOUT_ICON_URI = "platform:/plugin/org.eclipse.e4.ui.workbench.addons.swt/icons/full/obj16/layout_co.gif"; //$NON-NLS-1$
private static final String RESTORE_ICON_URI = "platform:/plugin/org.eclipse.e4.ui.workbench.addons.swt/icons/full/etool16/fastview_restore.gif"; //$NON-NLS-1$
private static final String STATE_XSIZE = "XSize"; //$NON-NLS-1$
private static final String STATE_YSIZE = "YSize"; //$NON-NLS-1$
private Image layoutImage;
private Image restoreImage;
private ToolBar trimStackTB;
/**
* The context menu for this trim stack's items.
*/
private Menu trimStackMenu;
/**
* The tool item that the cursor is currently hovering over.
*/
private ToolItem selectedToolItem;
private boolean isShowing = false;
private MUIElement minimizedElement;
private Composite hostPane;
@Inject
@Named("org.eclipse.e4.ui.workbench.IResourceUtilities")
private IResourceUtilities<ImageDescriptor> resUtils;
/**
* A map of created images from a part's icon URI path.
*/
private Map<String, Image> imageMap = new HashMap<String, Image>();
ControlListener caResizeListener = new ControlListener() {
public void controlResized(ControlEvent e) {
if (hostPane != null && hostPane.isVisible())
setPaneLocation(hostPane);
}
public void controlMoved(ControlEvent e) {
}
};
// Listens to ESC and closes the active fast view
private Listener escapeListener = new Listener() {
public void handleEvent(Event event) {
if (event.character == SWT.ESC) {
partService.requestActivation();
}
}
};
@Inject
EModelService modelService;
@Inject
EPartService partService;
@Inject
MWindow window;
@Inject
MToolControl toolControl;
@Inject
protected IEventBroker eventBroker;
private EventHandler closeHandler = new EventHandler() {
public void handleEvent(org.osgi.service.event.Event event) {
if (!isShowing)
return;
// The only time we don't close is if I've selected my tab.
MUIElement changedElement = (MUIElement) event.getProperty(UIEvents.EventTags.ELEMENT);
// Perspective changed, close the visible stacks
if (changedElement instanceof MPerspectiveStack) {
showStack(false);
return;
}
if (changedElement == getLeafPart(minimizedElement)) {
fixToolItemSelection((MPart) changedElement);
return;
}
showStack(false);
}
};
private void fixToolItemSelection(MPart part) {
if (trimStackTB == null || trimStackTB.isDisposed())
return;
if (isEditorStack()) {
trimStackTB.getItem(1).setSelection(part != null);
if (part != null)
trimStackTB.getItem(1).setData(part);
} else {
for (ToolItem item : trimStackTB.getItems()) {
boolean result = item.getData() == null ? false : item.getData() == part;
item.setSelection(result);
}
}
}
private boolean isEditorStack() {
return minimizedElement instanceof MPlaceholder;
}
private MPart getLeafPart(MUIElement element) {
if (element instanceof MPlaceholder)
return getLeafPart(((MPlaceholder) element).getRef());
if (element instanceof MElementContainer<?>)
return getLeafPart(((MElementContainer<?>) element).getSelectedElement());
if (element instanceof MPart)
return (MPart) element;
return null;
}
private EventHandler openHandler = new EventHandler() {
public void handleEvent(org.osgi.service.event.Event event) {
if (isShowing)
return;
MUIElement changedElement = (MUIElement) event.getProperty(UIEvents.EventTags.ELEMENT);
// Open if shared area
if (getLeafPart(minimizedElement) == changedElement) {
showStack(true);
return;
}
MUIElement selectedElement = null;
if (minimizedElement instanceof MPlaceholder) {
selectedElement = ((MPlaceholder) minimizedElement).getRef();
} else if (minimizedElement instanceof MPartStack) {
selectedElement = ((MPartStack) minimizedElement).getSelectedElement();
}
if (selectedElement == null)
return;
if (selectedElement instanceof MPlaceholder)
selectedElement = ((MPlaceholder) selectedElement).getRef();
if (changedElement != selectedElement)
return;
showStack(true);
}
};
private EventHandler toBeRenderedHandler = new EventHandler() {
public void handleEvent(org.osgi.service.event.Event event) {
if (minimizedElement == null || trimStackTB == null)
return;
MUIElement changedElement = (MUIElement) event.getProperty(UIEvents.EventTags.ELEMENT);
// if our stack is going away, so should we
if (changedElement == minimizedElement && !minimizedElement.isToBeRendered()) {
restoreStack();
return;
}
// if one of the kids changes state, re-scrape the CTF
MUIElement parentElement = changedElement.getParent();
if (parentElement == minimizedElement) {
trimStackTB.getDisplay().asyncExec(new Runnable() {
public void run() {
updateTrimStackItems();
}
});
}
}
};
private EventHandler childrenHandler = new EventHandler() {
public void handleEvent(org.osgi.service.event.Event event) {
if (minimizedElement == null || trimStackTB == null)
return;
Object changedObj = event.getProperty(UIEvents.EventTags.ELEMENT);
// if a child has been added or removed, re-scape the CTF
if (changedObj == minimizedElement) {
trimStackTB.getDisplay().asyncExec(new Runnable() {
public void run() {
updateTrimStackItems();
}
});
}
}
};
private EventHandler widgetHandler = new EventHandler() {
public void handleEvent(org.osgi.service.event.Event event) {
Object changedObj = event.getProperty(UIEvents.EventTags.ELEMENT);
if (changedObj != minimizedElement)
return;
if (minimizedElement.getWidget() != null) {
trimStackTB.getDisplay().asyncExec(new Runnable() {
public void run() {
updateTrimStackItems();
}
});
}
}
};
// Listener attached to every ToolItem in a TrimStack. Responsible for activating the
// appropriate part.
private SelectionListener toolItemSelectionListener = new SelectionListener() {
public void widgetSelected(SelectionEvent e) {
ToolItem toolItem = (ToolItem) e.widget;
MUIElement uiElement = (MUIElement) toolItem.getData();
if (toolItem.getSelection()) {
partService.activate((MPart) uiElement);
} else {
// Get partService to activate a part visible in the presentation
partService.requestActivation();
}
}
public void widgetDefaultSelected(SelectionEvent e) {
widgetSelected(e);
}
};
private MTrimBar bar;
private int fixedSides;
@PostConstruct
void addListeners() {
eventBroker.subscribe(UIEvents.ElementContainer.TOPIC_CHILDREN, childrenHandler);
eventBroker.subscribe(UIEvents.UIElement.TOPIC_TOBERENDERED, toBeRenderedHandler);
eventBroker.subscribe(UIEvents.UIElement.TOPIC_WIDGET, widgetHandler);
eventBroker.subscribe(UIEvents.UILifeCycle.BRINGTOTOP, openHandler);
eventBroker.subscribe(UIEvents.UILifeCycle.ACTIVATE, closeHandler);
}
@PreDestroy
void removeListeners() {
eventBroker.unsubscribe(toBeRenderedHandler);
eventBroker.unsubscribe(childrenHandler);
eventBroker.unsubscribe(widgetHandler);
eventBroker.unsubscribe(openHandler);
eventBroker.unsubscribe(closeHandler);
}
@PostConstruct
void createWidget(Composite parent, MToolControl me) {
if (minimizedElement == null) {
minimizedElement = findElement();
}
MUIElement meParent = me.getParent();
int orientation = SWT.HORIZONTAL;
if (meParent instanceof MTrimBar) {
bar = (MTrimBar) meParent;
if (bar.getSide() == SideValue.RIGHT || bar.getSide() == SideValue.LEFT)
orientation = SWT.VERTICAL;
}
trimStackTB = new ToolBar(parent, orientation | SWT.FLAT | SWT.WRAP);
trimStackTB.addListener(SWT.MenuDetect, new Listener() {
public void handleEvent(Event event) {
// remap the coordinate relative to the control
Point point = trimStackTB.getDisplay().map(null, trimStackTB,
new Point(event.x, event.y));
// get the selected item in question
selectedToolItem = trimStackTB.getItem(point);
}
});
if (minimizedElement instanceof MPartStack)
createPopupMenu();
ToolItem restoreBtn = new ToolItem(trimStackTB, SWT.PUSH);
restoreBtn.setToolTipText(Messages.TrimStack_RestoreText);
restoreBtn.setImage(getRestoreImage());
restoreBtn.addSelectionListener(new SelectionListener() {
public void widgetSelected(SelectionEvent e) {
minimizedElement.getTags().remove(IPresentationEngine.MINIMIZED);
}
public void widgetDefaultSelected(SelectionEvent e) {
minimizedElement.getTags().remove(IPresentationEngine.MINIMIZED);
}
});
updateTrimStackItems();
}
@PreDestroy
void destroy() {
for (Image image : imageMap.values()) {
image.dispose();
}
if (layoutImage != null) {
layoutImage.dispose();
layoutImage = null;
}
if (restoreImage != null) {
restoreImage.dispose();
restoreImage = null;
}
}
private MUIElement findElement() {
MUIElement result;
List<MPerspectiveStack> ps = modelService.findElements(window, null,
MPerspectiveStack.class, null);
if (ps.size() == 0) {
String toolControlId = toolControl.getElementId();
int index = toolControlId.indexOf('(');
String stackId = toolControlId.substring(0, index);
result = modelService.find(stackId, window);
} else {
String toolControlId = toolControl.getElementId();
int index = toolControlId.indexOf('(');
String stackId = toolControlId.substring(0, index);
String perspId = toolControlId.substring(index + 1, toolControlId.length() - 1);
MPerspective persp = (MPerspective) modelService.find(perspId, ps.get(0));
if (persp != null) {
result = modelService.find(stackId, persp);
} else {
result = modelService.find(stackId, window);
}
}
return result;
}
private String getLabel(MUILabel label) {
String string = label.getLabel();
return string == null ? "" : string; //$NON-NLS-1$
}
private Image getImage(MUILabel element) {
String iconURI = element.getIconURI();
if (iconURI != null && iconURI.length() > 0) {
Image image = imageMap.get(iconURI);
if (image == null) {
image = resUtils.imageDescriptorFromURI(URI.createURI(iconURI)).createImage();
imageMap.put(iconURI, image);
}
return image;
}
return null;
}
private MPart getPart(MStackElement element) {
if (element instanceof MPart) {
return (MPart) element;
}
return (MPart) ((MPlaceholder) element).getRef();
}
private void updateTrimStackItems() {
// Prevent exceptions on shutdown
if (trimStackTB == null || trimStackTB.isDisposed())
return;
if (isEditorStack()) {
if (trimStackTB.getItemCount() == 1) {
MUIElement data = getLeafPart(minimizedElement);
ToolItem ti = new ToolItem(trimStackTB, SWT.CHECK);
ti.setToolTipText(Messages.TrimStack_SharedAreaTooltip);
ti.setImage(getLayoutImage());
ti.setData(data);
ti.addSelectionListener(toolItemSelectionListener);
}
} else if (minimizedElement instanceof MPartStack) {
MPartStack theStack = (MPartStack) minimizedElement;
if (theStack.getWidget() == null) {
return;
}
// check to see if this stack has any valid elements
boolean check = false;
for (MStackElement stackElement : theStack.getChildren()) {
if (stackElement.isToBeRendered()) {
check = true;
break;
}
}
if (!check) {
// doesn't have any children that's showing, place it back in the presentation
restoreStack();
return;
}
// Remove any current items except the 'restore' button
while (trimStackTB.getItemCount() > 1) {
trimStackTB.getItem(trimStackTB.getItemCount() - 1).dispose();
}
for (MStackElement stackElement : theStack.getChildren()) {
if (!stackElement.isToBeRendered()) {
continue;
}
MPart part = getPart(stackElement);
ToolItem newItem = new ToolItem(trimStackTB, SWT.CHECK);
newItem.setData(part);
newItem.setImage(getImage(part));
newItem.setToolTipText(getLabel(part));
newItem.addSelectionListener(toolItemSelectionListener);
}
}
trimStackTB.pack();
trimStackTB.getShell().layout(new Control[] { trimStackTB }, SWT.DEFER);
trimStackTB.addDisposeListener(new DisposeListener() {
public void widgetDisposed(DisposeEvent e) {
trimStackTB = null;
}
});
}
void restoreStack() {
minimizedElement.setVisible(true);
minimizedElement.getTags().remove(IPresentationEngine.MINIMIZED);
toolControl.setToBeRendered(false);
if (hostPane != null && !hostPane.isDisposed())
hostPane.dispose();
hostPane = null;
}
/**
* Create the popup menu that will appear when a minimized part has been selected by the cursor.
*/
private void createPopupMenu() {
trimStackMenu = new Menu(trimStackTB);
trimStackTB.setMenu(trimStackMenu);
MenuItem closeItem = new MenuItem(trimStackMenu, SWT.NONE);
closeItem.setText(Messages.TrimStack_CloseText);
closeItem.addListener(SWT.Selection, new Listener() {
public void handleEvent(Event event) {
partService.hidePart((MPart) selectedToolItem.getData());
}
});
}
protected void showStack(boolean show) {
Control ctf = (Control) minimizedElement.getWidget();
Composite clientArea = getShellClientComposite();
if (clientArea == null)
return;
if (show && !isShowing) {
hostPane = getHostPane();
ctf.setParent(hostPane);
clientArea.addControlListener(caResizeListener);
// Set the initial location
setPaneLocation(hostPane);
hostPane.layout(true);
hostPane.moveAbove(null);
hostPane.setVisible(true);
isShowing = true;
} else if (!show && isShowing) {
// Check to ensure that the client area is non-null since the
// trimstack may be currently hosted in the limbo shell
if (clientArea != null)
clientArea.removeControlListener(caResizeListener);
if (hostPane != null && hostPane.isVisible()) {
hostPane.setVisible(false);
// capture the current shell's bounds
Point size = hostPane.getSize();
toolControl.getPersistedState().put(STATE_XSIZE, Integer.toString(size.x));
toolControl.getPersistedState().put(STATE_YSIZE, Integer.toString(size.y));
}
fixToolItemSelection(null);
isShowing = false;
}
}
Composite getShellClientComposite() {
if (trimStackTB == null || trimStackTB.isDisposed()) {
return null;
}
Shell theShell = trimStackTB.getShell();
if (!(theShell.getLayout() instanceof TrimmedPartLayout))
return null;
TrimmedPartLayout tpl = (TrimmedPartLayout) theShell.getLayout();
return tpl.clientArea;
}
private void setPaneLocation(Composite someShell) {
Composite clientComp = getShellClientComposite();
if (clientComp == null || clientComp.isDisposed())
return;
Rectangle caRect = getShellClientComposite().getBounds();
Point paneSize = hostPane.getSize();
Point loc = new Point(0, 0);
if (isFixed(SWT.LEFT))
loc.x = caRect.x;
else
loc.x = (caRect.x + caRect.width) - paneSize.x;
if (isFixed(SWT.TOP))
loc.y = caRect.y;
else
loc.y = (caRect.y + caRect.height) - paneSize.y;
someShell.setLocation(loc);
}
private Composite getHostPane() {
if (hostPane != null)
return hostPane;
// Create one
hostPane = new Composite(trimStackTB.getShell(), SWT.NONE);
hostPane.setData(ShellActivationListener.DIALOG_IGNORE_KEY, Boolean.TRUE);
int xSize = 600;
String xSizeStr = toolControl.getPersistedState().get(STATE_XSIZE);
if (xSizeStr != null)
xSize = Integer.parseInt(xSizeStr);
int ySize = 400;
String ySizeStr = toolControl.getPersistedState().get(STATE_YSIZE);
if (ySizeStr != null)
ySize = Integer.parseInt(ySizeStr);
hostPane.setSize(xSize, ySize);
hostPane.addListener(SWT.Traverse, escapeListener);
// Set a special layout that allows resizing
fixedSides = getFixedSides();
hostPane.setLayout(new TrimPaneLayout(fixedSides));
return hostPane;
}
private int getFixedSides() {
int verticalValue = SWT.TOP; // should be based on the center of the TB
if (bar.getSide() == SideValue.LEFT)
return SWT.LEFT | verticalValue;
else if (bar.getSide() == SideValue.RIGHT)
return SWT.RIGHT | verticalValue;
return SWT.TOP | SWT.LEFT;
}
private Image getLayoutImage() {
if (layoutImage == null) {
layoutImage = resUtils.imageDescriptorFromURI(URI.createURI(LAYOUT_ICON_URI))
.createImage();
}
return layoutImage;
}
private Image getRestoreImage() {
if (restoreImage == null) {
restoreImage = resUtils.imageDescriptorFromURI(URI.createURI(RESTORE_ICON_URI))
.createImage();
}
return restoreImage;
}
private boolean isFixed(int swtSide) {
return (fixedSides & swtSide) != 0;
}
}