| /******************************************************************************* |
| * Copyright (c) 2000, 2003 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Common Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/cpl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.swt.examples.paint; |
| |
| import org.eclipse.swt.events.*; |
| import org.eclipse.swt.graphics.*; |
| import org.eclipse.swt.widgets.*; |
| |
| /** |
| * Manages a simple drawing surface. |
| */ |
| public class PaintSurface { |
| private Point currentPosition = new Point(0, 0); |
| private Canvas paintCanvas; |
| |
| private PaintSession paintSession; |
| private Image image; |
| private Image paintImage; // buffer for refresh blits |
| private int imageWidth, imageHeight; |
| private int visibleWidth, visibleHeight; |
| |
| private FigureDrawContext displayFDC = new FigureDrawContext(); |
| private FigureDrawContext imageFDC = new FigureDrawContext(); |
| private FigureDrawContext paintFDC = new FigureDrawContext(); |
| |
| /* Rubberband */ |
| private ContainerFigure rubberband = new ContainerFigure(); |
| // the active rubberband selection |
| private int rubberbandHiddenNestingCount = 0; |
| // always >= 0, if > 0 rubberband has been hidden |
| |
| /* Status */ |
| private Text statusText; |
| private String statusActionInfo, statusMessageInfo, statusCoordInfo; |
| |
| /** |
| * Constructs a PaintSurface. |
| * <p> |
| * paintCanvas must have SWT.NO_REDRAW_RESIZE and SWT.NO_BACKGROUND styles, |
| * and may have SWT.V_SCROLL and/or SWT.H_SCROLL. |
| * </p> |
| * @param paintCanvas the Canvas object in which to render |
| * @param paintStatus the PaintStatus object to use for providing user feedback |
| * @param fillColor the color to fill the canvas with initially |
| */ |
| public PaintSurface(Canvas paintCanvas, Text statusText, Color fillColor) { |
| this.paintCanvas = paintCanvas; |
| this.statusText = statusText; |
| clearStatus(); |
| |
| /* Set up the drawing surface */ |
| Rectangle displayRect = paintCanvas.getDisplay().getClientArea(); |
| imageWidth = displayRect.width; |
| imageHeight = displayRect.height; |
| image = new Image(paintCanvas.getDisplay(), imageWidth, imageHeight); |
| |
| imageFDC.gc = new GC(image); |
| imageFDC.gc.setBackground(fillColor); |
| imageFDC.gc.fillRectangle(0, 0, imageWidth, imageHeight); |
| displayFDC.gc = new GC(paintCanvas); |
| |
| /* Initialize the session */ |
| setPaintSession(null); |
| |
| /* Add our listeners */ |
| paintCanvas.addDisposeListener(new DisposeListener() { |
| public void widgetDisposed(DisposeEvent e) { |
| displayFDC.gc.dispose(); |
| } |
| }); |
| paintCanvas.addMouseListener(new MouseAdapter() { |
| public void mouseDown(MouseEvent event) { |
| processMouseEventCoordinates(event); |
| if (paintSession != null) paintSession.mouseDown(event); |
| } |
| public void mouseUp(MouseEvent event) { |
| processMouseEventCoordinates(event); |
| if (paintSession != null) paintSession.mouseUp(event); |
| } |
| public void mouseDoubleClick(MouseEvent event) { |
| processMouseEventCoordinates(event); |
| if (paintSession != null) paintSession.mouseDoubleClick(event); |
| } |
| }); |
| paintCanvas.addMouseMoveListener(new MouseMoveListener() { |
| public void mouseMove(MouseEvent event) { |
| processMouseEventCoordinates(event); |
| if (paintSession != null) paintSession.mouseMove(event); |
| } |
| }); |
| paintCanvas.addPaintListener(new PaintListener() { |
| public void paintControl(PaintEvent event) { |
| if (rubberband.isEmpty()) { |
| // Nothing to merge, so we just refresh |
| event.gc.drawImage(image, |
| displayFDC.xOffset + event.x, displayFDC.yOffset + event.y, event.width, event.height, |
| event.x, event.y, event.width, event.height); |
| } else { |
| /* |
| * Avoid flicker when merging overlayed objects by constructing the image on |
| * a backbuffer first, then blitting it to the screen. |
| */ |
| // Check that the backbuffer is large enough |
| if (paintImage != null) { |
| Rectangle rect = paintImage.getBounds(); |
| if ((event.width + event.x > rect.width) || |
| (event.height + event.y > rect.height)) { |
| paintFDC.gc.dispose(); |
| paintImage.dispose(); |
| paintImage = null; |
| } |
| } |
| if (paintImage == null) { |
| Display display = getDisplay(); |
| Rectangle rect = display.getClientArea(); |
| paintImage = new Image(display, |
| Math.max(rect.width, event.width + event.x), |
| Math.max(rect.height, event.height + event.y)); |
| paintFDC.gc = new GC(paintImage); |
| } |
| // Setup clipping and the FDC |
| Region clipRegion = new Region(); |
| event.gc.getClipping(clipRegion); |
| paintFDC.gc.setClipping(clipRegion); |
| clipRegion.dispose(); |
| |
| paintFDC.xOffset = displayFDC.xOffset; |
| paintFDC.yOffset = displayFDC.yOffset; |
| paintFDC.xScale = displayFDC.xScale; |
| paintFDC.yScale = displayFDC.yScale; |
| |
| // Merge the overlayed objects into the image, then blit |
| paintFDC.gc.drawImage(image, |
| displayFDC.xOffset + event.x, displayFDC.yOffset + event.y, event.width, event.height, |
| event.x, event.y, event.width, event.height); |
| rubberband.draw(paintFDC); |
| event.gc.drawImage(paintImage, |
| event.x, event.y, event.width, event.height, |
| event.x, event.y, event.width, event.height); |
| } |
| } |
| }); |
| paintCanvas.addControlListener(new ControlAdapter() { |
| public void controlResized(ControlEvent event) { |
| handleResize(); |
| } |
| }); |
| |
| /* Set up the paint canvas scroll bars */ |
| ScrollBar horizontal = paintCanvas.getHorizontalBar(); |
| horizontal.setVisible(true); |
| horizontal.addSelectionListener(new SelectionAdapter() { |
| public void widgetSelected(SelectionEvent event) { |
| scrollHorizontally((ScrollBar)event.widget); |
| } |
| }); |
| ScrollBar vertical = paintCanvas.getVerticalBar(); |
| vertical.setVisible(true); |
| vertical.addSelectionListener(new SelectionAdapter() { |
| public void widgetSelected(SelectionEvent event) { |
| scrollVertically((ScrollBar)event.widget); |
| } |
| }); |
| handleResize(); |
| } |
| |
| /** |
| * Disposes of the PaintSurface's resources. |
| */ |
| public void dispose() { |
| imageFDC.gc.dispose(); |
| image.dispose(); |
| if (paintImage != null) { |
| paintImage.dispose(); |
| paintFDC.gc.dispose(); |
| } |
| |
| currentPosition = null; |
| paintCanvas = null; |
| paintSession = null; |
| image = null; |
| paintImage = null; |
| displayFDC = null; |
| imageFDC = null; |
| paintFDC = null; |
| rubberband = null; |
| statusText = null; |
| statusActionInfo = null; |
| statusMessageInfo = null; |
| statusCoordInfo = null; |
| } |
| |
| /** |
| * Called when we must grab focus. |
| */ |
| public void setFocus() { |
| paintCanvas.setFocus(); |
| } |
| |
| /** |
| * Returns the Display on which the PaintSurface resides. |
| * @return the Display |
| */ |
| public Display getDisplay() { |
| return paintCanvas.getDisplay(); |
| } |
| |
| /** |
| * Returns the Shell in which the PaintSurface resides. |
| * @return the Shell |
| */ |
| public Shell getShell() { |
| return paintCanvas.getShell(); |
| } |
| |
| /** |
| * Sets the current paint session. |
| * <p> |
| * If oldPaintSession != paintSession calls oldPaintSession.end() |
| * and paintSession.begin() |
| * </p> |
| * |
| * @param paintSession the paint session to activate; null to disable all sessions |
| */ |
| public void setPaintSession(PaintSession paintSession) { |
| if (this.paintSession != null) { |
| if (this.paintSession == paintSession) return; |
| this.paintSession.endSession(); |
| } |
| this.paintSession = paintSession; |
| clearStatus(); |
| if (paintSession != null) { |
| setStatusAction(paintSession.getDisplayName()); |
| paintSession.beginSession(); |
| } else { |
| setStatusAction(PaintPlugin.getResourceString("tool.Null.label")); |
| setStatusMessage(PaintPlugin.getResourceString("session.Null.message")); |
| } |
| } |
| |
| /** |
| * Returns the current paint session. |
| * |
| * @return the current paint session, null if none is active |
| */ |
| public PaintSession getPaintSession() { |
| return paintSession; |
| } |
| |
| /** |
| * Returns the current paint tool. |
| * |
| * @return the current paint tool, null if none is active (though some other session |
| * might be) |
| */ |
| public PaintTool getPaintTool() { |
| return (paintSession != null && paintSession instanceof PaintTool) ? |
| (PaintTool)paintSession : null; |
| } |
| |
| /** |
| * Returns the current position in an interactive operation. |
| * |
| * @return the last known position of the pointer |
| */ |
| public Point getCurrentPosition() { |
| return currentPosition; |
| } |
| |
| /** |
| * Draws a Figure object to the screen and to the backing store permanently. |
| * |
| * @param object the object to draw onscreen |
| */ |
| public void drawFigure(Figure object) { |
| object.draw(imageFDC); |
| object.draw(displayFDC); |
| } |
| |
| /** |
| * Adds a Figure object to the active rubberband selection. |
| * <p> |
| * This object will be drawn to the screen as a preview and refreshed appropriately |
| * until the selection is either cleared or committed. |
| * </p> |
| * |
| * @param object the object to add to the selection |
| */ |
| public void addRubberbandSelection(Figure object) { |
| rubberband.add(object); |
| if (! isRubberbandHidden()) object.draw(displayFDC); |
| } |
| |
| /** |
| * Clears the active rubberband selection. |
| * <p> |
| * Erases any rubberband objects on the screen then clears the selection. |
| * </p> |
| */ |
| public void clearRubberbandSelection() { |
| if (! isRubberbandHidden()) { |
| Region region = new Region(); |
| rubberband.addDamagedRegion(displayFDC, region); |
| Rectangle r = region.getBounds(); |
| paintCanvas.redraw(r.x, r.y, r.width, r.height, true); |
| region.dispose(); |
| } |
| rubberband.clear(); |
| |
| } |
| |
| /** |
| * Commits the active rubberband selection. |
| * <p> |
| * Redraws any rubberband objects on the screen as permanent objects then clears the selection. |
| * </p> |
| */ |
| public void commitRubberbandSelection() { |
| rubberband.draw(imageFDC); |
| if (isRubberbandHidden()) rubberband.draw(displayFDC); |
| rubberband.clear(); |
| } |
| |
| /** |
| * Hides the rubberband (but does not eliminate it). |
| * <p> |
| * Increments by one the rubberband "hide" nesting count. The rubberband |
| * is hidden from view (but remains active) if it wasn't already hidden. |
| * </p> |
| */ |
| public void hideRubberband() { |
| if (rubberbandHiddenNestingCount++ <= 0) { |
| Region region = new Region(); |
| rubberband.addDamagedRegion(displayFDC, region); |
| Rectangle r = region.getBounds(); |
| paintCanvas.redraw(r.x, r.y, r.width, r.height, true); |
| region.dispose(); |
| } |
| } |
| |
| /** |
| * Shows (un-hides) the rubberband. |
| * <p> |
| * Decrements by one the rubberband "hide" nesting count. The rubberband |
| * is only made visible when showRubberband() has been called once for each |
| * previous hideRubberband(). It is not permitted to call showRubberband() if |
| * the rubber band is not presently hidden. |
| * </p> |
| */ |
| public void showRubberband() { |
| if (rubberbandHiddenNestingCount <= 0) |
| throw new IllegalStateException("rubberbandHiddenNestingCount > 0"); |
| if (--rubberbandHiddenNestingCount == 0) { |
| rubberband.draw(displayFDC); |
| } |
| } |
| |
| /** |
| * Determines if the rubberband is hidden. |
| * |
| * @return true iff the rubber is hidden |
| */ |
| public boolean isRubberbandHidden() { |
| return rubberbandHiddenNestingCount > 0; |
| } |
| |
| /** |
| * Handles a horizontal scroll event |
| * |
| * @param scrollbar the horizontal scroll bar that posted this event |
| */ |
| public void scrollHorizontally(ScrollBar scrollBar) { |
| if (image == null) return; |
| if (imageWidth > visibleWidth) { |
| final int oldOffset = displayFDC.xOffset; |
| final int newOffset = Math.min(scrollBar.getSelection(), imageWidth - visibleWidth); |
| if (oldOffset != newOffset) { |
| paintCanvas.update(); |
| displayFDC.xOffset = newOffset; |
| paintCanvas.scroll(Math.max(oldOffset - newOffset, 0), 0, Math.max(newOffset - oldOffset, 0), 0, |
| visibleWidth, visibleHeight, false); |
| } |
| } |
| } |
| |
| /** |
| * Handles a vertical scroll event |
| * |
| * @param scrollbar the vertical scroll bar that posted this event |
| */ |
| public void scrollVertically(ScrollBar scrollBar) { |
| if (image == null) return; |
| if (imageHeight > visibleHeight) { |
| final int oldOffset = displayFDC.yOffset; |
| final int newOffset = Math.min(scrollBar.getSelection(), imageHeight - visibleHeight); |
| if (oldOffset != newOffset) { |
| paintCanvas.update(); |
| displayFDC.yOffset = newOffset; |
| paintCanvas.scroll(0, Math.max(oldOffset - newOffset, 0), 0, Math.max(newOffset - oldOffset, 0), |
| visibleWidth, visibleHeight, false); |
| } |
| } |
| } |
| |
| /** |
| * Handles resize events |
| */ |
| private void handleResize() { |
| paintCanvas.update(); |
| |
| Rectangle visibleRect = paintCanvas.getClientArea(); |
| visibleWidth = visibleRect.width; |
| visibleHeight = visibleRect.height; |
| |
| ScrollBar horizontal = paintCanvas.getHorizontalBar(); |
| if (horizontal != null) { |
| displayFDC.xOffset = Math.min(horizontal.getSelection(), imageWidth - visibleWidth); |
| if (imageWidth <= visibleWidth) { |
| horizontal.setEnabled(false); |
| horizontal.setSelection(0); |
| } else { |
| final int max = imageWidth - visibleWidth; |
| horizontal.setEnabled(true); |
| horizontal.setValues(displayFDC.xOffset, 0, imageWidth, visibleWidth, |
| 8, visibleWidth); |
| } |
| } |
| |
| ScrollBar vertical = paintCanvas.getVerticalBar(); |
| if (vertical != null) { |
| displayFDC.yOffset = Math.min(vertical.getSelection(), imageHeight - visibleHeight); |
| if (imageHeight <= visibleHeight) { |
| vertical.setEnabled(false); |
| vertical.setSelection(0); |
| } else { |
| final int max = imageHeight - visibleHeight; |
| vertical.setEnabled(true); |
| vertical.setValues(displayFDC.yOffset, 0, imageHeight, visibleHeight, |
| 8, visibleHeight); |
| } |
| } |
| } |
| |
| /** |
| * Virtualizes MouseEvent coordinates and stores the current position. |
| */ |
| private void processMouseEventCoordinates(MouseEvent event) { |
| currentPosition.x = event.x = |
| Math.min(Math.max(event.x, 0), visibleWidth - 1) + displayFDC.xOffset; |
| currentPosition.y = event.y = |
| Math.min(Math.max(event.y, 0), visibleHeight - 1) + displayFDC.yOffset; |
| } |
| |
| /** |
| * Clears the status bar. |
| */ |
| public void clearStatus() { |
| statusActionInfo = ""; |
| statusMessageInfo = ""; |
| statusCoordInfo = ""; |
| updateStatus(); |
| } |
| |
| /** |
| * Sets the status bar action text. |
| * |
| * @param action the action in progress, null to clear |
| */ |
| public void setStatusAction(String action) { |
| statusActionInfo = (action != null) ? action : ""; |
| updateStatus(); |
| } |
| |
| /** |
| * Sets the status bar message text. |
| * |
| * @param message the message to display, null to clear |
| */ |
| public void setStatusMessage(String message) { |
| statusMessageInfo = (message != null) ? message : ""; |
| updateStatus(); |
| } |
| |
| /** |
| * Sets the coordinates in the status bar. |
| * |
| * @param coord the coordinates to display, null to clear |
| */ |
| public void setStatusCoord(Point coord) { |
| statusCoordInfo = (coord != null) ? PaintPlugin.getResourceString("status.Coord.format", new Object[] |
| { new Integer(coord.x), new Integer(coord.y)}) : ""; |
| updateStatus(); |
| } |
| |
| /** |
| * Sets the coordinate range in the status bar. |
| * |
| * @param a the "from" coordinate, must not be null |
| * @param b the "to" coordinate, must not be null |
| */ |
| public void setStatusCoordRange(Point a, Point b) { |
| statusCoordInfo = PaintPlugin.getResourceString("status.CoordRange.format", new Object[] |
| { new Integer(a.x), new Integer(a.y), new Integer(b.x), new Integer(b.y)}); |
| updateStatus(); |
| } |
| |
| /** |
| * Updates the display. |
| */ |
| private void updateStatus() { |
| statusText.setText( |
| PaintPlugin.getResourceString("status.Bar.format", new Object[] |
| { statusActionInfo, statusMessageInfo, statusCoordInfo })); |
| } |
| } |