| /******************************************************************************* |
| * 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; |
| } |
| } |