| /******************************************************************************* |
| * Copyright (c) 2008, 2018 IBM Corporation and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| * Lars Vogel <Lars.Vogel@vogella.com> - Bug 429728, 430166, 441150, 442285, 472654, 495718 |
| * Andrey Loskutov <loskutov@gmx.de> - Bug 337588, 388476, 461573 |
| * Simon Scholz <simon.scholz@vogella.com> - Bug 442285, 487348 |
| * Patrik Suzzi <psuzzi@gmail.com> - Bug 497618 |
| *******************************************************************************/ |
| package org.eclipse.e4.ui.workbench.renderers.swt; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.Set; |
| import java.util.function.Consumer; |
| import javax.annotation.PostConstruct; |
| import javax.annotation.PreDestroy; |
| import javax.inject.Inject; |
| import javax.inject.Named; |
| import org.eclipse.core.runtime.preferences.IEclipsePreferences; |
| import org.eclipse.core.runtime.preferences.IEclipsePreferences.IPreferenceChangeListener; |
| import org.eclipse.core.runtime.preferences.IEclipsePreferences.PreferenceChangeEvent; |
| import org.eclipse.e4.core.contexts.IEclipseContext; |
| import org.eclipse.e4.core.di.annotations.Optional; |
| import org.eclipse.e4.core.di.extensions.Preference; |
| import org.eclipse.e4.core.services.events.IEventBroker; |
| import org.eclipse.e4.ui.css.swt.dom.WidgetElement; |
| import org.eclipse.e4.ui.css.swt.properties.custom.CSSPropertyMruVisibleSWTHandler; |
| import org.eclipse.e4.ui.di.UIEventTopic; |
| import org.eclipse.e4.ui.di.UISynchronize; |
| import org.eclipse.e4.ui.internal.workbench.OpaqueElementUtil; |
| import org.eclipse.e4.ui.internal.workbench.renderers.swt.BasicPartList; |
| import org.eclipse.e4.ui.internal.workbench.renderers.swt.SWTRenderersMessages; |
| import org.eclipse.e4.ui.internal.workbench.swt.AbstractPartRenderer; |
| import org.eclipse.e4.ui.internal.workbench.swt.CSSConstants; |
| import org.eclipse.e4.ui.internal.workbench.swt.CSSRenderingUtils; |
| import org.eclipse.e4.ui.model.application.ui.MDirtyable; |
| import org.eclipse.e4.ui.model.application.ui.MElementContainer; |
| import org.eclipse.e4.ui.model.application.ui.MUIElement; |
| import org.eclipse.e4.ui.model.application.ui.MUILabel; |
| import org.eclipse.e4.ui.model.application.ui.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.MPartSashContainer; |
| 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.MWindow; |
| import org.eclipse.e4.ui.model.application.ui.menu.MMenu; |
| import org.eclipse.e4.ui.model.application.ui.menu.MMenuElement; |
| import org.eclipse.e4.ui.model.application.ui.menu.MPopupMenu; |
| import org.eclipse.e4.ui.model.application.ui.menu.MToolBar; |
| 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.modeling.ISaveHandler; |
| import org.eclipse.jface.action.IContributionItem; |
| import org.eclipse.jface.action.LegacyActionTools; |
| import org.eclipse.jface.action.MenuManager; |
| import org.eclipse.jface.resource.JFaceResources; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.accessibility.ACC; |
| import org.eclipse.swt.accessibility.Accessible; |
| import org.eclipse.swt.accessibility.AccessibleListener; |
| import org.eclipse.swt.custom.CTabFolder; |
| import org.eclipse.swt.custom.CTabFolder2Adapter; |
| import org.eclipse.swt.custom.CTabFolderEvent; |
| import org.eclipse.swt.custom.CTabItem; |
| import org.eclipse.swt.events.ControlListener; |
| import org.eclipse.swt.events.MouseAdapter; |
| import org.eclipse.swt.events.MouseEvent; |
| import org.eclipse.swt.events.MouseListener; |
| import org.eclipse.swt.events.SelectionEvent; |
| import org.eclipse.swt.events.SelectionListener; |
| import org.eclipse.swt.graphics.Font; |
| import org.eclipse.swt.graphics.Image; |
| import org.eclipse.swt.graphics.Point; |
| import org.eclipse.swt.graphics.Rectangle; |
| import org.eclipse.swt.layout.RowData; |
| import org.eclipse.swt.layout.RowLayout; |
| import org.eclipse.swt.widgets.Composite; |
| import org.eclipse.swt.widgets.Control; |
| import org.eclipse.swt.widgets.Display; |
| import org.eclipse.swt.widgets.Menu; |
| import org.eclipse.swt.widgets.MenuItem; |
| import org.eclipse.swt.widgets.Monitor; |
| import org.eclipse.swt.widgets.Shell; |
| import org.eclipse.swt.widgets.ToolBar; |
| import org.eclipse.swt.widgets.ToolItem; |
| import org.eclipse.swt.widgets.Widget; |
| import org.osgi.service.event.Event; |
| import org.osgi.service.event.EventHandler; |
| import org.w3c.dom.css.CSSValue; |
| |
| /** |
| * SWT default renderer for a MPartStack model elements |
| * |
| * Style bits for the underlying CTabFolder can be set via the |
| * IPresentation.STYLE_OVERRIDE_KEY key |
| * |
| */ |
| public class StackRenderer extends LazyStackRenderer implements IPreferenceChangeListener { |
| /** |
| * |
| */ |
| private static final String THE_PART_KEY = "thePart"; //$NON-NLS-1$ |
| |
| /** |
| * Key to control the default default value of the "most recently used" order |
| * enablement |
| */ |
| public static final String MRU_KEY_DEFAULT = "enableMRUDefault"; //$NON-NLS-1$ |
| |
| /** |
| * Key to control the actual boolean preference of the "most recently used" |
| * order enablement |
| */ |
| public static final String MRU_KEY = "enableMRU"; //$NON-NLS-1$ |
| |
| /** |
| * Key to switch if the "most recently used" behavior controlled via CSS or |
| * preferences |
| */ |
| public static final String MRU_CONTROLLED_BY_CSS_KEY = "MRUControlledByCSS"; //$NON-NLS-1$ |
| |
| /** |
| * Default default value for MRU behavior. |
| */ |
| public static final boolean MRU_DEFAULT = true; |
| |
| /* |
| * org.eclipse.ui.internal.dialogs.ViewsPreferencePage controls currently the |
| * MRU behavior via IEclipsePreferences, so that CSS values from the themes |
| * aren't used. |
| * |
| * TODO once we can use preferences from CSS (and update the value on the fly) |
| * we can switch this default to true, see discussion on bug 388476. |
| */ |
| private static final boolean MRU_CONTROLLED_BY_CSS_DEFAULT = false; |
| |
| /* |
| * JFace key for default workbench tab font |
| */ |
| private static final String TAB_FONT_KEY = "org.eclipse.ui.workbench.TAB_TEXT_FONT"; //$NON-NLS-1$ |
| |
| @Inject |
| @Preference(nodePath = "org.eclipse.e4.ui.workbench.renderers.swt") |
| private IEclipsePreferences preferences; |
| |
| @Inject |
| @Named(WorkbenchRendererFactory.SHARED_ELEMENTS_STORE) |
| Map<MUIElement, Set<MPlaceholder>> renderedMap; |
| |
| public static final String TAG_VIEW_MENU = "ViewMenu"; //$NON-NLS-1$ |
| private static final String SHELL_CLOSE_EDITORS_MENU = "shell_close_editors_menu"; //$NON-NLS-1$ |
| private static final String STACK_SELECTED_PART = "stack_selected_part"; //$NON-NLS-1$ |
| |
| /** |
| * Add this tag to prevent the next tab's activation from granting focus toac |
| * the part. This is used to keep the focus on the CTabFolder when traversing |
| * the tabs using the keyboard. |
| */ |
| private static final String INHIBIT_FOCUS = "InhibitFocus"; //$NON-NLS-1$ |
| |
| // Minimum characters in for stacks outside the shared area |
| private static int MIN_VIEW_CHARS = 1; |
| |
| // Minimum characters in for stacks inside the shared area |
| private static int MIN_EDITOR_CHARS = 15; |
| |
| private Image viewMenuImage; |
| private String viewMenuURI = "platform:/plugin/org.eclipse.e4.ui.workbench.renderers.swt/icons/full/elcl16/view_menu.png"; //$NON-NLS-1$ |
| |
| @Inject |
| private IEventBroker eventBroker; |
| |
| @Inject |
| private IPresentationEngine renderer; |
| |
| private boolean ignoreTabSelChanges; |
| |
| private TabStateHandler tabStateHandler; |
| |
| private boolean imageChanged; |
| |
| List<CTabItem> getItemsToSet(MPart part) { |
| List<CTabItem> itemsToSet = new ArrayList<>(); |
| |
| MUIElement partParent = part.getParent(); |
| if (partParent instanceof MPartStack) { |
| addItemToSet(itemsToSet, part); |
| } else if (partParent instanceof MPartSashContainer) { |
| MElementContainer<MUIElement> parentParent = partParent.getParent(); |
| if (parentParent instanceof MPart) { |
| MPart parentParentMPart = (MPart) parentParent; |
| addItemToSet(itemsToSet, parentParentMPart); |
| } |
| } else if (part.getCurSharedRef() != null) { |
| MWindow topWin = modelService.getTopLevelWindowFor(part); |
| List<MPlaceholder> partRefs = modelService.findElements(topWin, part.getElementId(), MPlaceholder.class); |
| for (MPlaceholder ref : partRefs) { |
| CTabItem item = findItemForPart(ref, null); |
| if (item != null) { |
| itemsToSet.add(item); |
| } |
| } |
| } |
| |
| return itemsToSet; |
| } |
| |
| private void addItemToSet(List<CTabItem> itemsToSet, MPart parentParent) { |
| CTabItem item = findItemForPart(parentParent); |
| if (item != null) { |
| itemsToSet.add(item); |
| } |
| } |
| |
| @SuppressWarnings("unchecked") |
| @Inject |
| @Optional |
| void subscribeTopicTransientDataChanged( |
| @UIEventTopic(UIEvents.ApplicationElement.TOPIC_TRANSIENTDATA) org.osgi.service.event.Event event) { |
| Object changedElement = event.getProperty(UIEvents.EventTags.ELEMENT); |
| |
| if (!(changedElement instanceof MPart)) |
| 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 (!IPresentationEngine.OVERRIDE_ICON_IMAGE_KEY.equals(key) |
| && !IPresentationEngine.OVERRIDE_TITLE_TOOL_TIP_KEY.equals(key)) |
| return; |
| |
| MPart part = (MPart) changedElement; |
| List<CTabItem> itemsToSet = getItemsToSet(part); |
| for (CTabItem item : itemsToSet) { |
| if (key.equals(IPresentationEngine.OVERRIDE_ICON_IMAGE_KEY)) { |
| changePartTabImage(part, item); |
| } else if (key.equals(IPresentationEngine.OVERRIDE_TITLE_TOOL_TIP_KEY)) { |
| String newTip = getToolTip(part); |
| item.setToolTipText(getToolTip(newTip)); |
| } |
| } |
| } |
| |
| /** |
| * Handles changes in tags |
| * |
| * @param event |
| */ |
| @Inject |
| @Optional |
| void subscribeTopicTagsChanged(@UIEventTopic(UIEvents.ApplicationElement.TOPIC_TAGS) Event event) { |
| |
| if (tabStateHandler == null) { |
| tabStateHandler = new TabStateHandler(); |
| } |
| tabStateHandler.handleEvent(event); |
| |
| Object changedObj = event.getProperty(EventTags.ELEMENT); |
| |
| if (!(changedObj instanceof MPart)) |
| return; |
| |
| final MPart part = (MPart) changedObj; |
| CTabItem item = findItemForPart(part); |
| if (item == null || item.isDisposed()) |
| return; |
| |
| if (UIEvents.isADD(event)) { |
| if (UIEvents.contains(event, UIEvents.EventTags.NEW_VALUE, IPresentationEngine.ADORNMENT_PIN)) { |
| item.setImage(getImage(part)); |
| } |
| } else if (UIEvents.isREMOVE(event)) { |
| if (UIEvents.contains(event, UIEvents.EventTags.OLD_VALUE, IPresentationEngine.ADORNMENT_PIN)) { |
| item.setImage(getImage(part)); |
| } |
| } |
| } |
| |
| @Inject |
| @Optional |
| void subscribeTopicChildrenChanged(@UIEventTopic(UIEvents.ElementContainer.TOPIC_CHILDREN) Event event) { |
| |
| Object changedObj = event.getProperty(UIEvents.EventTags.ELEMENT); |
| // only interested in changes to toolbars and view menu (not popup menus) |
| if (!(changedObj instanceof MToolBar) |
| && !(changedObj instanceof MMenu && !(changedObj instanceof MPopupMenu))) { |
| return; |
| } |
| |
| MUIElement container = modelService.getContainer((MUIElement) changedObj); |
| // check if this is a part's toolbar |
| if (container instanceof MPart) { |
| MElementContainer<?> parent = ((MPart) container).getParent(); |
| // only relayout if this part is the selected element and we |
| // actually rendered this element |
| if (parent instanceof MPartStack && parent.getSelectedElement() == container |
| && parent.getRenderer() == StackRenderer.this) { |
| Object widget = parent.getWidget(); |
| if (widget instanceof CTabFolder) { |
| adjustTopRight((CTabFolder) widget); |
| } |
| } |
| } |
| } |
| |
| @Inject |
| @Optional |
| void subscribeTopicUILabelChanged(@UIEventTopic(UIEvents.UILabel.TOPIC_ALL) Event event) { |
| Object element = event.getProperty(UIEvents.EventTags.ELEMENT); |
| if (!(element instanceof MPart)) |
| return; |
| |
| MPart part = (MPart) element; |
| |
| String attName = (String) event.getProperty(UIEvents.EventTags.ATTNAME); |
| Object newValue = event.getProperty(UIEvents.EventTags.NEW_VALUE); |
| |
| // is this a direct child of the stack? |
| if (part.getParent() != null && part.getParent().getRenderer() == StackRenderer.this) { |
| CTabItem cti = findItemForPart(part); |
| if (cti != null) { |
| updateTab(cti, part, attName, newValue); |
| } |
| return; |
| } |
| |
| // Do we have any stacks with place holders for the element |
| // that's changed? |
| MWindow win = modelService.getTopLevelWindowFor(part); |
| List<MPlaceholder> refs = modelService.findElements(win, null, MPlaceholder.class, null); |
| if (refs != null) { |
| for (MPlaceholder ref : refs) { |
| if (ref.getRef() != part) |
| continue; |
| |
| MElementContainer<MUIElement> refParent = ref.getParent(); |
| // can be null, see bug 328296 |
| if (refParent != null && refParent.getRenderer() instanceof StackRenderer) { |
| CTabItem cti = findItemForPart(ref, refParent); |
| if (cti != null) { |
| updateTab(cti, part, attName, newValue); |
| } |
| } |
| } |
| } |
| } |
| |
| @Inject |
| @Optional |
| void subscribeTopicDirtyChanged(@UIEventTopic(UIEvents.Dirtyable.TOPIC_DIRTY) Event event) { |
| Object objElement = event.getProperty(UIEvents.EventTags.ELEMENT); |
| |
| // Ensure that this event is for a MMenuItem |
| if (!(objElement instanceof MPart)) { |
| return; |
| } |
| |
| // Extract the data bits |
| MPart part = (MPart) objElement; |
| |
| updatePartTab(event, part); |
| } |
| |
| @Inject |
| @Optional |
| void subscribeTopicClosablePartChanged(@UIEventTopic(UIEvents.Part.TOPIC_CLOSEABLE) Event event) { |
| updateClosableTab(event); |
| } |
| |
| @Inject |
| @Optional |
| void subscribeTopicClosablePlaceholderChanged(@UIEventTopic(UIEvents.Placeholder.TOPIC_CLOSEABLE) Event event) { |
| updateClosableTab(event); |
| } |
| |
| private void updateClosableTab(Event event) { |
| Object element = event.getProperty(UIEvents.EventTags.ELEMENT); |
| |
| MPart part = null; |
| if (element instanceof MPart) { |
| part = (MPart) element; |
| } else if (element instanceof MPlaceholder) { |
| MUIElement ref = ((MPlaceholder) element).getRef(); |
| if (ref instanceof MPart) { |
| part = (MPart) ref; |
| } |
| } |
| |
| if (part == null) { |
| return; |
| } |
| |
| updatePartTab(event, part); |
| } |
| |
| private void updatePartTab(Event event, MPart part) { |
| String attName = (String) event.getProperty(UIEvents.EventTags.ATTNAME); |
| Object newValue = event.getProperty(UIEvents.EventTags.NEW_VALUE); |
| |
| // Is the part directly under the stack? |
| MElementContainer<MUIElement> parent = part.getParent(); |
| if (parent != null && parent.getRenderer() == StackRenderer.this) { |
| CTabItem cti = findItemForPart(part, parent); |
| if (cti != null) { |
| updateTab(cti, part, attName, newValue); |
| } |
| return; |
| } |
| |
| // Do we have any stacks with place holders for the element |
| // that's changed? |
| Set<MPlaceholder> refs = renderedMap.get(part); |
| if (refs != null) { |
| for (MPlaceholder ref : refs) { |
| MElementContainer<MUIElement> refParent = ref.getParent(); |
| if (refParent.getRenderer() instanceof StackRenderer) { |
| CTabItem cti = findItemForPart(ref, refParent); |
| if (cti != null) { |
| updateTab(cti, part, attName, newValue); |
| } |
| } |
| } |
| } |
| } |
| |
| @Inject |
| @Optional |
| void subscribeTopicVisibleChanged(@UIEventTopic(UIEvents.UIElement.TOPIC_VISIBLE) Event event) { |
| shouldTopRightAdjusted(event); |
| } |
| |
| @Inject |
| @Optional |
| void subscribeTopicToBeRenderedChanged(@UIEventTopic(UIEvents.UIElement.TOPIC_TOBERENDERED) Event event) { |
| shouldTopRightAdjusted(event); |
| } |
| |
| @Inject |
| @Optional |
| private UISynchronize synchronize; |
| |
| /** |
| * An event handler for listening to changes to the state of view menus and part |
| * toolbars. Depending on what state these items are in, the view menu and |
| * toolbar should or should not be rendered on the tab folder. |
| */ |
| private void shouldTopRightAdjusted(Event event) { |
| Object objElement = event.getProperty(UIEvents.EventTags.ELEMENT); |
| |
| // Ensure that this event is for a MMenuItem or MToolBar |
| if (!(objElement instanceof MMenuElement) && !(objElement instanceof MToolBar)) { |
| return; |
| } |
| |
| // Ensure that it's a View part's menu or toolbar |
| MUIElement uiElement = (MUIElement) objElement; |
| MUIElement parent = modelService.getContainer(uiElement); |
| if (!(parent instanceof MPart)) { |
| return; |
| } |
| |
| // Get the partstack and the element in the partstack |
| MStackElement element = (MPart) parent; |
| if (element.getCurSharedRef() != null) { |
| element = element.getCurSharedRef(); |
| } |
| MUIElement parentElement = element.getParent(); |
| |
| if (!(parentElement instanceof MPartStack)) { |
| return; |
| } |
| |
| Object widget = parentElement.getWidget(); |
| if (widget instanceof CTabFolder) { |
| // The widget is created by the PartRenderingEngine, however the order of this |
| // call and the call in the engine are unknown. Therefore, call it |
| // asynchronously such that it is always last. |
| synchronize.asyncExec(() -> adjustTopRight((CTabFolder) widget)); |
| } |
| } |
| |
| @Inject |
| @Optional |
| void subscribeTopicActivateChanged(@UIEventTopic(UIEvents.UILifeCycle.ACTIVATE) Event event) { |
| // Manages CSS styling based on active part changes |
| MUIElement changed = (MUIElement) event.getProperty(UIEvents.EventTags.ELEMENT); |
| if (!(changed instanceof MPart)) { |
| return; |
| } |
| |
| MPart newActivePart = (MPart) changed; |
| MUIElement partParent = newActivePart.getParent(); |
| if (partParent == null && newActivePart.getCurSharedRef() != null) { |
| partParent = newActivePart.getCurSharedRef().getParent(); |
| } |
| |
| // Skip sash containers |
| while (partParent != null && partParent instanceof MPartSashContainer) { |
| partParent = partParent.getParent(); |
| } |
| |
| // Ensure the stack of a split part gets updated when one |
| // of its internal parts gets activated |
| if (partParent instanceof MCompositePart) { |
| partParent = partParent.getParent(); |
| } |
| |
| MPartStack pStack = (MPartStack) (partParent instanceof MPartStack ? partParent : null); |
| |
| List<String> tags = new ArrayList<>(); |
| tags.add(CSSConstants.CSS_ACTIVE_CLASS); |
| List<MUIElement> activeElements = modelService.findElements(modelService.getTopLevelWindowFor(newActivePart), |
| null, MUIElement.class, tags); |
| for (MUIElement element : activeElements) { |
| if (element instanceof MPartStack && element != pStack) { |
| styleElement(element, false); |
| } else if (element instanceof MPart && element != newActivePart) { |
| styleElement(element, false); |
| } |
| } |
| |
| if (pStack != null) { |
| styleElement(pStack, true); |
| } |
| styleElement(newActivePart, true); |
| } |
| |
| @Inject |
| @Optional |
| void subscribeTopicSelectedelementChanged( |
| @UIEventTopic(UIEvents.ElementContainer.TOPIC_SELECTEDELEMENT) Event event) { |
| if (tabStateHandler == null) { |
| tabStateHandler = new TabStateHandler(); |
| } |
| tabStateHandler.handleEvent(event); |
| } |
| |
| @Override |
| protected boolean requiresFocus(MPart element) { |
| MUIElement inStack = element.getCurSharedRef() != null ? element.getCurSharedRef() : element; |
| if (inStack.getParent() != null && inStack.getParent().getTransientData().containsKey(INHIBIT_FOCUS)) { |
| inStack.getParent().getTransientData().remove(INHIBIT_FOCUS); |
| return false; |
| } |
| |
| return super.requiresFocus(element); |
| } |
| |
| @PostConstruct |
| public void init() { |
| super.init(eventBroker); |
| |
| preferences.addPreferenceChangeListener(this); |
| preferenceChange(null); |
| } |
| |
| protected void updateTab(CTabItem cti, MPart part, String attName, Object newValue) { |
| switch (attName) { |
| case UIEvents.UILabel.LABEL: |
| case UIEvents.UILabel.LOCALIZED_LABEL: |
| String newName = (String) newValue; |
| cti.setText(getLabel(part, newName)); |
| break; |
| case UIEvents.Dirtyable.DIRTY: |
| cti.setText(getLabel(part, part.getLocalizedLabel())); |
| break; |
| case UIEvents.UILabel.ICONURI: |
| changePartTabImage(part, cti); |
| break; |
| case UIEvents.UILabel.TOOLTIP: |
| case UIEvents.UILabel.LOCALIZED_TOOLTIP: |
| String newTTip = (String) newValue; |
| cti.setToolTipText(getToolTip(newTTip)); |
| break; |
| case UIEvents.Part.CLOSEABLE: |
| Boolean closeableState = (Boolean) newValue; |
| cti.setShowClose(closeableState.booleanValue()); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| private void changePartTabImage(MPart part, CTabItem item) { |
| this.imageChanged = true; |
| item.setImage(getImage(part)); |
| this.imageChanged = false; |
| } |
| |
| @PreDestroy |
| public void contextDisposed() { |
| super.contextDisposed(eventBroker); |
| } |
| |
| private String getLabel(MUILabel itemPart, String newName) { |
| if (newName == null) { |
| newName = ""; //$NON-NLS-1$ |
| } else { |
| newName = LegacyActionTools.escapeMnemonics(newName); |
| } |
| |
| if (itemPart instanceof MDirtyable && ((MDirtyable) itemPart).isDirty()) { |
| newName = '*' + newName; |
| } |
| return newName; |
| } |
| |
| private String getToolTip(String newToolTip) { |
| return newToolTip == null || newToolTip.length() == 0 ? null : LegacyActionTools.escapeMnemonics(newToolTip); |
| } |
| |
| @Override |
| public Object createWidget(MUIElement element, Object parent) { |
| if (!(element instanceof MPartStack) || !(parent instanceof Composite)) |
| return null; |
| |
| MPartStack pStack = (MPartStack) element; |
| |
| Composite parentComposite = (Composite) parent; |
| |
| // Ensure that all rendered PartStacks have an Id |
| if (element.getElementId() == null || element.getElementId().length() == 0) { |
| String generatedId = "PartStack@" + Integer.toHexString(element.hashCode()); //$NON-NLS-1$ |
| element.setElementId(generatedId); |
| } |
| |
| int styleOverride = getStyleOverride(pStack); |
| int style = styleOverride == -1 ? SWT.BORDER : styleOverride; |
| final CTabFolder tabFolder = new CTabFolder(parentComposite, style); |
| tabFolder.setMRUVisible(getMRUValue(tabFolder)); |
| |
| // Adjust the minimum chars based on the location |
| int location = modelService.getElementLocation(element); |
| if ((location & EModelService.IN_SHARED_AREA) != 0) { |
| tabFolder.setMinimumCharacters(MIN_EDITOR_CHARS); |
| tabFolder.setUnselectedCloseVisible(true); |
| } else { |
| tabFolder.setMinimumCharacters(MIN_VIEW_CHARS); |
| tabFolder.setUnselectedCloseVisible(false); |
| } |
| |
| bindWidget(element, tabFolder); // ?? Do we need this ? |
| |
| // Add a composite to manage the view's TB and Menu |
| addTopRight(tabFolder); |
| |
| return tabFolder; |
| } |
| |
| private boolean getInitialMRUValue(Control control) { |
| CSSRenderingUtils util = context.get(CSSRenderingUtils.class); |
| if (util == null) { |
| return getMRUValueFromPreferences(); |
| } |
| |
| CSSValue value = util.getCSSValue(control, "MPartStack", "swt-mru-visible"); //$NON-NLS-1$ //$NON-NLS-2$ |
| |
| if (value == null) { |
| value = util.getCSSValue(control, "MPartStack", "mru-visible"); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| if (value == null) { |
| return getMRUValueFromPreferences(); |
| } |
| return Boolean.parseBoolean(value.getCssText()); |
| } |
| |
| private boolean getMRUValue(Control control) { |
| if (CSSPropertyMruVisibleSWTHandler.isMRUControlledByCSS()) { |
| return getInitialMRUValue(control); |
| } |
| return getMRUValueFromPreferences(); |
| } |
| |
| private boolean getMRUValueFromPreferences() { |
| boolean initialMRUValue = preferences.getBoolean(MRU_KEY_DEFAULT, MRU_DEFAULT); |
| return preferences.getBoolean(MRU_KEY, initialMRUValue); |
| } |
| |
| private void updateMRUValue(CTabFolder tabFolder) { |
| boolean actualMRUValue = getMRUValue(tabFolder); |
| tabFolder.setMRUVisible(actualMRUValue); |
| } |
| |
| @Override |
| public void preferenceChange(PreferenceChangeEvent event) { |
| boolean mruControlledByCSS = preferences.getBoolean(MRU_CONTROLLED_BY_CSS_KEY, MRU_CONTROLLED_BY_CSS_DEFAULT); |
| CSSPropertyMruVisibleSWTHandler.setMRUControlledByCSS(mruControlledByCSS); |
| } |
| |
| /** |
| * @param tabFolder |
| */ |
| private void addTopRight(CTabFolder tabFolder) { |
| Composite trComp = new Composite(tabFolder, SWT.NONE); |
| RowLayout rl = new RowLayout(); |
| trComp.setLayout(rl); |
| rl.spacing = 0; |
| rl.marginBottom = rl.marginTop = rl.marginRight = rl.marginLeft = 0; |
| tabFolder.setTopRight(trComp, SWT.RIGHT | SWT.WRAP); |
| |
| // Initially it's not visible |
| trComp.setVisible(false); |
| |
| // Create a toolbar for the view's drop-down menu |
| ToolBar menuTB = new ToolBar(trComp, SWT.FLAT | SWT.RIGHT); |
| menuTB.setData(TAG_VIEW_MENU); |
| tabFolder.setData(TAG_VIEW_MENU, menuTB); |
| RowData rd = new RowData(); |
| menuTB.setLayoutData(rd); |
| ToolItem ti = new ToolItem(menuTB, SWT.PUSH); |
| ti.setImage(getViewMenuImage()); |
| ti.setHotImage(null); |
| ti.setToolTipText(SWTRenderersMessages.viewMenu); |
| |
| // Initially it's not visible |
| rd.exclude = true; |
| menuTB.setVisible(false); |
| |
| ti.addSelectionListener(new SelectionListener() { |
| @Override |
| public void widgetSelected(SelectionEvent e) { |
| showMenu((ToolItem) e.widget); |
| } |
| |
| @Override |
| public void widgetDefaultSelected(SelectionEvent e) { |
| showMenu((ToolItem) e.widget); |
| } |
| }); |
| menuTB.getAccessible().addAccessibleListener(AccessibleListener.getNameAdapter(e -> { |
| if (e.childID != ACC.CHILDID_SELF) { |
| Accessible accessible = (Accessible) e.getSource(); |
| ToolBar toolBar = (ToolBar) accessible.getControl(); |
| if (0 <= e.childID && e.childID < toolBar.getItemCount()) { |
| ToolItem item = toolBar.getItem(e.childID); |
| if (item != null) { |
| e.result = item.getToolTipText(); |
| } |
| } |
| } |
| })); |
| |
| // Set an initial bounds |
| trComp.pack(); |
| } |
| |
| |
| protected void adjustTopRight(final CTabFolder tabFolder) { |
| if (tabFolder.isDisposed()) { |
| return; |
| } |
| |
| // Gather the parameters |
| MPartStack stack = (MPartStack) tabFolder.getData(OWNING_ME); |
| MUIElement element = stack.getSelectedElement(); |
| MPart part = null; |
| if (element != null) { |
| part = (MPart) ((element instanceof MPart) ? element : ((MPlaceholder) element).getRef()); |
| } |
| |
| Composite trComp = (Composite) tabFolder.getTopRight(); |
| |
| boolean needsTB = part != null && part.getToolbar() != null && part.getToolbar().isToBeRendered(); |
| |
| // View menu (if any) |
| MMenu viewMenu = getViewMenu(part); |
| boolean needsMenu = viewMenu != null && hasVisibleMenuItems(viewMenu, part); |
| |
| // Check the current state of the TB's |
| ToolBar menuTB = (ToolBar) tabFolder.getData(TAG_VIEW_MENU); |
| |
| // We need to modify the 'exclude' bit based on if the menuTB is |
| // visible or not |
| RowData rd = (RowData) menuTB.getLayoutData(); |
| if (needsMenu) { |
| menuTB.getItem(0).setData(THE_PART_KEY, part); |
| rd.exclude = false; |
| menuTB.setVisible(true); |
| } else { |
| menuTB.getItem(0).setData(THE_PART_KEY, null); |
| rd.exclude = true; |
| menuTB.setVisible(false); |
| } |
| |
| menuTB.moveBelow(null); |
| |
| if (needsMenu || needsTB) { |
| tabFolder.getTopRight().setVisible(true); |
| } else { |
| tabFolder.getTopRight().setVisible(false); |
| } |
| |
| // Pack the result |
| trComp.pack(); |
| trComp.requestLayout(); |
| |
| updateMRUValue(tabFolder); |
| } |
| |
| @Override |
| protected void createTab(MElementContainer<MUIElement> stack, MUIElement element) { |
| |
| // an invisible element won't have the correct widget hierarchy |
| if (!element.isVisible()) { |
| return; |
| } |
| |
| MPart part = null; |
| if (element instanceof MPart) |
| part = (MPart) element; |
| else if (element instanceof MPlaceholder) { |
| part = (MPart) ((MPlaceholder) element).getRef(); |
| if (part != null) { |
| part.setCurSharedRef((MPlaceholder) element); |
| } |
| } |
| |
| CTabFolder tabFolder = (CTabFolder) stack.getWidget(); |
| |
| CTabItem tabItem = findItemForPart(element, stack); |
| if (tabItem != null) { |
| if (element.getWidget() != null && tabItem.getControl() != element.getWidget()) |
| tabItem.setControl((Control) element.getWidget()); |
| return; |
| } |
| updateMRUValue(tabFolder); |
| int createFlags = SWT.NONE; |
| if (part != null && isClosable(part)) { |
| createFlags |= SWT.CLOSE; |
| } |
| |
| // Create the tab; we may have more visible tabs than currently shown |
| // (e.g., a result of calling partStack.getChildren().addAll(partList)) |
| int index = Math.min(calcIndexFor(stack, element), tabFolder.getItemCount()); |
| tabItem = new CTabItem(tabFolder, createFlags, index); |
| |
| tabItem.setData(OWNING_ME, element); |
| tabItem.setText(getLabel(part, part.getLocalizedLabel())); |
| tabItem.setImage(getImage(part)); |
| |
| String toolTip = getToolTip(part); |
| if (toolTip == null) |
| toolTip = part.getLocalizedTooltip(); |
| tabItem.setToolTipText(getToolTip(toolTip)); |
| if (element.getWidget() != null) { |
| // The part might have a widget but may not yet have been placed |
| // under this stack, check this |
| Control control = (Control) element.getWidget(); |
| if (control.getParent() == tabFolder) |
| tabItem.setControl((Control) element.getWidget()); |
| } |
| } |
| |
| private int calcIndexFor(MElementContainer<MUIElement> stack, final MUIElement part) { |
| int index = 0; |
| |
| // Find the -visible- part before this element |
| for (MUIElement mPart : stack.getChildren()) { |
| if (mPart == part) |
| return index; |
| if (mPart.isToBeRendered() && mPart.isVisible()) |
| index++; |
| } |
| return index; |
| } |
| |
| @Override |
| public void childRendered(final MElementContainer<MUIElement> parentElement, MUIElement element) { |
| super.childRendered(parentElement, element); |
| |
| if (!(((MUIElement) parentElement) instanceof MPartStack) || !(element instanceof MStackElement)) |
| return; |
| |
| createTab(parentElement, element); |
| } |
| |
| private CTabItem findItemForPart(MUIElement element, MElementContainer<MUIElement> stack) { |
| if (stack == null) |
| stack = element.getParent(); |
| if (!(stack.getWidget() instanceof CTabFolder)) |
| return null; |
| CTabFolder tabFolder = (CTabFolder) stack.getWidget(); |
| if (tabFolder == null || tabFolder.isDisposed()) |
| return null; |
| |
| CTabItem[] items = tabFolder.getItems(); |
| for (CTabItem item : items) { |
| if (item.getData(OWNING_ME) == element) |
| return item; |
| } |
| return null; |
| } |
| |
| public CTabItem findItemForPart(MPart part) { |
| // Invisible parts don't have items |
| if (!part.isToBeRendered()) |
| return null; |
| |
| // is this a direct child of the stack? |
| if (part.getParent() != null && part.getParent().getRenderer() == StackRenderer.this) { |
| CTabItem cti = findItemForPart(part, part.getParent()); |
| if (cti != null) { |
| return cti; |
| } |
| } |
| |
| // Do we have any stacks with place holders for the element |
| // that's changed? |
| MWindow win = modelService.getTopLevelWindowFor(part); |
| |
| if (win == null) |
| return null; |
| |
| List<MPlaceholder> refs = modelService.findElements(win, null, MPlaceholder.class); |
| if (refs != null) { |
| for (MPlaceholder ref : refs) { |
| if (ref.getRef() != part) |
| continue; |
| |
| MElementContainer<MUIElement> refParent = ref.getParent(); |
| // can be null, see bug 328296 |
| if (refParent != null && refParent.getRenderer() instanceof StackRenderer) { |
| CTabItem cti = findItemForPart(ref, refParent); |
| if (cti != null) { |
| return cti; |
| } |
| } |
| } |
| } |
| return null; |
| } |
| |
| @Override |
| public void hideChild(MElementContainer<MUIElement> parentElement, MUIElement child) { |
| super.hideChild(parentElement, child); |
| |
| CTabFolder tabFolder = (CTabFolder) parentElement.getWidget(); |
| if (tabFolder == null) |
| return; |
| |
| // Check if we have to reset the currently active child for the stack |
| CTabItem tabItem = findItemForPart(child, parentElement); |
| if (tabItem == tabFolder.getSelection()) { |
| // If we're the only part we need to clear the top right... |
| if (tabFolder.getItemCount() == 1) { |
| adjustTopRight(tabFolder); |
| } |
| } |
| |
| // find the 'stale' tab for this element and dispose it |
| if (tabItem != null && !tabItem.isDisposed()) { |
| tabItem.setControl(null); |
| tabItem.dispose(); |
| } |
| } |
| |
| @Override |
| public void hookControllerLogic(final MUIElement me) { |
| super.hookControllerLogic(me); |
| |
| if (!(me instanceof MElementContainer<?>)) |
| return; |
| |
| @SuppressWarnings("unchecked") |
| final MElementContainer<MUIElement> stack = (MElementContainer<MUIElement>) me; |
| |
| // Match the selected TabItem to its Part |
| final CTabFolder tabFolder = (CTabFolder) me.getWidget(); |
| |
| // Handle traverse events for accessibility |
| tabFolder.addTraverseListener(e -> { |
| if (e.detail == SWT.TRAVERSE_ARROW_NEXT || e.detail == SWT.TRAVERSE_ARROW_PREVIOUS) { |
| me.getTransientData().put(INHIBIT_FOCUS, true); |
| } else if (e.detail == SWT.TRAVERSE_RETURN) { |
| me.getTransientData().remove(INHIBIT_FOCUS); |
| CTabItem cti = tabFolder.getSelection(); |
| if (cti != null) { |
| MUIElement stackElement = (MUIElement) cti.getData(OWNING_ME); |
| if (stackElement instanceof MPlaceholder) |
| stackElement = ((MPlaceholder) stackElement).getRef(); |
| if ((stackElement instanceof MPart) && (tabFolder.isFocusControl())) { |
| MPart thePart = (MPart) stackElement; |
| renderer.focusGui(thePart); |
| } |
| } |
| } |
| }); |
| |
| // Detect activation...picks up cases where the user clicks on the |
| // (already active) tab |
| tabFolder.addListener(SWT.Activate, event -> { |
| if (event.detail == SWT.MouseDown) { |
| CTabFolder tabFolder1 = (CTabFolder) event.widget; |
| if (tabFolder1.getSelection() == null) |
| return; |
| |
| // get the item under the cursor |
| Point cp = event.display.getCursorLocation(); |
| cp = event.display.map(null, tabFolder1, cp); |
| CTabItem overItem = tabFolder1.getItem(cp); |
| |
| // If the item we're over is *not* the current one do |
| // nothing (it'll get activated when the tab changes) |
| if (overItem == null || overItem == tabFolder1.getSelection()) { |
| MUIElement uiElement = (MUIElement) tabFolder1.getSelection().getData(OWNING_ME); |
| if (uiElement instanceof MPlaceholder) |
| uiElement = ((MPlaceholder) uiElement).getRef(); |
| if (uiElement instanceof MPart) |
| activate((MPart) uiElement); |
| } |
| } |
| }); |
| |
| tabFolder.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> { |
| // prevent recursions |
| if (ignoreTabSelChanges) |
| return; |
| |
| MUIElement ele = (MUIElement) e.item.getData(OWNING_ME); |
| ele.getParent().setSelectedElement(ele); |
| if (ele instanceof MPlaceholder) |
| ele = ((MPlaceholder) ele).getRef(); |
| if (ele instanceof MPart) |
| activate((MPart) ele); |
| })); |
| |
| MouseListener mouseListener = new MouseAdapter() { |
| |
| @Override |
| public void mouseDoubleClick(MouseEvent e) { |
| CTabItem item = tabFolder.getSelection(); |
| if (item != null) { |
| MUIElement ele = (MUIElement) item.getData(OWNING_ME); |
| if (ele.getParent().getSelectedElement() == ele) { |
| Control ctrl = (Control) ele.getWidget(); |
| if (ctrl != null) { |
| ctrl.setFocus(); |
| } |
| } |
| } |
| } |
| |
| @Override |
| public void mouseUp(MouseEvent e) { |
| if (tabFolder.isDisposed()) { |
| // 517654: MouseUp may be sent after stack has been disposed |
| return; |
| } |
| CTabItem item = tabFolder.getItem(new Point(e.x, e.y)); |
| |
| // If the user middle clicks on a tab, close it |
| if (item != null && e.button == 2) { |
| closePart(item); |
| } |
| |
| // If the user clicks on the tab or empty stack space, call |
| // setFocus() to transfer it from the tabfolder to the client widget |
| if (e.button == 1) { |
| if (item == null) { |
| Rectangle clientArea = tabFolder.getClientArea(); |
| if (!clientArea.contains(e.x, e.y)) { |
| // User clicked in empty space |
| item = tabFolder.getSelection(); |
| } |
| } |
| |
| // but only transfer focus if we have it. |
| // If we don't own it, the widget has already the focus |
| // so don't set it second time |
| if (item != null && tabFolder.isFocusControl()) { |
| MUIElement ele = (MUIElement) item.getData(OWNING_ME); |
| if (ele.getParent().getSelectedElement() == ele) { |
| Control ctrl = (Control) ele.getWidget(); |
| if (ctrl != null) { |
| ctrl.setFocus(); |
| } |
| } |
| } |
| } |
| } |
| }; |
| tabFolder.addMouseListener(mouseListener); |
| |
| CTabFolder2Adapter closeListener = new CTabFolder2Adapter() { |
| @Override |
| public void close(CTabFolderEvent event) { |
| event.doit = closePart(event.item); |
| } |
| |
| @Override |
| public void showList(CTabFolderEvent event) { |
| event.doit = false; |
| showAvailableItems(stack, tabFolder); |
| } |
| }; |
| tabFolder.addCTabFolder2Listener(closeListener); |
| |
| tabFolder.addMenuDetectListener(e -> { |
| Point absolutePoint = new Point(e.x, e.y); |
| Point relativePoint = tabFolder.getDisplay().map(null, tabFolder, absolutePoint); |
| CTabItem eventTabItem = tabFolder.getItem(relativePoint); |
| |
| // If click happened in empty area, still show the menu |
| if (eventTabItem == null) { |
| Rectangle clientArea = tabFolder.getClientArea(); |
| if (!clientArea.contains(relativePoint)) { |
| eventTabItem = tabFolder.getSelection(); |
| } |
| } |
| |
| if (eventTabItem != null) { |
| MUIElement uiElement = (MUIElement) eventTabItem.getData(AbstractPartRenderer.OWNING_ME); |
| MPart tabPart = (MPart) ((uiElement instanceof MPart) ? uiElement |
| : ((MPlaceholder) uiElement).getRef()); |
| openMenuFor(tabPart, tabFolder, absolutePoint); |
| } |
| }); |
| |
| tabFolder.addControlListener(ControlListener.controlResizedAdapter(e -> updateMRUValue(tabFolder))); |
| } |
| |
| /** |
| * Shows a popup dialog with the list of editors availavle in a given |
| * {@link CTabFolder}. By default the popup origin will be located close to the |
| * chevron location. |
| * |
| * @param stack |
| * @param tabFolder |
| */ |
| public void showAvailableItems(MElementContainer<?> stack, CTabFolder tabFolder) { |
| showAvailableItems(stack, tabFolder, false); |
| } |
| |
| /** |
| * Shows a popup dialog with the list of editors available in the given |
| * CTabFolder. If {@code forceCenter} enabled, the dialog is centered |
| * horizontally; otherwise, the dialog origin is placed at chevron location. he |
| * dialog is placed at |
| * |
| * @param stack |
| * @param tabFolder |
| * @param forceCenter center the dialog if true |
| */ |
| public void showAvailableItems(MElementContainer<?> stack, CTabFolder tabFolder, boolean forceCenter) { |
| IEclipseContext ctxt = getContext(stack); |
| final BasicPartList editorList = new BasicPartList(tabFolder.getShell(), SWT.ON_TOP, |
| SWT.V_SCROLL | SWT.H_SCROLL, ctxt.get(EPartService.class), stack, this, getMRUValueFromPreferences()); |
| editorList.setInput(); |
| |
| Point size = editorList.computeSizeHint(); |
| editorList.setSize(size.x, size.y); |
| |
| Point location = null; |
| if (forceCenter) { |
| // placed to the center |
| Rectangle ca = tabFolder.getClientArea(); |
| location = tabFolder.toDisplay(ca.x, ca.y); |
| location.x = Math.max(0, (location.x + ((ca.width - size.x) / 2))); |
| location.y = Math.max(0, (location.y + ((ca.height - size.y) / 3))); |
| } else { |
| // placed at chevron location |
| location = tabFolder.toDisplay(getChevronLocation(tabFolder)); |
| Monitor mon = tabFolder.getMonitor(); |
| Rectangle bounds = mon.getClientArea(); |
| if (location.x + size.x > bounds.x + bounds.width) { |
| location.x = bounds.x + bounds.width - size.x; |
| } |
| if (location.y + size.y > bounds.y + bounds.height) { |
| location.y = bounds.y + bounds.height - size.y; |
| } |
| } |
| editorList.setLocation(location); |
| |
| editorList.setVisible(true); |
| editorList.setFocus(); |
| editorList.getShell().addListener(SWT.Deactivate, event -> { |
| editorList.getShell().getDisplay().asyncExec(() -> { |
| if (!editorList.hasFocus()) { |
| editorList.dispose(); |
| } |
| }); |
| }); |
| } |
| |
| private Point getChevronLocation(CTabFolder tabFolder) { |
| // get the last visible item |
| int numItems = tabFolder.getItemCount(); |
| CTabItem item = null; |
| for (int i = 0; i < numItems; i++) { |
| CTabItem tempItem = tabFolder.getItem(i); |
| if (tempItem.isShowing()) { |
| item = tempItem; |
| } |
| } |
| |
| // if we have no visible tabs, abort. |
| if (item == null) { |
| return new Point(0, 0); |
| } |
| |
| Rectangle itemBounds = item.getBounds(); |
| int x = itemBounds.x + itemBounds.width; |
| int y = itemBounds.y + itemBounds.height; |
| return new Point(x, y); |
| } |
| |
| /** |
| * Closes the part that's backed by the given widget. |
| * |
| * @param widget the part that owns this widget |
| * @return <tt>true</tt> if the part was closed, <tt>false</tt> otherwise |
| */ |
| private boolean closePart(Widget widget) { |
| MUIElement uiElement = (MUIElement) widget.getData(AbstractPartRenderer.OWNING_ME); |
| MPart part = (MPart) ((uiElement instanceof MPart) ? uiElement : ((MPlaceholder) uiElement).getRef()); |
| if (!isClosable(part)) { |
| return false; |
| } |
| |
| IEclipseContext partContext = part.getContext(); |
| IEclipseContext parentContext = getContextForParent(part); |
| // a part may not have a context if it hasn't been rendered |
| IEclipseContext context = partContext == null ? parentContext : partContext; |
| // ask user to save if necessary and close part if it is not dirty |
| EPartService partService = context.get(EPartService.class); |
| if (partService.savePart(part, true)) { |
| partService.hidePart(part); |
| return true; |
| } |
| // the user has canceled the save operation, so the part is not closed |
| return false; |
| } |
| |
| @Override |
| protected void showTab(MUIElement element) { |
| super.showTab(element); |
| |
| // an invisible element won't have the correct widget hierarchy |
| if (!element.isVisible()) { |
| return; |
| } |
| |
| final CTabFolder tabFolder = (CTabFolder) getParentWidget(element); |
| CTabItem tabItem = findItemForPart(element, null); |
| if (tabItem == null) { |
| createTab(element.getParent(), element); |
| tabItem = findItemForPart(element, element.getParent()); |
| } |
| Control ctrl = (Control) element.getWidget(); |
| if (ctrl != null && ctrl.getParent() != tabFolder) { |
| ctrl.setParent(tabFolder); |
| tabItem.setControl(ctrl); |
| } else if (element.getWidget() == null) { |
| Control tabCtrl = (Control) renderer.createGui(element); |
| tabItem.setControl(tabCtrl); |
| } |
| |
| ignoreTabSelChanges = true; |
| // Ensure that the newly selected control is correctly sized |
| if (tabItem.getControl() instanceof Composite) { |
| Composite ctiComp = (Composite) tabItem.getControl(); |
| // see bug 461573, 528720: call below is still needed to make view |
| // descriptions visible after unhiding the view with changed bounds |
| ctiComp.requestLayout(); |
| } |
| tabFolder.setSelection(tabItem); |
| ignoreTabSelChanges = false; |
| |
| // Show the new state |
| adjustTopRight(tabFolder); |
| } |
| |
| /** |
| * @param item |
| */ |
| protected void showMenu(ToolItem item) { |
| MPart part = (MPart) item.getData(THE_PART_KEY); |
| if (part == null) { |
| return; |
| } |
| Control ctrl = (Control) part.getWidget(); |
| MMenu menuModel = getViewMenu(part); |
| if (menuModel == null || !menuModel.isToBeRendered()) |
| return; |
| |
| final Menu swtMenu = (Menu) renderer.createGui(menuModel, ctrl.getShell(), part.getContext()); |
| if (swtMenu == null) |
| return; |
| |
| ctrl.addDisposeListener(e -> { |
| if (!swtMenu.isDisposed()) { |
| swtMenu.dispose(); |
| } |
| }); |
| |
| // ...and Show it... |
| Rectangle ib = item.getBounds(); |
| Point displayAt = item.getParent().toDisplay(ib.x, ib.y + ib.height); |
| swtMenu.setLocation(displayAt); |
| swtMenu.setVisible(true); |
| |
| Display display = swtMenu.getDisplay(); |
| while (!swtMenu.isDisposed() && swtMenu.isVisible()) { |
| if (!display.readAndDispatch()) |
| display.sleep(); |
| } |
| if (!swtMenu.isDisposed() && !(swtMenu.getData() instanceof MenuManager)) { |
| swtMenu.dispose(); |
| } |
| } |
| |
| private Image getViewMenuImage() { |
| if (viewMenuImage == null) { |
| viewMenuImage = getImageFromURI(viewMenuURI); |
| } |
| return viewMenuImage; |
| } |
| |
| private void openMenuFor(MPart part, CTabFolder folder, Point point) { |
| Menu tabMenu = createTabMenu(folder, part); |
| tabMenu.setData(STACK_SELECTED_PART, part); |
| tabMenu.setLocation(point.x, point.y); |
| tabMenu.setVisible(true); |
| } |
| |
| protected boolean isClosable(MPart part) { |
| // if it's a shared part check its current ref |
| if (part.getCurSharedRef() != null) { |
| return !(part.getCurSharedRef().getTags().contains(IPresentationEngine.NO_CLOSE)); |
| } |
| |
| return part.isCloseable(); |
| } |
| |
| private Menu createTabMenu(CTabFolder folder, MPart part) { |
| Shell shell = folder.getShell(); |
| Menu cachedMenu = (Menu) shell.getData(SHELL_CLOSE_EDITORS_MENU); |
| if (cachedMenu == null) { |
| cachedMenu = new Menu(folder); |
| shell.setData(SHELL_CLOSE_EDITORS_MENU, cachedMenu); |
| } else { |
| for (MenuItem item : cachedMenu.getItems()) { |
| item.dispose(); |
| } |
| } |
| |
| final Menu menu = cachedMenu; |
| populateTabMenu(menu, part); |
| return menu; |
| } |
| |
| /** |
| * Populate the tab's context menu for the given part. |
| * |
| * @param menu the menu to be populated |
| * @param part the relevant part |
| */ |
| protected void populateTabMenu(final Menu menu, MPart part) { |
| |
| int closeableElements = 0; |
| if (isClosable(part)) { |
| createMenuItem(menu, SWTRenderersMessages.menuClose, e -> closePart(menu)); |
| closeableElements++; |
| } |
| |
| MElementContainer<MUIElement> parent = getParent(part); |
| if (parent != null) { |
| closeableElements += getCloseableSiblingParts(part).size(); |
| |
| if (closeableElements >= 2) { |
| createMenuItem(menu, SWTRenderersMessages.menuCloseOthers, e -> closeSiblingParts(menu, true)); |
| |
| // create menu for parts on the left |
| if (!getCloseableSideParts(part, true).isEmpty()) { |
| createMenuItem(menu, SWTRenderersMessages.menuCloseLeft, e -> closeSideParts(menu, true)); |
| } |
| |
| // create menu for parts on the right |
| if (!getCloseableSideParts(part, false).isEmpty()) { |
| createMenuItem(menu, SWTRenderersMessages.menuCloseRight, e -> closeSideParts(menu, false)); |
| } |
| |
| new MenuItem(menu, SWT.SEPARATOR); |
| |
| createMenuItem(menu, SWTRenderersMessages.menuCloseAll, e -> closeSiblingParts(menu, false)); |
| } |
| } |
| |
| if (isDetachable(part)) { |
| if (closeableElements > 0) { |
| new MenuItem(menu, SWT.SEPARATOR); |
| } |
| |
| createMenuItem(menu, SWTRenderersMessages.menuDetach, e -> detachActivePart(menu)); |
| } |
| } |
| |
| protected boolean isDetachable(MPart part) { |
| // if it's a shared part check its current ref |
| if (part.getCurSharedRef() != null) { |
| return !part.getCurSharedRef().getTags().contains(IPresentationEngine.NO_DETACH); |
| } |
| |
| return !part.getTags().contains(IPresentationEngine.NO_DETACH); |
| } |
| |
| /** |
| * |
| * Detaches the currently selected part |
| * |
| * @param menu |
| */ |
| private void detachActivePart(final Menu menu) { |
| MPart selectedPart = (MPart) menu.getData(STACK_SELECTED_PART); |
| CTabItem cti = findItemForPart(selectedPart); |
| if (cti == null || cti.getParent() == null) { |
| return; |
| } |
| CTabFolder parent = cti.getParent(); |
| |
| EModelService modelService = getContextForParent(selectedPart).get(EModelService.class); |
| Rectangle bounds = parent.getBounds(); |
| Point display = parent.toDisplay(bounds.x, bounds.y); |
| modelService.detach(selectedPart, display.x, display.y, bounds.width, bounds.height); |
| } |
| |
| /** |
| * |
| * Closes the currently selected part |
| * |
| * @param menu |
| */ |
| private void closePart(final Menu menu) { |
| MPart selectedPart = (MPart) menu.getData(STACK_SELECTED_PART); |
| EPartService partService = getContextForParent(selectedPart).get(EPartService.class); |
| if (partService.savePart(selectedPart, true)) { |
| partService.hidePart(selectedPart); |
| } |
| } |
| |
| /** |
| * Helper method for creating menu items |
| */ |
| private MenuItem createMenuItem(final Menu menu, String menuItemText, Consumer<SelectionEvent> c) { |
| MenuItem menuItem = new MenuItem(menu, SWT.NONE); |
| menuItem.setText(menuItemText); |
| menuItem.addSelectionListener(SelectionListener.widgetSelectedAdapter(c)); |
| return menuItem; |
| |
| } |
| |
| private MElementContainer<MUIElement> getParent(MPart part) { |
| MElementContainer<MUIElement> parent = part.getParent(); |
| if (parent == null) { |
| MPlaceholder placeholder = part.getCurSharedRef(); |
| return placeholder == null ? null : placeholder.getParent(); |
| } |
| return parent; |
| } |
| |
| private List<MPart> getCloseableSideParts(MPart part, boolean left) { |
| MElementContainer<MUIElement> container = getParent(part); |
| if (container == null) { |
| return new ArrayList<>(); |
| } |
| |
| int thisPartIdx = getPartIndex(part, container); |
| if (thisPartIdx == -1) { |
| return new ArrayList<>(); |
| } |
| List<MUIElement> children = container.getChildren(); |
| final int start = left ? 0 : thisPartIdx + 1; |
| final int end = left ? thisPartIdx : children.size(); |
| |
| return getCloseableSiblingParts(part, children, start, end); |
| } |
| |
| private int getPartIndex(MPart part, MElementContainer<MUIElement> container) { |
| List<MUIElement> children = container.getChildren(); |
| for (int i = 0; i < children.size(); i++) { |
| MUIElement child = children.get(i); |
| MPart otherPart = null; |
| if (child instanceof MPart) { |
| otherPart = (MPart) child; |
| } else if (child instanceof MPlaceholder) { |
| MUIElement otherItem = ((MPlaceholder) child).getRef(); |
| if (otherItem instanceof MPart) { |
| otherPart = (MPart) otherItem; |
| } |
| } |
| if (otherPart == part) { |
| return i; |
| } |
| } |
| return -1; |
| } |
| |
| private List<MPart> getCloseableSiblingParts(MPart part) { |
| MElementContainer<MUIElement> container = getParent(part); |
| if (container == null) { |
| return new ArrayList<>(); |
| } |
| |
| List<MUIElement> children = container.getChildren(); |
| return getCloseableSiblingParts(part, children, 0, children.size()); |
| } |
| |
| private List<MPart> getCloseableSiblingParts(MPart part, List<MUIElement> children, final int start, |
| final int end) { |
| // broken out from closeSiblingParts so it can be used to determine how |
| // many closeable siblings are available |
| List<MPart> closeableSiblings = new ArrayList<>(); |
| for (int i = start; i < end; i++) { |
| MUIElement child = children.get(i); |
| // If the element isn't showing skip it |
| if (!child.isToBeRendered()) |
| continue; |
| |
| MPart otherPart = null; |
| if (child instanceof MPart) |
| otherPart = (MPart) child; |
| else if (child instanceof MPlaceholder) { |
| MUIElement otherItem = ((MPlaceholder) child).getRef(); |
| if (otherItem instanceof MPart) |
| otherPart = (MPart) otherItem; |
| } |
| if (otherPart == null) |
| continue; |
| |
| if (part.equals(otherPart)) |
| continue; // skip selected item |
| if (otherPart.isToBeRendered() && isClosable(otherPart)) |
| closeableSiblings.add(otherPart); |
| } |
| return closeableSiblings; |
| } |
| |
| private void closeSideParts(Menu menu, boolean left) { |
| MPart selectedPart = (MPart) menu.getData(STACK_SELECTED_PART); |
| MElementContainer<MUIElement> container = getParent(selectedPart); |
| if (container == null) { |
| return; |
| } |
| List<MPart> others = getCloseableSideParts(selectedPart, left); |
| closeSiblingParts(selectedPart, others, true); |
| } |
| |
| private void closeSiblingParts(Menu menu, boolean skipThisPart) { |
| MPart part = (MPart) menu.getData(STACK_SELECTED_PART); |
| MElementContainer<MUIElement> container = getParent(part); |
| if (container == null) { |
| return; |
| } |
| List<MPart> others = getCloseableSiblingParts(part); |
| closeSiblingParts(part, others, skipThisPart); |
| } |
| |
| private void closeSiblingParts(MPart part, List<MPart> others, boolean skipThisPart) { |
| MElementContainer<MUIElement> container = getParent(part); |
| |
| // add the current part last so that we unrender obscured items first |
| if (!skipThisPart && part.isToBeRendered() && isClosable(part)) { |
| others.add(part); |
| } |
| |
| // add the selected element of the stack at the end, else we may end up |
| // selecting another part when we hide it since it is the selected |
| // element |
| MUIElement selectedElement = container.getSelectedElement(); |
| if (others.remove(selectedElement)) { |
| others.add((MPart) selectedElement); |
| } else if (selectedElement instanceof MPlaceholder) { |
| selectedElement = ((MPlaceholder) selectedElement).getRef(); |
| if (others.remove(selectedElement)) { |
| others.add((MPart) selectedElement); |
| } |
| } |
| |
| EPartService partService = getContextForParent(part).get(EPartService.class); |
| // try using the ISaveHandler first... This gives better control of |
| // dialogs... |
| ISaveHandler saveHandler = getContextForParent(part).get(ISaveHandler.class); |
| if (saveHandler != null) { |
| final List<MPart> toPrompt = new ArrayList<>(others); |
| toPrompt.retainAll(partService.getDirtyParts()); |
| |
| boolean cancel = false; |
| if (toPrompt.size() > 1) { |
| cancel = !saveHandler.saveParts(toPrompt, true); |
| } else if (toPrompt.size() == 1) { |
| cancel = !saveHandler.save(toPrompt.get(0), true); |
| } |
| if (cancel) { |
| return; |
| } |
| |
| for (MPart other : others) { |
| partService.hidePart(other); |
| } |
| return; |
| } |
| |
| // No ISaveHandler, fall back to just using the part service... |
| for (MPart otherPart : others) { |
| if (partService.savePart(otherPart, true)) |
| partService.hidePart(otherPart); |
| } |
| } |
| |
| public static MMenu getViewMenu(MPart part) { |
| if (part == null || part.getMenus() == null) { |
| return null; |
| } |
| for (MMenu menu : part.getMenus()) { |
| if (menu.getTags().contains(TAG_VIEW_MENU)) { |
| return menu; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Determine whether the given view menu has any visible menu items. |
| * |
| * @param viewMenu the view menu to check |
| * @param part the view menu's parent part |
| * @return <tt>true</tt> if the specified view menu has visible children, |
| * <tt>false</tt> otherwise |
| */ |
| private boolean hasVisibleMenuItems(MMenu viewMenu, MPart part) { |
| if (!viewMenu.isToBeRendered() || !viewMenu.isVisible()) { |
| return false; |
| } |
| |
| for (MMenuElement menuElement : viewMenu.getChildren()) { |
| if (menuElement.isToBeRendered() && menuElement.isVisible()) { |
| if (OpaqueElementUtil.isOpaqueMenuItem(menuElement) |
| || OpaqueElementUtil.isOpaqueMenuSeparator(menuElement)) { |
| IContributionItem item = (IContributionItem) OpaqueElementUtil.getOpaqueItem(menuElement); |
| if (item != null && item.isVisible()) { |
| return true; |
| } |
| } else { |
| return true; |
| } |
| } |
| } |
| |
| Object menuRenderer = viewMenu.getRenderer(); |
| if (menuRenderer instanceof MenuManagerRenderer) { |
| MenuManager manager = ((MenuManagerRenderer) menuRenderer).getManager(viewMenu); |
| if (manager != null && manager.isVisible()) { |
| return true; |
| } |
| } |
| |
| Control control = (Control) part.getWidget(); |
| if (control != null) { |
| Menu menu = (Menu) renderer.createGui(viewMenu, control.getShell(), part.getContext()); |
| if (menu != null) { |
| menuRenderer = viewMenu.getRenderer(); |
| if (menuRenderer instanceof MenuManagerRenderer) { |
| MenuManagerRenderer menuManagerRenderer = (MenuManagerRenderer) menuRenderer; |
| MenuManager manager = menuManagerRenderer.getManager(viewMenu); |
| if (manager != null) { |
| // remark ourselves as dirty so that the menu will be |
| // reconstructed |
| manager.markDirty(); |
| } |
| } |
| return menu.getItemCount() != 0; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * An event handler for listening to changes to the children of an element |
| * container. The tab folder may need to layout itself again if a part's toolbar |
| * has been changed. |
| */ |
| @SuppressWarnings("javadoc") |
| public class TabStateHandler implements EventHandler { |
| |
| @SuppressWarnings("restriction") |
| @Override |
| public void handleEvent(Event event) { |
| Object element = event.getProperty(UIEvents.EventTags.ELEMENT); |
| Object newValue = event.getProperty(UIEvents.EventTags.NEW_VALUE); |
| Object oldValue = event.getProperty(UIEvents.EventTags.OLD_VALUE); |
| |
| if (!validateElement(element) || !validateValues(oldValue, newValue)) { |
| return; |
| } |
| |
| MPart part = newValue instanceof MPlaceholder ? (MPart) ((MPlaceholder) newValue).getRef() |
| : (MPart) element; |
| CTabItem cti = findItemForPart(part); |
| if (cti == null) { |
| return; |
| } |
| |
| boolean isCssEngineActive = isCssEngineActive(cti); |
| boolean isSelectedTab = cti == cti.getParent().getSelection(); |
| boolean partActivatedEvent = newValue instanceof MPlaceholder; |
| |
| if (CSSConstants.CSS_CONTENT_CHANGE_CLASS.equals(newValue)) { |
| part.getTags().remove(CSSConstants.CSS_CONTENT_CHANGE_CLASS); |
| if (!isSelectedTab) { |
| addHighlight(part, cti, isCssEngineActive); |
| } |
| } else if (partActivatedEvent && part.getTags().contains(CSSConstants.CSS_HIGHLIGHTED_CLASS)) { |
| removeHighlight(part, cti, isCssEngineActive); |
| } |
| |
| String prevCssCls = WidgetElement.getCSSClass(cti); |
| setCSSInfo(part, cti); |
| |
| if (prevCssCls == null || !prevCssCls.equals(WidgetElement.getCSSClass(cti))) { |
| reapplyStyles(cti.getParent()); |
| } |
| |
| // Only update tab busy state if the CSS engine is not active |
| if (isCssEngineActive || partActivatedEvent) { |
| return; |
| } |
| |
| updateBusyStateNoCss(cti, newValue, oldValue); |
| } |
| |
| public boolean validateElement(Object element) { |
| return element instanceof MPart || element instanceof MPartStack; |
| } |
| |
| public boolean validateValues(Object oldValue, Object newValue) { |
| return newValue instanceof MPlaceholder |
| // part gets active |
| || isTagAdded(CSSConstants.CSS_BUSY_CLASS, oldValue, newValue) |
| // part gets busy |
| || isTagRemoved(CSSConstants.CSS_BUSY_CLASS, oldValue, newValue) |
| // part gets idle |
| || isTagAdded(CSSConstants.CSS_CONTENT_CHANGE_CLASS, oldValue, newValue); |
| // content of part changed |
| } |
| |
| private boolean isTagAdded(String tagName, Object oldValue, Object newValue) { |
| return oldValue == null && tagName.equals(newValue); |
| } |
| |
| private boolean isTagRemoved(String tagName, Object oldValue, Object newValue) { |
| return newValue == null && tagName.equals(oldValue); |
| } |
| } |
| |
| @SuppressWarnings("restriction") |
| static boolean isCssEngineActive(CTabItem cti) { |
| return WidgetElement.getEngine(cti.getParent()) != null; |
| } |
| |
| static void removeHighlight(MPart part, CTabItem cti, boolean cssEngineActive) { |
| part.getTags().remove(CSSConstants.CSS_HIGHLIGHTED_CLASS); |
| if (!cssEngineActive) { |
| cti.setFont(JFaceResources.getFontRegistry().get(TAB_FONT_KEY)); |
| } |
| } |
| |
| static void addHighlight(MPart part, CTabItem cti, boolean cssEngineActive) { |
| part.getTags().add(CSSConstants.CSS_HIGHLIGHTED_CLASS); |
| if (!cssEngineActive) { |
| cti.setFont(JFaceResources.getFontRegistry().getBold(TAB_FONT_KEY)); |
| } |
| } |
| |
| /** |
| * Updates the visual for busy state of the part tab in case CSS engine is not |
| * active |
| */ |
| static void updateBusyStateNoCss(CTabItem cti, Object newValue, Object oldValue) { |
| Font updatedFont = null; |
| if (CSSConstants.CSS_BUSY_CLASS.equals(newValue)) { |
| updatedFont = JFaceResources.getFontRegistry().getItalic(TAB_FONT_KEY); |
| } else if (CSSConstants.CSS_BUSY_CLASS.equals(oldValue)) { |
| updatedFont = JFaceResources.getFontRegistry().get(TAB_FONT_KEY); |
| } |
| if (updatedFont != null) { |
| cti.setFont(updatedFont); |
| } |
| } |
| |
| @Override |
| protected boolean imageChanged() { |
| return this.imageChanged; |
| } |
| } |