diff --git a/bundles/org.eclipse.jface/src/org/eclipse/jface/messages.properties b/bundles/org.eclipse.jface/src/org/eclipse/jface/messages.properties index 2dbb4c71ffb..4c8b01ed4a2 100644 --- a/bundles/org.eclipse.jface/src/org/eclipse/jface/messages.properties +++ b/bundles/org.eclipse.jface/src/org/eclipse/jface/messages.properties @@ -198,6 +198,11 @@ F20 = F20 SafeRunnable.errorMessage = An error has occurred. See error log for more details. ColorSelector.Name=Color Selector +############################################################# +# org.eclipse.jface.viewers +############################################################# +BreadcrumbItemDropDown_showDropDownMenu_action_toolTip=Show Children + ############################################################# # org.eclipse.jface.viewers.deferred ############################################################# diff --git a/bundles/org.eclipse.jface/src/org/eclipse/jface/viewers/BreadcrumbItem.java b/bundles/org.eclipse.jface/src/org/eclipse/jface/viewers/BreadcrumbItem.java new file mode 100644 index 00000000000..320274b9518 --- /dev/null +++ b/bundles/org.eclipse.jface/src/org/eclipse/jface/viewers/BreadcrumbItem.java @@ -0,0 +1,315 @@ +/******************************************************************************* + * Copyright (c) 2008, 2011 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 + * Fabio Zadrozny - Bug 465666 + *******************************************************************************/ +package org.eclipse.jface.viewers; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.Font; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Item; +import org.eclipse.swt.widgets.Shell; + +/** + * An item in a breadcrumb viewer. + *

+ * The item shows a label and an image. It also has the ability to expand, that + * is to open a drop down menu. + *

+ *

+ * The drop down allows to select any child of the items input element. The item + * shows the label and icon of its data element, if any. + *

+ * + * @since 3.41 + */ +class BreadcrumbItem extends Item { + + private final BreadcrumbViewer fParent; + private Composite fContainer; + + private BreadcrumbItemDetails fDetailsBlock; + private BreadcrumbItemDropDown fExpandBlock; + private boolean fIsLast; + + /** + * A new breadcrumb item which is shown inside the given viewer. + * + * @param viewer the items viewer + * @param parent the container containing the item + */ + public BreadcrumbItem(BreadcrumbViewer viewer, Composite parent) { + super(parent, SWT.NONE); + + fParent = viewer; + + fContainer = new Composite(parent, SWT.NONE); + fContainer.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false)); + GridLayout layout = new GridLayout(2, false); + layout.marginBottom = 1; + layout.marginHeight = 0; + layout.marginWidth = 0; + layout.horizontalSpacing = 0; + fContainer.setLayout(layout); + + fDetailsBlock = new BreadcrumbItemDetails(this, fContainer); + + fExpandBlock = new BreadcrumbItemDropDown(this, fContainer); + + fContainer.setData("org.eclipse.e4.ui.css.id", "BreadcrumbItemComposite"); //$NON-NLS-1$ //$NON-NLS-2$ + } + + /** + * Returns this items viewer. + * + * @return the viewer showing this item + */ + public BreadcrumbViewer getViewer() { + return fParent; + } + + /* + * @see org.eclipse.swt.widgets.Widget#dispose() + */ + @Override + public void dispose() { + Shell shell = fExpandBlock.getDropDownShell(); + if (shell != null && !shell.isDisposed()) { + shell.dispose(); + } + fContainer.dispose(); + super.dispose(); + } + + /** + * Should this item show a text label. + * + * @param enabled true if it should + */ + void setShowText(boolean enabled) { + fDetailsBlock.setTextVisible(enabled); + } + + /** + * Does this item show a text label? + * + * @return true if it does. + */ + boolean isShowText() { + return fDetailsBlock.isTextVisible(); + } + + /** + * Returns the width of this item. + * + * @return the width of this item + */ + int getWidth() { + return fDetailsBlock.getWidth() + fExpandBlock.getWidth() + 2; + } + + /** + * Sets whether this item has to be marked as selected or not. + * + * @param selected true if marked as selected + */ + void setSelected(boolean selected) { + fDetailsBlock.setSelected(selected); + } + + /** + * Sets whether this item has the keyboard focus. + * + * @param state true if it has focus, false otherwise + */ + void setFocus(boolean state) { + fDetailsBlock.setFocus(state); + } + + /** + * Returns whether this item has the keyboard focus. + * + * @return true if this item has the keyboard focus + */ + boolean hasFocus() { + return fDetailsBlock.hasFocus(); + } + + /** + * Redraws this item using the viewer's current providers. + */ + void refresh() { + IBaseLabelProvider labelProvider = fParent.getLabelProvider(); + + if (labelProvider instanceof CellLabelProvider cellLabelProvider) { + BreadcrumbViewerRow row = new BreadcrumbViewerRow(fParent, this); + ViewerCell cell = row.getCell(0); + cellLabelProvider.update(cell); + } else { + ILabelProvider simpleLabelProvider = (ILabelProvider) labelProvider; + String text = simpleLabelProvider.getText(getData()); + Image image = simpleLabelProvider.getImage(getData()); + + setText(text); + setImage(image); + } + + refreshToolTip(); + refreshArrow(); + } + + void refreshToolTip() { + ILabelProvider toolTipLabelProvider = fParent.getToolTipLabelProvider(); + if (toolTipLabelProvider != null) { + fDetailsBlock.setToolTip(toolTipLabelProvider.getText(getData())); + } else { + fDetailsBlock.setToolTip(getText()); + } + } + + /** + * Refresh the arrows visibility. + */ + void refreshArrow() { + ITreeContentProvider contentProvider = (ITreeContentProvider) fParent.getContentProvider(); + fExpandBlock.setEnabled(contentProvider.hasChildren(getData())); + } + + /** + * Set whether this is the last item in the breadcrumb item chain or not. + * + * @param isLast true if this is the last item, false + * otherwise + */ + void setIsLastItem(boolean isLast) { + fIsLast = isLast; + + GridData data = (GridData) fContainer.getLayoutData(); + data.grabExcessHorizontalSpace = isLast; + } + + /** + * Sets whether or not the this item should show the details (name and label). + * + * @param visible true if the item shows details + */ + void setDetailsVisible(boolean visible) { + fDetailsBlock.setVisible(visible); + } + + /** + * Expand this item, shows the drop down menu. + */ + void openDropDownMenu() { + fExpandBlock.showMenu(); + } + + /** + * @return true if this item is expanded + */ + boolean isMenuShown() { + return fExpandBlock.isMenuShown(); + } + + /** + * Returns the drop down shell. + * + * @return the shell of the drop down if shown, null otherwise + */ + Shell getDropDownShell() { + return fExpandBlock.getDropDownShell(); + } + + /** + * Returns the drop down selection provider of this item. + * + * @return the selection provider of the drop down or null + */ + ISelectionProvider getDropDownSelectionProvider() { + return fExpandBlock.getDropDownSelectionProvider(); + } + + /** + * Returns the bounds of this item. + * + * @return the bounds of this item + */ + public Rectangle getBounds() { + return fContainer.getBounds(); + } + + /** + * Set the tool tip of the item to the given text. + * + * @param text the tool tip for the item + */ + public void setToolTip(String text) { + fDetailsBlock.setToolTip(text); + } + + Color getForeground() { + return fDetailsBlock.getForeground(); + } + + void setForeground(Color color) { + fDetailsBlock.setForeground(color); + } + + Color getBackground() { + return fDetailsBlock.getBackground(); + } + + void setBackground(Color color) { + fDetailsBlock.setBackground(color); + } + + Font getFont() { + return fDetailsBlock.getFont(); + } + + void setFont(Font font) { + fDetailsBlock.setFont(font); + } + + /* + * @see org.eclipse.swt.widgets.Item#setText(java.lang.String) + */ + @Override + public void setText(String string) { + if (string == null) { + string = ""; //$NON-NLS-1$ + } + + super.setText(string); + fDetailsBlock.setText(string); + + // more or less space might be required for the label + if (fIsLast) + fContainer.layout(true, true); + } + + /* + * @see org.eclipse.swt.widgets.Item#setImage(org.eclipse.swt.graphics.Image) + */ + @Override + public void setImage(Image image) { + super.setImage(image); + fDetailsBlock.setImage(image); + } + +} diff --git a/bundles/org.eclipse.jface/src/org/eclipse/jface/viewers/BreadcrumbItemDetails.java b/bundles/org.eclipse.jface/src/org/eclipse/jface/viewers/BreadcrumbItemDetails.java new file mode 100644 index 00000000000..6ab8496a992 --- /dev/null +++ b/bundles/org.eclipse.jface/src/org/eclipse/jface/viewers/BreadcrumbItemDetails.java @@ -0,0 +1,485 @@ +/******************************************************************************* + * Copyright (c) 2008, 2011 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 + * Fabio Zadrozny - Bug 465666 + *******************************************************************************/ +package org.eclipse.jface.viewers; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.accessibility.AccessibleAdapter; +import org.eclipse.swt.accessibility.AccessibleEvent; +import org.eclipse.swt.events.FocusEvent; +import org.eclipse.swt.events.FocusListener; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.events.KeyListener; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.MouseListener; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.Font; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Shell; + +/** + * The label and icon part of the breadcrumb item. + * + * @since 3.41 + */ +class BreadcrumbItemDetails { + + private final Label fElementImage; + private final Label fElementText; + private final Composite fDetailComposite; + private final BreadcrumbItem fParent; + private final Composite fTextComposite; + private final Composite fImageComposite; + + private boolean fTextVisible; + private boolean fSelected; + private boolean fHasFocus; + + private Color fForeground; + private Color fDefaultForeground; + private Font fFont; + private Font fDefaultFont; + private Color fBackground; + private Color fDefaultDetailBackground; + private Color fDefaultTextBackground; + private Color fDefaultImageBackground; + private Color fDefaultElementTextBackground; + private Color fDefaultElementImageBackground; + + public BreadcrumbItemDetails(BreadcrumbItem parent, Composite parentContainer) { + fParent = parent; + fTextVisible = true; + + fDetailComposite = new Composite(parentContainer, SWT.NONE); + fDetailComposite.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false)); + GridLayout layout = new GridLayout(2, false); + layout.marginHeight = 0; + layout.marginWidth = 0; + layout.horizontalSpacing = 0; + fDetailComposite.setLayout(layout); + addElementListener(fDetailComposite); + + fImageComposite = new Composite(fDetailComposite, SWT.NONE); + fImageComposite.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false)); + layout = new GridLayout(1, false); + layout.marginHeight = 1; + layout.marginWidth = 2; + fImageComposite.setLayout(layout); + fImageComposite.addPaintListener(e -> { + if (fHasFocus && !isTextVisible()) { + e.gc.drawFocus(e.x, e.y, e.width, e.height); + } + }); + installFocusComposite(fImageComposite); + addElementListener(fImageComposite); + + fElementImage = new Label(fImageComposite, SWT.NONE); + GridData layoutData = new GridData(SWT.BEGINNING, SWT.CENTER, false, false); + fElementImage.setLayoutData(layoutData); + addElementListener(fElementImage); + + fTextComposite = new Composite(fDetailComposite, SWT.NONE); + fTextComposite.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false)); + layout = new GridLayout(1, false); + layout.marginHeight = 2; + layout.marginWidth = 2; + fTextComposite.setLayout(layout); + addElementListener(fTextComposite); + fTextComposite.addPaintListener(e -> { + if (fHasFocus && isTextVisible()) { + e.gc.drawFocus(e.x, e.y, e.width, e.height); + } + }); + installFocusComposite(fTextComposite); + + fElementText = new Label(fTextComposite, SWT.NONE); + layoutData = new GridData(SWT.BEGINNING, SWT.CENTER, false, false); + fElementText.setLayoutData(layoutData); + addElementListener(fElementText); + + fTextComposite.getAccessible().addAccessibleListener(new AccessibleAdapter() { + @Override + public void getName(AccessibleEvent e) { + e.result = fElementText.getText(); + } + }); + fImageComposite.getAccessible().addAccessibleListener(new AccessibleAdapter() { + @Override + public void getName(AccessibleEvent e) { + e.result = fElementText.getText(); + } + }); + + fDetailComposite.setTabList(new Control[] { fTextComposite }); + + fDetailComposite.setData("org.eclipse.e4.ui.css.id", "BreadcrumbItemDetailComposite"); //$NON-NLS-1$ //$NON-NLS-2$ + fTextComposite.setData("org.eclipse.e4.ui.css.id", "BreadcrumbItemDetailTextComposite"); //$NON-NLS-1$ //$NON-NLS-2$ + fImageComposite.setData("org.eclipse.e4.ui.css.id", "BreadcrumbItemDetailImageComposite"); //$NON-NLS-1$ //$NON-NLS-2$ + fElementImage.setData("org.eclipse.e4.ui.css.id", "BreadcrumbItemDetailImageLabel"); //$NON-NLS-1$ //$NON-NLS-2$ + fElementText.setData("org.eclipse.e4.ui.css.id", "BreadcrumbItemDetailTextLabel"); //$NON-NLS-1$ //$NON-NLS-2$ + } + + /** + * Returns whether this element has the keyboard focus. + * + * @return true if this element has the keyboard focus. + */ + public boolean hasFocus() { + return fHasFocus; + } + + /** + * Sets the tool tip to the given text. + * + * @param text the tool tip + */ + public void setToolTip(String text) { + if (isTextVisible()) { + fElementText.getParent().setToolTipText(text); + fElementText.setToolTipText(text); + + fElementImage.setToolTipText(text); + } else { + fElementText.getParent().setToolTipText(null); + fElementText.setToolTipText(null); + + fElementImage.setToolTipText(text); + } + } + + /** + * Sets the image to the given image. + * + * @param image the image to use + */ + public void setImage(Image image) { + if (image != fElementImage.getImage()) { + fElementImage.setImage(image); + } + } + + /** + * Sets the text to the given text. + * + * @param text the text to use + */ + public void setText(String text) { + if (text == null) { + text = ""; //$NON-NLS-1$ + } + if (!text.equals(fElementText.getText())) { + fElementText.setText(text); + } + } + + public Color getForeground() { + return fForeground; + } + + public void setForeground(Color color) { + if (fForeground == null && color == null) { + return; + } + + if (fForeground == null) { + fDefaultForeground = fElementText.getForeground(); + } + + fForeground = color; + fElementText.setForeground(color != null ? color : fDefaultForeground); + } + + public Color getBackground() { + return fBackground; + } + + public void setBackground(Color color) { + if (fBackground == null && color == null) { + return; + } + + if (fBackground == null) { + // Preserve the themed defaults so they can be restored when the provider + // returns null. + fDefaultDetailBackground = fDetailComposite.getBackground(); + fDefaultTextBackground = fTextComposite.getBackground(); + fDefaultImageBackground = fImageComposite.getBackground(); + fDefaultElementTextBackground = fElementText.getBackground(); + fDefaultElementImageBackground = fElementImage.getBackground(); + } + + fBackground = color; + + fDetailComposite.setBackground(color != null ? color : fDefaultDetailBackground); + fTextComposite.setBackground(color != null ? color : fDefaultTextBackground); + fImageComposite.setBackground(color != null ? color : fDefaultImageBackground); + fElementText.setBackground(color != null ? color : fDefaultElementTextBackground); + fElementImage.setBackground(color != null ? color : fDefaultElementImageBackground); + } + + public Font getFont() { + return fFont; + } + + public void setFont(Font font) { + if (fFont == null && font == null) { + return; + } + + if (fFont == null) { + fDefaultFont = fElementText.getFont(); + } + + fFont = font; + fElementText.setFont(font != null ? font : fDefaultFont); + } + + /** + * Returns the width of this element. + * + * @return current width of this element + */ + public int getWidth() { + int result = 2; + + if (fElementImage.getImage() != null) + result += fElementImage.computeSize(SWT.DEFAULT, SWT.DEFAULT).x; + + if (fTextVisible && fElementText.getText().length() > 0) + result += fElementText.computeSize(SWT.DEFAULT, SWT.DEFAULT).x; + + return result; + } + + public void setTextVisible(boolean enabled) { + if (fTextVisible == enabled) + return; + + fTextVisible = enabled; + + GridData data = (GridData) fTextComposite.getLayoutData(); + data.exclude = !enabled; + fTextComposite.setVisible(enabled); + + if (fTextVisible) { + fDetailComposite.setTabList(new Control[] { fTextComposite }); + } else { + fDetailComposite.setTabList(new Control[] { fImageComposite }); + } + + if (fHasFocus) { + if (isTextVisible()) { + fTextComposite.setFocus(); + } else { + fImageComposite.setFocus(); + } + } + } + + /** + * Tells whether this item shows a text or only an image. + * + * @return true if it shows a text and an image, false if it only + * shows the image + */ + public boolean isTextVisible() { + return fTextVisible; + } + + /** + * Sets whether details should be shown. + * + * @param visible true if details should be shown + */ + public void setVisible(boolean visible) { + fDetailComposite.setVisible(visible); + + GridData data = (GridData) fDetailComposite.getLayoutData(); + data.exclude = !visible; + } + + public void setSelected(boolean selected) { + if (selected == fSelected) + return; + + fSelected = selected; + if (!fSelected) + fHasFocus = false; + + } + + public void setFocus(boolean enabled) { + if (enabled == fHasFocus) + return; + + fHasFocus = enabled; + if (fHasFocus) { + if (isTextVisible()) { + fTextComposite.setFocus(); + } else { + fImageComposite.setFocus(); + } + } + } + + /** + * Install focus and key listeners to the given composite. + * + * @param composite the composite which may get focus + */ + private void installFocusComposite(Composite composite) { + composite.addTraverseListener(e -> { + if (e.detail == SWT.TRAVERSE_TAB_NEXT || e.detail == SWT.TRAVERSE_TAB_PREVIOUS) { + int index = fParent.getViewer().getIndexOfItem(fParent); + if (e.detail == SWT.TRAVERSE_TAB_NEXT) { + index++; + } else { + index--; + } + + if (index > 0 && index < fParent.getViewer().getItemCount()) { + fParent.getViewer().selectItem(fParent.getViewer().getItem(index)); + } + + e.doit = true; + } + }); + composite.addKeyListener(new KeyListener() { + @Override + public void keyPressed(KeyEvent e) { + BreadcrumbViewer viewer = fParent.getViewer(); + + switch (e.keyCode) { + case SWT.ARROW_LEFT: + if (fSelected) { + viewer.doTraverse(false); + e.doit = false; + } else { + viewer.selectItem(fParent); + } + break; + case SWT.ARROW_RIGHT: + if (fSelected) { + viewer.doTraverse(true); + e.doit = false; + } else { + viewer.selectItem(fParent); + } + break; + case SWT.ARROW_DOWN: + case SWT.KEYPAD_ADD: + if (!fSelected) { + viewer.selectItem(fParent); + } + openDropDown(); + e.doit = false; + break; + case SWT.CR: + if (!fSelected) { + viewer.selectItem(fParent); + } + viewer.fireOpen(); + break; + default: + if (e.character == ' ') { + if (!fSelected) { + viewer.selectItem(fParent); + } + openDropDown(); + e.doit = false; + } + break; + } + } + + private void openDropDown() { + BreadcrumbViewer viewer = fParent.getViewer(); + + int index = viewer.getIndexOfItem(fParent); + BreadcrumbItem parent = fParent.getViewer().getItem(index - 1); + + Shell shell = parent.getDropDownShell(); + if (shell == null) { + parent.openDropDownMenu(); + shell = parent.getDropDownShell(); + } + shell.setFocus(); + } + + @Override + public void keyReleased(KeyEvent e) { + } + }); + + composite.addFocusListener(new FocusListener() { + @Override + public void focusGained(FocusEvent e) { + if (!fHasFocus) { + fHasFocus = true; + } + } + + @Override + public void focusLost(FocusEvent e) { + if (fHasFocus) { + fHasFocus = false; + } + } + }); + } + + /** + * Add mouse listeners to the given control. + * + * @param control the control to which may be clicked + */ + private void addElementListener(Control control) { + control.addMouseListener(new MouseListener() { + @Override + public void mouseDoubleClick(MouseEvent e) { + } + + @Override + public void mouseDown(MouseEvent e) { + BreadcrumbViewer viewer = fParent.getViewer(); + + int parentIndex = viewer.getIndexOfItem(fParent) - 1; + Shell shell = null; + if (parentIndex >= 0) {// sanity check, must always hold + BreadcrumbItem dropDownItem = viewer.getItem(parentIndex); + shell = dropDownItem.getDropDownShell(); + } + + viewer.selectItem(fParent); + if (shell == null && e.button == 1 && e.stateMask == 0) { + fParent.getViewer().fireDoubleClick(); + } + } + + @Override + public void mouseUp(MouseEvent e) { + } + }); + control.addMenuDetectListener(e -> { + BreadcrumbViewer viewer = fParent.getViewer(); + viewer.selectItem(fParent); + fParent.getViewer().fireMenuDetect(e); + }); + } +} diff --git a/bundles/org.eclipse.jface/src/org/eclipse/jface/viewers/BreadcrumbItemDropDown.java b/bundles/org.eclipse.jface/src/org/eclipse/jface/viewers/BreadcrumbItemDropDown.java new file mode 100644 index 00000000000..a81edbb237b --- /dev/null +++ b/bundles/org.eclipse.jface/src/org/eclipse/jface/viewers/BreadcrumbItemDropDown.java @@ -0,0 +1,739 @@ +/******************************************************************************* + * 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 + * Fabio Zadrozny - Bug 465666 + *******************************************************************************/ +package org.eclipse.jface.viewers; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.jface.action.Action; +import org.eclipse.jface.action.ToolBarManager; +import org.eclipse.jface.dialogs.IDialogSettings; +import org.eclipse.jface.resource.CompositeImageDescriptor; +import org.eclipse.jface.resource.JFaceResources; +import org.eclipse.jface.util.OpenStrategy; +import org.eclipse.jface.util.Util; +import org.eclipse.swt.SWT; +import org.eclipse.swt.accessibility.AccessibleAdapter; +import org.eclipse.swt.accessibility.AccessibleEvent; +import org.eclipse.swt.events.ControlAdapter; +import org.eclipse.swt.events.ControlEvent; +import org.eclipse.swt.events.ControlListener; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.events.KeyListener; +import org.eclipse.swt.events.MouseAdapter; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.MouseListener; +import org.eclipse.swt.events.MouseMoveListener; +import org.eclipse.swt.events.ShellEvent; +import org.eclipse.swt.events.ShellListener; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.ImageData; +import org.eclipse.swt.graphics.ImageDataProvider; +import org.eclipse.swt.graphics.ImageGcDrawer; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.RGB; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Item; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.ToolBar; +import org.eclipse.swt.widgets.Tree; +import org.eclipse.swt.widgets.TreeItem; +import org.eclipse.swt.widgets.Widget; + +/** + * The part of the breadcrumb item with the drop down menu. + * + * @since 3.41 + */ +class BreadcrumbItemDropDown { + private static final boolean IS_MAC_WORKAROUND = "carbon".equals(SWT.getPlatform()); //$NON-NLS-1$ + + /** + * An arrow image descriptor. The images color is related to the list fore- and + * background color. This makes the arrow visible even in high contrast mode. If + * ltr is true the arrow points to the right, otherwise it points + * to the left. + */ + private final class AccessibleArrowImage extends CompositeImageDescriptor { + + private final static int ARROW_SIZE = 5; + + private final boolean fLTR; + + public AccessibleArrowImage(boolean ltr) { + fLTR = ltr; + } + + @Override + protected void drawCompositeImage(int width, int height) { + Display display = fParentComposite.getDisplay(); + + ImageDataProvider imageProvider = zoom -> { + ImageGcDrawer drawer = new ImageGcDrawer() { + @Override + public void drawOn(GC gc, int iWidth, int iHeight) { + gc.setAntialias(SWT.ON); + Color triangleColor = createColor(SWT.COLOR_LIST_FOREGROUND, SWT.COLOR_LIST_BACKGROUND, 20, + display); + gc.setBackground(triangleColor); + gc.fillPolygon(new int[] { 0, 0, ARROW_SIZE, ARROW_SIZE, 0, ARROW_SIZE * 2 }); + triangleColor.dispose(); + } + + @Override + public int getGcStyle() { + return fLTR ? SWT.LEFT_TO_RIGHT : SWT.RIGHT_TO_LEFT; + } + }; + Image image = new Image(display, drawer, ARROW_SIZE, ARROW_SIZE * 2); + + ImageData imageData = image.getImageData(zoom); + image.dispose(); + int zoomedArrowSize = ARROW_SIZE * zoom / 100; + for (int y1 = 0; y1 < zoomedArrowSize; y1++) { + // set opaque pixels for top half of the breadcrumb arrow + for (int x1 = 0; x1 <= y1; x1++) { + imageData.setAlpha(fLTR ? x1 : zoomedArrowSize - x1 - 1, y1, 255); + } + // set transparent pixels for top half of the breadcrumbe arrow + for (int x1 = y1 + 1; x1 < zoomedArrowSize; x1++) { + imageData.setAlpha(fLTR ? x1 : zoomedArrowSize - x1 - 1, y1, 0); + } + } + for (int y2 = 0; y2 < zoomedArrowSize; y2++) { + // set opaque pixels for bottom half of the breadcrumb arrow + for (int x2 = 0; x2 <= y2; x2++) { + imageData.setAlpha(fLTR ? x2 : zoomedArrowSize - x2 - 1, zoomedArrowSize * 2 - y2 - 1, 255); + } + // set transparent pixels for bottom half of the breadcrumbe arrow + for (int x2 = y2 + 1; x2 < zoomedArrowSize; x2++) { + imageData.setAlpha(fLTR ? x2 : zoomedArrowSize - x2 - 1, zoomedArrowSize * 2 - y2 - 1, 0); + } + } + return imageData; + }; + drawImage(imageProvider, (width / 2) - (ARROW_SIZE / 2), (height / 2) - ARROW_SIZE); + } + + @Override + protected Point getSize() { + return new Point(10, 16); + } + + private Color createColor(int color1, int color2, int ratio, Display display) { + RGB rgb1 = display.getSystemColor(color1).getRGB(); + RGB rgb2 = display.getSystemColor(color2).getRGB(); + + RGB blend = blend(rgb2, rgb1, ratio); + + return new Color(blend); + } + + private static RGB blend(RGB rgb1, RGB rgb2, int ratio) { + int r = (rgb1.red * ratio + rgb2.red * (100 - ratio)) / 100; + int g = (rgb1.green * ratio + rgb2.green * (100 - ratio)) / 100; + int b = (rgb1.blue * ratio + rgb2.blue * (100 - ratio)) / 100; + + return new RGB(r, g, b); + } + } + + private static final int DROP_DOWN_MIN_WIDTH = 250; + private static final int DROP_DOWN_MAX_WIDTH = 500; + + private static final int DROP_DOWN_DEFAULT_MIN_HEIGHT = 200; + private static final int DROP_DOWN_DEFAULT_MAX_HEIGHT = 300; + + private static final String DIALOG_SETTINGS = "BreadcrumbItemDropDown"; //$NON-NLS-1$ + private static final String DIALOG_HEIGHT = "height"; //$NON-NLS-1$ + + private final BreadcrumbItem fParent; + private final Composite fParentComposite; + private final ToolBar fToolBar; + + private boolean fMenuIsShown; + private boolean fEnabled; + private TreeViewer fDropDownViewer; + private Shell fShell; + private boolean isResizingProgrammatically; + + public BreadcrumbItemDropDown(BreadcrumbItem parent, Composite composite) { + fParent = parent; + fParentComposite = composite; + fMenuIsShown = false; + fEnabled = true; + + String showChildrenText = JFaceResources.getString("BreadcrumbItemDropDown_showDropDownMenu_action_toolTip"); //$NON-NLS-1$ + + fToolBar = new ToolBar(composite, SWT.FLAT); + fToolBar.setLayoutData(new GridData(SWT.END, SWT.CENTER, false, false)); + fToolBar.getAccessible().addAccessibleListener(new AccessibleAdapter() { + @Override + public void getName(AccessibleEvent event) { + event.result = showChildrenText; + } + }); + ToolBarManager manager = new ToolBarManager(fToolBar); + + final Action showDropDownMenuAction = new Action(null, SWT.NONE) { + @Override + public void run() { + Shell shell = fParent.getDropDownShell(); + if (shell != null) + return; + + shell = fParent.getViewer().getDropDownShell(); + if (shell != null) + shell.close(); + + showMenu(); + + fShell.setFocus(); + } + }; + + showDropDownMenuAction.setImageDescriptor(new AccessibleArrowImage(isLTR())); + showDropDownMenuAction + .setToolTipText(showChildrenText); + manager.add(showDropDownMenuAction); + + manager.update(true); + if (IS_MAC_WORKAROUND) { + manager.getControl().addMouseListener(new MouseAdapter() { + // see also BreadcrumbItemDetails#addElementListener(Control) + @Override + public void mouseDown(MouseEvent e) { + showDropDownMenuAction.run(); + } + }); + } + fToolBar.setData("org.eclipse.e4.ui.css.id", "BreadcrumbItemDropDownToolBar"); //$NON-NLS-1$ //$NON-NLS-2$ + } + + /** + * Return the width of this element. + * + * @return the width of this element + */ + public int getWidth() { + return fToolBar.computeSize(SWT.DEFAULT, SWT.DEFAULT).x; + } + + /** + * Set whether the drop down menu is available. + * + * @param enabled true if available + */ + public void setEnabled(boolean enabled) { + fEnabled = enabled; + + fToolBar.setVisible(enabled); + } + + /** + * Tells whether the menu is shown. + * + * @return true if the menu is open + */ + public boolean isMenuShown() { + return fMenuIsShown; + } + + /** + * Returns the shell used for the drop down menu if it is shown. + * + * @return the drop down shell or null + */ + public Shell getDropDownShell() { + if (!isMenuShown()) + return null; + + return fShell; + } + + /** + * Returns the drop down selection provider. + * + * @return the selection provider of the drop down if the menu is shown, + * {@code null} otherwise. + */ + public ISelectionProvider getDropDownSelectionProvider() { + if (!fMenuIsShown) + return null; + + return fDropDownViewer; + } + + /** + * Opens the drop down menu. + */ + public void showMenu() { + + if (!fEnabled || fMenuIsShown) + return; + + fMenuIsShown = true; + + fShell = new Shell(fToolBar.getShell(), SWT.RESIZE | SWT.TOOL | SWT.ON_TOP); + + GridLayout layout = new GridLayout(1, false); + layout.marginHeight = 0; + layout.marginWidth = 0; + fShell.setLayout(layout); + + Composite composite = new Composite(fShell, SWT.NONE); + composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + GridLayout gridLayout = new GridLayout(1, false); + gridLayout.marginHeight = 0; + gridLayout.marginWidth = 0; + composite.setLayout(gridLayout); + + fDropDownViewer = fParent.getViewer().createDropDownViewer(composite, SWT.SINGLE | SWT.H_SCROLL | SWT.V_SCROLL); + fDropDownViewer.setUseHashlookup(true); + + final Tree tree = (Tree) fDropDownViewer.getControl(); + tree.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + + Object input = fParent.getData(); + fParent.getViewer().configureDropDownViewer(fDropDownViewer, input); + fDropDownViewer.setInput(input); + + setShellBounds(fShell); + + fShell.addControlListener(new ControlAdapter() { + /* + * @see + * org.eclipse.swt.events.ControlAdapter#controlResized(org.eclipse.swt.events. + * ControlEvent) + */ + @Override + public void controlResized(ControlEvent e) { + if (isResizingProgrammatically) + return; + + Point size = fShell.getSize(); + getDialogSettings().put(DIALOG_HEIGHT, size.y); + } + }); + + fDropDownViewer.addOpenListener(event -> { + + ISelection selection = event.getSelection(); + if (!(selection instanceof IStructuredSelection)) + return; + + Object element = ((IStructuredSelection) selection).getFirstElement(); + if (element == null) + return; + + openElement(element); + }); + + tree.addMouseListener(new MouseListener() { + @Override + public void mouseUp(MouseEvent e) { + + if (e.button != 1) + return; + + if ((OpenStrategy.getOpenMethod() & OpenStrategy.SINGLE_CLICK) != 0) + return; + + Item item = tree.getItem(new Point(e.x, e.y)); + if (item == null) + return; + + openElement(item.getData()); + } + + @Override + public void mouseDown(MouseEvent e) { + } + + @Override + public void mouseDoubleClick(MouseEvent e) { + } + }); + + tree.addMouseMoveListener(new MouseMoveListener() { + TreeItem fLastItem = null; + + @Override + public void mouseMove(MouseEvent e) { + if (tree.equals(e.getSource())) { + TreeItem o = tree.getItem(new Point(e.x, e.y)); + if ((o != null && !o.equals(fLastItem)) || (fLastItem != null && o == null)) { + boolean showHandPointer = false; + if (o != null) { + Object itemData = o.getData(); + showHandPointer = itemData != null && fParent.getViewer().isElementOpenable(itemData); + } + tree.setCursor(showHandPointer ? tree.getDisplay().getSystemCursor(SWT.CURSOR_HAND) : null); + } + if (o != null) { + Rectangle clientArea = tree.getClientArea(); + TreeItem currentItem = o; + if (!o.equals(fLastItem)) { + fLastItem = o; + tree.setSelection(new TreeItem[] { fLastItem }); + } else if (e.y - clientArea.y < tree.getItemHeight() / 4) { + // Scroll up + if (currentItem.getParentItem() == null) { + int index = tree.indexOf(o); + if (index < 1) + return; + + fLastItem = tree.getItem(index - 1); + tree.setSelection(new TreeItem[] { fLastItem }); + } else { + Point p = tree.toDisplay(e.x, e.y); + Item item = fDropDownViewer.scrollUp(p.x, p.y); + if (item instanceof TreeItem) { + fLastItem = (TreeItem) item; + tree.setSelection(new TreeItem[] { fLastItem }); + } + } + } else if (clientArea.y + clientArea.height - e.y < tree.getItemHeight() / 4) { + // Scroll down + if (currentItem.getParentItem() == null) { + int index = tree.indexOf(o); + if (index >= tree.getItemCount() - 1) + return; + + fLastItem = tree.getItem(index + 1); + tree.setSelection(new TreeItem[] { fLastItem }); + } else { + Point p = tree.toDisplay(e.x, e.y); + Item item = fDropDownViewer.scrollDown(p.x, p.y); + if (item instanceof TreeItem) { + fLastItem = (TreeItem) item; + tree.setSelection(new TreeItem[] { fLastItem }); + } + } + } + } else { + fLastItem = null; + } + } + } + }); + + tree.addKeyListener(new KeyListener() { + @Override + public void keyPressed(KeyEvent e) { + if (e.keyCode == SWT.ARROW_UP) { + TreeItem[] selection = tree.getSelection(); + if (selection.length != 1) + return; + + int selectionIndex = tree.indexOf(selection[0]); + if (selectionIndex != 0) + return; + + fShell.close(); + } + } + + @Override + public void keyReleased(KeyEvent e) { + } + }); + + fDropDownViewer.addTreeListener(new ITreeViewerListener() { + @Override + public void treeCollapsed(TreeExpansionEvent event) { + } + + @Override + public void treeExpanded(TreeExpansionEvent event) { + Shell shell = fShell; + tree.setRedraw(false); + shell.getDisplay().asyncExec(() -> { + if (shell.isDisposed() || tree.isDisposed()) + return; + + try { + resizeShell(shell); + } finally { + if (!tree.isDisposed()) + tree.setRedraw(true); + } + }); + } + }); + + int index = fParent.getViewer().getIndexOfItem(fParent); + if (index < fParent.getViewer().getItemCount() - 1) { + BreadcrumbItem childItem = fParent.getViewer().getItem(index + 1); + Object child = childItem.getData(); + + fDropDownViewer.setSelection(new StructuredSelection(child), true); + + TreeItem[] selection = tree.getSelection(); + if (selection.length > 0) { + tree.setTopItem(selection[0]); + } + } + + fShell.setVisible(true); + installCloser(fShell); + } + + private void openElement(Object data) { + if (data == null) + return; + + // This might or might not open an editor + fParent.getViewer().fireMenuSelection(data); + + boolean treeHasFocus = !fShell.isDisposed() && fDropDownViewer.getTree().isFocusControl(); + + if (fShell.isDisposed()) + return; + + if (!treeHasFocus) { + fShell.close(); + return; + } + + toggleExpansionState(data); + } + + private void toggleExpansionState(Object element) { + Tree tree = fDropDownViewer.getTree(); + if (fDropDownViewer.getExpandedState(element)) + fDropDownViewer.collapseToLevel(element, 1); + else { + tree.setRedraw(false); + try { + fDropDownViewer.expandToLevel(element, 1); + resizeShell(fShell); + } finally { + tree.setRedraw(true); + } + } + } + + /** + * The closer closes the given shell when the focus is lost. + * + * @param shell the shell to install the closer to + */ + private void installCloser(final Shell shell) { + final Listener focusListener = event -> { + Widget focusElement = event.widget; + boolean isFocusBreadcrumbTreeFocusWidget = focusElement == shell + || focusElement instanceof Tree && ((Tree) focusElement).getShell() == shell; + boolean isFocusWidgetParentShell = focusElement instanceof Control + && ((Control) focusElement).getShell().getParent() == shell; + + switch (event.type) { + case SWT.FocusIn: + + if (!isFocusBreadcrumbTreeFocusWidget && !isFocusWidgetParentShell) { + shell.close(); + } + break; + + case SWT.FocusOut: + + if (event.display.getActiveShell() == null) { + shell.close(); + } + break; + + default: + Assert.isTrue(false); + } + }; + + final Display display = shell.getDisplay(); + display.addFilter(SWT.FocusIn, focusListener); + display.addFilter(SWT.FocusOut, focusListener); + + final ControlListener controlListener = new ControlListener() { + @Override + public void controlMoved(ControlEvent e) { + shell.close(); + } + + @Override + public void controlResized(ControlEvent e) { + shell.close(); + } + }; + fToolBar.getShell().addControlListener(controlListener); + + shell.addDisposeListener(e -> { + + display.removeFilter(SWT.FocusIn, focusListener); + display.removeFilter(SWT.FocusOut, focusListener); + + if (!fToolBar.isDisposed()) { + fToolBar.getShell().removeControlListener(controlListener); + } + + // The shell may be disposed directly without receiving shellClosed. + fMenuIsShown = false; + fDropDownViewer = null; + }); + shell.addShellListener(new ShellListener() { + @Override + public void shellActivated(ShellEvent e) { + } + + @Override + public void shellClosed(ShellEvent e) { + + if (!fMenuIsShown) + return; + + fMenuIsShown = false; + fDropDownViewer = null; + } + + @Override + public void shellDeactivated(ShellEvent e) { + } + + @Override + public void shellDeiconified(ShellEvent e) { + } + + @Override + public void shellIconified(ShellEvent e) { + } + }); + } + + private IDialogSettings getDialogSettings() { + IDialogSettings viewerSettings = fParent.getViewer().getDialogSettings(); + IDialogSettings settings = viewerSettings.getSection(DIALOG_SETTINGS); + if (settings == null) + settings = viewerSettings.addNewSection(DIALOG_SETTINGS); + return settings; + } + + private int getMaxHeight() { + try { + return getDialogSettings().getInt(DIALOG_HEIGHT); + } catch (NumberFormatException e) { + return DROP_DOWN_DEFAULT_MAX_HEIGHT; + } + } + + /** + * Calculates a useful size for the given shell. + * + * @param shell the shell to calculate the size for. + */ + private void setShellBounds(Shell shell) { + Rectangle rect = fParentComposite.getBounds(); + Rectangle toolbarBounds = fToolBar.getBounds(); + + shell.pack(); + Point size = shell.getSize(); + int height = Math.max(Math.min(size.y, getMaxHeight()), DROP_DOWN_DEFAULT_MIN_HEIGHT); + int width = Math.max(Math.min(size.x, DROP_DOWN_MAX_WIDTH), DROP_DOWN_MIN_WIDTH); + + int imageBoundsX = 0; + if (fDropDownViewer.getTree().getItemCount() > 0) { + TreeItem item = fDropDownViewer.getTree().getItem(0); + imageBoundsX = item.getImageBounds(0).x; + } + + Rectangle trim = fShell.computeTrim(0, 0, width, height); + int x = toolbarBounds.x + toolbarBounds.width + 2 + trim.x - imageBoundsX; + if (!isLTR()) + x += width; + + // toDisplay expects coordinates relative to fParentComposite. + Point pt = new Point(x, rect.height); + pt = fParentComposite.toDisplay(pt); + + Rectangle monitor = Util.getClosestMonitor(shell.getDisplay(), pt).getClientArea(); + int overlap = (pt.x + width) - (monitor.x + monitor.width); + if (overlap > 0) + pt.x -= overlap; + if (pt.x < monitor.x) + pt.x = monitor.x; + + shell.setLocation(pt); + shell.setSize(width, height); + } + + /** + * Set the size of the given shell such that more content can be shown. The + * shell size does not exceed a user-configurable maximum. + * + * @param shell the shell to resize + */ + private void resizeShell(final Shell shell) { + Point size = shell.getSize(); + int currentWidth = size.x; + int currentHeight = size.y; + + int maxHeight = getMaxHeight(); + + if (currentHeight >= maxHeight && currentWidth >= DROP_DOWN_MAX_WIDTH) + return; + + Point preferedSize = shell.computeSize(SWT.DEFAULT, SWT.DEFAULT, true); + + int newWidth; + if (currentWidth >= DROP_DOWN_MAX_WIDTH) { + newWidth = currentWidth; + } else { + newWidth = Math.min(Math.max(preferedSize.x, currentWidth), DROP_DOWN_MAX_WIDTH); + } + int newHeight; + if (currentHeight >= maxHeight) { + newHeight = currentHeight; + } else { + newHeight = Math.min(Math.max(preferedSize.y, currentHeight), maxHeight); + } + + if (newHeight != currentHeight || newWidth != currentWidth) { + shell.setRedraw(false); + try { + isResizingProgrammatically = true; + shell.setSize(newWidth, newHeight); + if (!isLTR()) { + Point location = shell.getLocation(); + shell.setLocation(location.x - (newWidth - currentWidth), location.y); + } + } finally { + isResizingProgrammatically = false; + shell.setRedraw(true); + } + } + } + + /** + * Tells whether this the breadcrumb is in LTR or RTL mode. + * + * @return true if the breadcrumb in left-to-right mode, + * false otherwise + */ + private boolean isLTR() { + return (fParentComposite.getStyle() & SWT.RIGHT_TO_LEFT) == 0; + } +} diff --git a/bundles/org.eclipse.jface/src/org/eclipse/jface/viewers/BreadcrumbViewer.java b/bundles/org.eclipse.jface/src/org/eclipse/jface/viewers/BreadcrumbViewer.java new file mode 100644 index 00000000000..ddec7e7a139 --- /dev/null +++ b/bundles/org.eclipse.jface/src/org/eclipse/jface/viewers/BreadcrumbViewer.java @@ -0,0 +1,825 @@ +/******************************************************************************* + * Copyright (c) 2008, 2011 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 + * Fabio Zadrozny - Bug 465666 + *******************************************************************************/ +package org.eclipse.jface.viewers; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.ListenerList; +import org.eclipse.jface.dialogs.DialogSettings; +import org.eclipse.jface.dialogs.IDialogSettings; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.DisposeEvent; +import org.eclipse.swt.events.MenuDetectEvent; +import org.eclipse.swt.events.MenuDetectListener; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Widget; + +/** + * A breadcrumb viewer shows a the parent chain of its input element in a list. + * Each breadcrumb item of that list can be expanded and a sibling of the + * element presented by the breadcrumb item can be selected. + *

+ * Content providers for breadcrumb viewers must implement the + * ITreeContentProvider interface. + *

+ *

+ * Label providers for breadcrumb viewers must either implement + * ILabelProvider or extend CellLabelProvider. + *

+ * + * @since 3.41 + */ +public abstract class BreadcrumbViewer extends StructuredViewer { + + private static final boolean IS_GTK = "gtk".equals(SWT.getPlatform()); //$NON-NLS-1$ + + private final Composite fContainer; + private final ArrayList fBreadcrumbItems; + private final ListenerList fMenuListeners; + + private Image fGradientBackground; + private BreadcrumbItem fSelectedItem; + private ILabelProvider fToolTipLabelProvider; + private final IDialogSettings fDialogSettings = new DialogSettings("BreadcrumbViewer"); //$NON-NLS-1$ + + private final boolean fVertical; + + /** + * Create a new BreadcrumbViewer. + *

+ * Style is one of: + *

+ * + * @param parent the container for the viewer + * @param style the style flag used for this viewer + */ + public BreadcrumbViewer(Composite parent, int style) { + fBreadcrumbItems = new ArrayList<>(); + fMenuListeners = new ListenerList<>(); + + fVertical = (SWT.VERTICAL & style) != 0; + + fContainer = new Composite(parent, SWT.NONE); + GridData layoutData = new GridData(SWT.FILL, SWT.TOP, true, false); + fContainer.setLayoutData(layoutData); + fContainer.addTraverseListener(e -> e.doit = true); + fContainer.setBackgroundMode(SWT.INHERIT_DEFAULT); + fContainer.setData("org.eclipse.e4.ui.css.id", "BreadcrumbComposite"); //$NON-NLS-1$ //$NON-NLS-2$ + + hookControl(fContainer); + + int columns = 1000; + if (fVertical) { + columns = 1; + } + + GridLayout gridLayout = new GridLayout(columns, false); + gridLayout.marginWidth = 0; + gridLayout.marginHeight = 0; + gridLayout.verticalSpacing = 0; + gridLayout.horizontalSpacing = 0; + fContainer.setLayout(gridLayout); + + fContainer.addListener(SWT.Resize, event -> refresh()); + } + + /** + * Configure the given drop down viewer. The given input is used for the viewers + * input. Clients must at least set the label and the content provider for the + * viewer. + * + * @param viewer the viewer to configure + * @param input the input for the viewer + */ + protected abstract void configureDropDownViewer(TreeViewer viewer, Object input); + + /** + * The tool tip to use for the tool tip labels. null if the viewers + * label provider should be used. + * + * @param toolTipLabelProvider the label provider for the tool tips or + * null + */ + public void setToolTipLabelProvider(ILabelProvider toolTipLabelProvider) { + fToolTipLabelProvider = toolTipLabelProvider; + + for (BreadcrumbItem item : fBreadcrumbItems) { + item.refreshToolTip(); + } + } + + ILabelProvider getToolTipLabelProvider() { + return fToolTipLabelProvider; + } + + /* + * @see org.eclipse.jface.viewers.Viewer#getControl() + */ + @Override + public Control getControl() { + return fContainer; + } + + /* + * @see org.eclipse.jface.viewers.StructuredViewer#getRoot() + */ + @Override + protected Object getRoot() { + if (fBreadcrumbItems.isEmpty()) + return null; + + return fBreadcrumbItems.get(0).getData(); + } + + /* + * @see org.eclipse.jface.viewers.StructuredViewer#reveal(java.lang.Object) + */ + @Override + public void reveal(Object element) { + // all elements are always visible + } + + /** + * Transfers the keyboard focus into the viewer. + */ + public void setFocus() { + fContainer.setFocus(); + + if (fSelectedItem != null) { + fSelectedItem.setFocus(true); + } else { + if (fBreadcrumbItems.isEmpty()) + return; + + BreadcrumbItem item = fBreadcrumbItems.get(fBreadcrumbItems.size() - 1); + if (item.getData() == null) { + if (fBreadcrumbItems.size() < 2) + return; + + item = fBreadcrumbItems.get(fBreadcrumbItems.size() - 2); + } + item.setFocus(true); + } + } + + /** + * @return true if any of the items in the viewer is expanded + */ + public boolean isDropDownOpen() { + for (BreadcrumbItem item : fBreadcrumbItems) { + if (item.isMenuShown()) + return true; + } + + return false; + } + + /** + * The shell used for the shown drop down or null if no drop down + * is shown at the moment. + * + * @return the drop downs shell or null + */ + public Shell getDropDownShell() { + for (BreadcrumbItem item : fBreadcrumbItems) { + if (item.isMenuShown()) + return item.getDropDownShell(); + } + + return null; + } + + /** + * Returns the selection provider which provides the selection of the drop down + * currently opened or null if no drop down is open at the moment. + * + * @return the selection provider of the open drop down or null + */ + public ISelectionProvider getDropDownSelectionProvider() { + for (BreadcrumbItem item : fBreadcrumbItems) { + if (item.isMenuShown()) { + return item.getDropDownSelectionProvider(); + } + } + + return null; + } + + /** + * Add the given listener to the set of listeners which will be informed when a + * context menu is requested for a breadcrumb item. + * + * @param listener the listener to add + */ + public void addMenuDetectListener(MenuDetectListener listener) { + fMenuListeners.add(listener); + } + + /** + * Remove the given listener from the set of menu detect listeners. Does nothing + * if the listener is not element of the set. + * + * @param listener the listener to remove + */ + public void removeMenuDetectListener(MenuDetectListener listener) { + fMenuListeners.remove(listener); + } + + /* + * @see + * org.eclipse.jface.viewers.StructuredViewer#assertContentProviderType(org. + * eclipse.jface.viewers.IContentProvider) + */ + @Override + protected void assertContentProviderType(IContentProvider provider) { + super.assertContentProviderType(provider); + Assert.isTrue(provider instanceof ITreeContentProvider); + } + + /* + * @see org.eclipse.jface.viewers.Viewer#inputChanged(java.lang.Object, + * java.lang.Object) + */ + @Override + protected void inputChanged(Object fInput, Object oldInput) { + if (fContainer.isDisposed()) + return; + + disableRedraw(); + try { + rebuildItemChain(fInput); + } finally { + enableRedraw(); + } + } + + private void rebuildItemChain(Object input) { + if (fBreadcrumbItems.size() > 0) { + BreadcrumbItem last = fBreadcrumbItems.get(fBreadcrumbItems.size() - 1); + last.setIsLastItem(false); + } + + int lastIndex = buildItemChain(input); + + if (lastIndex > 0) { + BreadcrumbItem last = fBreadcrumbItems.get(lastIndex - 1); + last.setIsLastItem(true); + } + + while (lastIndex < fBreadcrumbItems.size()) { + BreadcrumbItem item = fBreadcrumbItems.remove(fBreadcrumbItems.size() - 1); + if (item == fSelectedItem) { + selectItem(null); + } + if (item.getData() != null) + unmapElement(item.getData(), item); + item.dispose(); + } + + updateSize(); + fContainer.layout(true, true); + } + + /* + * @see + * org.eclipse.jface.viewers.StructuredViewer#doFindInputItem(java.lang.Object) + */ + @Override + protected Widget doFindInputItem(Object element) { + if (element == null) + return null; + + if (equals(element, getInput())) + return doFindItem(element); + + return null; + } + + /* + * @see org.eclipse.jface.viewers.StructuredViewer#doFindItem(java.lang.Object) + */ + @Override + protected Widget doFindItem(Object element) { + if (element == null) + return null; + + for (BreadcrumbItem item : fBreadcrumbItems) { + if (equals(element, item.getData())) + return item; + } + + return null; + } + + /* + * @see org.eclipse.jface.viewers.StructuredViewer#doUpdateItem(org.eclipse.swt. + * widgets.Widget, java.lang.Object, boolean) + */ + @Override + protected void doUpdateItem(Widget widget, Object element, boolean fullMap) { + if (widget instanceof BreadcrumbItem) { + final BreadcrumbItem item = (BreadcrumbItem) widget; + + // remember element we are showing + if (fullMap) { + associate(element, item); + } else { + Object data = item.getData(); + if (data != null) { + unmapElement(data, item); + } + item.setData(element); + mapElement(element, item); + } + + BreadcrumbViewerRow row = new BreadcrumbViewerRow(this, item); + ViewerCell cell = row.getCell(0); + + IBaseLabelProvider labelProvider = getLabelProvider(); + if (labelProvider instanceof CellLabelProvider cellLabelProvider) { + cellLabelProvider.update(cell); + } else { + ILabelProvider simpleLabelProvider = (ILabelProvider) labelProvider; + cell.setText(simpleLabelProvider.getText(element)); + cell.setImage(simpleLabelProvider.getImage(element)); + } + + item.refreshArrow(); + item.refreshToolTip(); + + updateSize(); + fContainer.layout(true, true); + } + } + + /* + * @see org.eclipse.jface.viewers.StructuredViewer#getSelectionFromWidget() + */ + @Override + protected List getSelectionFromWidget() { + if (fSelectedItem == null) + return Collections.EMPTY_LIST; + + if (fSelectedItem.getData() == null) + return Collections.EMPTY_LIST; + + ArrayList result = new ArrayList<>(); + result.add(fSelectedItem.getData()); + return result; + } + + /* + * @see + * org.eclipse.jface.viewers.StructuredViewer#internalRefresh(java.lang.Object) + */ + @Override + protected void internalRefresh(Object element) { + + disableRedraw(); + try { + if (equals(element, getRoot())) { + rebuildItemChain(getInput()); + return; + } + + BreadcrumbItem item = (BreadcrumbItem) doFindItem(element); + if (item == null) { + for (BreadcrumbItem item1 : fBreadcrumbItems) { + item1.refresh(); + } + } else { + item.refresh(); + } + + updateSize(); + fContainer.layout(true, true); + } finally { + enableRedraw(); + } + } + + /* + * @see + * org.eclipse.jface.viewers.StructuredViewer#setSelectionToWidget(java.util. + * List, boolean) + */ + @Override + protected void setSelectionToWidget(List l, boolean reveal) { + BreadcrumbItem focusItem = null; + + for (BreadcrumbItem item : fBreadcrumbItems) { + if (item.hasFocus()) + focusItem = item; + + item.setSelected(false); + } + + fSelectedItem = null; + + if (l == null) + return; + + for (Object element : l) { + BreadcrumbItem item = (BreadcrumbItem) doFindItem(element); + if (item != null) { + item.setSelected(true); + fSelectedItem = item; + if (item == focusItem) { + item.setFocus(true); + } + } + } + } + + /** + * Set a single selection to the given item. null to deselect all. + * + * @param item the item to select or null + */ + void selectItem(BreadcrumbItem item) { + if (fSelectedItem != null) + fSelectedItem.setSelected(false); + + fSelectedItem = item; + setSelectionToWidget(getSelection(), false); + + if (item != null) { + setFocus(); + } else { + for (BreadcrumbItem listItem : fBreadcrumbItems) { + listItem.setFocus(false); + } + } + + fireSelectionChanged(new SelectionChangedEvent(this, getSelection())); + } + + /** + * Returns the item count. + * + * @return number of items shown in the viewer + */ + int getItemCount() { + return fBreadcrumbItems.size(); + } + + /** + * Returns the item for the given item index. + * + * @param index the index of the item + * @return the item ad the given index + */ + BreadcrumbItem getItem(int index) { + return fBreadcrumbItems.get(index); + } + + /** + * Returns the index of the given item. + * + * @param item the item to search + * @return the index of the item or -1 if not found + */ + int getIndexOfItem(BreadcrumbItem item) { + for (int i = 0, size = fBreadcrumbItems.size(); i < size; i++) { + BreadcrumbItem pItem = fBreadcrumbItems.get(i); + if (pItem == item) + return i; + } + + return -1; + } + + /** + * Notifies all double click listeners. + */ + void fireDoubleClick() { + fireDoubleClick(new DoubleClickEvent(this, getSelection())); + } + + /** + * Notifies all open listeners. + */ + void fireOpen() { + fireOpen(new OpenEvent(this, getSelection())); + } + + /** + * The given element was selected from a drop down menu. + * + * @param element the selected element + */ + void fireMenuSelection(Object element) { + fireOpen(new OpenEvent(this, new StructuredSelection(element))); + } + + /** + * A context menu has been requested for the selected breadcrumb item. + * + * @param event the event issued the menu detection + */ + void fireMenuDetect(MenuDetectEvent event) { + for (MenuDetectListener listener : fMenuListeners) { + listener.menuDetected(event); + } + } + + /** + * Set selection to the next or previous element if possible. + * + * @param next true if the next element should be selected, + * otherwise the previous one will be selected + */ + void doTraverse(boolean next) { + if (fSelectedItem == null) + return; + + int index = fBreadcrumbItems.indexOf(fSelectedItem); + if (next) { + if (index == fBreadcrumbItems.size() - 1) { + BreadcrumbItem current = fBreadcrumbItems.get(index); + + ITreeContentProvider contentProvider = (ITreeContentProvider) getContentProvider(); + if (!contentProvider.hasChildren(current.getData())) + return; + + current.openDropDownMenu(); + current.getDropDownShell().setFocus(); + } else { + BreadcrumbItem nextItem = fBreadcrumbItems.get(index + 1); + selectItem(nextItem); + } + } else { + if (index == 1) { + BreadcrumbItem root = fBreadcrumbItems.get(0); + root.openDropDownMenu(); + root.getDropDownShell().setFocus(); + } else { + selectItem(fBreadcrumbItems.get(index - 1)); + } + } + } + + /** + * Generates the parent chain of the given element. + * + * @param element element to build the parent chain for + * @return the first index of an item in fBreadcrumbItems which is not part of + * the chain + */ + private int buildItemChain(Object element) { + if (element == null) + return 0; + + ITreeContentProvider contentProvider = (ITreeContentProvider) getContentProvider(); + Object parent = contentProvider.getParent(element); + + int index = buildItemChain(parent); + + BreadcrumbItem item; + if (index < fBreadcrumbItems.size()) { + item = fBreadcrumbItems.get(index); + } else { + item = createItem(); + fBreadcrumbItems.add(item); + } + + if (equals(element, item.getData())) { + item.refresh(); + } else { + if (item.getData() != null) + unmapElement(item.getData(), item); + + item.setData(element); + item.refresh(); + } + if (parent == null) { + // don't show the models root + item.setDetailsVisible(false); + } + + mapElement(element, item); + + return index + 1; + } + + /** + * Creates and returns a new instance of a breadcrumb item. + * + * @return new instance of a breadcrumb item + */ + private BreadcrumbItem createItem() { + return new BreadcrumbItem(this, fContainer); + } + + /** + * Update the size of the items such that all items are visible, if possible. + * + * @return true if any item has changed, false + * otherwise + */ + private boolean updateSize() { + int width = fContainer.getClientArea().width; + + int currentWidth = getCurrentWidth(); + + boolean requiresLayout = false; + + if (currentWidth > width) { + int index = 0; + while (currentWidth > width && index < fBreadcrumbItems.size() - 1) { + BreadcrumbItem viewer = fBreadcrumbItems.get(index); + if (viewer.isShowText()) { + viewer.setShowText(false); + currentWidth = getCurrentWidth(); + requiresLayout = true; + } + + index++; + } + + } else if (currentWidth < width) { + + int index = fBreadcrumbItems.size() - 1; + while (currentWidth < width && index >= 0) { + + BreadcrumbItem viewer = fBreadcrumbItems.get(index); + if (!viewer.isShowText()) { + viewer.setShowText(true); + currentWidth = getCurrentWidth(); + if (currentWidth > width) { + viewer.setShowText(false); + index = 0; + } else { + requiresLayout = true; + } + } + + index--; + } + } + + return requiresLayout; + } + + /** + * Returns the current width of all items in the list. + * + * @return the width of all items in the list + */ + private int getCurrentWidth() { + int result = 0; + for (BreadcrumbItem viewer : fBreadcrumbItems) { + if (fVertical) { + result = Math.max(result, viewer.getWidth()); + } else { + result += viewer.getWidth(); + } + } + + return result; + } + + /** + * Enables redrawing of the breadcrumb. + */ + private void enableRedraw() { + if (IS_GTK) // flickers on GTK + return; + + fContainer.setRedraw(true); + } + + /** + * Disables redrawing of the breadcrumb. + * + *

+ * A call to this method must be followed by a call to + * {@link #enableRedraw()} + *

+ */ + private void disableRedraw() { + if (IS_GTK) // flickers on GTK + return; + + fContainer.setRedraw(false); + } + + /* + * @see + * org.eclipse.jface.viewers.StructuredViewer#handleDispose(org.eclipse.swt. + * events.DisposeEvent) + * + */ + @Override + protected void handleDispose(DisposeEvent event) { + if (fGradientBackground != null) { + fGradientBackground.dispose(); + fGradientBackground = null; + } + + if (fToolTipLabelProvider != null) { + fToolTipLabelProvider.dispose(); + fToolTipLabelProvider = null; + } + + if (fBreadcrumbItems != null) { + Iterator iterator = fBreadcrumbItems.iterator(); + while (iterator.hasNext()) { + BreadcrumbItem item = iterator.next(); + item.dispose(); + } + } + + super.handleDispose(event); + } + + /** + * Returns whether the given element can be opened directly. + *

+ * Subclasses may override this method to define which elements can be opened. + * The default implementation returns {@code false}. + *

+ * + * @param element the element to check + * @return {@code true} if the element can be opened directly, {@code false} + * otherwise + */ + protected boolean isElementOpenable(Object element) { + return false; + } + + /** + * Creates the tree viewer used for the breadcrumb drop down. + *

+ * Subclasses may override this method to provide a specialized + * {@link TreeViewer}. The default implementation creates a standard + * {@link TreeViewer}. + *

+ * + * @param parent the parent composite + * @param style the SWT style bits + * @return the tree viewer used for the drop down + */ + protected TreeViewer createDropDownViewer(Composite parent, int style) { + return new TreeViewer(parent, style); + } + + /** + * Opens the drop down menu of the parent breadcrumb item for the given element. + * Does nothing if the element is not shown in the breadcrumb or has no parent + * item. + * + * @param element the element whose parent drop down should be opened + */ + public void openParentDropDown(Object element) { + BreadcrumbItem item = (BreadcrumbItem) doFindItem(element); + if (item == null) + return; + + int index = getIndexOfItem(item); + if (index <= 0) + return; + + BreadcrumbItem parent = getItem(index - 1); + if (parent != null) + parent.openDropDownMenu(); + } + + /** + * Returns the dialog settings used to store breadcrumb UI state. + *

+ * Subclasses may override this method to provide persistent dialog settings. + * The default implementation returns non-persistent settings. + *

+ * + * @return the dialog settings + */ + protected IDialogSettings getDialogSettings() { + return fDialogSettings; + } +} diff --git a/bundles/org.eclipse.jface/src/org/eclipse/jface/viewers/BreadcrumbViewerRow.java b/bundles/org.eclipse.jface/src/org/eclipse/jface/viewers/BreadcrumbViewerRow.java new file mode 100644 index 00000000000..7f389a76b8b --- /dev/null +++ b/bundles/org.eclipse.jface/src/org/eclipse/jface/viewers/BreadcrumbViewerRow.java @@ -0,0 +1,194 @@ +/******************************************************************************* + * Copyright (c) 2007, 2011 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 + *******************************************************************************/ +package org.eclipse.jface.viewers; + +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.Font; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Widget; + +/** + * A viewer row for the breadcrumb viewer. + * + * @since 3.41 + */ +class BreadcrumbViewerRow extends ViewerRow { + + private final BreadcrumbItem fItem; + private final BreadcrumbViewer fViewer; + + public BreadcrumbViewerRow(BreadcrumbViewer viewer, BreadcrumbItem item) { + fViewer = viewer; + fItem = item; + } + + /* + * @see org.eclipse.jface.viewers.ViewerRow#clone() + */ + @Override + public Object clone() { + return new BreadcrumbViewerRow(fViewer, fItem); + } + + /* + * @see org.eclipse.jface.viewers.ViewerRow#getBackground(int) + */ + @Override + public Color getBackground(int columnIndex) { + return fItem.getBackground(); + } + + /* + * @see org.eclipse.jface.viewers.ViewerRow#getBounds(int) + */ + @Override + public Rectangle getBounds(int columnIndex) { + return getBounds(); + } + + /* + * @see org.eclipse.jface.viewers.ViewerRow#getBounds() + */ + @Override + public Rectangle getBounds() { + return fItem.getBounds(); + } + + /* + * @see org.eclipse.jface.viewers.ViewerRow#getColumnCount() + */ + @Override + public int getColumnCount() { + return 1; + } + + /* + * @see org.eclipse.jface.viewers.ViewerRow#getControl() + */ + @Override + public Control getControl() { + return fViewer.getControl(); + } + + /* + * @see org.eclipse.jface.viewers.ViewerRow#getElement() + */ + @Override + public Object getElement() { + return fItem.getData(); + } + + /* + * @see org.eclipse.jface.viewers.ViewerRow#getFont(int) + */ + @Override + public Font getFont(int columnIndex) { + return fItem.getFont(); + } + + /* + * @see org.eclipse.jface.viewers.ViewerRow#getForeground(int) + */ + @Override + public Color getForeground(int columnIndex) { + return fItem.getForeground(); + } + + /* + * @see org.eclipse.jface.viewers.ViewerRow#getImage(int) + */ + @Override + public Image getImage(int columnIndex) { + return fItem.getImage(); + } + + /* + * @see org.eclipse.jface.viewers.ViewerRow#getItem() + */ + @Override + public Widget getItem() { + return fItem; + } + + /* + * @see org.eclipse.jface.viewers.ViewerRow#getNeighbor(int, boolean) + */ + @Override + public ViewerRow getNeighbor(int direction, boolean sameLevel) { + return null; + } + + /* + * @see org.eclipse.jface.viewers.ViewerRow#getText(int) + */ + @Override + public String getText(int columnIndex) { + return fItem.getText(); + } + + /* + * @see org.eclipse.jface.viewers.ViewerRow#getTreePath() + */ + @Override + public TreePath getTreePath() { + return new TreePath(new Object[] { getElement() }); + } + + /* + * @see org.eclipse.jface.viewers.ViewerRow#setBackground(int, + * org.eclipse.swt.graphics.Color) + */ + @Override + public void setBackground(int columnIndex, Color color) { + fItem.setBackground(color); + } + + /* + * @see org.eclipse.jface.viewers.ViewerRow#setFont(int, + * org.eclipse.swt.graphics.Font) + */ + @Override + public void setFont(int columnIndex, Font font) { + fItem.setFont(font); + } + + /* + * @see org.eclipse.jface.viewers.ViewerRow#setForeground(int, + * org.eclipse.swt.graphics.Color) + */ + @Override + public void setForeground(int columnIndex, Color color) { + fItem.setForeground(color); + } + + /* + * @see org.eclipse.jface.viewers.ViewerRow#setImage(int, + * org.eclipse.swt.graphics.Image) + */ + @Override + public void setImage(int columnIndex, Image image) { + fItem.setImage(image); + } + + /* + * @see org.eclipse.jface.viewers.ViewerRow#setText(int, java.lang.String) + */ + @Override + public void setText(int columnIndex, String text) { + fItem.setText(text); + } + +} diff --git a/bundles/org.eclipse.jface/src/org/eclipse/jface/viewers/TreePathBreadcrumbDropDownSite.java b/bundles/org.eclipse.jface/src/org/eclipse/jface/viewers/TreePathBreadcrumbDropDownSite.java new file mode 100644 index 00000000000..65494a45468 --- /dev/null +++ b/bundles/org.eclipse.jface/src/org/eclipse/jface/viewers/TreePathBreadcrumbDropDownSite.java @@ -0,0 +1,41 @@ +/******************************************************************************* + * Copyright (c) 2009 Wind River Systems 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: + * Wind River Systems - initial API and implementation + *******************************************************************************/ +package org.eclipse.jface.viewers; + +/** + * Interface allowing breadcrumb drop-down implementors to communicate with + * their containing breadcrumb. + * + * @since 3.41 + */ +public interface TreePathBreadcrumbDropDownSite { + + /** + * Notifies the breadcrumb that the given selection was made in the drop-down + * viewer. + * @param selection Selection to set to breadcrumb. + */ + void notifySelection(ISelection selection); + + /** + * Notifies the breadcrumb that the drop-down viewer should be closed. + */ + void close(); + + /** + * Notifies the breadcrumb that the drop-down viewer's contents have + * changed and viewer shell should be adjusted for the new size. + */ + void updateSize(); +} diff --git a/bundles/org.eclipse.jface/src/org/eclipse/jface/viewers/TreePathBreadcrumbItem.java b/bundles/org.eclipse.jface/src/org/eclipse/jface/viewers/TreePathBreadcrumbItem.java new file mode 100644 index 00000000000..e9eb1e5df2e --- /dev/null +++ b/bundles/org.eclipse.jface/src/org/eclipse/jface/viewers/TreePathBreadcrumbItem.java @@ -0,0 +1,237 @@ +/******************************************************************************* + * Copyright (c) 2008, 2009 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 + * Pawel Piech (Wind River) - adapted breadcrumb for use in Debug view (Bug 252677) + *******************************************************************************/ +package org.eclipse.jface.viewers; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Item; +import org.eclipse.swt.widgets.Shell; + + +/** + * An item in a breadcrumb viewer. + *

+ * The item shows a label and an image. It also has the ability to expand, that + * is to open a drop down menu. + *

+ *

+ * The drop down allows to select any child of the items input element. The item + * shows the label and icon of its data element, if any. + *

+ * + * @since 3.41 + */ +class TreePathBreadcrumbItem extends Item { + + private TreePath fPath; + + private final TreePathBreadcrumbViewer fParent; + private final Composite fContainer; + + private final TreePathBreadcrumbItemDropDown fExpandBlock; + private final TreePathBreadcrumbItemDetails fDetailsBlock; + + private boolean fIsLast; + + /** + * A new breadcrumb item which is shown inside the given viewer. + * + * @param viewer the items viewer + * @param parent the container containing the item + */ + public TreePathBreadcrumbItem(TreePathBreadcrumbViewer viewer, Composite parent) { + super(parent, SWT.NONE); + + fParent= viewer; + + fContainer= new Composite(parent, SWT.NONE); + fContainer.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false)); + GridLayout layout= new GridLayout(2, false); + layout.marginBottom= 1; + layout.marginHeight= 0; + layout.marginWidth= 0; + layout.horizontalSpacing= 0; + fContainer.setLayout(layout); + + fExpandBlock= new TreePathBreadcrumbItemDropDown(this, fContainer); + fDetailsBlock= new TreePathBreadcrumbItemDetails(this, fContainer); + fContainer.setData("org.eclipse.e4.ui.css.id", "DebugBreadcrumbItemComposite"); //$NON-NLS-1$ //$NON-NLS-2$ + } + + /** + * Returns this items viewer. + * + * @return the viewer showing this item + */ + public TreePathBreadcrumbViewer getViewer() { + return fParent; + } + + /* + * @see org.eclipse.swt.widgets.Widget#dispose() + */ + @Override + public void dispose() { + Shell shell = fExpandBlock.getDropDownShell(); + if (shell != null && !shell.isDisposed()) { + shell.dispose(); + } + fContainer.dispose(); + super.dispose(); + } + + public TreePath getPath() { + return fPath; + } + + public void setPath(TreePath path) { + fPath = path; + } + + /** + * Should this item show a text label. + * + * @param enabled true if it should + */ + void setShowText(boolean enabled) { + fDetailsBlock.setTextVisible(enabled); + } + + /** + * Does this item show a text label? + * + * @return true if it does. + */ + boolean isShowText() { + return fDetailsBlock.isTextVisible(); + } + + /** + * Returns the width of this item. + * + * @return the width of this item + */ + int getWidth() { + return fDetailsBlock.getWidth() + fExpandBlock.getWidth() + 2; + } + + /** + * Sets whether this item has to be marked as + * selected or not. + * + * @param selected true if marked as selected + */ + void setSelected(boolean selected) { + fDetailsBlock.setSelected(selected); + } + + /** + * Sets whether this item has the keyboard focus. + * + * @param state true if it has focus, false otherwise + */ + void setFocus(boolean state) { + fDetailsBlock.setFocus(state); + } + + /** + * Returns whether this item has the keyboard focus. + * + * @return true if this item has the keyboard focus + */ + boolean hasFocus() { + return fDetailsBlock.hasFocus(); + } + + /** + * Set whether this is the last item in the breadcrumb item chain or not. + * + * @param isLast true if this is the last item, false otherwise + */ + void setIsLastItem(boolean isLast) { + fIsLast= isLast; + + GridData data= (GridData) fContainer.getLayoutData(); + data.grabExcessHorizontalSpace= isLast; + } + + /** + * Expand this item, shows the drop down menu. + */ + void openDropDownMenu() { + fExpandBlock.showMenu(); + } + + /** + * @return true if this item is expanded + */ + boolean isMenuShown() { + return fExpandBlock.isMenuShown(); + } + + /** + * Returns the drop down shell. + * + * @return the shell of the drop down if shown, null otherwise + */ + Shell getDropDownShell() { + return fExpandBlock.getDropDownShell(); + } + + /** + * Returns the bounds of this item. + * + * @return the bounds of this item + */ + public Rectangle getBounds() { + return fContainer.getBounds(); + } + + /** + * Set the tool tip of the item to the given text. + * + * @param text the tool tip for the item + */ + public void setToolTip(String text) { + fDetailsBlock.setToolTip(text); + } + + /* + * @see org.eclipse.swt.widgets.Item#setText(java.lang.String) + */ + @Override + public void setText(String string) { + super.setText(string); + fDetailsBlock.setText(string); + + //more or less space might be required for the label + if (fIsLast) { + fContainer.layout(true, true); + } + } + + /* + * @see org.eclipse.swt.widgets.Item#setImage(org.eclipse.swt.graphics.Image) + */ + @Override + public void setImage(Image image) { + super.setImage(image); + fDetailsBlock.setImage(image); + } +} diff --git a/bundles/org.eclipse.jface/src/org/eclipse/jface/viewers/TreePathBreadcrumbItemDetails.java b/bundles/org.eclipse.jface/src/org/eclipse/jface/viewers/TreePathBreadcrumbItemDetails.java new file mode 100644 index 00000000000..f25841611c6 --- /dev/null +++ b/bundles/org.eclipse.jface/src/org/eclipse/jface/viewers/TreePathBreadcrumbItemDetails.java @@ -0,0 +1,393 @@ +/******************************************************************************* + * Copyright (c) 2008, 2009 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 + * Pawel Piech (Wind River) - adapted breadcrumb for use in Debug view (Bug 252677) + *******************************************************************************/ +package org.eclipse.jface.viewers; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.accessibility.AccessibleAdapter; +import org.eclipse.swt.accessibility.AccessibleEvent; +import org.eclipse.swt.events.FocusEvent; +import org.eclipse.swt.events.FocusListener; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.events.KeyListener; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.MouseListener; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Shell; + + +/** + * The label and icon part of the breadcrumb item. + * + * @since 3.41 + */ +class TreePathBreadcrumbItemDetails { + + private final Label fElementImage; + private final Label fElementText; + private final Composite fDetailComposite; + private final TreePathBreadcrumbItem fParent; + private final Composite fTextComposite; + private final Composite fImageComposite; + + private boolean fTextVisible; + private boolean fSelected; + private boolean fHasFocus; + + + public TreePathBreadcrumbItemDetails(TreePathBreadcrumbItem parent, Composite parentContainer) { + fParent= parent; + fTextVisible= true; + + fDetailComposite= new Composite(parentContainer, SWT.NONE); + fDetailComposite.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false)); + GridLayout layout= new GridLayout(2, false); + layout.marginHeight= 0; + layout.marginWidth= 0; + layout.horizontalSpacing= 0; + fDetailComposite.setLayout(layout); + addElementListener(fDetailComposite); + + fImageComposite= new Composite(fDetailComposite, SWT.NONE); + fImageComposite.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false)); + layout= new GridLayout(1, false); + layout.marginHeight= 1; + layout.marginWidth= 2; + fImageComposite.setLayout(layout); + fImageComposite.addPaintListener(e -> { + if (fHasFocus && !isTextVisible()) { + e.gc.drawFocus(e.x, e.y, e.width, e.height); + } + }); + installFocusComposite(fImageComposite); + addElementListener(fImageComposite); + + fElementImage= new Label(fImageComposite, SWT.NONE); + GridData layoutData= new GridData(SWT.BEGINNING, SWT.CENTER, false, false); + fElementImage.setLayoutData(layoutData); + addElementListener(fElementImage); + + fTextComposite= new Composite(fDetailComposite, SWT.NONE); + fTextComposite.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false)); + layout= new GridLayout(1, false); + layout.marginHeight= 2; + layout.marginWidth= 2; + fTextComposite.setLayout(layout); + addElementListener(fTextComposite); + fTextComposite.addPaintListener(e -> { + if (fHasFocus && isTextVisible()) { + e.gc.drawFocus(e.x, e.y, e.width, e.height); + } + }); + installFocusComposite(fTextComposite); + + fElementText= new Label(fTextComposite, SWT.NONE); + + layoutData= new GridData(SWT.BEGINNING, SWT.CENTER, false, false); + fElementText.setLayoutData(layoutData); + addElementListener(fElementText); + + fTextComposite.getAccessible().addAccessibleListener(new AccessibleAdapter() { + @Override + public void getName(AccessibleEvent e) { + e.result= fElementText.getText(); + } + }); + fImageComposite.getAccessible().addAccessibleListener(new AccessibleAdapter() { + @Override + public void getName(AccessibleEvent e) { + e.result= fElementText.getText(); + } + }); + + fDetailComposite.setTabList(new Control[] { fTextComposite }); + + fDetailComposite.setData("org.eclipse.e4.ui.css.id", "DebugBreadcrumbItemDetailComposite"); //$NON-NLS-1$ //$NON-NLS-2$ + fTextComposite.setData("org.eclipse.e4.ui.css.id", "DebugBreadcrumbItemDetailTextComposite"); //$NON-NLS-1$ //$NON-NLS-2$ + fImageComposite.setData("org.eclipse.e4.ui.css.id", "DebugBreadcrumbItemDetailImageComposite"); //$NON-NLS-1$ //$NON-NLS-2$ + fElementImage.setData("org.eclipse.e4.ui.css.id", "DebugBreadcrumbItemDetailImageLabel"); //$NON-NLS-1$ //$NON-NLS-2$ + fElementText.setData("org.eclipse.e4.ui.css.id", "DebugBreadcrumbItemDetailTextLabel"); //$NON-NLS-1$ //$NON-NLS-2$ + + } + + /** + * Returns whether this element has the keyboard focus. + * + * @return true if this element has the keyboard focus. + */ + public boolean hasFocus() { + return fHasFocus; + } + + /** + * Sets the tool tip to the given text. + * + * @param text the tool tip + */ + public void setToolTip(String text) { + if (isTextVisible()) { + fElementText.getParent().setToolTipText(text); + fElementText.setToolTipText(text); + + fElementImage.setToolTipText(text); + } else { + fElementText.getParent().setToolTipText(null); + fElementText.setToolTipText(null); + + fElementImage.setToolTipText(text); + } + } + + /** + * Sets the image to the given image. + * + * @param image the image to use + */ + public void setImage(Image image) { + if (image != fElementImage.getImage()) { + fElementImage.setImage(image); + } + } + + /** + * Sets the text to the given text. + * + * @param text the text to use + */ + public void setText(String text) { + if (text == null) { + text= ""; //$NON-NLS-1$ + } + if (!text.equals(fElementText.getText())) { + fElementText.setText(text); + } + } + + /** + * Returns the width of this element. + * + * @return current width of this element + */ + public int getWidth() { + int result= 2; + + if (fElementImage.getImage() != null) { + result+= fElementImage.computeSize(SWT.DEFAULT, SWT.DEFAULT).x; + } + + if (fTextVisible && fElementText.getText().length() > 0) { + result+= fElementText.computeSize(SWT.DEFAULT, SWT.DEFAULT).x; + } + + return result; + } + + public void setTextVisible(boolean enabled) { + if (fTextVisible == enabled) { + return; + } + + fTextVisible= enabled; + + GridData data= (GridData) fTextComposite.getLayoutData(); + data.exclude= !enabled; + fTextComposite.setVisible(enabled); + + if (fTextVisible) { + fDetailComposite.setTabList(new Control[] { fTextComposite }); + } else { + fDetailComposite.setTabList(new Control[] { fImageComposite }); + } + + if (fHasFocus) { + if (isTextVisible()) { + fTextComposite.setFocus(); + } else { + fImageComposite.setFocus(); + } + } + } + + /** + * Tells whether this item shows a text or only an image. + * + * @return true if it shows a text and an image, false if it only shows the image + */ + public boolean isTextVisible() { + return fTextVisible; + } + + public void setSelected(boolean selected) { + if (selected == fSelected) { + return; + } + + fSelected= selected; + + } + + public void setFocus(boolean enabled) { + if (enabled == fHasFocus) { + return; + } + + fHasFocus= enabled; + if (fHasFocus) { + if (isTextVisible()) { + fTextComposite.setFocus(); + } else { + fImageComposite.setFocus(); + } + } + } + + /** + * Install focus and key listeners to the given composite. + * + * @param composite the composite which may get focus + */ + private void installFocusComposite(Composite composite) { + composite.addTraverseListener(e -> { + if (e.detail == SWT.TRAVERSE_TAB_NEXT || e.detail == SWT.TRAVERSE_TAB_PREVIOUS) { + int index= fParent.getViewer().getIndexOfItem(fParent); + if (e.detail == SWT.TRAVERSE_TAB_NEXT) { + index++; + } else { + index--; + } + + if (index > 0 && index < fParent.getViewer().getItemCount()) { + fParent.getViewer().selectItem(fParent.getViewer().getItem(index)); + } + + e.doit= true; + } + }); + composite.addKeyListener(new KeyListener() { + @Override + public void keyPressed(KeyEvent e) { + TreePathBreadcrumbViewer viewer= fParent.getViewer(); + + switch (e.keyCode) { + case SWT.ARROW_LEFT: + if (fSelected) { + viewer.doTraverse(false); + e.doit= false; + } else { + viewer.selectItem(fParent); + } + break; + case SWT.ARROW_RIGHT: + if (fSelected) { + viewer.doTraverse(true); + e.doit= false; + } else { + viewer.selectItem(fParent); + } + break; + case SWT.ARROW_DOWN: + case SWT.ARROW_UP: + case SWT.KEYPAD_ADD: + if (!fSelected) { + viewer.selectItem(fParent); + } + openDropDown(); + e.doit= false; + break; + case SWT.CR: + if (!fSelected) { + viewer.selectItem(fParent); + } + viewer.fireOpen(); + break; + default: + if (e.character == ' ') { + if (!fSelected) { + viewer.selectItem(fParent); + } + openDropDown(); + e.doit= false; + } + break; + } + } + + private void openDropDown() { + Shell shell = fParent.getDropDownShell(); + if (shell == null) { + fParent.openDropDownMenu(); + shell = fParent.getDropDownShell(); + } + shell.setFocus(); + } + + @Override + public void keyReleased(KeyEvent e) { + } + }); + + composite.addFocusListener(new FocusListener() { + @Override + public void focusGained(FocusEvent e) { + if (!fHasFocus) { + fHasFocus= true; + } + } + + @Override + public void focusLost(FocusEvent e) { + if (fHasFocus) { + fHasFocus= false; + } + } + }); + } + + /** + * Add mouse listeners to the given control. + * + * @param control the control to which may be clicked + */ + private void addElementListener(Control control) { + control.addMouseListener(new MouseListener() { + @Override + public void mouseDoubleClick(MouseEvent e) { + } + + @Override + public void mouseDown(MouseEvent e) { + TreePathBreadcrumbViewer viewer= fParent.getViewer(); + Shell shell= fParent.getDropDownShell(); + viewer.selectItem(fParent); + if (shell == null && e.button == 1 && e.stateMask == 0) { + fParent.getViewer().fireDoubleClick(); + } + } + + @Override + public void mouseUp(MouseEvent e) { + } + }); + control.addMenuDetectListener(e -> { + TreePathBreadcrumbViewer viewer= fParent.getViewer(); + viewer.selectItem(fParent); + fParent.getViewer().fireMenuDetect(e); + }); + } +} diff --git a/bundles/org.eclipse.jface/src/org/eclipse/jface/viewers/TreePathBreadcrumbItemDropDown.java b/bundles/org.eclipse.jface/src/org/eclipse/jface/viewers/TreePathBreadcrumbItemDropDown.java new file mode 100644 index 00000000000..a3425a10168 --- /dev/null +++ b/bundles/org.eclipse.jface/src/org/eclipse/jface/viewers/TreePathBreadcrumbItemDropDown.java @@ -0,0 +1,583 @@ +/******************************************************************************* + * Copyright (c) 2008, 2025 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 + * Pawel Piech (Wind River) - adapted breadcrumb for use in Debug view (Bug 252677) + *******************************************************************************/ +package org.eclipse.jface.viewers; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.jface.action.Action; +import org.eclipse.jface.action.ToolBarManager; +import org.eclipse.jface.dialogs.IDialogSettings; +import org.eclipse.jface.resource.CompositeImageDescriptor; +import org.eclipse.jface.resource.JFaceResources; +import org.eclipse.jface.util.Util; +import org.eclipse.swt.SWT; +import org.eclipse.swt.accessibility.AccessibleAdapter; +import org.eclipse.swt.accessibility.AccessibleEvent; +import org.eclipse.swt.events.ControlAdapter; +import org.eclipse.swt.events.ControlEvent; +import org.eclipse.swt.events.ControlListener; +import org.eclipse.swt.events.MouseAdapter; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.ShellEvent; +import org.eclipse.swt.events.ShellListener; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.ImageData; +import org.eclipse.swt.graphics.ImageDataProvider; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.RGB; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.ToolBar; +import org.eclipse.swt.widgets.Widget; + + +/** + * The part of the breadcrumb item with the drop down menu. + * + * @since 3.41 + */ +class TreePathBreadcrumbItemDropDown implements TreePathBreadcrumbDropDownSite { + + private static final boolean IS_MAC_WORKAROUND= "carbon".equals(SWT.getPlatform()); //$NON-NLS-1$ + + /** + * An arrow image descriptor. The images color is related to the list + * fore- and background color. This makes the arrow visible even in high contrast + * mode. If ltr is true the arrow points to the right, otherwise it + * points to the left. + */ + private final class AccessibleArrowImage extends CompositeImageDescriptor { + + private final static int ARROW_SIZE= 5; + + private final boolean fLTR; + + public AccessibleArrowImage(boolean ltr) { + fLTR= ltr; + } + + /* + * @see org.eclipse.jface.resource.CompositeImageDescriptor#drawCompositeImage(int, int) + */ + @Override + protected void drawCompositeImage(int width, int height) { + Display display= fParentComposite.getDisplay(); + ImageDataProvider imageProvider = zoom -> { + Image image = new Image(display, ARROW_SIZE, ARROW_SIZE * 2); + + GC gc = new GC(image, fLTR ? SWT.LEFT_TO_RIGHT : SWT.RIGHT_TO_LEFT); + gc.setAntialias(SWT.ON); + + Color triangleColor = createColor(SWT.COLOR_LIST_FOREGROUND, SWT.COLOR_LIST_BACKGROUND, 0.2, display); + gc.setBackground(triangleColor); + gc.fillPolygon(new int[] { + 0, 0, ARROW_SIZE, ARROW_SIZE, 0, ARROW_SIZE * 2 }); + gc.dispose(); + + ImageData imageData = image.getImageData(zoom); + image.dispose(); + int zoomedArrowSize = ARROW_SIZE * zoom / 100; + for (int y1 = 0; y1 < zoomedArrowSize; y1++) { + // set opaque pixels for top half of the breadcrumb arrow + for (int x1 = 0; x1 <= y1; x1++) { + imageData.setAlpha(fLTR ? x1 : zoomedArrowSize - x1 - 1, y1, 255); + } + // set transparent pixels for top half of the breadcrumbe arrow + for (int x1 = y1 + 1; x1 < zoomedArrowSize; x1++) { + imageData.setAlpha(fLTR ? x1 : zoomedArrowSize - x1 - 1, y1, 0); + } + } + for (int y2 = 0; y2 < zoomedArrowSize; y2++) { + // set opaque pixels for bottom half of the breadcrumb arrow + for (int x2 = 0; x2 <= y2; x2++) { + imageData.setAlpha(fLTR ? x2 : zoomedArrowSize - x2 - 1, zoomedArrowSize * 2 - y2 - 1, 255); + } + // set transparent pixels for bottom half of the breadcrumbe arrow + for (int x2 = y2 + 1; x2 < zoomedArrowSize; x2++) { + imageData.setAlpha(fLTR ? x2 : zoomedArrowSize - x2 - 1, zoomedArrowSize * 2 - y2 - 1, 0); + } + } + return imageData; + }; + drawImage(imageProvider, (width / 2) - (ARROW_SIZE / 2), (height / 2) - ARROW_SIZE); + + } + + /* + * @see org.eclipse.jface.resource.CompositeImageDescriptor#getSize() + */ + @Override + protected Point getSize() { + return new Point(10, 16); + } + + private Color createColor(int color1, int color2, double weight, Display display) { + RGB rgb1= display.getSystemColor(color1).getRGB(); + RGB rgb2= display.getSystemColor(color2).getRGB(); + + return new Color(rgb1.blend(rgb2, weight)); + } + } + + // Workaround for bug 258196: set the minimum size to 500 because on Linux + // the size is not adjusted correctly in a virtual tree. + private static final int DROP_DOWN_MIN_WIDTH= 500; + private static final int DROP_DOWN_MAX_WIDTH= 501; + + private static final int DROP_DOWN_DEFAULT_MIN_HEIGHT= 100; + private static final int DROP_DOWN_DEFAULT_MAX_HEIGHT= 500; + + private static final String DIALOG_SETTINGS= "BreadcrumbItemDropDown"; //$NON-NLS-1$ + private static final String DIALOG_HEIGHT= "height"; //$NON-NLS-1$ + private static final String DIALOG_WIDTH= "width"; //$NON-NLS-1$ + + private final TreePathBreadcrumbItem fParent; + private final Composite fParentComposite; + private final ToolBar fToolBar; + + private boolean fMenuIsShown; + private boolean fEnabled; + private Shell fShell; + private boolean fIsResizingProgrammatically; + private int fCurrentWidth = -1; + private int fCurrentHeight = -1; + + + public TreePathBreadcrumbItemDropDown(TreePathBreadcrumbItem parent, Composite composite) { + fParent= parent; + fParentComposite= composite; + fMenuIsShown= false; + fEnabled= true; + + fToolBar= new ToolBar(composite, SWT.FLAT); + fToolBar.setLayoutData(new GridData(SWT.END, SWT.CENTER, false, false)); + fToolBar.getAccessible().addAccessibleListener(new AccessibleAdapter() { + @Override + public void getName(AccessibleEvent e) { + e.result= JFaceResources.getString("BreadcrumbItemDropDown_showDropDownMenu_action_toolTip"); //$NON-NLS-1$ + } + }); + ToolBarManager manager= new ToolBarManager(fToolBar); + + final Action showDropDownMenuAction= new Action(null, SWT.NONE) { + @Override + public void run() { + Shell shell= fParent.getDropDownShell(); + if (shell != null) { + return; + } + + shell= fParent.getViewer().getDropDownShell(); + if (shell != null && !shell.isDisposed()) { + shell.close(); + } + + showMenu(); + + fShell.setFocus(); + } + }; + + showDropDownMenuAction.setImageDescriptor(new AccessibleArrowImage(isLeft())); + showDropDownMenuAction + .setToolTipText(JFaceResources.getString("BreadcrumbItemDropDown_showDropDownMenu_action_toolTip")); //$NON-NLS-1$ + manager.add(showDropDownMenuAction); + + manager.update(true); + if (IS_MAC_WORKAROUND) { + manager.getControl().addMouseListener(new MouseAdapter() { + // see also BreadcrumbItemDetails#addElementListener(Control) + @Override + public void mouseDown(MouseEvent e) { + showDropDownMenuAction.run(); + } + }); + } + fToolBar.setData("org.eclipse.e4.ui.css.id", "DebugBreadcrumbItemDropDownToolBar"); //$NON-NLS-1$ //$NON-NLS-2$ + } + + /** + * Return the width of this element. + * + * @return the width of this element + */ + public int getWidth() { + return fToolBar.computeSize(SWT.DEFAULT, SWT.DEFAULT).x; + } + + /** + * Set whether the drop down menu is available. + * + * @param enabled true if available + */ + public void setEnabled(boolean enabled) { + fEnabled= enabled; + + fToolBar.setVisible(enabled); + } + + /** + * Tells whether the menu is shown. + * + * @return true if the menu is open + */ + public boolean isMenuShown() { + return fMenuIsShown; + } + + /** + * Returns the shell used for the drop down menu if it is shown. + * + * @return the drop down shell or null + */ + public Shell getDropDownShell() { + if (!isMenuShown()) { + return null; + } + + return fShell; + } + + /** + * Opens the drop down menu. + */ + public void showMenu() { + + if (!fEnabled || fMenuIsShown) { + return; + } + + fMenuIsShown= true; + + fShell= new Shell(fToolBar.getShell(), SWT.RESIZE | SWT.TOOL | SWT.ON_TOP); + + fShell.addControlListener(new ControlAdapter() { + /* + * @see org.eclipse.swt.events.ControlAdapter#controlResized(org.eclipse.swt.events.ControlEvent) + */ + @Override + public void controlResized(ControlEvent e) { + if (fIsResizingProgrammatically) { + return; + } + + Point size= fShell.getSize(); + fCurrentWidth = size.x; + fCurrentHeight = size.y; + getDialogSettings().put(DIALOG_WIDTH, size.x); + getDialogSettings().put(DIALOG_HEIGHT, size.y); + } + }); + + GridLayout layout= new GridLayout(1, false); + layout.marginHeight= 0; + layout.marginWidth= 0; + fShell.setLayout(layout); + + Composite composite= new Composite(fShell, SWT.NONE); + composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + GridLayout gridLayout= new GridLayout(1, false); + gridLayout.marginHeight= 0; + gridLayout.marginWidth= 0; + composite.setLayout(gridLayout); + + TreePath path= fParent.getPath(); + + Control control = fParent.getViewer().createDropDown(composite, this, path); + + control.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + + setShellBounds(fShell); + fShell.setVisible(true); + installCloser(fShell); + } + + /** + * The closer closes the given shell when the focus is lost. + * + * @param shell the shell to install the closer to + */ + private void installCloser(final Shell shell) { + final Listener focusListener= event -> { + Widget focusElement= event.widget; + boolean isFocusBreadcrumbTreeFocusWidget= focusElement == shell || focusElement instanceof Control && ((Control)focusElement).getShell() == shell; + boolean isFocusWidgetParentShell= focusElement instanceof Control && ((Control)focusElement).getShell().getParent() == shell; + + switch (event.type) { + case SWT.FocusIn: + + if (!isFocusBreadcrumbTreeFocusWidget && !isFocusWidgetParentShell) { + shell.close(); + } + break; + + case SWT.FocusOut: + if (event.display.getActiveShell() == null) { + shell.close(); + } + break; + + default: + Assert.isTrue(false); + } + }; + + final Display display= shell.getDisplay(); + display.addFilter(SWT.FocusIn, focusListener); + display.addFilter(SWT.FocusOut, focusListener); + + final ControlListener controlListener= new ControlListener() { + @Override + public void controlMoved(ControlEvent e) { + if (!shell.isDisposed()) { + shell.close(); + } + } + + @Override + public void controlResized(ControlEvent e) { + if (!shell.isDisposed()) { + shell.close(); + } + } + }; + fToolBar.getShell().addControlListener(controlListener); + + shell.addDisposeListener(e -> { + + display.removeFilter(SWT.FocusIn, focusListener); + display.removeFilter(SWT.FocusOut, focusListener); + + if (!fToolBar.isDisposed()) { + fToolBar.getShell().removeControlListener(controlListener); + } + // The shell may be disposed directly without receiving shellClosed. + fMenuIsShown = false; + }); + shell.addShellListener(new ShellListener() { + @Override + public void shellActivated(ShellEvent e) { + } + + @Override + public void shellClosed(ShellEvent e) { + if (!fMenuIsShown) { + return; + } + + fMenuIsShown= false; + } + + @Override + public void shellDeactivated(ShellEvent e) { + } + + @Override + public void shellDeiconified(ShellEvent e) { + } + + @Override + public void shellIconified(ShellEvent e) { + } + }); + } + + private IDialogSettings getDialogSettings() { + IDialogSettings viewerSettings = fParent.getViewer().getDialogSettings(); + IDialogSettings settings = viewerSettings.getSection(DIALOG_SETTINGS); + if (settings == null) { + settings = viewerSettings.addNewSection(DIALOG_SETTINGS); + } + return settings; + } + + private int getMaxWidth() { + try { + return getDialogSettings().getInt(DIALOG_WIDTH); + } catch (NumberFormatException e) { + return DROP_DOWN_MAX_WIDTH; + } + } + + private int getMaxHeight() { + try { + return getDialogSettings().getInt(DIALOG_HEIGHT); + } catch (NumberFormatException e) { + return DROP_DOWN_DEFAULT_MAX_HEIGHT; + } + } + + /** + * Calculates a useful size for the given shell. + * + * @param shell the shell to calculate the size for. + */ + private void setShellBounds(Shell shell) { + + Rectangle rect= fParentComposite.getBounds(); + Rectangle toolbarBounds= fToolBar.getBounds(); + + Point size = shell.computeSize(SWT.DEFAULT, SWT.DEFAULT, false); + int height= Math.max(Math.min(size.y, getMaxHeight()), DROP_DOWN_DEFAULT_MIN_HEIGHT); + int width= Math.max(getMaxWidth(), DROP_DOWN_MIN_WIDTH); + + int imageBoundsX= 0; + if (fParent.getImage() != null) { + imageBoundsX= fParent.getImage().getImageData().width; + } + + Rectangle trim= fShell.computeTrim(0, 0, width, height); + int x= toolbarBounds.x + toolbarBounds.width + 2 + trim.x - imageBoundsX; + if (!isLeft()) { + x+= width; + } + + int y = 0; + if (isTop()) { + y+= rect.height; + } else { + y-= height; + } + + Point pt= new Point(x, y); + pt= fParentComposite.toDisplay(pt); + + Rectangle monitor = Util.getClosestMonitor(shell.getDisplay(), pt).getClientArea(); + int overlap= (pt.x + width) - (monitor.x + monitor.width); + if (overlap > 0) { + pt.x-= overlap; + } + if (pt.x < monitor.x) { + pt.x= monitor.x; + } + + shell.setLocation(pt); + fIsResizingProgrammatically= true; + try { + shell.setSize(width, height); + fCurrentWidth = width; + fCurrentHeight = height; + } finally { + fIsResizingProgrammatically= false; + } + } + + /** + * Set the size of the given shell such that more content can be shown. The shell size does not + * exceed a user-configurable maximum. + * + * @param shell the shell to resize + */ + private void resizeShell(final Shell shell) { + int maxHeight= getMaxHeight(); + int maxWidth = getMaxWidth(); + + if (fCurrentHeight >= maxHeight && fCurrentWidth >= maxWidth) { + return; + } + + Point preferedSize= shell.computeSize(SWT.DEFAULT, SWT.DEFAULT, true); + + int newWidth; + if (fCurrentWidth >= DROP_DOWN_MAX_WIDTH) { + newWidth= fCurrentWidth; + } else { + // Workaround for bug 319612: Do not resize width below the + // DROP_DOWN_MIN_WIDTH. This can happen because the Shell.getSize() + // is incorrectly small on Linux. + newWidth= Math.min(Math.max(Math.max(preferedSize.x, fCurrentWidth), DROP_DOWN_MIN_WIDTH), maxWidth); + } + int newHeight; + if (fCurrentHeight >= maxHeight) { + newHeight= fCurrentHeight; + } else { + newHeight= Math.min(Math.max(preferedSize.y, fCurrentHeight), maxHeight); + } + + if (newHeight != fCurrentHeight || newWidth != fCurrentWidth) { + shell.setRedraw(false); + try { + fIsResizingProgrammatically= true; + shell.setSize(newWidth, newHeight); + + Point location = shell.getLocation(); + Point newLocation = location; + if (!isLeft()) { + newLocation = new Point(newLocation.x - (newWidth - fCurrentWidth), newLocation.y); + } + if (!isTop()) { + newLocation = new Point(newLocation.x, newLocation.y - (newHeight - fCurrentHeight)); + } + if (!location.equals(newLocation)) { + shell.setLocation(newLocation.x, newLocation.y); + } + fCurrentWidth = newWidth; + fCurrentHeight = newHeight; + } finally { + fIsResizingProgrammatically= false; + shell.setRedraw(true); + } + } + } + + /** + * Tells whether this the breadcrumb is in LTR mode or RTL mode. Or whether the breadcrumb + * is on the right-side status coolbar, which has the same effect on layout. + * + * @return true if the breadcrumb in left-to-right mode, false + * otherwise + */ + private boolean isLeft() { + return (fParentComposite.getStyle() & SWT.RIGHT_TO_LEFT) == 0 && + (fParent.getViewer().getStyle() & SWT.RIGHT) == 0; + } + + /** + * Tells whether this the breadcrumb is in LTR mode or RTL mode. Or whether the breadcrumb + * is on the right-side status coolbar, which has the same effect on layout. + * + * @return true if the breadcrumb in left-to-right mode, false + * otherwise + */ + private boolean isTop() { + return (fParent.getViewer().getStyle() & SWT.BOTTOM) == 0; + } + + @Override + public void close() { + if (fShell != null && !fShell.isDisposed()) { + fShell.close(); + } + } + + @Override + public void notifySelection(ISelection selection) { + fParent.getViewer().fireMenuSelection(selection); + } + + @Override + public void updateSize() { + if (fShell != null && !fShell.isDisposed()) { + resizeShell(fShell); + } + } +} diff --git a/bundles/org.eclipse.jface/src/org/eclipse/jface/viewers/TreePathBreadcrumbViewer.java b/bundles/org.eclipse.jface/src/org/eclipse/jface/viewers/TreePathBreadcrumbViewer.java new file mode 100644 index 00000000000..db357794b25 --- /dev/null +++ b/bundles/org.eclipse.jface/src/org/eclipse/jface/viewers/TreePathBreadcrumbViewer.java @@ -0,0 +1,832 @@ +/******************************************************************************* + * 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 + * Pawel Piech (Wind River) - adapted breadcrumb for use in Debug view (Bug 252677) + *******************************************************************************/ +package org.eclipse.jface.viewers; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.ListenerList; +import org.eclipse.jface.dialogs.DialogSettings; +import org.eclipse.jface.dialogs.IDialogSettings; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.DisposeEvent; +import org.eclipse.swt.events.MenuDetectEvent; +import org.eclipse.swt.events.MenuDetectListener; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Widget; + + +/** + * A breadcrumb viewer shows a the parent chain of its input element in a list. + * Each breadcrumb item of that list can be expanded and a sibling of the + * element presented by the breadcrumb item can be selected. + *

+ * Content providers for breadcrumb viewers must implement the + * ITreePathContentProvider interface. + *

+ *

+ * Label providers for breadcrumb viewers must implement the + * ITreePathLabelProvider interface. + *

+ * + * @since 3.41 + */ +public abstract class TreePathBreadcrumbViewer extends StructuredViewer { + + private static final boolean IS_GTK= "gtk".equals(SWT.getPlatform()); //$NON-NLS-1$ + private final IDialogSettings fDialogSettings = new DialogSettings("TreePathBreadcrumbViewer"); //$NON-NLS-1$ + + private final int fStyle; + private final Composite fContainer; + private final ArrayList fTreePathBreadcrumbItems; + private final ListenerList fMenuListeners; + + private Image fGradientBackground; + private TreePathBreadcrumbItem fSelectedItem; + + /** + * Create a new TreePathBreadcrumbViewer. + *

+ * Style is one of: + *

    + *
  • SWT.NONE
  • + *
  • SWT.VERTICAL
  • + *
  • SWT.HORIZONTAL
  • + *
  • SWT.BOTTOM
  • + *
  • SWT.RIGHT
  • + *
+ * + * @param parent the container for the viewer + * @param style the style flag used for this viewer + */ + public TreePathBreadcrumbViewer(Composite parent, int style) { + fStyle = style; + fTreePathBreadcrumbItems = new ArrayList<>(); + fMenuListeners= new ListenerList<>(); + + fContainer= new Composite(parent, SWT.NONE); + GridData layoutData= new GridData(SWT.FILL, SWT.TOP, true, false); + fContainer.setLayoutData(layoutData); + fContainer.addTraverseListener(e -> e.doit = true); + fContainer.setBackgroundMode(SWT.INHERIT_DEFAULT); + + hookControl(fContainer); + + int columns= 1000; + if ((SWT.VERTICAL & style) != 0) { + columns= 2; + } + + GridLayout gridLayout= new GridLayout(columns, false); + gridLayout.marginWidth= 0; + gridLayout.marginHeight= 0; + gridLayout.verticalSpacing= 0; + gridLayout.horizontalSpacing= 0; + fContainer.setLayout(gridLayout); + + fContainer.addListener(SWT.Resize, event -> { + updateSize(); + fContainer.layout(true, true); + }); + } + + int getStyle() { + return fStyle; + } + + /** + * Configure the given drop down viewer. The given input is used for the viewers input. Clients + * must at least set the label and the content provider for the viewer. + * @param parent the parent composite + * @param site the site to create the drop down for + * @param path the path to show + * @return the drop down control + */ + protected abstract Control createDropDown(Composite parent, TreePathBreadcrumbDropDownSite site, TreePath path); + + /* + * @see org.eclipse.jface.viewers.Viewer#getControl() + */ + @Override + public Control getControl() { + return fContainer; + } + + /* + * @see org.eclipse.jface.viewers.StructuredViewer#reveal(java.lang.Object) + */ + @Override + public void reveal(Object element) { + //all elements are always visible + } + + /** + * Transfers the keyboard focus into the viewer. + */ + public void setFocus() { + fContainer.setFocus(); + + if (fSelectedItem != null) { + fSelectedItem.setFocus(true); + } else { + if (fTreePathBreadcrumbItems.isEmpty()) { + return; + } + + TreePathBreadcrumbItem item= fTreePathBreadcrumbItems.get(fTreePathBreadcrumbItems.size() - 1); + item.setFocus(true); + } + } + + /** + * @return true if any of the items in the viewer is expanded + */ + public boolean isDropDownOpen() { + for (TreePathBreadcrumbItem item : fTreePathBreadcrumbItems) { + if (item.isMenuShown()) { + return true; + } + } + + return false; + } + + /** + * The shell used for the shown drop down or null + * if no drop down is shown at the moment. + * + * @return the drop downs shell or null + */ + public Shell getDropDownShell() { + for (TreePathBreadcrumbItem item : fTreePathBreadcrumbItems) { + if (item.isMenuShown()) { + return item.getDropDownShell(); + } + } + + return null; + } + + /** + * Add the given listener to the set of listeners which will be informed + * when a context menu is requested for a breadcrumb item. + * + * @param listener the listener to add + */ + public void addMenuDetectListener(MenuDetectListener listener) { + fMenuListeners.add(listener); + } + + /** + * Remove the given listener from the set of menu detect listeners. + * Does nothing if the listener is not element of the set. + * + * @param listener the listener to remove + */ + public void removeMenuDetectListener(MenuDetectListener listener) { + fMenuListeners.remove(listener); + } + + /* + * @see org.eclipse.jface.viewers.StructuredViewer#assertContentProviderType(org.eclipse.jface.viewers.IContentProvider) + */ + @Override + protected void assertContentProviderType(IContentProvider provider) { + super.assertContentProviderType(provider); + Assert.isTrue(provider instanceof ITreePathContentProvider); + } + + /* + * @see org.eclipse.jface.viewers.Viewer#inputChanged(java.lang.Object, java.lang.Object) + */ + @Override + protected void inputChanged(final Object input, Object oldInput) { + if (fContainer.isDisposed()) { + return; + } + + disableRedraw(); + try { + preservingSelection(() -> buildItemChain(input)); + } finally { + enableRedraw(); + } + } + + /* + * @see org.eclipse.jface.viewers.StructuredViewer#doFindInputItem(java.lang.Object) + */ + @Override + protected Widget doFindInputItem(Object element) { + if (element == null) { + return null; + } + + if (equals(element, getInput())) { + return doFindItem(element); + } + + return null; + } + + /* + * @see org.eclipse.jface.viewers.StructuredViewer#doFindItem(java.lang.Object) + */ + @Override + protected Widget doFindItem(Object element) { + if (element == null) { + return null; + } + + for (TreePathBreadcrumbItem item : fTreePathBreadcrumbItems) { + if (equals(element, item.getData())) { + return item; + } + } + + return null; + } + + /* + * @see org.eclipse.jface.viewers.StructuredViewer#doUpdateItem(org.eclipse.swt.widgets.Widget, java.lang.Object, boolean) + */ + @Override + protected void doUpdateItem(Widget widget, Object element, boolean fullMap) { + if (myDoUpdateItem(widget, element, fullMap)) { + updateSize(); + fContainer.layout(true, true); + } + } + + private boolean myDoUpdateItem(Widget widget, Object element, boolean fullMap) { + if (widget instanceof final TreePathBreadcrumbItem item) { + // remember element we are showing + if (fullMap) { + associate(element, item); + } else { + Object data= item.getData(); + if (data != null) { + unmapElement(data, item); + } + item.setData(element); + mapElement(element, item); + } + + return refreshItem(item); + } + return false; + } + + /** + * This implementation of getSelection() returns an instance of + * ITreeSelection. + * @return the current selection + */ + @Override + public ISelection getSelection() { + Control control = getControl(); + if (control == null || control.isDisposed()) { + return TreeSelection.EMPTY; + } + if (fSelectedItem != null) { + TreePath path = getTreePathFromItem(fSelectedItem); + if (path != null) { + return new TreeSelection(new TreePath[] { path }); + } + } + return TreeSelection.EMPTY; + } + + private TreePath getTreePathFromItem(TreePathBreadcrumbItem item) { + if (!fTreePathBreadcrumbItems.contains(item)) { + return null; + } + return item.getPath(); + } + + /* + * @see org.eclipse.jface.viewers.StructuredViewer#getSelectionFromWidget() + */ + @Override + protected List getSelectionFromWidget() { + if (fSelectedItem == null) { + return Collections.EMPTY_LIST; + } + + if (fSelectedItem.getData() == null) { + return Collections.EMPTY_LIST; + } + + ArrayList result = new ArrayList<>(); + result.add(fSelectedItem.getData()); + return result; + } + + /* + * @see org.eclipse.jface.viewers.StructuredViewer#internalRefresh(java.lang.Object) + */ + @Override + protected void internalRefresh(Object element) { + + disableRedraw(); + try { + if (equals(element, getRoot())) { + buildItemChain(getInput()); + return; + } + + boolean layoutChanged = false; + + TreePathBreadcrumbItem item= (TreePathBreadcrumbItem) doFindItem(element); + if (item == null) { + for (TreePathBreadcrumbItem currItem : fTreePathBreadcrumbItems) { + layoutChanged = refreshItem(currItem) || layoutChanged; + } + } else { + layoutChanged = refreshItem(item) || layoutChanged; + } + + if (layoutChanged) { + updateSize(); + fContainer.layout(true, true); + } + } finally { + enableRedraw(); + } + } + + /* + * @see + * org.eclipse.jface.viewers.StructuredViewer#setSelectionToWidget(org.eclipse. + * jface.viewers.ISelection, boolean) + */ + @Override + protected void setSelectionToWidget(ISelection selection, boolean reveal) { + if (selection instanceof ITreeSelection treeSelection) { + TreePathBreadcrumbItem focusItem = null; + + for (TreePathBreadcrumbItem item : fTreePathBreadcrumbItems) { + if (item.hasFocus()) { + focusItem = item; + } + item.setSelected(false); + } + + fSelectedItem = null; + + for (TreePath path : treeSelection.getPaths()) { + for (TreePathBreadcrumbItem item : fTreePathBreadcrumbItems) { + if (path.equals(item.getPath(), getComparer())) { + item.setSelected(true); + fSelectedItem = item; + if (item == focusItem) { + focusItem = null; + } + break; + } + } + } + + if (fSelectedItem != null && focusItem != null) { + focusItem.setFocus(false); + } + return; + } + + super.setSelectionToWidget(selection, reveal); + } + + /* + * @see org.eclipse.jface.viewers.StructuredViewer#setSelectionToWidget(java.util.List, boolean) + */ + @Override + protected void setSelectionToWidget(List l, boolean reveal) { + TreePathBreadcrumbItem focusItem= null; + + // Unselect the currently selected items, and remember the focused item. + for (TreePathBreadcrumbItem item : fTreePathBreadcrumbItems) { + if (item.hasFocus()) { + focusItem= item; + } + + item.setSelected(false); + } + + if (l == null) { + l = Collections.EMPTY_LIST; + } + + // Set the new selection to items. + fSelectedItem = null; + for (Iterator iterator = l.iterator(); iterator.hasNext();) { + Object element= iterator.next(); + TreePathBreadcrumbItem item= (TreePathBreadcrumbItem) doFindItem(element); + if (item != null) { + item.setSelected(true); + fSelectedItem= item; + if (item == focusItem) { + focusItem = null; + } + } + } + + // If there is a new selection, and it does not overlap the old selection, + // remove the focus marker from the old focus item. + if (fSelectedItem != null && focusItem != null) { + focusItem.setFocus(false); + } + } + + /** + * Opens the drop-down menu for the breadcrumb item representing the given + * element. Does nothing if the element is not shown in the breadcrumb. + * + * @param element the element whose drop-down menu should be opened + * @since 3.41 + */ + public void openDropDownMenu(Object element) { + TreePathBreadcrumbItem item = (TreePathBreadcrumbItem) doFindItem(element); + if (item != null) { + item.openDropDownMenu(); + } + } + + /** + * Set a single selection to the given item. null to deselect all. + * + * @param item the item to select or null + */ + void selectItem(TreePathBreadcrumbItem item) { + if (fSelectedItem != null) { + fSelectedItem.setSelected(false); + } + + fSelectedItem= item; + setSelectionToWidget(getSelection(), false); + setFocus(); + + fireSelectionChanged(new SelectionChangedEvent(this, getSelection())); + } + + /** + * Returns the item count. + * + * @return number of items shown in the viewer + */ + int getItemCount() { + return fTreePathBreadcrumbItems.size(); + } + + /** + * Returns the item for the given item index. + * + * @param index the index of the item + * @return the item ad the given index + */ + TreePathBreadcrumbItem getItem(int index) { + return fTreePathBreadcrumbItems.get(index); + } + + /** + * Returns the index of the given item. + * + * @param item the item to search + * @return the index of the item or -1 if not found + */ + int getIndexOfItem(TreePathBreadcrumbItem item) { + for (int i= 0, size= fTreePathBreadcrumbItems.size(); i < size; i++) { + TreePathBreadcrumbItem pItem= fTreePathBreadcrumbItems.get(i); + if (pItem == item) { + return i; + } + } + + return -1; + } + + /** + * Notifies all double click listeners. + */ + void fireDoubleClick() { + fireDoubleClick(new DoubleClickEvent(this, getSelection())); + } + + /** + * Notifies all open listeners. + */ + void fireOpen() { + fireOpen(new OpenEvent(this, getSelection())); + } + + /** + * The given element was selected from a drop down menu. + * + * @param selection the selected element + */ + void fireMenuSelection(ISelection selection) { + fireOpen(new OpenEvent(this, selection)); + } + + /** + * A context menu has been requested for the selected breadcrumb item. + * + * @param event the event issued the menu detection + */ + void fireMenuDetect(MenuDetectEvent event) { + for (MenuDetectListener menuDetectListener : fMenuListeners) { + menuDetectListener.menuDetected(event); + } + } + + /** + * Set selection to the next or previous element if possible. + * + * @param next true if the next element should be selected, otherwise the previous + * one will be selected + */ + void doTraverse(boolean next) { + if (fSelectedItem == null) { + return; + } + + int index= fTreePathBreadcrumbItems.indexOf(fSelectedItem); + if (next) { + if (index == fTreePathBreadcrumbItems.size() - 1) { + TreePathBreadcrumbItem current= fTreePathBreadcrumbItems.get(index); + + current.openDropDownMenu(); + current.getDropDownShell().setFocus(); + } else { + TreePathBreadcrumbItem nextItem= fTreePathBreadcrumbItems.get(index + 1); + selectItem(nextItem); + } + } else { + if (index == 0) { + TreePathBreadcrumbItem root= fTreePathBreadcrumbItems.get(index); + root.openDropDownMenu(); + root.getDropDownShell().setFocus(); + } else { + selectItem(fTreePathBreadcrumbItems.get(index - 1)); + } + } + } + + /** + * Generates the parent chain of the given element. + * @param input element to build the parent chain for + */ + private void buildItemChain(Object input) { + if (fTreePathBreadcrumbItems.size() > 0) { + TreePathBreadcrumbItem last= fTreePathBreadcrumbItems.get(fTreePathBreadcrumbItems.size() - 1); + last.setIsLastItem(false); + } + + int index = 0; + boolean updateLayout = false; + if (input != null) { + ITreePathContentProvider contentProvider= (ITreePathContentProvider) getContentProvider(); + TreePath path = new TreePath(new Object[0]); + + // Top level elements need to be retrieved using getElements(), rest + // using getChildren(). + Object[] children = contentProvider.getElements(input); + Object element = children != null && children.length != 0 ? children[0] : null; + while (element != null) { + path = path.createChildPath(element); + + // All but last item are hidden if the viewer is in a vertical toolbar. + children = contentProvider.getChildren(path); + if ((getStyle() & SWT.VERTICAL) == 0 || children == null || children.length == 0) { + updateLayout = updateOrCreateItem(index++, path, element) || updateLayout; + } + + if (children != null && children.length != 0) { + element = children[0]; + } else { + break; + } + + } + } + + TreePathBreadcrumbItem last = null; + if (index > 0 && index <= fTreePathBreadcrumbItems.size()) { + last = (fTreePathBreadcrumbItems.get(index - 1)); + last.setIsLastItem(true); + } + + while (index < fTreePathBreadcrumbItems.size()) { + updateLayout = true; + TreePathBreadcrumbItem item= fTreePathBreadcrumbItems.remove(fTreePathBreadcrumbItems.size() - 1); + if (item.hasFocus() && last != null) { + last.setFocus(true); + } + if (item == fSelectedItem) { + selectItem(null); + } + if (item.getData() != null) { + unmapElement(item.getData(), item); + } + item.dispose(); + } + + if (updateLayout) { + updateSize(); + fContainer.layout(true, true); + } + } + + /** + * @param item Item to refresh. + * @return returns whether the item's size and layout needs to be updated. + */ + private boolean refreshItem(TreePathBreadcrumbItem item) { + boolean layoutChanged = false; + + TreePath path = getTreePathFromItem(item); + + ViewerLabel label = new ViewerLabel(item.getText(), item.getImage()); + ((ITreePathLabelProvider)getLabelProvider()).updateLabel(label, path); + + if (label.hasNewText()) { + item.setText(label.getText()); + layoutChanged = true; + } + if (label.hasNewImage()) { + item.setImage(label.getImage()); + layoutChanged = true; + } + if (label.hasNewTooltipText()) { + item.setToolTip(label.getTooltipText()); + } + return layoutChanged; + } + + /** + * Creates or updates a breadcrumb item. + * @param index the index + * @param path the path + * @param element the element + * + * @return whether breadcrumb layout needs to be updated due to this change + */ + private boolean updateOrCreateItem(int index, TreePath path, Object element) { + TreePathBreadcrumbItem item; + if (fTreePathBreadcrumbItems.size() > index) { + item = fTreePathBreadcrumbItems.get(index); + if (item.getData() != null) { + unmapElement(item.getData(), item); + } + } else { + item = new TreePathBreadcrumbItem(this, fContainer); + fTreePathBreadcrumbItems.add(item); + } + + boolean updateLayout = false; + + if (equals(element, item.getData())) { + item.setPath(path); + updateLayout = myDoUpdateItem(item, element, false); + } else { + item.setData(element); + item.setPath(path); + mapElement(element, item); + updateLayout = refreshItem(item); + } + + return updateLayout; + } + + /** + * Update the size of the items such that all items are visible, if possible. + * + * @return true if any item has changed, false otherwise + */ + private boolean updateSize() { + int width= fContainer.getClientArea().width; + + int currentWidth= getCurrentWidth(); + + boolean requiresLayout= false; + + if (currentWidth > width) { + int index= 0; + while (currentWidth > width && index < fTreePathBreadcrumbItems.size() - 1) { + TreePathBreadcrumbItem viewer= fTreePathBreadcrumbItems.get(index); + if (viewer.isShowText()) { + viewer.setShowText(false); + currentWidth= getCurrentWidth(); + requiresLayout= true; + } + + index++; + } + + } else if (currentWidth < width) { + + int index= fTreePathBreadcrumbItems.size() - 1; + while (currentWidth < width && index >= 0) { + + TreePathBreadcrumbItem viewer= fTreePathBreadcrumbItems.get(index); + if (!viewer.isShowText()) { + viewer.setShowText(true); + currentWidth= getCurrentWidth(); + if (currentWidth > width) { + viewer.setShowText(false); + index= 0; + } else { + requiresLayout= true; + } + } + + index--; + } + } + + return requiresLayout; + } + + /** + * Returns the current width of all items in the list. + * + * @return the width of all items in the list + */ + private int getCurrentWidth() { + int result= 0; + for (TreePathBreadcrumbItem viewer : fTreePathBreadcrumbItems) { + result+= viewer.getWidth(); + } + + return result; + } + + /** + * Enables redrawing of the breadcrumb. + */ + private void enableRedraw() { + if (IS_GTK) { + return; + } + + fContainer.setRedraw(true); + } + + /** + * Disables redrawing of the breadcrumb. + * + *

+ * A call to this method must be followed by a call to {@link #enableRedraw()} + *

+ */ + private void disableRedraw() { + if (IS_GTK) { + return; + } + + fContainer.setRedraw(false); + } + + /* + * @see + * org.eclipse.jface.viewers.StructuredViewer#handleDispose(org.eclipse.swt.events.DisposeEvent) + * + */ + @Override + protected void handleDispose(DisposeEvent event) { + if (fGradientBackground != null) { + fGradientBackground.dispose(); + fGradientBackground= null; + } + + if (fTreePathBreadcrumbItems != null) { + for (TreePathBreadcrumbItem item : fTreePathBreadcrumbItems) { + item.dispose(); + } + } + + super.handleDispose(event); + } + protected IDialogSettings getDialogSettings() { + return fDialogSettings; + } + }