blob: bb3bad649ab3bf1d0a5550d79fd22bdb0c1c8877 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010, 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@vogella.com - Bug 454712, 485851
* dirk.fauth@googlemail.com - Bug 446095
******************************************************************************/
package org.eclipse.e4.ui.workbench.addons.minmax;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.inject.Inject;
import javax.inject.Named;
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.CSSRenderingUtils;
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.MGenericStack;
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.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.MCompositePart;
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.UIEvents.EventTags;
import org.eclipse.e4.ui.workbench.addons.minmax.TrimStackIdHelper.TrimStackIdPart;
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.custom.CTabFolder;
import org.eclipse.swt.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.ControlListener;
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.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.png"; //$NON-NLS-1$
private static final String RESTORE_ICON_URI = "platform:/plugin/org.eclipse.e4.ui.workbench.addons.swt/icons/full/etool16/fastview_restore.png"; //$NON-NLS-1$
public static final String USE_OVERLAYS_KEY = "UseOverlays"; //$NON-NLS-1$
static final String STATE_XSIZE = "XSize"; //$NON-NLS-1$
static final String STATE_YSIZE = "YSize"; //$NON-NLS-1$
public static final String MINIMIZED_AND_SHOWING = "MinimizedAndShowing"; //$NON-NLS-1$
private Image layoutImage;
private Image restoreImage;
private ToolBar trimStackTB;
/**
* The context menu for this trim stack's items.
*/
private Menu trimStackMenu;
private boolean cachedUseOverlays = true;
private boolean isShowing = false;
private MUIElement minimizedElement;
// private Composite clientAreaComposite;
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<>();
ControlListener caResizeListener = new ControlAdapter() {
@Override
public void controlResized(ControlEvent e) {
if (hostPane != null && hostPane.isVisible()) {
setPaneLocation();
}
}
};
@Inject
EModelService modelService;
@Inject
EPartService partService;
@Inject
MWindow window;
@Inject
MToolControl toolControl;
@Inject
protected IEventBroker eventBroker;
// Listens to ESC and closes the active fast view
private Listener escapeListener = event -> {
if (event.character == SWT.ESC) {
showStack(false);
partService.requestActivation();
}
};
@Inject
@Optional
private void subscribeTopicTagsChanged(
@UIEventTopic(UIEvents.ApplicationElement.TOPIC_TAGS) org.osgi.service.event.Event event) {
Object changedObj = event.getProperty(EventTags.ELEMENT);
if (!(changedObj instanceof MToolControl)) {
return;
}
final MToolControl changedElement = (MToolControl) changedObj;
if (changedElement.getObject() != this) {
return;
}
if (UIEvents.isREMOVE(event)) {
if (UIEvents.contains(event, UIEvents.EventTags.OLD_VALUE, MINIMIZED_AND_SHOWING)) {
showStack(false);
}
}
}
private Image getOverrideImage(MUIElement element) {
Image result = null;
Object imageObject = element.getTransientData().get(
IPresentationEngine.OVERRIDE_ICON_IMAGE_KEY);
if (imageObject != null && imageObject instanceof Image
&& !((Image) imageObject).isDisposed()) {
result = (Image) imageObject;
}
return result;
}
private String getOverrideTitleToolTip(MUIElement element) {
String result = null;
Object stringObject = element.getTransientData().get(
IPresentationEngine.OVERRIDE_TITLE_TOOL_TIP_KEY);
if (stringObject != null && stringObject instanceof String) {
result = (String) stringObject;
}
if (result == null || result.length() == 0) {
return null;
}
if (element instanceof MUILabel) {
String label = ((MUILabel)element).getLocalizedLabel();
if (label != null && label.length() > 0) {
result = label + ' ' + '(' + result + ')';
}
}
return result;
}
/**
* This is the new way to handle UIEvents (as opposed to subscring and unsubscribing them with
* the event broker.
*
* The method is described in detail at http://wiki.eclipse.org/Eclipse4/RCP/Event_Model
*/
@SuppressWarnings("unchecked")
@Inject
@Optional
private void handleTransientDataEvents(
@UIEventTopic(UIEvents.ApplicationElement.TOPIC_TRANSIENTDATA) org.osgi.service.event.Event event) {
// Prevent exceptions on shutdown
if (trimStackTB == null || trimStackTB.isDisposed() || minimizedElement.getWidget() == null) {
return;
}
Object changedElement = event.getProperty(UIEvents.EventTags.ELEMENT);
if (!(changedElement instanceof MUIElement)) {
return;
}
String key;
if (UIEvents.isREMOVE(event)) {
key = ((Entry<String, Object>) event.getProperty(UIEvents.EventTags.OLD_VALUE))
.getKey();
} else {
key = ((Entry<String, Object>) event.getProperty(UIEvents.EventTags.NEW_VALUE))
.getKey();
}
if (key.equals(IPresentationEngine.OVERRIDE_ICON_IMAGE_KEY)) {
ToolItem toolItem = getChangedToolItem((MUIElement) changedElement);
if (toolItem != null) {
toolItem.setImage(getImage((MUILabel) toolItem.getData()));
}
} else if (key.equals(IPresentationEngine.OVERRIDE_TITLE_TOOL_TIP_KEY)) {
ToolItem toolItem = getChangedToolItem((MUIElement) changedElement);
if (toolItem != null) {
toolItem.setToolTipText(getLabelText((MUILabel) toolItem.getData()));
}
}
}
private ToolItem getChangedToolItem(MUIElement changedElement) {
ToolItem[] toolItems = trimStackTB.getItems();
for (ToolItem toolItem : toolItems) {
if (changedElement.equals(toolItem.getData())) {
return toolItem;
}
}
return null;
}
/**
* This is the old way to subscribe to UIEvents. You should consider using the new way as shown
* by handleTransientDataEvents() and described in the article at
* http://wiki.eclipse.org/Eclipse4/RCP/Event_Model
*/
private EventHandler closeHandler = 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 instanceof MCompositePart) {
MPart innerPart = getLeafPart(changedElement);
if (innerPart != null) {
fixToolItemSelection();
return;
}
}
if (changedElement == getLeafPart(minimizedElement)) {
fixToolItemSelection();
return;
}
showStack(false);
};
// Close any open stacks before shutting down
private EventHandler shutdownHandler = event -> showStack(false);
private void fixToolItemSelection() {
if (trimStackTB == null || trimStackTB.isDisposed()) {
return;
}
if (!isShowing) {
// Not open...no selection
for (ToolItem item : trimStackTB.getItems()) {
item.setSelection(false);
}
} else {
if (isEditorStack() || minimizedElement instanceof MPlaceholder) {
trimStackTB.getItem(1).setSelection(true);
} else if (isPerspectiveStack()) {
MPerspectiveStack pStack = (MPerspectiveStack) minimizedElement;
MUIElement selElement = pStack.getSelectedElement();
for (ToolItem item : trimStackTB.getItems()) {
item.setSelection(item.getData() == selElement);
}
} else {
MPartStack partStack = (MPartStack) minimizedElement;
MUIElement selElement = partStack.getSelectedElement();
if (selElement instanceof MPlaceholder) {
selElement = ((MPlaceholder) selElement).getRef();
}
for (ToolItem item : trimStackTB.getItems()) {
boolean isSel = item.getData() == selElement;
item.setSelection(isSel);
}
}
}
}
private boolean isEditorStack() {
if (!(minimizedElement instanceof MPlaceholder)) {
return false;
}
MPlaceholder ph = (MPlaceholder) minimizedElement;
return ph.getRef() instanceof MArea;
}
private boolean isPerspectiveStack() {
return minimizedElement instanceof MPerspectiveStack;
}
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;
}
/**
* This is the old way to subscribe to UIEvents. You should consider using the new way as shown
* by handleTransientDataEvents() and described in the article at
* http://wiki.eclipse.org/Eclipse4/RCP/Event_Model
*/
private EventHandler openHandler = event -> {
if (isShowing) {
return;
}
MUIElement changedElement = (MUIElement) event.getProperty(UIEvents.EventTags.ELEMENT);
// Open if shared area
if (getLeafPart(minimizedElement) == changedElement && !(minimizedElement instanceof MPerspectiveStack)) {
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);
};
/**
* This is the old way to subscribe to UIEvents. You should consider using the new way as shown
* by handleTransientDataEvents() and described in the article at
* http://wiki.eclipse.org/Eclipse4/RCP/Event_Model
*/
private EventHandler toBeRenderedHandler = 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(() -> updateTrimStackItems());
}
};
/**
* This is the old way to subscribe to UIEvents. You should consider using the new way as shown
* by handleTransientDataEvents() and described in the article at
* http://wiki.eclipse.org/Eclipse4/RCP/Event_Model
*/
private EventHandler childrenHandler = 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(() -> updateTrimStackItems());
}
};
/**
* This is the old way to subscribe to UIEvents. You should consider using the new way as shown
* by handleTransientDataEvents() and described in the article at
* http://wiki.eclipse.org/Eclipse4/RCP/Event_Model
*/
private EventHandler widgetHandler = event -> {
Object changedObj = event.getProperty(UIEvents.EventTags.ELEMENT);
if (changedObj != minimizedElement) {
return;
}
if (minimizedElement.getWidget() != null) {
trimStackTB.getDisplay().asyncExec(() -> updateTrimStackItems());
}
};
/**
* This is the old way to subscribe to UIEvents. You should consider using the new way as shown
* by handleTransientDataEvents() and described in the article at
* http://wiki.eclipse.org/Eclipse4/RCP/Event_Model
*/
// Listener attached to every ToolItem in a TrimStack. Responsible for activating the
// appropriate part.
private SelectionListener toolItemSelectionListener = new SelectionListener() {
@Override
public void widgetSelected(SelectionEvent e) {
ToolItem toolItem = (ToolItem) e.widget;
MUIElement uiElement = (MUIElement) toolItem.getData();
// Clicking on the already showing item ? NOTE: the selection will already have been
// turned off by the time the event arrives
if (!toolItem.getSelection()) {
partService.requestActivation();
showStack(false);
return;
}
if (uiElement instanceof MPart) {
partService.activate((MPart) uiElement);
} else if (uiElement instanceof MPerspective) {
uiElement.getParent().setSelectedElement(uiElement);
}
showStack(true);
}
@Override
public void widgetDefaultSelected(SelectionEvent e) {
widgetSelected(e);
}
};
// private MTrimBar bar;
private int fixedSides;
private Composite originalParent;
/**
* This is the old way to subscribe to UIEvents. You should consider using the new way as shown
* by handleTransientDataEvents() and described in the article at
* http://wiki.eclipse.org/Eclipse4/RCP/Event_Model
*/
@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);
eventBroker.subscribe(UIEvents.UILifeCycle.APP_SHUTDOWN_STARTED, shutdownHandler);
}
private Composite getCAComposite() {
if (trimStackTB == null) {
return null;
}
// Get the shell's client area composite
Shell theShell = trimStackTB.getShell();
if (theShell.getLayout() instanceof TrimmedPartLayout) {
TrimmedPartLayout tpl = (TrimmedPartLayout) theShell.getLayout();
if (!tpl.clientArea.isDisposed()) {
return tpl.clientArea;
}
}
return null;
}
/**
* This is the old way to subscribe to UIEvents. You should consider using the new way as shown
* by handleTransientDataEvents() and described in the article at
* http://wiki.eclipse.org/Eclipse4/RCP/Event_Model
*/
@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, CSSRenderingUtils cssUtils) {
if (minimizedElement == null) {
minimizedElement = findElement();
}
MUIElement meParent = me.getParent();
int orientation = SWT.HORIZONTAL;
if (meParent instanceof MTrimBar) {
MTrimBar bar = (MTrimBar) meParent;
if (bar.getSide() == SideValue.RIGHT || bar.getSide() == SideValue.LEFT) {
orientation = SWT.VERTICAL;
}
// TrimStacks are draggable by default
me.getTags().add(IPresentationEngine.DRAGGABLE);
}
trimStackTB = new ToolBar(parent, orientation | SWT.FLAT | SWT.WRAP);
trimStackTB.addDisposeListener(e -> {
showStack(false);
trimStackTB = null;
trimStackMenu = null;
});
trimStackTB.addListener(SWT.MenuDetect, event -> {
// Clear any existing menus
while (trimStackMenu.getItemCount() > 0) {
trimStackMenu.getItem(0).dispose();
}
// Only open the menu if a tool item is selected
Point point = trimStackTB.getDisplay().map(null, trimStackTB, new Point(event.x, event.y));
ToolItem selectedToolItem = trimStackTB.getItem(point);
if (selectedToolItem == null) {
return;
}
// Are we hovering over a valid tool item (vs restore button)
Object data = selectedToolItem.getData();
if (data instanceof MPart) {
// A part on a stack or editor area
createPartMenu((MPart) data);
} else if (data instanceof MPerspective) {
// A perspective in a perspective stack (for now we just support restore)
createEmtpyEditorAreaMenu();
} else if (isEditorStack()) {
// An empty editor area
createEmtpyEditorAreaMenu();
} else {
createUseOverlaysMenu();
}
});
trimStackMenu = new Menu(trimStackTB);
trimStackTB.setMenu(trimStackMenu);
ToolItem restoreBtn = new ToolItem(trimStackTB, SWT.PUSH);
restoreBtn.setToolTipText(Messages.TrimStack_RestoreText);
restoreBtn.setImage(getRestoreImage());
restoreBtn.addSelectionListener(SelectionListener
.widgetSelectedAdapter(e -> minimizedElement.getTags().remove(IPresentationEngine.MINIMIZED)));
updateTrimStackItems();
}
/**
* Creates a restore menu item that removes the minimized tag from the {@link #minimizedElement}
*/
private void createEmtpyEditorAreaMenu() {
MenuItem restoreItem = new MenuItem(trimStackMenu, SWT.NONE);
restoreItem.setText(Messages.TrimStack_RestoreText);
restoreItem.addListener(SWT.Selection,
event -> minimizedElement.getTags().remove(IPresentationEngine.MINIMIZED));
}
/**
* Creates a restore menu item that removes the minimized tag from the {@link #minimizedElement}
*/
private void createUseOverlaysMenu() {
MenuItem useOverlaysItem = new MenuItem(trimStackMenu, SWT.CHECK);
useOverlaysItem.setText(Messages.TrimStack_Show_In_Original_Location);
useOverlaysItem.setSelection(!useOverlays());
useOverlaysItem.addListener(SWT.Selection, event -> {
if (toolControl != null) {
toolControl.getPersistedState().put(USE_OVERLAYS_KEY, Boolean.toString(!useOverlays()));
}
});
}
/**
* Creates a series of menu items when a part is selected. The orientation submenu changes the
* layout tags on the {@link #minimizedElement}. The restore item will remove the minimized tag.
* The close item is not available on the editor stack, but will ask the part service to hide
* the part.
*
* @param selectedPart
* the part from the data of the selected tool item
*/
private void createPartMenu(final MPart selectedPart) {
MenuItem orientationItem = new MenuItem(trimStackMenu, SWT.CASCADE);
orientationItem.setText(Messages.TrimStack_OrientationMenu);
Menu orientationMenu = new Menu(orientationItem);
orientationItem.setMenu(orientationMenu);
MenuItem defaultItem = new MenuItem(orientationMenu, SWT.RADIO);
defaultItem.setText(Messages.TrimStack_DefaultOrientationItem);
defaultItem.addListener(SWT.Selection, event -> {
boolean doRefresh = minimizedElement.getTags().remove(IPresentationEngine.ORIENTATION_HORIZONTAL);
doRefresh |= minimizedElement.getTags().remove(IPresentationEngine.ORIENTATION_VERTICAL);
if (isShowing && doRefresh) {
setPaneLocation();
}
});
MenuItem horizontalItem = new MenuItem(orientationMenu, SWT.RADIO);
horizontalItem.setText(Messages.TrimStack_Horizontal);
horizontalItem.addListener(SWT.Selection, event -> {
if (!minimizedElement.getTags().contains(IPresentationEngine.ORIENTATION_HORIZONTAL)) {
minimizedElement.getTags().remove(IPresentationEngine.ORIENTATION_VERTICAL);
minimizedElement.getTags().add(IPresentationEngine.ORIENTATION_HORIZONTAL);
if (isShowing) {
setPaneLocation();
}
}
});
MenuItem verticalItem = new MenuItem(orientationMenu, SWT.RADIO);
verticalItem.setText(Messages.TrimStack_Vertical);
verticalItem.addListener(SWT.Selection, event -> {
if (!minimizedElement.getTags().contains(IPresentationEngine.ORIENTATION_VERTICAL)) {
minimizedElement.getTags().remove(IPresentationEngine.ORIENTATION_HORIZONTAL);
minimizedElement.getTags().add(IPresentationEngine.ORIENTATION_VERTICAL);
if (isShowing) {
setPaneLocation();
}
}
});
// Set initial orientation selection
if (minimizedElement.getTags().contains(IPresentationEngine.ORIENTATION_HORIZONTAL)) {
horizontalItem.setSelection(true);
} else if (minimizedElement.getTags().contains(IPresentationEngine.ORIENTATION_VERTICAL)) {
verticalItem.setSelection(true);
} else {
defaultItem.setSelection(true);
}
MenuItem restoreItem = new MenuItem(trimStackMenu, SWT.NONE);
restoreItem.setText(Messages.TrimStack_RestoreText);
restoreItem.addListener(SWT.Selection, event -> {
minimizedElement.getTags().remove(IPresentationEngine.MINIMIZED);
partService.activate(selectedPart);
});
// Do not allow the shared editor area to be closed
if (!isEditorStack()) {
MenuItem closeItem = new MenuItem(trimStackMenu, SWT.NONE);
closeItem.setText(Messages.TrimStack_CloseText);
closeItem.addListener(SWT.Selection, event -> partService.hidePart(selectedPart));
}
}
@PreDestroy
void destroy() {
for (Image image : imageMap.values()) {
image.dispose();
}
if (layoutImage != null) {
layoutImage.dispose();
layoutImage = null;
}
if (restoreImage != null) {
restoreImage.dispose();
restoreImage = null;
}
}
public MUIElement getMinimizedElement() {
return minimizedElement;
}
private MUIElement findElement() {
MUIElement result;
List<MPerspectiveStack> ps = modelService.findElements(window, null,
MPerspectiveStack.class, null);
if (ps.isEmpty()) {
String toolControlId = toolControl.getElementId();
int index = toolControlId.indexOf('(');
String stackId = toolControlId.substring(0, index);
result = modelService.find(stackId, window);
} else {
String toolControlId = toolControl.getElementId();
Map<TrimStackIdPart, String> parsedIds = TrimStackIdHelper.parseTrimStackId(toolControlId);
String stackId = parsedIds.get(TrimStackIdPart.ELEMENT_ID);
String perspId = parsedIds.get(TrimStackIdPart.PERSPECTIVE_ID);
MPerspective persp = null;
if (perspId != null) {
List<MPerspective> perspectives = modelService.findElements(ps.get(0), perspId, MPerspective.class,
null);
if (perspectives != null && !perspectives.isEmpty()) {
persp = perspectives.get(0);
}
}
if (persp != null) {
result = modelService.find(stackId, persp);
} else {
result = modelService.find(stackId, window);
}
}
return result;
}
private String getLabelText(MUILabel label) {
// Use override text if available
if (label instanceof MUIElement) {
String text = getOverrideTitleToolTip((MUIElement) label);
if (text != null && text.length() > 0) {
return text;
}
}
String string = label.getLocalizedLabel();
return string == null ? "" : string; //$NON-NLS-1$
}
private Image getImage(MUILabel element) {
// Use override image if available
if (element instanceof MUIElement) {
Image image = getOverrideImage((MUIElement) element);
if (image != null) {
return image;
}
}
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 MUILabel getLabelElement(MUIElement element) {
if (element instanceof MPlaceholder) {
element = ((MPlaceholder) element).getRef();
}
return (MUILabel) (element instanceof MUILabel ? element : null);
}
private void updateTrimStackItems() {
// Prevent exceptions on shutdown
if (trimStackTB == null || trimStackTB.isDisposed() || minimizedElement.getWidget() == null) {
return;
}
// Remove any current items except the 'restore' button
while (trimStackTB.getItemCount() > 1) {
trimStackTB.getItem(trimStackTB.getItemCount() - 1).dispose();
}
if (isEditorStack() && trimStackTB.getItemCount() == 1) {
ToolItem ti = new ToolItem(trimStackTB, SWT.CHECK);
ti.setToolTipText(Messages.TrimStack_SharedAreaTooltip);
ti.setImage(getLayoutImage());
ti.addSelectionListener(toolItemSelectionListener);
} else if (minimizedElement instanceof MPlaceholder) {
MPlaceholder ph = (MPlaceholder) minimizedElement;
if (ph.getRef() instanceof MPart) {
MPart part = (MPart) ph.getRef();
ToolItem ti = new ToolItem(trimStackTB, SWT.CHECK);
ti.setData(part);
ti.setImage(getImage(part));
ti.setToolTipText(getLabelText(part));
ti.addSelectionListener(toolItemSelectionListener);
}
} else if (minimizedElement instanceof MGenericStack<?>) {
// Handle *both* PartStacks and PerspectiveStacks here...
MGenericStack<?> theStack = (MGenericStack<?>) minimizedElement;
// check to see if this stack has any valid elements
boolean hasRenderedElements = false;
for (MUIElement stackElement : theStack.getChildren()) {
if (stackElement.isToBeRendered()) {
hasRenderedElements = true;
break;
}
}
if (hasRenderedElements) {
for (MUIElement stackElement : theStack.getChildren()) {
if (!stackElement.isToBeRendered()) {
continue;
}
MUILabel labelElement = getLabelElement(stackElement);
ToolItem newItem = new ToolItem(trimStackTB, SWT.CHECK);
newItem.setData(labelElement);
newItem.setImage(getImage(labelElement));
newItem.setToolTipText(getLabelText(labelElement));
newItem.addSelectionListener(toolItemSelectionListener);
}
} else if (theStack.getTags().contains(IPresentationEngine.NO_AUTO_COLLAPSE)) {
// OK to be empty and still minimized
ToolItem ti = new ToolItem(trimStackTB, SWT.CHECK);
ti.setToolTipText(Messages.TrimStack_EmptyStackTooltip);
ti.setImage(getLayoutImage());
ti.addSelectionListener(toolItemSelectionListener);
} else {
// doesn't have any children that's showing, place it back in the presentation
restoreStack();
return;
}
}
trimStackTB.pack();
trimStackTB.requestLayout();
}
void restoreStack() {
showStack(false);
minimizedElement.setVisible(true);
minimizedElement.getTags().remove(IPresentationEngine.MINIMIZED);
// Activate the part that is being brought up...
if (minimizedElement instanceof MPartStack) {
MPartStack theStack = (MPartStack) minimizedElement;
MStackElement curSel = theStack.getSelectedElement();
Control ctrl = (Control) minimizedElement.getWidget();
// Hack for elems that are lazy initialized
if (ctrl instanceof CTabFolder && ((CTabFolder) ctrl).getSelection() == null) {
theStack.setSelectedElement(null);
theStack.setSelectedElement(curSel);
}
}
toolControl.setToBeRendered(false);
if (hostPane != null && !hostPane.isDisposed()) {
hostPane.dispose();
}
hostPane = null;
}
/**
* Sets whether this stack should be visible or hidden
*
* @param show
* whether the stack should be visible
*/
public void showStack(boolean show) {
Control ctrl = (Control) minimizedElement.getWidget();
if (ctrl == null) {
return;
}
CTabFolder ctf = ctrl instanceof CTabFolder? (CTabFolder) ctrl: null;
Composite clientAreaComposite = getCAComposite();
if (clientAreaComposite == null || clientAreaComposite.isDisposed()) {
return;
}
if (show && !isShowing) {
if (useOverlays()) {
hostPane = getHostPane();
originalParent = ctrl.getParent();
ctrl.setParent(hostPane);
// Hack ! Force a resize of the CTF to make sure the hosted
// view is the correct size...see bug 434062 for details
if (ctf != null) {
Rectangle bb = ctf.getBounds();
bb.width--;
ctf.setBounds(bb);
}
clientAreaComposite.addControlListener(caResizeListener);
// Set the initial location
setPaneLocation();
hostPane.addListener(SWT.Traverse, escapeListener);
hostPane.layout(true);
hostPane.moveAbove(null);
hostPane.setVisible(true);
// Cache the value to ensure that a stack is hidden using the same mode it was
// opened in
cachedUseOverlays = true;
} else {
minimizedElement.setVisible(true);
ctrl.addListener(SWT.Traverse, escapeListener);
// Cache the value to ensure that a stack is hidden using the same mode it was
// opened in
cachedUseOverlays = false;
}
isShowing = true;
toolControl.getTags().add(MINIMIZED_AND_SHOWING);
// Activate the part that is being brought up...
if (minimizedElement instanceof MPartStack) {
MPartStack theStack = (MPartStack) minimizedElement;
MStackElement curSel = theStack.getSelectedElement();
// Hack for elems that are lazy initialized
if (ctf != null && ctf.getSelection() == null) {
theStack.setSelectedElement(null);
theStack.setSelectedElement(curSel);
}
if (curSel instanceof MPart) {
partService.activate((MPart) curSel);
} else if (curSel instanceof MPlaceholder) {
MPlaceholder ph = (MPlaceholder) curSel;
if (ph.getRef() instanceof MPart) {
partService.activate((MPart) ph.getRef());
}
}
} else if (isEditorStack()) {
MArea area = (MArea) ((MPlaceholder) minimizedElement).getRef();
// See if we can find an element to activate...
MPart partToActivate = null;
MUIElement selectedElement = area.getSelectedElement();
while (partToActivate == null && selectedElement != null) {
if (selectedElement instanceof MPart) {
partToActivate = (MPart) selectedElement;
} else if (selectedElement instanceof MPlaceholder) {
MPlaceholder ph = (MPlaceholder) selectedElement;
if (ph.getRef() instanceof MPart) {
partToActivate = (MPart) ph.getRef();
} else {
selectedElement = null;
}
} else if (selectedElement instanceof MElementContainer<?>) {
MElementContainer<?> container = (MElementContainer<?>) selectedElement;
selectedElement = container.getSelectedElement();
}
}
// If we haven't found one then use the first
if (partToActivate == null) {
List<MPart> parts = modelService.findElements(area, null, MPart.class, null);
for (MPart part : parts) {
if (partService.isPartVisible(part)) {
partToActivate = part;
break;
}
}
}
if (partToActivate != null) {
partService.activate(partToActivate);
}
} else if (minimizedElement instanceof MPlaceholder) {
MPlaceholder ph = (MPlaceholder) minimizedElement;
if (ph.getRef() instanceof MPart) {
MPart part = (MPart) ph.getRef();
partService.activate(part);
}
}
fixToolItemSelection();
} else if (!show && isShowing) {
if (cachedUseOverlays) {
clientAreaComposite.removeControlListener(caResizeListener);
ctrl.setParent(originalParent);
hostPane.dispose();
hostPane = null;
} else {
if (ctrl != null && !ctrl.isDisposed()) {
ctrl.removeListener(SWT.Traverse, escapeListener);
}
minimizedElement.setVisible(false);
}
isShowing = false;
toolControl.getTags().remove(MINIMIZED_AND_SHOWING);
fixToolItemSelection();
}
}
/**
* @return 'true' iff the minimized stack should overlay the current presentation, 'false' means
* to temporarily restore the stack into the current presentation.
*/
private boolean useOverlays() {
if (toolControl == null) {
return true;
}
String useOverlays = toolControl.getPersistedState().get(USE_OVERLAYS_KEY);
if (useOverlays == null)
{
useOverlays = "true"; //$NON-NLS-1$
}
return Boolean.parseBoolean(useOverlays);
}
private void setPaneLocation() {
Composite clientAreaComposite = getCAComposite();
if (clientAreaComposite == null || clientAreaComposite.isDisposed()) {
return;
}
Rectangle caRect = clientAreaComposite.getBounds();
// NOTE: always starts in the persisted (or default) size
Point paneSize = hostPane.getSize();
// Ensure it's not clipped
if (paneSize.x > caRect.width) {
paneSize.x = caRect.width;
}
if (paneSize.y > caRect.height) {
paneSize.y = caRect.height;
}
if (minimizedElement.getTags().contains(IPresentationEngine.ORIENTATION_HORIZONTAL)) {
paneSize.x = caRect.width;
}
if (minimizedElement.getTags().contains(IPresentationEngine.ORIENTATION_VERTICAL)) {
paneSize.y = caRect.height;
}
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;
}
hostPane.setSize(paneSize);
hostPane.setLocation(loc);
}
private void setHostSize() {
if (hostPane == null || hostPane.isDisposed()) {
return;
}
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);
}
private Composite getHostPane() {
// Create one
hostPane = new Composite(trimStackTB.getShell(), SWT.NONE);
hostPane.setData(ShellActivationListener.DIALOG_IGNORE_KEY, Boolean.TRUE);
hostPane.addDisposeListener(e -> hostPane = null);
setHostSize();
// Set a special layout that allows resizing
fixedSides = getFixedSides();
hostPane.setLayout(new TrimPaneLayout(toolControl, fixedSides));
return hostPane;
}
private int getFixedSides() {
MUIElement tcParent = toolControl.getParent();
if (!(tcParent instanceof MTrimBar)) {
return 0;
}
MTrimBar bar = (MTrimBar) tcParent;
Composite trimComp = (Composite) bar.getWidget();
Rectangle trimBounds = trimComp.getBounds();
Point trimCenter = new Point(trimBounds.width / 2, trimBounds.height / 2); // adjusted to
// (0,0)
Control trimCtrl = (Control) toolControl.getWidget();
Rectangle ctrlBounds = trimCtrl.getBounds();
Point ctrlCenter = new Point(ctrlBounds.x + (ctrlBounds.width / 2), ctrlBounds.y
+ (ctrlBounds.height / 2));
if (bar.getSide() == SideValue.LEFT) {
int verticalValue = ctrlCenter.y < trimCenter.y ? SWT.TOP : SWT.BOTTOM;
return SWT.LEFT | verticalValue;
} else if (bar.getSide() == SideValue.RIGHT) {
int verticalValue = ctrlCenter.y < trimCenter.y ? SWT.TOP : SWT.BOTTOM;
return SWT.RIGHT | verticalValue;
} else if (bar.getSide() == SideValue.TOP) {
int horizontalValue = ctrlCenter.x < trimCenter.x ? SWT.LEFT : SWT.RIGHT;
return SWT.TOP | horizontalValue;
} else if (bar.getSide() == SideValue.BOTTOM) {
int horizontalValue = ctrlCenter.x < trimCenter.x ? SWT.LEFT : SWT.RIGHT;
return SWT.BOTTOM | horizontalValue;
}
return SWT.BOTTOM | SWT.RIGHT;
}
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;
}
}