/******************************************************************************* * Copyright (c) 2000, 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 java.util.List; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.draw2d.Animation; import org.eclipse.draw2d.Border; 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.ColorConstants; import org.eclipse.draw2d.CompoundBorder; import org.eclipse.draw2d.Figure; import org.eclipse.draw2d.FigureUtilities; import org.eclipse.draw2d.Graphics; import org.eclipse.draw2d.IFigure; import org.eclipse.draw2d.Label; import org.eclipse.draw2d.LayoutManager; import org.eclipse.draw2d.MarginBorder; import org.eclipse.draw2d.MouseEvent; import org.eclipse.draw2d.MouseListener; import org.eclipse.draw2d.MouseMotionListener; import org.eclipse.draw2d.SchemeBorder; import org.eclipse.draw2d.ScrollPane; import org.eclipse.draw2d.Toggle; import org.eclipse.draw2d.ToolbarLayout; import org.eclipse.draw2d.geometry.Dimension; import org.eclipse.draw2d.geometry.Insets; import org.eclipse.draw2d.geometry.Rectangle; import org.eclipse.gef.internal.ui.palette.PaletteColorUtil; import org.eclipse.gef.ui.palette.PaletteViewerPreferences; import org.eclipse.gef.ui.palette.editparts.PaletteToolbarLayout; /** * @author Pratik Shah */ public class DrawerFigure extends Figure { /** Foreground color constant **/ protected static final Color FG_COLOR = FigureUtilities.mixColors( PaletteColorUtil.WIDGET_NORMAL_SHADOW, PaletteColorUtil.WIDGET_BACKGROUND); /** Scrollpane border constant for icon and column layout mode **/ protected static final Border SCROLL_PANE_BORDER = new MarginBorder(2, 2, 2, 2); /** Scrollpane border constant for list and details layout mode **/ protected static final Border SCROLL_PANE_LIST_BORDER = new MarginBorder(2, 0, 2, 0); /** Title margin border constant **/ protected static final Border TITLE_MARGIN_BORDER = new MarginBorder(4, 2, 2, 2); /** Toggle button border constant**/ protected static final Border TOGGLE_BUTTON_BORDER = new RaisedBorder(); /** Tooltip border constant **/ protected static final Border TOOLTIP_BORDER = new CompoundBorder( new SchemeBorder(SchemeBorder.SCHEMES.RAISED), new MarginBorder(1)); private Toggle collapseToggle; private Label drawerLabel, tipLabel; private boolean addedScrollpane = false; private int layoutMode = -1; private PinFigure pinFigure; private ScrollPane scrollpane; private boolean showPin = true, skipNextEvent; private EditPartTipHelper tipHelper; /** * This is the figure for the entire drawer label button. */ private class CollapseToggle extends Toggle { public CollapseToggle(IFigure contents) { super(contents); setSelected(true); setRequestFocusEnabled(true); addChangeListener(new ChangeListener() { public void handleStateChanged(ChangeEvent e) { if (e.getPropertyName().equals(ButtonModel.SELECTED_PROPERTY)) { Animation.markBegin(); handleExpandStateChanged(); Animation.run(150); } else if (e.getPropertyName().equals( ButtonModel.MOUSEOVER_PROPERTY)) { repaint(); } } }); } public IFigure getToolTip() { return buildTooltip(); } protected void paintFigure(Graphics g) { super.paintFigure(g); Rectangle r = Rectangle.SINGLETON; r.setBounds(getBounds()); // draw top border of drawer figure g.setForegroundColor(PaletteColorUtil.WIDGET_NORMAL_SHADOW); g.drawLine(r.getTopLeft(), r.getTopRight()); g.setForegroundColor(ColorConstants.white); g.drawLine(r.getTopLeft().getTranslated(0, 1), r.getTopRight() .getTranslated(0, 1)); r.crop(new Insets(2, 0, 0, 0)); if (isExpanded()) { g.setForegroundColor(PaletteColorUtil.WIDGET_BACKGROUND_NORMAL_SHADOW_65); g.drawLine(r.getLocation(), r.getTopRight()); r.crop(new Insets(1, 0, 0, 0)); } // draw bottom border of drawer figure if (!isExpanded()) { g.setForegroundColor(ColorConstants.white); g.drawLine(r.getBottomLeft().getTranslated(0, -1), r.getBottomRight() .getTranslated(0, -1)); r.crop(new Insets(0, 0, 1, 0)); } paintToggleGradient(g, r); } } /** * Constructor * * @param control The Control of the LWS to which this Figure belongs (it is used to * display the drawer header with an EditPartTipHelper, if the * header is not completely visible). It can be <code>null</code> * (the tip won't be displayed). */ public DrawerFigure(final Control control) { /* * A PaletteToolbarLayout is being used here instead of a ToolbarLayout so that the * ScrollPane can be stretched to take up vertical space. This affects selection * and appearance (background color). */ setLayoutManager(new PaletteToolbarLayout() { protected boolean isChildGrowing(IFigure child) { int wHint = child.getBounds().width; return child.getPreferredSize(wHint, -1).height != child.getMinimumSize(wHint, -1).height; } }); Figure title = new Figure(); title.setBorder(TITLE_MARGIN_BORDER); BorderLayout borderLayout = new BorderLayout(); borderLayout.setHorizontalSpacing(2); title.setLayoutManager(borderLayout); drawerLabel = new Label(); drawerLabel.setLabelAlignment(Label.LEFT); pinFigure = new PinFigure(); title.add(pinFigure, BorderLayout.RIGHT); title.add(drawerLabel, BorderLayout.CENTER); collapseToggle = new CollapseToggle(title); /* * @TODO:Pratik * * There is a bug here. Right-click on the name pop-up for the header of a drawer * figure in the palette. This will hide the pop-up. Right-click again, this time * on the collapse toggle, to bring up the drawer's context menu. Now, left-click * on the collapse toggle. The context menu will disappear, the name pop-up will * re-appear, and the drawer will collapse/expand. If the drawer was in such a * position, that collapsing/expanding it will cause its header to move, the name * pop-up will now be floating over where the collapse toggle used to be, but is not * anymore. To fix this, you can detect the left mouse click on the collapse toggle * and hide the name pop-up then. The problem is that when you click on the * collapseToggle after the context menu has been brought up, it does not fire a mouse * pressed event. The listener below, that is commented out for now, is never * notified. */ // collapseToggle.addMouseListener(new MouseListener.Stub(){ // public void mousePressed(MouseEvent me) { // System.out.println("AAA"); // } // }); add(collapseToggle); createScrollpane(); createHoverHelp(control); } /** * Paints the background gradient on the drawer toggle figure. * * @param g * the graphics object * @param rect * the rectangle which the background gradient should cover */ private void paintToggleGradient(Graphics g, Rectangle rect) { if (isExpanded()) { g.setBackgroundColor(PaletteColorUtil.WIDGET_BACKGROUND_LIST_BACKGROUND_85); g.fillRectangle(rect); } else if (collapseToggle.getModel().isMouseOver()) { Color color1 = PaletteColorUtil.WIDGET_BACKGROUND_LIST_BACKGROUND_60; Color color2 = PaletteColorUtil.WIDGET_BACKGROUND_NORMAL_SHADOW_90; Color color3 = PaletteColorUtil.WIDGET_BACKGROUND_NORMAL_SHADOW_95; Color color4 = PaletteColorUtil.WIDGET_BACKGROUND_LIST_BACKGROUND_90; g.setForegroundColor(color1); g.setBackgroundColor(color2); g.fillGradient(rect.x, rect.y, rect.width, rect.height - 4, true); g.setForegroundColor(color2); g.setBackgroundColor(color3); g.fillGradient(rect.x, rect.bottom() - 4, rect.width, 2, true); g.setForegroundColor(color3); g.setBackgroundColor(color4); g.fillGradient(rect.x, rect.bottom() - 2, rect.width, 2, true); } else { g.setForegroundColor(PaletteColorUtil.WIDGET_BACKGROUND_LIST_BACKGROUND_85); g.setBackgroundColor(PaletteColorUtil.WIDGET_BACKGROUND_NORMAL_SHADOW_45); g.fillGradient(rect, true); } } private void createHoverHelp(final Control control) { if (control == null) { return; } // If a control was provided, create the tipLabel -- if the text in the header is // truncated, it will display it as a tooltip. tipLabel = new Label() { /** * @see org.eclipse.draw2d.Figure#getToolTip() */ public IFigure getToolTip() { return buildTooltip(); } protected void paintFigure(Graphics graphics) { Rectangle r = Rectangle.SINGLETON; r.setBounds(getBounds()); graphics.pushState(); paintToggleGradient(graphics, getBounds()); graphics.popState(); super.paintFigure(graphics); } }; tipLabel.setOpaque(false); tipLabel.setBorder(TOOLTIP_BORDER); collapseToggle.addMouseMotionListener(new MouseMotionListener.Stub() { public void mouseMoved(MouseEvent e) { if (!drawerLabel.getBounds().contains(e.getLocation())) return; if (skipNextEvent) { skipNextEvent = false; return; } if (drawerLabel.isTextTruncated() && !EditPartTipHelper.isCurrent(tipHelper)) { tipLabel.setText(drawerLabel.getText()); tipLabel.setIcon(drawerLabel.getIcon()); tipLabel.setFont(drawerLabel.getFont()); tipHelper = new EditPartTipHelper(control); Rectangle bounds = drawerLabel.getBounds().getExpanded(2, 2); drawerLabel.translateToAbsolute(bounds); org.eclipse.swt.graphics.Rectangle loc = new org.eclipse.swt.graphics.Rectangle( bounds.x, bounds.y, bounds.width, bounds.height); loc = Display.getCurrent().map(control, null, loc); tipHelper.displayToolTipAt(tipLabel, loc.x, loc.y); } } }); tipLabel.addMouseListener(new MouseListener.Stub() { public void mousePressed(MouseEvent e) { if (e.button == 1) { Rectangle original = getCollapseToggle().getBounds().getCopy(); getCollapseToggle().requestFocus(); setExpanded(!isExpanded()); // Hide the tip if expanding the drawer causes the collapse toggle to move if (!original.equals(getCollapseToggle().getBounds())) { tipHelper.hide(); } } else { tipHelper.hide(); if (e.button == 3) { skipNextEvent = true; } } } }); } private void createScrollpane() { scrollpane = new ScrollPane(); scrollpane.getViewport().setContentsTracksWidth(true); scrollpane.setMinimumSize(new Dimension(0, 0)); scrollpane.setHorizontalScrollBarVisibility(ScrollPane.NEVER); scrollpane.setVerticalScrollBar(new PaletteScrollBar()); scrollpane.getVerticalScrollBar().setStepIncrement(20); scrollpane.setLayoutManager(new OverlayScrollPaneLayout()); scrollpane.setContents(new Figure()); scrollpane.getContents().setOpaque(true); scrollpane.getContents().setBackgroundColor(PaletteColorUtil.WIDGET_LIST_BACKGROUND); } IFigure buildTooltip() { return null; } /** * @return The Clickable that is used to expand/collapse the drawer. */ public Clickable getCollapseToggle() { return collapseToggle; } /** * @return The content pane for this figure, i.e. the Figure to which children can be * added. */ public IFigure getContentPane() { return scrollpane.getContents(); } /** * @see Figure#getMinimumSize(int, int) */ public Dimension getMinimumSize(int wHint, int hHint) { /* * Fix related to Bug #35176 * The figure returns a minimum size that is of at least a certain height, so as to * prevent each drawer from getting too small (in which case, the scrollbars cover up * the entire available space). */ if (isExpanded()) { List children = getContentPane().getChildren(); if (!children.isEmpty()) { Dimension result = collapseToggle.getPreferredSize(wHint, hHint).getCopy(); result.height += getContentPane().getInsets().getHeight(); IFigure child = (IFigure)children.get(0); result.height += Math.min(80, child.getPreferredSize(wHint, -1).height + 9); return result.intersect(getPreferredSize(wHint, hHint)); } } return super.getMinimumSize(wHint, hHint); } /** * Returns the ScrollPane associated with this DrawerFigure * @return the ScrollPane */ public ScrollPane getScrollpane() { return scrollpane; } protected void handleExpandStateChanged() { if (isExpanded()) { if (scrollpane.getParent() != this) add(scrollpane); } else { if (scrollpane.getParent() == this) remove(scrollpane); // collapse all pinnable palette stack children that aren't pinned for (Iterator iterator = getContentPane().getChildren().iterator(); iterator.hasNext();) { Object child = iterator.next(); if (child instanceof PinnablePaletteStackFigure && !((PinnablePaletteStackFigure) child).isPinnedOpen()) { ((PinnablePaletteStackFigure) child).setExpanded(false); } } } if (pinFigure != null) { pinFigure.setVisible(isExpanded() && showPin); } } /** * @return <code>true</code> if the drawer is expanded */ public boolean isExpanded() { return collapseToggle.isSelected(); } /** * @return <code>true</code> if the drawer is expanded and is pinned (i.e., it can't be * automatically collapsed) */ public boolean isPinnedOpen() { return isExpanded() && pinFigure.isVisible() && pinFigure.isSelected(); } /** * @return <code>true</code> if the drawer is expanded and its pin is showing */ public boolean isPinShowing() { return isExpanded() && showPin; } public void setAnimating(boolean isAnimating) { if (isAnimating) { if (scrollpane.getParent() != this) { addedScrollpane = true; add(scrollpane); } scrollpane.setVerticalScrollBarVisibility(ScrollPane.NEVER); } else { scrollpane.setVerticalScrollBarVisibility(ScrollPane.AUTOMATIC); if (addedScrollpane) { remove(scrollpane); addedScrollpane = false; } } } public void setExpanded(boolean value) { collapseToggle.setSelected(value); } public void setLayoutMode(int layoutMode) { if (this.layoutMode == layoutMode) { return; } this.layoutMode = layoutMode; LayoutManager manager; if (layoutMode == PaletteViewerPreferences.LAYOUT_COLUMNS) { manager = new ColumnsLayout(); getContentPane().setBorder(SCROLL_PANE_BORDER); } else if (layoutMode == PaletteViewerPreferences.LAYOUT_ICONS) { PaletteContainerFlowLayout fl = new PaletteContainerFlowLayout(); fl.setMinorSpacing(0); fl.setMajorSpacing(0); manager = fl; getContentPane().setBorder(SCROLL_PANE_BORDER); } else { manager = new ToolbarLayout(); getContentPane().setBorder(SCROLL_PANE_LIST_BORDER); } getContentPane().setLayoutManager(manager); } /** * Pins or unpins the drawer. The drawer 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 drawer is to be pinned */ public void setPinned(boolean pinned) { if (!isExpanded() || !showPin) { return; } pinFigure.setSelected(pinned); } /** * Displays the given text in the drawer's header as its title. * * @param s The title of the drawer */ public void setTitle(String s) { drawerLabel.setText(s); } /** * Displays the given image in the header as the drawer's icon. * * @param icon The icon for this drawer. */ public void setTitleIcon(Image icon) { drawerLabel.setIcon(icon); } public void showPin(boolean show) { showPin = show; handleExpandStateChanged(); } }