blob: 1cbe73831307b0254ebedde9c584d558c865bea5 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008, 2020 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
* Christoph Läubrich - Bug 433465
*******************************************************************************/
package org.eclipse.e4.ui.workbench.renderers.swt;
import static java.util.Collections.singletonList;
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.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.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;
/**
* 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$
/**
* Default default value for MRU behavior.
*/
public static final boolean MRU_DEFAULT = true;
/*
* 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<MUIElement> activeElements = modelService.findElements(modelService.getTopLevelWindowFor(newActivePart),
null, MUIElement.class, singletonList(CSSConstants.CSS_ACTIVE_CLASS));
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 getMRUValue(Control 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) {
}
/**
* @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) {
MPlaceholder curSharedRef = part.getCurSharedRef();
if (curSharedRef != null) {
// if it's a shared part check its current ref
if (curSharedRef.getTags().contains(IPresentationEngine.NO_CLOSE)) {
return false;
}
return curSharedRef.isCloseable();
}
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;
}
}