package org.xmind.cathy.internal.ui.workbench.addons.minmax; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; 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 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.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.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.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.UIEvents; import org.eclipse.e4.ui.workbench.UIEvents.EventTags; import org.eclipse.e4.ui.workbench.modeling.EModelService; import org.eclipse.e4.ui.workbench.modeling.EPartService; import org.eclipse.e4.ui.workbench.renderers.swt.TrimmedPartLayout; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.swt.SWT; 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.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.Shell; import org.eclipse.swt.widgets.ToolBar; import org.eclipse.swt.widgets.ToolItem; /** * Class for representing window trim containing minimized views and shared * areas */ public class XTrimStack { /** * Contribution URI for this class */ public static String CONTRIBUTION_URI_XTRIMSTACK = "bundleclass://org.xmind.cathy/org.xmind.cathy.internal.ui.workbench.addons.minmax.XTrimStack"; //$NON-NLS-1$ private ToolBar trimStackTB; private MUIElement mGenericStack; /** * A map of created images from a part's icon URI path. */ private Map<String, Image> imageMap = new HashMap<String, Image>(); // Listens to ESC and closes the active fast view private Listener escapeListener = new Listener() { public void handleEvent(Event event) { if (event.character == SWT.ESC) { setStateForShowStack(false); partService.requestActivation(); } } }; @Inject EModelService modelService; @Inject EPartService partService; @Inject MWindow window; @Inject MToolControl toolControl; @Inject protected IEventBroker eventBroker; @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()) 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; } // 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(); // 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(); setStateForShowStack(false); return; } if (uiElement instanceof MPart) { partService.activate((MPart) uiElement); } else if (uiElement instanceof MPerspective) { uiElement.getParent().setSelectedElement(uiElement); } setStateForShowStack(true); } public void widgetDefaultSelected(SelectionEvent e) { widgetSelected(e); } }; /** * Add or remove someone of MGenericStack's children * * @param event */ @Inject @Optional private void handleChildrenEvents( @UIEventTopic(UIEvents.ElementContainer.TOPIC_CHILDREN) org.osgi.service.event.Event event) { if (mGenericStack == null || trimStackTB == null) return; Object changedObj = event.getProperty(UIEvents.EventTags.ELEMENT); if (changedObj == mGenericStack) { trimStackTB.getDisplay().asyncExec(new Runnable() { public void run() { updateTrimStackItems(); } }); } } /** * Someone of MGenericStack's children changes state * * @param event */ @Inject @Optional private void handleToBeRenderedEvents( @UIEventTopic(UIEvents.UIElement.TOPIC_TOBERENDERED) org.osgi.service.event.Event event) { if (mGenericStack == null || trimStackTB == null) return; MUIElement changedElement = (MUIElement) event .getProperty(UIEvents.EventTags.ELEMENT); MUIElement parentElement = changedElement.getParent(); if (parentElement == mGenericStack) { trimStackTB.getDisplay().asyncExec(new Runnable() { public void run() { updateTrimStackItems(); } }); } } /** * Handle changes in tags of stack * * @param event */ @Inject @Optional private void subscribeTopicTagsChanged( @UIEventTopic(UIEvents.ApplicationElement.TOPIC_TAGS) org.osgi.service.event.Event event) { Object changedObj = event.getProperty(EventTags.ELEMENT); if (changedObj != mGenericStack) return; if (UIEvents.isADD(event)) { if (UIEvents.contains(event, UIEvents.EventTags.NEW_VALUE, MinMaxAddon.STACK_HIDDEN)) { fixToolItemSelection(); } else if (UIEvents.contains(event, UIEvents.EventTags.NEW_VALUE, MinMaxAddon.STACK_VISIBLE)) { fixToolItemSelection(); } } } @Inject @Optional private void handleWidgeEvents( @UIEventTopic(UIEvents.UIElement.TOPIC_WIDGET) org.osgi.service.event.Event event) { Object changedObj = event.getProperty(UIEvents.EventTags.ELEMENT); if (changedObj != mGenericStack) return; if (mGenericStack.getWidget() != null) { trimStackTB.getDisplay().asyncExec(new Runnable() { public void run() { updateTrimStackItems(); } }); } } /** * Someone of MGenericStack's children was selected * * @param event */ @Inject @Optional private void handleBringToTopEvents( @UIEventTopic(UIEvents.UILifeCycle.BRINGTOTOP) org.osgi.service.event.Event event) { MUIElement changedElement = (MUIElement) event .getProperty(UIEvents.EventTags.ELEMENT); // Open if shared area if (getLeafPart(mGenericStack) == changedElement && !(mGenericStack instanceof MPerspectiveStack)) { setStateForShowStack(true); return; } MUIElement selectedElement = null; if (mGenericStack instanceof MPlaceholder) { selectedElement = ((MPlaceholder) mGenericStack).getRef(); } else if (mGenericStack instanceof MPartStack) { selectedElement = ((MPartStack) mGenericStack).getSelectedElement(); } if (selectedElement == null) return; if (selectedElement instanceof MPlaceholder) selectedElement = ((MPlaceholder) selectedElement).getRef(); if (changedElement != selectedElement) return; setStateForShowStack(true); } @Inject @Optional private void handleAppShutDownAndStartedEvents( @UIEventTopic(UIEvents.UILifeCycle.APP_SHUTDOWN_STARTED) org.osgi.service.event.Event event) { setStateForShowStack(false); } @PostConstruct void createWidget(Composite parent, MToolControl me, CSSRenderingUtils cssUtils) { if (mGenericStack == null) { mGenericStack = 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; } trimStackTB = new ToolBar(parent, orientation | SWT.FLAT | SWT.WRAP); trimStackTB.addDisposeListener(new DisposeListener() { public void widgetDisposed(DisposeEvent e) { setStateForShowStack(false); trimStackTB = null; } }); updateTrimStackItems(); } @PreDestroy void destroy() { for (Image image : imageMap.values()) { image.dispose(); } } public MUIElement getMinimizedElement() { return mGenericStack; } //Find stack 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 = 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 = imageDescriptorFromURI(iconURI).createImage(); imageMap.put(iconURI, image); } return image; } return null; } public ImageDescriptor imageDescriptorFromURI(String iconPath) { try { URI uri = new URI(iconPath); return ImageDescriptor.createFromURL(new URL(uri.toString())); } catch (MalformedURLException e) { } catch (URISyntaxException e) { } 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()) return; while (trimStackTB.getItemCount() > 0) { trimStackTB.getItem(trimStackTB.getItemCount() - 1).dispose(); } if (mGenericStack instanceof MPlaceholder) { MPlaceholder ph = (MPlaceholder) mGenericStack; 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 (mGenericStack instanceof MGenericStack<?>) { // Handle *both* PartStacks and PerspectiveStacks here... MGenericStack<?> theStack = (MGenericStack<?>) mGenericStack; for (MUIElement stackElement : theStack.getChildren()) { boolean trimStackExists = stackElement.getTags() .contains(MinMaxAddon.TRIM_STACK_EXIST); if (stackElement instanceof MPlaceholder) { MUIElement part = ((MPlaceholder) stackElement).getRef(); trimStackExists = part.getTags() .contains(MinMaxAddon.TRIM_STACK_EXIST); } if (/* stackElement.isToBeRendered() || FIXME */ trimStackExists) { 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); } } } trimStackTB.pack(); trimStackTB.getShell().layout(new Control[] { trimStackTB }, SWT.DEFER); fixToolItemSelection(); } /** * Sets whether this stack should be visible or hidden * * @param show * whether the stack should be visible */ public void setStateForShowStack(boolean show) { Control ctrl = (Control) mGenericStack.getWidget(); Composite clientAreaComposite = getCAComposite(); if (clientAreaComposite == null || clientAreaComposite.isDisposed()) return; if (show) { mGenericStack.getTags().remove(MinMaxAddon.STACK_HIDDEN); mGenericStack.getTags().remove(MinMaxAddon.STACK_VISIBLE); mGenericStack.getTags().add(MinMaxAddon.STACK_VISIBLE); ctrl.removeListener(SWT.Traverse, escapeListener); ctrl.addListener(SWT.Traverse, escapeListener); } else { if (ctrl != null && !ctrl.isDisposed()) ctrl.removeListener(SWT.Traverse, escapeListener); mGenericStack.getTags().remove(MinMaxAddon.STACK_HIDDEN); mGenericStack.getTags().remove(MinMaxAddon.STACK_VISIBLE); mGenericStack.getTags().add(MinMaxAddon.STACK_HIDDEN); } } private void fixToolItemSelection() { if (trimStackTB == null || trimStackTB.isDisposed()) return; boolean toHide = mGenericStack.getTags() .contains(MinMaxAddon.STACK_HIDDEN); boolean toShow = mGenericStack.getTags() .contains(MinMaxAddon.STACK_VISIBLE); if (toHide) { // Not open...no selection for (ToolItem item : trimStackTB.getItems()) { item.setSelection(false); } } else if (toShow) { if (mGenericStack instanceof MPlaceholder) { trimStackTB.getItem(1).setSelection(true); } else if (isPerspectiveStack()) { MPerspectiveStack pStack = (MPerspectiveStack) mGenericStack; MUIElement selElement = pStack.getSelectedElement(); for (ToolItem item : trimStackTB.getItems()) { item.setSelection(item.getData() == selElement); } } else { MPartStack partStack = (MPartStack) mGenericStack; 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 isPerspectiveStack() { return mGenericStack 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; } 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; } 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; } }