| /******************************************************************************* |
| * Copyright (c) 2008, 2010 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| |
| package org.eclipse.gef.internal.ui.palette.editparts; |
| |
| import java.util.Iterator; |
| |
| import org.eclipse.draw2d.AbstractLayout; |
| import org.eclipse.draw2d.Animation; |
| import org.eclipse.draw2d.BorderLayout; |
| import org.eclipse.draw2d.ButtonModel; |
| import org.eclipse.draw2d.ChangeEvent; |
| import org.eclipse.draw2d.ChangeListener; |
| import org.eclipse.draw2d.Clickable; |
| import org.eclipse.draw2d.Figure; |
| import org.eclipse.draw2d.FlowLayout; |
| import org.eclipse.draw2d.Graphics; |
| import org.eclipse.draw2d.IFigure; |
| import org.eclipse.draw2d.MarginBorder; |
| import org.eclipse.draw2d.StackLayout; |
| import org.eclipse.draw2d.Toggle; |
| import org.eclipse.draw2d.ToolbarLayout; |
| import org.eclipse.draw2d.geometry.Dimension; |
| import org.eclipse.draw2d.geometry.Point; |
| import org.eclipse.draw2d.geometry.PointList; |
| import org.eclipse.draw2d.geometry.Rectangle; |
| |
| import org.eclipse.gef.internal.ui.palette.PaletteColorUtil; |
| import org.eclipse.gef.internal.ui.palette.editparts.ToolEntryEditPart.ToolEntryToggle; |
| import org.eclipse.gef.ui.palette.PaletteViewerPreferences; |
| |
| /** |
| * A pinnable palette stack figure. |
| * |
| * @author crevells |
| * @since 3.4 |
| */ |
| public class PinnablePaletteStackFigure extends Figure { |
| |
| private static final Dimension EMPTY_DIMENSION = new Dimension(0, 0); |
| |
| static final int ARROW_WIDTH = 9; |
| |
| /** |
| * A toggle with a triangle figure. |
| */ |
| class RolloverArrow extends Toggle { |
| |
| RolloverArrow() { |
| super(); |
| setRolloverEnabled(true); |
| setBorder(null); |
| setOpaque(false); |
| setPreferredSize(ARROW_WIDTH, -1); |
| } |
| |
| /** |
| * @return false so that the focus rectangle is not drawn. |
| */ |
| public boolean hasFocus() { |
| return false; |
| } |
| |
| public void paintFigure(Graphics graphics) { |
| Rectangle rect = getClientArea(); |
| |
| ButtonModel model = getModel(); |
| if (isRolloverEnabled() && model.isMouseOver()) { |
| graphics.setBackgroundColor(PaletteColorUtil.ARROW_HOVER); |
| graphics.fillRoundRectangle(rect, 3, 3); |
| } |
| |
| graphics.translate(getLocation()); |
| |
| // fill the arrow |
| int[] points = new int[8]; |
| |
| int middleY = rect.height / 2; |
| if (isSelected() |
| || layoutMode == PaletteViewerPreferences.LAYOUT_ICONS |
| || layoutMode == PaletteViewerPreferences.LAYOUT_COLUMNS) { |
| // pointing down |
| int startingX = (ARROW_WIDTH - 5) / 2; // triangle width = 5 |
| points[0] = startingX; |
| points[1] = middleY; |
| points[2] = startingX + 5; |
| points[3] = middleY; |
| points[4] = startingX + 2; |
| points[5] = middleY + 3; |
| points[6] = startingX; |
| points[7] = middleY; |
| } else { |
| // pointing to the right |
| int startingX = (ARROW_WIDTH - 3) / 2; // triangle width = 3 |
| points[0] = startingX; |
| points[1] = middleY - 2; |
| points[2] = startingX + 3; |
| points[3] = middleY + 1; |
| points[4] = startingX; |
| points[5] = middleY + 4; |
| points[6] = startingX; |
| points[7] = middleY - 2; |
| } |
| |
| graphics.setBackgroundColor(PaletteColorUtil.WIDGET_DARK_SHADOW); |
| graphics.fillPolygon(points); |
| |
| graphics.translate(getLocation().getNegated()); |
| } |
| } |
| |
| /** |
| * Layout manager for the palette stack header figure that handles the |
| * layout of the <code>headerFigure</code> (<code>arrowFigure</code> and the |
| * active tool figure) when in icons or columns layout mode. |
| */ |
| private class HeaderIconLayout extends StackLayout { |
| |
| protected boolean isSensitiveVertically(IFigure container) { |
| return false; |
| } |
| |
| public void layout(IFigure parent) { |
| |
| Rectangle r = parent.getClientArea(); |
| |
| if (activeToolFigure != null) { |
| activeToolFigure.setBounds(r); |
| } |
| |
| // All tool figures have saved an area in its margin for the arrow |
| // figure in |
| // case that tool figure is in a stack (see the BORDER variables in |
| // ToolEntryEditPart). Calculate the arrow figure bounds by using |
| // the insets |
| // of the active tool figure. |
| r.x = r.right() - ToolEntryEditPart.ICON_HIGHLIGHT_INSETS.right |
| - ARROW_WIDTH; |
| r.y += ToolEntryEditPart.ICON_HIGHLIGHT_INSETS.top; |
| r.width = ARROW_WIDTH; |
| r.height -= ToolEntryEditPart.ICON_HIGHLIGHT_INSETS.getHeight(); |
| arrowFigure.setBounds(r); |
| } |
| } |
| |
| /** |
| * Layout manager for the palette stack header figure that handles the |
| * layout of the <code>headerFigure</code> (<code>pinFigure</code>, |
| * <code>arrowFigure</code>, and the active tool figure) when in list or |
| * details layout mode. |
| */ |
| private class HeaderListLayout extends StackLayout { |
| |
| protected boolean isSensitiveVertically(IFigure container) { |
| return false; |
| } |
| |
| protected Dimension calculatePreferredSize(IFigure parent, int wHint, |
| int hHint) { |
| if (isExpanded()) { |
| Dimension pinSize = pinFigure.getSize(); |
| Dimension preferredSize = super.calculatePreferredSize(parent, |
| wHint - pinSize.width, hHint); |
| preferredSize.width += pinSize.width; |
| return preferredSize; |
| } else { |
| return super.calculatePreferredSize(parent, wHint, hHint); |
| } |
| } |
| |
| public void layout(IFigure parent) { |
| |
| Rectangle r = parent.getClientArea(); |
| Dimension pinSize = isExpanded() ? pinFigure.getPreferredSize() |
| : EMPTY_DIMENSION; |
| |
| // layout the pin figure |
| Rectangle.SINGLETON.setSize(pinSize); |
| Rectangle.SINGLETON.setLocation(r.right() - pinSize.width, |
| r.getCenter().y - (pinSize.height / 2)); |
| pinFigure.setBounds(Rectangle.SINGLETON); |
| |
| if (activeToolFigure != null) { |
| activeToolFigure.setBounds(r.getResized(-pinSize.width, 0)); |
| } |
| |
| // All tool figures have saved an area in its margin for the arrow |
| // figure in |
| // case that tool figure is in a stack (see the BORDER variables in |
| // ToolEntryEditPart). Calculate the arrow figure bounds by using |
| // the insets |
| // of the active tool figure. |
| r.x += ToolEntryEditPart.LIST_HIGHLIGHT_INSETS.left; |
| r.y += ToolEntryEditPart.LIST_HIGHLIGHT_INSETS.top; |
| r.width = ARROW_WIDTH; |
| r.height -= ToolEntryEditPart.LIST_HIGHLIGHT_INSETS.getHeight(); |
| arrowFigure.setBounds(r); |
| |
| } |
| } |
| |
| /** |
| * Layout manager for the palette stack figure that handles the layout of |
| * the <code>headerFigure</code>, <code>expandablePane</code>, and |
| * <code>pinFigure</code> when in icons or columns layout mode. |
| */ |
| private class PaletteStackIconLayout extends AbstractLayout { |
| |
| protected Dimension calculatePreferredSize(IFigure parent, int wHint, |
| int hHint) { |
| return parent.getSize(); |
| } |
| |
| public void layout(IFigure parent) { |
| if (isExpanded()) { |
| headerFigure.setBounds(headerBoundsLayoutHint); |
| |
| Rectangle paneBounds = parent.getClientArea(); |
| paneBounds.y += headerBoundsLayoutHint.height; |
| paneBounds.height -= headerBoundsLayoutHint.height; |
| expandablePane.setBounds(paneBounds); |
| |
| Rectangle pinBounds = Rectangle.SINGLETON; |
| Dimension pinSize = pinFigure.getPreferredSize(); |
| pinBounds.setSize(pinSize); |
| int pinFigureAreaHeight = expandablePane.getInsets().top; |
| pinBounds.setLocation(expandablePane.getClientArea().right() |
| - pinSize.width, |
| (paneBounds.y + pinFigureAreaHeight / 2) |
| - (pinSize.height / 2)); |
| pinFigure.setBounds(pinBounds); |
| } else { |
| headerFigure.setBounds(parent.getClientArea()); |
| } |
| } |
| } |
| |
| /** |
| * listens to selection events on the arrow figure |
| */ |
| private ChangeListener clickableArrowListener = new ChangeListener() { |
| |
| public void handleStateChanged(ChangeEvent event) { |
| Clickable clickable = (Clickable) event.getSource(); |
| if (event.getPropertyName() == ButtonModel.MOUSEOVER_PROPERTY |
| && getActiveFigure() instanceof ToolEntryToggle) { |
| ((ToolEntryToggle) getActiveFigure()) |
| .setShowHoverFeedback(clickable.getModel() |
| .isMouseOver()); |
| } |
| if (event.getPropertyName() == ButtonModel.SELECTED_PROPERTY) { |
| |
| Animation.markBegin(); |
| handleExpandStateChanged(); |
| Animation.run(150); |
| |
| // Now collapse other stacks when they are not pinned open or in |
| // the |
| // case of columns and icons layout mode (where only one stack |
| // can |
| // be expanded at a time for layout reasons). |
| if (isExpanded()) { |
| boolean collapseOtherStacks = (layoutMode == PaletteViewerPreferences.LAYOUT_COLUMNS || layoutMode == PaletteViewerPreferences.LAYOUT_ICONS); |
| |
| for (Iterator iterator = getParent().getChildren() |
| .iterator(); iterator.hasNext();) { |
| Object childFigure = iterator.next(); |
| if (childFigure instanceof PinnablePaletteStackFigure |
| && childFigure != PinnablePaletteStackFigure.this) { |
| |
| if (collapseOtherStacks |
| || (((PinnablePaletteStackFigure) childFigure) |
| .isExpanded() && !((PinnablePaletteStackFigure) childFigure) |
| .isPinnedOpen())) { |
| |
| ((PinnablePaletteStackFigure) childFigure) |
| .setExpanded(false); |
| } |
| } |
| } |
| } |
| } |
| } |
| }; |
| |
| private IFigure headerFigure; |
| |
| private IFigure activeToolFigure; |
| |
| private PinFigure pinFigure; |
| |
| private RolloverArrow arrowFigure; |
| |
| private IFigure expandablePane; |
| |
| private int layoutMode = -1; |
| |
| private Rectangle headerBoundsLayoutHint = new Rectangle(); |
| |
| public PinnablePaletteStackFigure() { |
| super(); |
| |
| arrowFigure = new RolloverArrow(); |
| arrowFigure.addChangeListener(clickableArrowListener); |
| |
| headerFigure = new Figure(); |
| headerFigure.add(arrowFigure); |
| |
| pinFigure = new PinFigure(); |
| pinFigure.setBorder(new MarginBorder(0, 0, 0, 2)); |
| |
| expandablePane = new Figure(); |
| |
| add(headerFigure); |
| } |
| |
| protected void paintFigure(Graphics g) { |
| super.paintFigure(g); |
| |
| if (!isExpanded()) { |
| return; |
| } |
| |
| Rectangle headerBounds = headerFigure.getBounds().getCopy(); |
| Rectangle paneBounds = expandablePane.getClientArea(); |
| |
| // fill expandable pane background |
| g.setBackgroundColor(PaletteColorUtil.WIDGET_BACKGROUND_LIST_BACKGROUND_40); |
| g.fillRectangle(paneBounds); |
| |
| if (layoutMode == PaletteViewerPreferences.LAYOUT_ICONS |
| || layoutMode == PaletteViewerPreferences.LAYOUT_COLUMNS) { |
| |
| int pinHeight = expandablePane.getInsets().top; |
| Rectangle pinAreaBounds = new Rectangle(paneBounds.x, |
| expandablePane.getBounds().y, paneBounds.width, pinHeight); |
| |
| // fill background colors |
| g.setForegroundColor(PaletteColorUtil.WIDGET_BACKGROUND_LIST_BACKGROUND_40); |
| g.setBackgroundColor(PaletteColorUtil.WIDGET_BACKGROUND_LIST_BACKGROUND_85); |
| g.fillGradient(headerBounds, true); |
| |
| g.setBackgroundColor(PaletteColorUtil.WIDGET_BACKGROUND_LIST_BACKGROUND_85); |
| g.fillRectangle(pinAreaBounds); |
| |
| // draw white lines |
| g.setForegroundColor(PaletteColorUtil.WIDGET_LIST_BACKGROUND); |
| g.drawLine(headerBounds.getTopLeft().getTranslated(1, 1), |
| headerBounds.getTopRight().getTranslated(-1, 1)); |
| g.drawLine(headerBounds.getBottomLeft().getTranslated(1, 0), |
| headerBounds.getTopLeft().getTranslated(1, 1)); |
| g.drawLine(headerBounds.getBottomRight().getTranslated(-2, 0), |
| headerBounds.getTopRight().getTranslated(-2, 1)); |
| |
| g.drawLine(pinAreaBounds.getTopLeft().getTranslated(0, 1), |
| pinAreaBounds.getTopRight().getTranslated(-1, 1)); |
| g.drawLine(pinAreaBounds.getBottomLeft().getTranslated(0, -2), |
| pinAreaBounds.getBottomRight().getTranslated(-1, -2)); |
| |
| // draw grey border around the whole palette stack |
| PointList points = new PointList(); |
| points.addPoint(headerBounds.getBottomLeft()); |
| points.addPoint(headerBounds.getTopLeft().getTranslated(0, 2)); |
| points.addPoint(headerBounds.getTopLeft().getTranslated(1, 1)); |
| points.addPoint(headerBounds.getTopLeft().getTranslated(2, 0)); |
| points.addPoint(headerBounds.getTopRight().getTranslated(-3, 0)); |
| points.addPoint(headerBounds.getTopRight().getTranslated(-2, 1)); |
| points.addPoint(headerBounds.getTopRight().getTranslated(-1, 2)); |
| points.addPoint(headerBounds.getBottomRight().getTranslated(-1, 0)); |
| points.addPoint(pinAreaBounds.getTopRight().getTranslated(-1, 0)); |
| points.addPoint(paneBounds.getBottomRight().getTranslated(-1, -1)); |
| points.addPoint(paneBounds.getBottomLeft().getTranslated(0, -1)); |
| points.addPoint(pinAreaBounds.getTopLeft().getTranslated(0, 0)); |
| points.addPoint(headerBounds.getBottomLeft()); |
| |
| g.setForegroundColor(PaletteColorUtil.WIDGET_BACKGROUND_NORMAL_SHADOW_40); |
| g.drawPolygon(points); |
| |
| g.setForegroundColor(PaletteColorUtil.WIDGET_BACKGROUND_NORMAL_SHADOW_80); |
| Point pt = headerBounds.getTopLeft().getTranslated(0, 1); |
| g.drawPoint(pt.x, pt.y); |
| pt = headerBounds.getTopLeft().getTranslated(1, 0); |
| g.drawPoint(pt.x, pt.y); |
| pt = headerBounds.getTopRight().getTranslated(-2, 0); |
| g.drawPoint(pt.x, pt.y); |
| pt = headerBounds.getTopRight().getTranslated(-1, 1); |
| g.drawPoint(pt.x, pt.y); |
| } else { |
| |
| // fill header background |
| g.setBackgroundColor(PaletteColorUtil.WIDGET_BACKGROUND_LIST_BACKGROUND_85); |
| g.fillRectangle(headerBounds); |
| |
| // draw top and bottom border lines of header figure |
| g.setForegroundColor(PaletteColorUtil.WIDGET_BACKGROUND_NORMAL_SHADOW_65); |
| g.drawLine(headerBounds.getTopLeft(), headerBounds.getTopRight()); |
| g.setForegroundColor(PaletteColorUtil.WIDGET_LIST_BACKGROUND); |
| g.drawLine(headerBounds.getBottomLeft().getTranslated(0, -2), |
| headerBounds.getBottomRight().getTranslated(0, -2)); |
| |
| // draw bottom border line of expandable pane |
| g.setForegroundColor(PaletteColorUtil.WIDGET_BACKGROUND_NORMAL_SHADOW_65); |
| g.drawLine(paneBounds.getBottomLeft().getTranslated(0, -1), |
| paneBounds.getBottomRight().getTranslated(0, -1)); |
| } |
| |
| } |
| |
| /** |
| * @return The content pane for this figure, i.e. the Figure to which |
| * children can be added. |
| */ |
| public IFigure getContentPane(IFigure figure) { |
| if (figure == activeToolFigure) { |
| return headerFigure; |
| } |
| return getContentPane(); |
| } |
| |
| public IFigure getContentPane() { |
| return expandablePane; |
| } |
| |
| public IFigure getActiveFigure() { |
| return activeToolFigure; |
| } |
| |
| /** |
| * @return <code>true</code> if the palette stack is expanded |
| */ |
| public boolean isExpanded() { |
| return arrowFigure.getModel().isSelected(); |
| } |
| |
| /** |
| * @return <code>true</code> if the palette stack is expanded and is pinned |
| * (i.e., it can't be automatically collapsed) |
| */ |
| public boolean isPinnedOpen() { |
| return isExpanded() && pinFigure.getModel().isSelected(); |
| } |
| |
| /** |
| * Pins or unpins the stack. The stack can be pinned open only when it is |
| * expanded. Attempts to pin it when it is collapsed will do nothing. |
| * |
| * @param pinned |
| * <code>true</code> if the stack is to be pinned |
| */ |
| public void setPinned(boolean pinned) { |
| if (isExpanded()) { |
| pinFigure.setSelected(pinned); |
| } |
| } |
| |
| public void setExpanded(boolean value) { |
| arrowFigure.setSelected(value); |
| if (!value) { |
| pinFigure.setSelected(false); |
| } |
| } |
| |
| public void setLayoutMode(int newLayoutMode) { |
| if (this.layoutMode == newLayoutMode) { |
| return; |
| } |
| |
| this.layoutMode = newLayoutMode; |
| |
| // Only one stack can be expanded in icons and layout mode, therefore |
| // for |
| // consistency let's always collapse stacks when changing the layout |
| // modes. |
| setExpanded(false); |
| |
| if (newLayoutMode == PaletteViewerPreferences.LAYOUT_LIST |
| || newLayoutMode == PaletteViewerPreferences.LAYOUT_DETAILS) { |
| |
| headerFigure.setLayoutManager(new HeaderListLayout()); |
| |
| expandablePane.setLayoutManager(new ToolbarLayout()); |
| expandablePane.setBorder(new MarginBorder(1, 0, 1, 0)); |
| setLayoutManager(new ToolbarLayout()); |
| |
| } else { |
| headerFigure.setLayoutManager(new HeaderIconLayout()); |
| if (activeToolFigure != null) { |
| headerFigure.setConstraint(activeToolFigure, |
| BorderLayout.CENTER); |
| } |
| |
| setLayoutManager(new PaletteStackIconLayout()); |
| |
| // account for space used by pin figure |
| expandablePane.setBorder(new MarginBorder(18, 0, 0, 0)); |
| |
| if (layoutMode == PaletteViewerPreferences.LAYOUT_COLUMNS) { |
| expandablePane.setLayoutManager(new ColumnsLayout()); |
| } else { // LAYOUT_ICONS |
| FlowLayout fl = new FlowLayout(); |
| fl.setMinorSpacing(0); |
| fl.setMajorSpacing(0); |
| expandablePane.setLayoutManager(fl); |
| } |
| } |
| } |
| |
| public void activeEntryChanged(IFigure oldFigure, int oldFigureIndex, |
| IFigure newFigure) { |
| |
| if (oldFigure != null) { |
| // if figure is null, its no longer a child. |
| expandablePane.add(oldFigure, oldFigureIndex); |
| } |
| |
| if (newFigure != null) { |
| activeToolFigure = newFigure; |
| headerFigure.add(activeToolFigure, BorderLayout.CENTER, 0); |
| } else { |
| activeToolFigure = null; |
| } |
| } |
| |
| private void handleExpandStateChanged() { |
| if (isExpanded()) { |
| if (expandablePane.getParent() != this) { |
| add(expandablePane); |
| |
| if (layoutMode == PaletteViewerPreferences.LAYOUT_LIST |
| || layoutMode == PaletteViewerPreferences.LAYOUT_DETAILS) { |
| headerFigure.add(pinFigure); |
| } else { |
| add(pinFigure); |
| } |
| } |
| } else { |
| if (expandablePane.getParent() == this) { |
| remove(expandablePane); |
| pinFigure.getParent().remove(pinFigure); |
| } |
| } |
| } |
| |
| /** |
| * Gets the preferred size of the expandable pane figure. Used by |
| * <code>PaletteContainerFlowLayout</code> when the layout is icons or |
| * columns mode. |
| * |
| * @param wHint |
| * width hint |
| * @param hHint |
| * height hint |
| * @return the preferred size of the expandable pane figure or (0,0) if the |
| * pane is collapsed |
| */ |
| public Dimension getExpandedContainerPreferredSize(int wHint, int hHint) { |
| if (isExpanded()) { |
| return expandablePane.getPreferredSize(wHint, hHint); |
| } else { |
| return EMPTY_DIMENSION; |
| } |
| } |
| |
| /** |
| * Sets the header bounds layout hint. Set by |
| * <code>PaletteContainerFlowLayout</code> when the layout is icons or |
| * columns mode and used by <code>PaletteStackIconLayout</code>. |
| * |
| * @param rect |
| * the new value |
| */ |
| public void setHeaderBoundsLayoutHint(Rectangle rect) { |
| headerBoundsLayoutHint.setBounds(rect); |
| } |
| |
| /** |
| * Gets the preferred size of the header figure. Used by |
| * <code>PaletteContainerFlowLayout</code> and <code>ColumnsLayout</code> |
| * when the layout is icons or columns mode. |
| * |
| * @param wHint |
| * width hint |
| * @param hHint |
| * height hint |
| * @return the preferred size of the header figure |
| */ |
| public Dimension getHeaderPreferredSize(int wHint, int hHint) { |
| return headerFigure.getPreferredSize(wHint, hHint); |
| } |
| |
| public boolean containsPoint(int x, int y) { |
| return headerFigure.containsPoint(x, y) |
| || (isExpanded() && expandablePane.containsPoint(x, y)); |
| } |
| |
| } |