/******************************************************************************* * Copyright (c) 2008 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)); } }