| /******************************************************************************* |
| * Copyright (c) 2003, 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.ui.rulers; |
| |
| import java.beans.PropertyChangeEvent; |
| import java.beans.PropertyChangeListener; |
| import java.util.ArrayList; |
| |
| import org.eclipse.swt.events.DisposeEvent; |
| import org.eclipse.swt.events.DisposeListener; |
| import org.eclipse.swt.events.KeyEvent; |
| import org.eclipse.swt.graphics.Font; |
| import org.eclipse.swt.graphics.FontData; |
| import org.eclipse.swt.graphics.Rectangle; |
| import org.eclipse.swt.widgets.Canvas; |
| import org.eclipse.swt.widgets.Composite; |
| import org.eclipse.swt.widgets.Display; |
| import org.eclipse.swt.widgets.Event; |
| import org.eclipse.swt.widgets.Listener; |
| import org.eclipse.swt.widgets.ScrollBar; |
| |
| import org.eclipse.core.runtime.Assert; |
| |
| import org.eclipse.draw2d.AbstractBorder; |
| import org.eclipse.draw2d.ColorConstants; |
| import org.eclipse.draw2d.DefaultRangeModel; |
| import org.eclipse.draw2d.FigureCanvas; |
| import org.eclipse.draw2d.Graphics; |
| import org.eclipse.draw2d.IFigure; |
| import org.eclipse.draw2d.PositionConstants; |
| import org.eclipse.draw2d.RangeModel; |
| import org.eclipse.draw2d.Viewport; |
| import org.eclipse.draw2d.geometry.Insets; |
| import org.eclipse.draw2d.rap.swt.SWT; |
| |
| import org.eclipse.gef.DragTracker; |
| import org.eclipse.gef.EditDomain; |
| import org.eclipse.gef.EditPart; |
| import org.eclipse.gef.GraphicalEditPart; |
| import org.eclipse.gef.GraphicalViewer; |
| import org.eclipse.gef.Handle; |
| import org.eclipse.gef.RootEditPart; |
| import org.eclipse.gef.internal.ui.rulers.GuideEditPart; |
| import org.eclipse.gef.internal.ui.rulers.RulerContextMenuProvider; |
| import org.eclipse.gef.internal.ui.rulers.RulerEditPart; |
| import org.eclipse.gef.internal.ui.rulers.RulerEditPartFactory; |
| import org.eclipse.gef.internal.ui.rulers.RulerRootEditPart; |
| import org.eclipse.gef.rulers.RulerProvider; |
| import org.eclipse.gef.ui.parts.GraphicalViewerKeyHandler; |
| import org.eclipse.gef.ui.parts.ScrollingGraphicalViewer; |
| |
| /** |
| * A RulerComposite is used to show rulers to the north and west of the control |
| * of a given {@link #setGraphicalViewer(ScrollingGraphicalViewer) graphical |
| * viewer}. The rulers will be shown based on whether or not |
| * {@link org.eclipse.gef.rulers.RulerProvider#PROPERTY_HORIZONTAL_RULER |
| * horizontal ruler} and |
| * {@link org.eclipse.gef.rulers.RulerProvider#PROPERTY_VERTICAL_RULER vertical |
| * ruler} properties are set on the given viewer, and the value of the |
| * {@link org.eclipse.gef.rulers.RulerProvider#PROPERTY_RULER_VISIBILITY |
| * visibility} property. |
| * |
| * @author Pratik Shah |
| * @since 3.0 |
| */ |
| public class RulerComposite extends Composite { |
| |
| private EditDomain rulerEditDomain; |
| private GraphicalViewer left, top; |
| private FigureCanvas editor; |
| private GraphicalViewer diagramViewer; |
| private Font font; |
| private Listener layoutListener; |
| private PropertyChangeListener propertyListener; |
| private boolean layingOut = false; |
| private boolean isRulerVisible = true; |
| private boolean needToLayout = false; |
| private Runnable runnable = new Runnable() { |
| public void run() { |
| layout(false); |
| } |
| }; |
| |
| /** |
| * Constructor |
| * |
| * @param parent |
| * a widget which will be the parent of the new instance (cannot |
| * be null) |
| * @param style |
| * the style of widget to construct |
| * @see Composite#Composite(org.eclipse.swt.widgets.Composite, int) |
| */ |
| public RulerComposite(Composite parent, int style) { |
| super(parent, style); |
| addDisposeListener(new DisposeListener() { |
| public void widgetDisposed(DisposeEvent e) { |
| disposeResources(); |
| } |
| }); |
| } |
| |
| /** |
| * Calculates the proper trim. Includes scrollbars' sizes only if they're |
| * visible. |
| * |
| * @param canvas |
| * The canvas. |
| * @since 3.6 |
| */ |
| public static Rectangle calculateEditorTrim(Composite canvas) { |
| /* |
| * Workaround for Bug# 87712 Calculating the trim using the clientArea. |
| */ |
| Rectangle bounds = canvas.getBounds(); |
| Rectangle clientArea = canvas.getClientArea(); |
| Rectangle result = new Rectangle(0, 0, bounds.width - clientArea.width, |
| bounds.height - clientArea.height); |
| if (result.width != 0 || result.height != 0) { |
| Rectangle trim = canvas.computeTrim(0, 0, 0, 0); |
| result.x = result.height == 0 ? 0 : trim.x; |
| result.y = result.width == 0 ? 0 : trim.y; |
| } |
| return result; |
| } |
| |
| /** |
| * Calculates the proper trim for the ruler. |
| * |
| * @param canvas |
| * The canvas. |
| * @since 3.6 |
| */ |
| public static Rectangle calculateRulerTrim(Composite canvas) { |
| if ("carbon".equals(SWT.getPlatform())) { //$NON-NLS-1$ |
| Rectangle trim = canvas.computeTrim(0, 0, 0, 0); |
| trim.width = 0 - trim.x * 2; |
| trim.height = 0 - trim.y * 2; |
| return trim; |
| } |
| return new Rectangle(0, 0, 0, 0); |
| } |
| |
| private GraphicalViewer createRulerContainer(int orientation) { |
| ScrollingGraphicalViewer viewer = new RulerViewer(); |
| final boolean isHorizontal = orientation == PositionConstants.NORTH |
| || orientation == PositionConstants.SOUTH; |
| |
| // Finish initializing the viewer |
| viewer.setRootEditPart(new RulerRootEditPart(isHorizontal)); |
| viewer.setEditPartFactory(new RulerEditPartFactory(diagramViewer)); |
| viewer.createControl(this); |
| ((GraphicalEditPart) viewer.getRootEditPart()).getFigure().setBorder( |
| new RulerBorder(isHorizontal)); |
| viewer.setProperty(GraphicalViewer.class.toString(), diagramViewer); |
| |
| // Configure the viewer's control |
| FigureCanvas canvas = (FigureCanvas) viewer.getControl(); |
| canvas.setScrollBarVisibility(FigureCanvas.NEVER); |
| if (font == null) { |
| FontData[] data = canvas.getFont().getFontData(); |
| for (int i = 0; i < data.length; i++) { |
| data[i].setHeight(data[i].getHeight() - 1); |
| } |
| font = new Font(Display.getCurrent(), data); |
| } |
| canvas.setFont(font); |
| if (isHorizontal) { |
| canvas.getViewport().setHorizontalRangeModel( |
| editor.getViewport().getHorizontalRangeModel()); |
| } else { |
| canvas.getViewport().setVerticalRangeModel( |
| editor.getViewport().getVerticalRangeModel()); |
| } |
| |
| // Add the viewer to the rulerEditDomain |
| if (rulerEditDomain == null) { |
| rulerEditDomain = new EditDomain(); |
| rulerEditDomain.setCommandStack(diagramViewer.getEditDomain() |
| .getCommandStack()); |
| } |
| rulerEditDomain.addViewer(viewer); |
| |
| return viewer; |
| } |
| |
| private void disposeResources() { |
| if (diagramViewer != null) |
| diagramViewer.removePropertyChangeListener(propertyListener); |
| if (font != null) |
| font.dispose(); |
| // layoutListener is not being removed from the scroll bars because they |
| // are already |
| // disposed at this point. |
| } |
| |
| private void disposeRulerViewer(GraphicalViewer viewer) { |
| if (viewer == null) |
| return; |
| /* |
| * There's a tie from the editor's range model to the RulerViewport (via |
| * a listener) to the RulerRootEditPart to the RulerViewer. Break this |
| * tie so that the viewer doesn't leak and can be garbage collected. |
| */ |
| RangeModel rModel = new DefaultRangeModel(); |
| Viewport port = ((FigureCanvas) viewer.getControl()).getViewport(); |
| port.setHorizontalRangeModel(rModel); |
| port.setVerticalRangeModel(rModel); |
| rulerEditDomain.removeViewer(viewer); |
| viewer.getControl().dispose(); |
| } |
| |
| /** |
| * Perform the ruler layout. |
| * |
| * @since 3.6 |
| */ |
| public void doLayout() { |
| if (left == null && top == null) { |
| Rectangle area = getClientArea(); |
| if (!editor.getBounds().equals(area)) |
| editor.setBounds(area); |
| return; |
| } |
| |
| int leftWidth = 0, topHeight = 0; |
| Rectangle leftTrim = null, topTrim = null; |
| if (left != null) { |
| leftTrim = calculateRulerTrim((Canvas) left.getControl()); |
| // Adding the trim width here because FigureCanvas#computeSize() |
| // does not |
| leftWidth = left.getControl().computeSize(SWT.DEFAULT, SWT.DEFAULT).x |
| + leftTrim.width; |
| } |
| if (top != null) { |
| topTrim = calculateRulerTrim((Canvas) top.getControl()); |
| topHeight = top.getControl().computeSize(SWT.DEFAULT, SWT.DEFAULT).y |
| + topTrim.height; |
| } |
| |
| Rectangle editorSize = getClientArea(); |
| editorSize.x = leftWidth; |
| editorSize.y = topHeight; |
| editorSize.width -= leftWidth; |
| editorSize.height -= topHeight; |
| editor.setBounds(editorSize); |
| |
| /* |
| * Fix for Bug# 67554 Take trim into account. Some platforms (such as |
| * MacOS and Motif) leave some trimming around some canvasses. |
| */ |
| Rectangle trim = calculateEditorTrim(editor); |
| if (left != null) { |
| // The - 1 and + 1 are to compensate for the RulerBorder |
| left.getControl().setBounds(0, topHeight - trim.x + leftTrim.x - 1, |
| leftWidth, |
| editorSize.height - trim.height + leftTrim.height + 1); |
| } |
| if (top != null) { |
| top.getControl().setBounds(leftWidth - trim.y + topTrim.y - 1, 0, |
| editorSize.width - trim.width + topTrim.width + 1, |
| topHeight); |
| } |
| } |
| |
| private GraphicalViewer getRulerContainer(int orientation) { |
| GraphicalViewer result = null; |
| switch (orientation) { |
| case PositionConstants.NORTH: |
| result = top; |
| break; |
| case PositionConstants.WEST: |
| result = left; |
| } |
| return result; |
| } |
| |
| /** |
| * @see org.eclipse.swt.widgets.Composite#layout(boolean) |
| */ |
| public void layout(boolean change) { |
| if (!layingOut && !isDisposed()) { |
| checkWidget(); |
| if (change || needToLayout) { |
| needToLayout = false; |
| layingOut = true; |
| doLayout(); |
| layingOut = false; |
| } |
| } |
| } |
| |
| /** |
| * Creates rulers for the given graphical viewer. |
| * <p> |
| * The primaryViewer or its Control cannot be <code>null</code>. The |
| * primaryViewer's Control should be a FigureCanvas and a child of this |
| * Composite. This method should only be invoked once. |
| * <p> |
| * To create ruler(s), simply add the RulerProvider(s) (with the right key: |
| * RulerProvider.PROPERTY_HORIZONTAL_RULER or |
| * RulerProvider.PROPERTY_VERTICAL_RULER) as a property on the given viewer. |
| * It can be done after this method is invoked. |
| * RulerProvider.PROPERTY_RULER_VISIBILITY can be used to show/hide the |
| * rulers. |
| * |
| * @param primaryViewer |
| * The graphical viewer for which the rulers have to be created |
| */ |
| public void setGraphicalViewer(ScrollingGraphicalViewer primaryViewer) { |
| // pre-conditions |
| Assert.isNotNull(primaryViewer); |
| Assert.isNotNull(primaryViewer.getControl()); |
| Assert.isTrue(diagramViewer == null); |
| |
| diagramViewer = primaryViewer; |
| editor = (FigureCanvas) diagramViewer.getControl(); |
| |
| // layout whenever the scrollbars are shown or hidden, and whenever the |
| // RulerComposite |
| // is resized |
| layoutListener = new Listener() { |
| public void handleEvent(Event event) { |
| // @TODO:Pratik If you use Display.asyncExec(runnable) here, |
| // some flashing |
| // occurs. You can see it when the palette is in the editor, and |
| // you hit |
| // the button to show/hide it. |
| layout(true); |
| } |
| }; |
| addListener(SWT.Resize, layoutListener); |
| ScrollBar horizontalBar = editor.getHorizontalBar(); |
| if (horizontalBar != null) { |
| horizontalBar.addListener(SWT.Show, layoutListener); |
| horizontalBar.addListener(SWT.Hide, layoutListener); |
| } |
| ScrollBar verticalBar = editor.getVerticalBar(); |
| if (verticalBar != null) { |
| verticalBar.addListener(SWT.Show, layoutListener); |
| verticalBar.addListener(SWT.Hide, layoutListener); |
| } |
| |
| propertyListener = new PropertyChangeListener() { |
| public void propertyChange(PropertyChangeEvent evt) { |
| String property = evt.getPropertyName(); |
| if (RulerProvider.PROPERTY_HORIZONTAL_RULER.equals(property)) { |
| setRuler( |
| (RulerProvider) diagramViewer |
| .getProperty(RulerProvider.PROPERTY_HORIZONTAL_RULER), |
| PositionConstants.NORTH); |
| } else if (RulerProvider.PROPERTY_VERTICAL_RULER |
| .equals(property)) { |
| setRuler( |
| (RulerProvider) diagramViewer |
| .getProperty(RulerProvider.PROPERTY_VERTICAL_RULER), |
| PositionConstants.WEST); |
| } else if (RulerProvider.PROPERTY_RULER_VISIBILITY |
| .equals(property)) |
| setRulerVisibility(((Boolean) diagramViewer |
| .getProperty(RulerProvider.PROPERTY_RULER_VISIBILITY)) |
| .booleanValue()); |
| } |
| }; |
| diagramViewer.addPropertyChangeListener(propertyListener); |
| Boolean rulerVisibility = (Boolean) diagramViewer |
| .getProperty(RulerProvider.PROPERTY_RULER_VISIBILITY); |
| if (rulerVisibility != null) |
| setRulerVisibility(rulerVisibility.booleanValue()); |
| setRuler( |
| (RulerProvider) diagramViewer |
| .getProperty(RulerProvider.PROPERTY_HORIZONTAL_RULER), |
| PositionConstants.NORTH); |
| setRuler( |
| (RulerProvider) diagramViewer |
| .getProperty(RulerProvider.PROPERTY_VERTICAL_RULER), |
| PositionConstants.WEST); |
| } |
| |
| private void setRuler(RulerProvider provider, int orientation) { |
| Object ruler = null; |
| if (isRulerVisible && provider != null) |
| // provider.getRuler() might return null (at least the API does not |
| // prevent that) |
| ruler = provider.getRuler(); |
| |
| if (ruler == null) { |
| // Ruler is not visible or is not present |
| setRulerContainer(null, orientation); |
| // Layout right-away to prevent an empty control from showing |
| layout(true); |
| return; |
| } |
| |
| GraphicalViewer container = getRulerContainer(orientation); |
| if (container == null) { |
| container = createRulerContainer(orientation); |
| setRulerContainer(container, orientation); |
| } |
| if (container.getContents() != ruler) { |
| container.setContents(ruler); |
| needToLayout = true; |
| Display.getCurrent().asyncExec(runnable); |
| } |
| } |
| |
| private void setRulerContainer(GraphicalViewer container, int orientation) { |
| if (orientation == PositionConstants.NORTH) { |
| if (top == container) |
| return; |
| disposeRulerViewer(top); |
| top = container; |
| } else if (orientation == PositionConstants.WEST) { |
| if (left == container) |
| return; |
| disposeRulerViewer(left); |
| left = container; |
| } |
| } |
| |
| private void setRulerVisibility(boolean isVisible) { |
| if (isRulerVisible != isVisible) { |
| isRulerVisible = isVisible; |
| if (diagramViewer != null) { |
| setRuler( |
| (RulerProvider) diagramViewer |
| .getProperty(RulerProvider.PROPERTY_HORIZONTAL_RULER), |
| PositionConstants.NORTH); |
| setRuler( |
| (RulerProvider) diagramViewer |
| .getProperty(RulerProvider.PROPERTY_VERTICAL_RULER), |
| PositionConstants.WEST); |
| } |
| } |
| } |
| |
| private static class RulerBorder extends AbstractBorder { |
| private static final Insets H_INSETS = new Insets(0, 1, 0, 0); |
| private static final Insets V_INSETS = new Insets(1, 0, 0, 0); |
| private boolean horizontal; |
| |
| /** |
| * Constructor |
| * |
| * @param isHorizontal |
| * whether or not the ruler being bordered is horizontal or |
| * not |
| */ |
| public RulerBorder(boolean isHorizontal) { |
| horizontal = isHorizontal; |
| } |
| |
| /** |
| * @see org.eclipse.draw2d.Border#getInsets(org.eclipse.draw2d.IFigure) |
| */ |
| public Insets getInsets(IFigure figure) { |
| return horizontal ? H_INSETS : V_INSETS; |
| } |
| |
| /** |
| * @see org.eclipse.draw2d.Border#paint(org.eclipse.draw2d.IFigure, |
| * org.eclipse.draw2d.Graphics, org.eclipse.draw2d.geometry.Insets) |
| */ |
| public void paint(IFigure figure, Graphics graphics, Insets insets) { |
| graphics.setForegroundColor(ColorConstants.buttonDarker); |
| if (horizontal) { |
| graphics.drawLine( |
| figure.getBounds().getTopLeft(), |
| figure.getBounds() |
| .getBottomLeft() |
| .translate( |
| new org.eclipse.draw2d.geometry.Point( |
| 0, -4))); |
| } else { |
| graphics.drawLine( |
| figure.getBounds().getTopLeft(), |
| figure.getBounds() |
| .getTopRight() |
| .translate( |
| new org.eclipse.draw2d.geometry.Point( |
| -4, 0))); |
| } |
| } |
| } |
| |
| /** |
| * Custom graphical viewer intended to be used for rulers. |
| * |
| * @author Pratik Shah |
| * @since 3.0 |
| */ |
| private static class RulerViewer extends ScrollingGraphicalViewer { |
| /** |
| * Constructor |
| */ |
| public RulerViewer() { |
| super(); |
| init(); |
| } |
| |
| /** |
| * @see org.eclipse.gef.EditPartViewer#appendSelection(org.eclipse.gef.EditPart) |
| */ |
| public void appendSelection(EditPart editpart) { |
| if (editpart instanceof RootEditPart) |
| editpart = ((RootEditPart) editpart).getContents(); |
| setFocus(editpart); |
| super.appendSelection(editpart); |
| } |
| |
| /** |
| * @see org.eclipse.gef.GraphicalViewer#findHandleAt(org.eclipse.draw2d.geometry.Point) |
| */ |
| public Handle findHandleAt(org.eclipse.draw2d.geometry.Point p) { |
| final GraphicalEditPart gep = (GraphicalEditPart) findObjectAtExcluding( |
| p, new ArrayList()); |
| if (gep == null || !(gep instanceof GuideEditPart)) |
| return null; |
| return new Handle() { |
| public DragTracker getDragTracker() { |
| return ((GuideEditPart) gep).getDragTracker(null); |
| } |
| |
| public org.eclipse.draw2d.geometry.Point getAccessibleLocation() { |
| return null; |
| } |
| }; |
| } |
| |
| /** |
| * @see org.eclipse.gef.ui.parts.AbstractEditPartViewer#init() |
| */ |
| protected void init() { |
| setContextMenu(new RulerContextMenuProvider(this)); |
| setKeyHandler(new RulerKeyHandler(this)); |
| } |
| |
| /** |
| * Requests to reveal a ruler are ignored since that causes undesired |
| * scrolling to the origin of the ruler |
| * |
| * @see org.eclipse.gef.EditPartViewer#reveal(org.eclipse.gef.EditPart) |
| */ |
| public void reveal(EditPart part) { |
| if (part != getContents()) |
| super.reveal(part); |
| } |
| |
| /** |
| * @see org.eclipse.gef.EditPartViewer#setContents(org.eclipse.gef.EditPart) |
| */ |
| public void setContents(EditPart editpart) { |
| super.setContents(editpart); |
| setFocus(getContents()); |
| } |
| |
| /** |
| * Custom KeyHandler intended to be used with a RulerViewer |
| * |
| * @author Pratik Shah |
| * @since 3.0 |
| */ |
| protected static class RulerKeyHandler extends |
| GraphicalViewerKeyHandler { |
| /** |
| * Constructor |
| * |
| * @param viewer |
| * The viewer for which this handler processes keyboard |
| * input |
| */ |
| public RulerKeyHandler(GraphicalViewer viewer) { |
| super(viewer); |
| } |
| |
| /** |
| * @see org.eclipse.gef.KeyHandler#keyPressed(org.eclipse.swt.events.KeyEvent) |
| */ |
| public boolean keyPressed(KeyEvent event) { |
| if (event.keyCode == SWT.DEL) { |
| // If a guide has focus, delete it |
| if (getFocusEditPart() instanceof GuideEditPart) { |
| RulerEditPart parent = (RulerEditPart) getFocusEditPart() |
| .getParent(); |
| getViewer() |
| .getEditDomain() |
| .getCommandStack() |
| .execute( |
| parent.getRulerProvider() |
| .getDeleteGuideCommand( |
| getFocusEditPart() |
| .getModel())); |
| event.doit = false; |
| return true; |
| } |
| return false; |
| } else if (((event.stateMask & SWT.ALT) != 0) |
| && (event.keyCode == SWT.ARROW_UP)) { |
| // ALT + UP_ARROW pressed |
| // If a guide has focus, give focus to the ruler |
| EditPart parent = getFocusEditPart().getParent(); |
| if (parent instanceof RulerEditPart) |
| navigateTo(getFocusEditPart().getParent(), event); |
| return true; |
| } |
| return super.keyPressed(event); |
| } |
| } |
| } |
| |
| /** |
| * Retrieve the left ruler graphical viewer. |
| * |
| * @return The left ruler graphical viewer. |
| * @since 3.6 |
| */ |
| protected GraphicalViewer getLeft() { |
| return left; |
| } |
| |
| /** |
| * Retrieve the top ruler graphical viewer. |
| * |
| * @return The top ruler graphical viewer. |
| * @since 3.6 |
| */ |
| protected GraphicalViewer getTop() { |
| return top; |
| } |
| |
| /** |
| * Retrieve the editor figure canvas. |
| * |
| * @return The editor figure canvas. |
| * @since 3.6 |
| */ |
| protected FigureCanvas getEditor() { |
| return editor; |
| } |
| } |