package com.indago.iddea.view.display; import edu.umd.cs.findbugs.annotations.Nullable; import java.awt.Color; import java.awt.Cursor; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Insets; import java.awt.Paint; import java.awt.Point; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.Shape; import java.awt.TexturePaint; import java.awt.geom.AffineTransform; import java.awt.geom.NoninvertibleTransformException; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.util.*; import java.awt.event.*; import java.awt.image.BufferedImage; import javax.swing.*; import javax.swing.undo.*; import org.jhotdraw.util.*; import org.jhotdraw.draw.AttributeKey; import org.jhotdraw.draw.Constrainer; import org.jhotdraw.draw.DefaultDrawingViewTransferHandler; import org.jhotdraw.draw.Drawing; import org.jhotdraw.draw.DrawingEditor; import org.jhotdraw.draw.DrawingView; import org.jhotdraw.draw.Figure; import org.jhotdraw.draw.GridConstrainer; import org.jhotdraw.draw.event.FigureSelectionEvent; import org.jhotdraw.draw.event.FigureSelectionListener; import org.jhotdraw.draw.handle.Handle; import org.jhotdraw.draw.event.HandleListener; import org.jhotdraw.draw.event.HandleEvent; import org.jhotdraw.draw.event.FigureListener; import org.jhotdraw.draw.event.FigureAdapter; import org.jhotdraw.draw.event.FigureEvent; import org.jhotdraw.draw.event.CompositeFigureListener; import org.jhotdraw.draw.event.CompositeFigureEvent; import org.jhotdraw.gui.EditableComponent; import static org.jhotdraw.draw.AttributeKeys.*; /** * InteractiveDrawingView provides modified drawing functions rather than jhotdraw's * DefaultDrawingView. * * @version 0.1beta * @since 8/12/13 5:08 PM * @author HongKee Moon */ public abstract class InteractiveDrawingView extends JComponent implements DrawingView, EditableComponent { /** * Set this to true to turn on debugging output on System.out. */ private final static boolean DEBUG = false; @Nullable protected Drawing drawing; /** * Holds the selected figures in an ordered put. The ordering reflects * the sequence that was used to select the figures. */ private Set<Figure> selectedFigures = new LinkedHashSet<Figure>(); private LinkedList<Handle> selectionHandles = new LinkedList<Handle>(); private boolean isConstrainerVisible = false; private Constrainer visibleConstrainer = new GridConstrainer(8, 8); private Constrainer invisibleConstrainer = new GridConstrainer(); private Handle secondaryHandleOwner; @Nullable private Handle activeHandle; private LinkedList<Handle> secondaryHandles = new LinkedList<Handle>(); private boolean handlesAreValid = true; @Nullable private transient Dimension cachedPreferredSize; private int detailLevel; @Nullable protected DrawingEditor editor; protected JLabel emptyDrawingLabel; protected BufferedImage backgroundTile; private FigureListener handleInvalidator = new FigureAdapter() { @Override public void figureHandlesChanged(FigureEvent e) { invalidateHandles(); } }; @Nullable private transient Rectangle2D.Double cachedDrawingArea; public final static String DRAWING_DOUBLE_BUFFERED_PROPERTY = "drawingDoubleBuffered"; /** Whether the drawing is double buffered*/ private boolean isDrawingDoubleBuffered = true; private boolean paintEnabled = true; public final static boolean isWindows; /** * The {@link AffineTransform} stores the previous transform to restore in the next transformation. */ //private AffineTransform preTransform = new AffineTransform(0.7775, 0.0, 0.0, 0.7775, 0.0, 66.75); protected AffineTransform preTransform = new AffineTransform(); static { boolean b = false; try { if (System.getProperty("os.name").toLowerCase().startsWith("win")) { b = true; } } catch (Throwable t) { } isWindows = b; } public void copyFrom(InteractiveDrawingView view) { this.setDrawing(view.getDrawing()); this.addToSelection(view.getSelectedFigures()); this.setSelectedFigures(view.getSelectedFigures()); this.setSelectionHandles(view.getSelectionHandles()); this.setSecondarySelectionHandles(view.getSecondaryHandles()); } private void setSelectedFigures(Set<Figure> figures) { selectedFigures = new LinkedHashSet<Figure>(); for(Figure f: figures) selectedFigures.add(f); } private void setSelectionHandles(List<Handle> handles) { selectionHandles = new LinkedList<Handle>(); for(Handle h : handles) selectionHandles.add(h); } private void setSecondarySelectionHandles(List<Handle> handles) { secondaryHandles = new LinkedList<Handle>(); for(Handle h : handles) secondaryHandles.add(h); } public Set<Figure> getAllFigures() { Set<Figure> allFigures = new HashSet<Figure>(); for (Figure figure : drawing.getChildren()) { if (figure.isSelectable()) { allFigures.add(figure); } } return allFigures; } @Override public void repaintHandles() { validateHandles(); Rectangle r = null; for (Handle h : getSelectionHandles()) { if (r == null) { r = h.getDrawingArea(); } else { r.add(h.getDrawingArea()); } } for (Handle h : getSecondaryHandles()) { if (r == null) { r = h.getDrawingArea(); } else { r.add(h.getDrawingArea()); } } if (r != null) { repaint(r); } } @Override public boolean isSelectionEmpty() { return selectedFigures.isEmpty(); } private class EventHandler implements FigureListener, CompositeFigureListener, HandleListener, FocusListener { @Override public void figureAdded(CompositeFigureEvent evt) { if (drawing.getChildCount() == 1 && getEmptyDrawingMessage() != null) { repaint(); } else { repaintDrawingArea(evt.getInvalidatedArea()); } invalidateDimension(); } @Override public void figureRemoved(CompositeFigureEvent evt) { if (drawing.getChildCount() == 0 && getEmptyDrawingMessage() != null) { repaint(); } else { repaintDrawingArea(evt.getInvalidatedArea()); } removeFromSelection(evt.getChildFigure()); invalidateDimension(); } @Override public void areaInvalidated(FigureEvent evt) { repaintDrawingArea(evt.getInvalidatedArea()); invalidateDimension(); } @Override public void areaInvalidated(HandleEvent evt) { repaint(evt.getInvalidatedArea()); invalidateDimension(); } @Override public void handleRequestSecondaryHandles(HandleEvent e) { secondaryHandleOwner = e.getHandle(); secondaryHandles.clear(); secondaryHandles.addAll(secondaryHandleOwner.createSecondaryHandles()); for (Handle h : secondaryHandles) { h.setView(InteractiveDrawingView.this); h.addHandleListener(eventHandler); } repaint(); } @Override public void focusGained(FocusEvent e) { // repaintHandles(); if (editor != null) { editor.setActiveView(InteractiveDrawingView.this); } } @Override public void focusLost(FocusEvent e) { // repaintHandles(); } @Override public void handleRequestRemove(HandleEvent e) { selectionHandles.remove(e.getHandle()); e.getHandle().dispose(); invalidateHandles(); repaint(e.getInvalidatedArea()); } @Override public void attributeChanged(FigureEvent e) { if (e.getSource() == drawing) { AttributeKey a = e.getAttribute(); if (a.equals(CANVAS_HEIGHT) || a.equals(CANVAS_WIDTH)) { validateViewTranslation(); repaint(); // must repaint everything } if (e.getInvalidatedArea() != null) { repaintDrawingArea(e.getInvalidatedArea()); } else { repaintDrawingArea(viewToDrawing(getCanvasViewBounds())); } } else { if (e.getInvalidatedArea() != null) { repaintDrawingArea(e.getInvalidatedArea()); } } } @Override public void figureHandlesChanged(FigureEvent e) { } @Override public void figureChanged(FigureEvent e) { repaintDrawingArea(e.getInvalidatedArea()); } @Override public void figureAdded(FigureEvent e) { } @Override public void figureRemoved(FigureEvent e) { } @Override public void figureRequestRemove(FigureEvent e) { } } private EventHandler eventHandler; /** Creates new instance. */ public InteractiveDrawingView() { initComponents(); eventHandler = createEventHandler(); setToolTipText("dummy"); // Set a dummy tool tip text to turn tooltips on setFocusable(true); addFocusListener(eventHandler); setTransferHandler(new DefaultDrawingViewTransferHandler()); setBackground(new Color(0xb0b0b0)); setOpaque(true); } protected EventHandler createEventHandler() { return new EventHandler(); } /** This method is called from within the constructor to * initialize the form.<p> * WARNING: Do NOT modify this code. The content of this method is * always regenerated by the Form Editor.<p> * NOTE: To prevent undesired layout effects when using floating * text fields, the DefaultDrawingView must not use a layout manager. */ // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents private void initComponents() { setLayout(null); }// </editor-fold>//GEN-END:initComponents @Override @Nullable public Drawing getDrawing() { return drawing; } @Override public String getToolTipText(MouseEvent evt) { if (getEditor() != null && getEditor().getTool() != null) { return getEditor().getTool().getToolTipText(this, evt); } return null; } public void setEmptyDrawingMessage(String newValue) { String oldValue = (emptyDrawingLabel == null) ? null : emptyDrawingLabel.getText(); if (newValue == null) { emptyDrawingLabel = null; } else { emptyDrawingLabel = new JLabel(newValue); emptyDrawingLabel.setHorizontalAlignment(JLabel.CENTER); } firePropertyChange("emptyDrawingMessage", oldValue, newValue); repaint(); } public String getEmptyDrawingMessage() { return (emptyDrawingLabel == null) ? null : emptyDrawingLabel.getText(); } public abstract void drawImage(Graphics2D g); /** * Paints the drawing view. * Uses rendering hints for fast painting. Paints the canvasColor, the * grid, the drawing, the handles and the current tool. */ @Override public void paintComponent(Graphics gr) { Graphics2D g = (Graphics2D) gr; setViewRenderingHints(g); drawImage(g); drawCanvas(g); drawConstrainer(g); drawDrawing(g); drawHandles(g); drawTool(g); } /** * Prints the drawing view. * Uses high quality rendering hints for printing. Only prints the drawing. * Doesn't print the canvasColor, the grid, the handles and the tool. */ @Override public void printComponent(Graphics gr) { Graphics2D g = (Graphics2D) gr; // Set rendering hints for quality g.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY); g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_NORMALIZE); g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON); g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); drawDrawing(g); } protected void setViewRenderingHints(Graphics2D g) { // Set rendering hints for speed g.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY); g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_NORMALIZE); g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON); g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR); g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED); g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); } /** * Returns the bounds of the canvas on the drawing view. * * @return The current bounds of the canvas on the drawing view. */ protected Rectangle getCanvasViewBounds() { // Position of the zero coordinate point on the view // int x = -translation.x; // int y = -translation.y; int x = 1; int y = 1; int w = getWidth(); int h = getHeight(); if (getDrawing() != null) { Double cw = getDrawing().get(CANVAS_WIDTH); Double ch = getDrawing().get(CANVAS_HEIGHT); if (cw != null && ch != null) { Point lowerRight = drawingToView( new Point2D.Double(cw, ch)); w = lowerRight.x - x; h = lowerRight.y - y; } } return new Rectangle(x, y, w, h); } /** Draws the canvas. If the {@code AttributeKeys.CANVAS_FILL_OPACITY} is * not fully opaque, the canvas area is filled with the background paint * before the {@code AttributeKeys.CANVAS_FILL_COLOR} is drawn. */ protected void drawCanvas(Graphics2D gr) { if (drawing != null) { Graphics2D g = (Graphics2D) gr.create(); AffineTransform tx = g.getTransform(); tx.concatenate(preTransform); g.setTransform(preTransform); drawing.setFontRenderContext(g.getFontRenderContext()); drawing.drawCanvas(g); g.dispose(); } } protected void drawConstrainer(Graphics2D g) { Shape clip = g.getClip(); Rectangle r = getCanvasViewBounds(); g.clipRect(r.x, r.y, r.width, r.height); getConstrainer().draw(g, this); g.setClip(clip); } protected void drawDrawing(Graphics2D gr) { if (drawing != null) { if (drawing.getChildCount() == 0 && emptyDrawingLabel != null) { emptyDrawingLabel.setBounds(0, 0, getWidth(), getHeight()); emptyDrawingLabel.paint(gr); } else { Graphics2D g = (Graphics2D) gr.create(); AffineTransform tx = g.getTransform(); tx.concatenate(preTransform); g.setTransform(tx); drawing.setFontRenderContext(g.getFontRenderContext()); drawing.draw(g); g.dispose(); } } } protected void drawHandles(java.awt.Graphics2D g) { if (editor != null && editor.getActiveView() == this) { validateHandles(); for (Handle h : getSelectionHandles()) { h.draw(g); } for (Handle h : getSecondaryHandles()) { h.draw(g); } } } protected void drawTool(Graphics2D g) { if (editor != null && editor.getActiveView() == this && editor.getTool() != null) { editor.getTool().draw(g); } } @Override public void setDrawing(@Nullable Drawing newValue) { Drawing oldValue = drawing; if (this.drawing != null) { this.drawing.removeCompositeFigureListener(eventHandler); this.drawing.removeFigureListener(eventHandler); clearSelection(); } this.drawing = newValue; if (this.drawing != null) { this.drawing.addCompositeFigureListener(eventHandler); this.drawing.addFigureListener(eventHandler); } firePropertyChange(DRAWING_PROPERTY, oldValue, newValue); // Revalidate without flickering revalidate(); validateViewTranslation(); paintEnabled = false; javax.swing.Timer t = new javax.swing.Timer(10, new ActionListener() { @Override public void actionPerformed(ActionEvent e) { repaint(); paintEnabled = true; } }); t.setRepeats(false); t.start(); } @Override public void paint(Graphics g) { if (paintEnabled) { super.paint(g); } } protected void repaintDrawingArea(Rectangle2D.Double r) { Rectangle vr = drawingToView(r); repaint(vr); } @Override public void invalidate() { } @Override public void removeNotify() { super.removeNotify(); } /** * Adds a figure to the current selection. */ @Override public void addToSelection(Figure figure) { if (DEBUG) { System.out.println("DefaultDrawingView" + ".addToSelection(" + figure + ")"); } Set<Figure> oldSelection = new HashSet<Figure>(selectedFigures); if (selectedFigures.add(figure)) { figure.addFigureListener(handleInvalidator); Set<Figure> newSelection = new HashSet<Figure>(selectedFigures); Rectangle invalidatedArea = null; if (handlesAreValid && getEditor() != null) { for (Handle h : figure.createHandles(detailLevel)) { h.setView(this); selectionHandles.add(h); h.addHandleListener(eventHandler); if (invalidatedArea == null) { invalidatedArea = h.getDrawingArea(); } else { invalidatedArea.add(h.getDrawingArea()); } } } fireSelectionChanged(oldSelection, newSelection); if (invalidatedArea != null) { repaint(invalidatedArea); } } } /** * Adds a collection of figures to the current selection. */ @Override public void addToSelection(Collection<Figure> figures) { Set<Figure> oldSelection = new HashSet<Figure>(selectedFigures); Set<Figure> newSelection = new HashSet<Figure>(selectedFigures); boolean selectionChanged = false; Rectangle invalidatedArea = null; for (Figure figure : figures) { if (selectedFigures.add(figure)) { selectionChanged = true; newSelection.add(figure); figure.addFigureListener(handleInvalidator); if (handlesAreValid && getEditor() != null) { for (Handle h : figure.createHandles(detailLevel)) { h.setView(this); selectionHandles.add(h); h.addHandleListener(eventHandler); if (invalidatedArea == null) { invalidatedArea = h.getDrawingArea(); } else { invalidatedArea.add(h.getDrawingArea()); } } } } } if (selectionChanged) { fireSelectionChanged(oldSelection, newSelection); if (invalidatedArea != null) { repaint(invalidatedArea); } } } /** * Removes a figure from the selection. */ @Override public void removeFromSelection(Figure figure) { Set<Figure> oldSelection = new HashSet<Figure>(selectedFigures); if (selectedFigures.remove(figure)) { Set<Figure> newSelection = new HashSet<Figure>(selectedFigures); invalidateHandles(); figure.removeFigureListener(handleInvalidator); fireSelectionChanged(oldSelection, newSelection); repaint(); } } /** * If a figure isn't selected it is added to the selection. * Otherwise it is removed from the selection. */ @Override public void toggleSelection(Figure figure) { if (selectedFigures.contains(figure)) { removeFromSelection(figure); } else { addToSelection(figure); } } @Override public void setEnabled(boolean b) { super.setEnabled(b); setCursor(Cursor.getPredefinedCursor(b ? Cursor.DEFAULT_CURSOR : Cursor.WAIT_CURSOR)); } /** * Selects all selectable figures. */ @Override public void selectAll() { Set<Figure> oldSelection = new HashSet<Figure>(selectedFigures); selectedFigures.clear(); for (Figure figure : drawing.getChildren()) { if (figure.isSelectable()) { selectedFigures.add(figure); } } Set<Figure> newSelection = new HashSet<Figure>(selectedFigures); invalidateHandles(); fireSelectionChanged(oldSelection, newSelection); repaint(); } /** * Clears the current selection. */ @Override public void clearSelection() { if (getSelectionCount() > 0) { Set<Figure> oldSelection = new HashSet<Figure>(selectedFigures); selectedFigures.clear(); Set<Figure> newSelection = new HashSet<Figure>(selectedFigures); invalidateHandles(); fireSelectionChanged(oldSelection, newSelection); } } /** * Test whether a given figure is selected. */ @Override public boolean isFigureSelected(Figure checkFigure) { return selectedFigures.contains(checkFigure); } /** * Gets the current selection as a FigureSelection. A FigureSelection * can be cut, copied, pasted. */ @Override public Set<Figure> getSelectedFigures() { return Collections.unmodifiableSet(selectedFigures); } /** * Gets the number of selected figures. */ @Override public int getSelectionCount() { return selectedFigures.size(); } /** * Gets the currently active selection handles. */ protected java.util.List<Handle> getSelectionHandles() { validateHandles(); return Collections.unmodifiableList(selectionHandles); } /** * Gets the currently active secondary handles. */ protected java.util.List<Handle> getSecondaryHandles() { validateHandles(); return Collections.unmodifiableList(secondaryHandles); } /** * Invalidates the handles. */ protected void invalidateHandles() { if (handlesAreValid) { handlesAreValid = false; Rectangle invalidatedArea = null; for (Handle handle : selectionHandles) { handle.removeHandleListener(eventHandler); if (invalidatedArea == null) { invalidatedArea = handle.getDrawingArea(); } else { invalidatedArea.add(handle.getDrawingArea()); } handle.dispose(); } for (Handle handle : secondaryHandles) { handle.removeHandleListener(eventHandler); if (invalidatedArea == null) { invalidatedArea = handle.getDrawingArea(); } else { invalidatedArea.add(handle.getDrawingArea()); } handle.dispose(); } selectionHandles.clear(); secondaryHandles.clear(); setActiveHandle(null); if (invalidatedArea != null) { repaint(invalidatedArea); } } } /** * Validates the handles. */ protected void validateHandles() { // Validate handles only, if they are invalid, and if // the DrawingView has a DrawingEditor. if (!handlesAreValid && getEditor() != null) { handlesAreValid = true; selectionHandles.clear(); Rectangle invalidatedArea = null; while (true) { for (Figure figure : getSelectedFigures()) { for (Handle handle : figure.createHandles(detailLevel)) { handle.setView(this); selectionHandles.add(handle); handle.addHandleListener(eventHandler); if (invalidatedArea == null) { invalidatedArea = handle.getDrawingArea(); } else { invalidatedArea.add(handle.getDrawingArea()); } } } if (selectionHandles.size() == 0 && detailLevel != 0) { // No handles are available at the desired detail level. // Retry with detail level 0. detailLevel = 0; continue; } break; } if (invalidatedArea != null) { repaint(invalidatedArea); } } } /** * Finds a handle at a given coordinates. * @return A handle, null if no handle is found. */ @Override public Handle findHandle( Point p) { validateHandles(); for (Handle handle : new ReversedList<Handle>(getSecondaryHandles())) { if (handle.contains(p)) { return handle; } } for (Handle handle : new ReversedList<Handle>(getSelectionHandles())) { if (handle.contains(p)) { return handle; } } return null; } /** * Gets compatible handles. * @return A collection containing the handle and all compatible handles. */ @Override public Collection<Handle> getCompatibleHandles(Handle master) { validateHandles(); HashSet<Figure> owners = new HashSet<Figure>(); LinkedList<Handle> compatibleHandles = new LinkedList<Handle>(); owners.add(master.getOwner()); compatibleHandles.add(master); for (Handle handle : getSelectionHandles()) { if (!owners.contains(handle.getOwner()) && handle.isCombinableWith(master)) { owners.add(handle.getOwner()); compatibleHandles.add(handle); } } return compatibleHandles; } /** * Finds a figure at a given coordinates. * @return A figure, null if no figure is found. */ @Override public Figure findFigure( Point p) { return getDrawing().findFigure(viewToDrawing(p)); } @Override public Collection<Figure> findFigures(Rectangle r) { return getDrawing().findFigures(viewToDrawing(r)); } @Override public Collection<Figure> findFiguresWithin(Rectangle r) { return getDrawing().findFiguresWithin(viewToDrawing(r)); } @Override public void addFigureSelectionListener(FigureSelectionListener fsl) { listenerList.add(FigureSelectionListener.class, fsl); } @Override public void removeFigureSelectionListener(FigureSelectionListener fsl) { listenerList.remove(FigureSelectionListener.class, fsl); } /** * Notify all listenerList that have registered interest for * notification on this event type. * Also notify listeners who listen for * {@link EditableComponent#SELECTION_EMPTY_PROPERTY}. */ protected void fireSelectionChanged( Set<Figure> oldValue, Set<Figure> newValue) { if (listenerList.getListenerCount() > 0) { FigureSelectionEvent event = null; // Notify all listeners that have registered interest for // Guaranteed to return a non-null array Object[] listeners = listenerList.getListenerList(); // Process the listeners last to first, notifying // those that are interested in this event for (int i = listeners.length - 2; i >= 0; i -= 2) { if (listeners[i] == FigureSelectionListener.class) { // Lazily create the event: if (event == null) { event = new FigureSelectionEvent(this, oldValue, newValue); } ((FigureSelectionListener) listeners[i + 1]).selectionChanged(event); } } } firePropertyChange(EditableComponent.SELECTION_EMPTY_PROPERTY, oldValue.isEmpty(), newValue.isEmpty()); } protected void invalidateDimension() { cachedPreferredSize = null; cachedDrawingArea = null; } @Override public Constrainer getConstrainer() { return isConstrainerVisible() ? visibleConstrainer : invisibleConstrainer; } @Override public Dimension getPreferredSize() { if (cachedPreferredSize == null) { Rectangle2D.Double r = getDrawingArea(); Double cw = getDrawing() == null ? null : getDrawing().get(CANVAS_WIDTH); Double ch = getDrawing() == null ? null : getDrawing().get(CANVAS_HEIGHT); Insets insets = getInsets(); if (cw == null || ch == null) { cachedPreferredSize = new Dimension( (int) Math.ceil((Math.max(0, r.x) + r.width)) + insets.left + insets.right, (int) Math.ceil((Math.max(0, r.y) + r.height)) + insets.top + insets.bottom); } else { cachedPreferredSize = new Dimension( (int) Math.ceil((-Math.min(0, r.x) + Math.max(Math.max(0, r.x) + r.width + Math.min(0, r.x), cw))) + insets.left + insets.right, (int) Math.ceil((-Math.min(0, r.y) + Math.max(Math.max(0, r.y) + r.height + Math.min(0, r.y), ch))) + insets.top + insets.bottom); } } return (Dimension) cachedPreferredSize.clone(); } protected Rectangle2D.Double getDrawingArea() { if (cachedDrawingArea == null) { if (drawing != null) { cachedDrawingArea = drawing.getDrawingArea(); } else { cachedDrawingArea = new Rectangle2D.Double(); } } return (Rectangle2D.Double) cachedDrawingArea.clone(); } /** * Side effect: Changes view Translation. */ @Override public void setBounds(int x, int y, int width, int height) { super.setBounds(x, y, width, height); validateViewTranslation(); } /** * Updates the view translation taking into account the current dimension * of the view JComponent, the size of the drawing, and the scale factor. */ protected void validateViewTranslation() { } /** * Converts drawing coordinates to view coordinates. */ @Override public Point drawingToView( Point2D.Double p) { Point2D po = preTransform.transform(p, null); return new Point((int) po.getX(), (int) po.getY()); } @Override public Rectangle drawingToView( Rectangle2D.Double r) { double[] drawing = {r.x, r.y, r.x + r.width, r.y, r.x + r.width, r.y + r.height, r.x, r.y + r.height}; double[] view = new double[8]; preTransform.transform(drawing, 0, view, 0, 4); int x1 = Math.min((int)view[0], (int)view[2]); x1 = Math.min(x1, (int)view[4]); x1 = Math.min(x1, (int)view[6]); int x2 = Math.max((int)view[0], (int)view[2]); x2 = Math.max(x2, (int)view[4]); x2 = Math.max(x2, (int)view[6]); int y1 = Math.min((int)view[1], (int)view[3]); y1 = Math.min(y1, (int)view[5]); y1 = Math.min(y1, (int)view[7]); int y2 = Math.max((int)view[1], (int)view[3]); y2 = Math.max(y2, (int)view[5]); y2 = Math.max(y2, (int)view[7]); return new Rectangle(x1, y1, x2 - x1, y2 - y1); } /** * Converts view coordinates to drawing coordinates. */ @Override public Point2D.Double viewToDrawing(Point p) { Point2D point = null; try { point = preTransform.inverseTransform(new Point2D.Double((double)p.x, (double)p.y), null); } catch (NoninvertibleTransformException e) { e.printStackTrace(); } return (Point2D.Double) point; } @Override public Rectangle2D.Double viewToDrawing(Rectangle r) { double[] drawing = {r.x, r.y, r.x + r.width, r.y, r.x + r.width, r.y + r.height, r.x, r.y + r.height}; double[] view = new double[8]; //preTransform.transform(drawing, 0, view, 0, 4); try { preTransform.inverseTransform(drawing, 0, view, 0, 4); } catch (NoninvertibleTransformException e) { e.printStackTrace(); } double x1 = Math.min(view[0], view[2]); x1 = Math.min(x1, view[4]); x1 = Math.min(x1, view[6]); double x2 = Math.max(view[0], view[2]); x2 = Math.max(x2, view[4]); x2 = Math.max(x2, view[6]); double y1 = Math.min(view[1], view[3]); y1 = Math.min(y1, view[5]); y1 = Math.min(y1, view[7]); double y2 = Math.max(view[1], view[3]); y2 = Math.max(y2, view[5]); y2 = Math.max(y2, view[7]); return new Rectangle2D.Double(x1, y1, x2 - x1, y2 - y1); } @Override public AffineTransform getDrawingToViewTransform() { AffineTransform t = new AffineTransform(); t.setTransform(preTransform); return t; } @Override public JComponent getComponent() { return this; } @Override public double getScaleFactor() { return 1; } @Override public void setScaleFactor(double newValue) { double oldValue = 1; //scaleFactor = newValue; validateViewTranslation(); invalidateHandles(); revalidate(); repaint(); firePropertyChange("scaleFactor", oldValue, newValue); } protected void fireViewTransformChanged() { for (Handle handle : selectionHandles) { handle.viewTransformChanged(); } for (Handle handle : secondaryHandles) { handle.viewTransformChanged(); } } @Override public void setHandleDetailLevel(int newValue) { if (newValue != detailLevel) { detailLevel = newValue; invalidateHandles(); validateHandles(); } } @Override public int getHandleDetailLevel() { return detailLevel; } @Override public void delete() { final java.util.List<Figure> deletedFigures = drawing.sort(getSelectedFigures()); // Abort, if not all of the selected figures may be removed from the // drawing for (Figure f : deletedFigures) { if (!f.isRemovable()) { getToolkit().beep(); return; } } // Get z-indices of deleted figures final int[] deletedFigureIndices = new int[deletedFigures.size()]; for (int i = 0; i < deletedFigureIndices.length; i++) { deletedFigureIndices[i] = drawing.indexOf(deletedFigures.get(i)); } clearSelection(); getDrawing().removeAll(deletedFigures); getDrawing().fireUndoableEditHappened(new AbstractUndoableEdit() { @Override public String getPresentationName() { ResourceBundleUtil labels = ResourceBundleUtil.getBundle("org.jhotdraw.draw.Labels"); return labels.getString("edit.delete.text"); } @Override public void undo() throws CannotUndoException { super.undo(); clearSelection(); Drawing d = getDrawing(); for (int i = 0; i < deletedFigureIndices.length; i++) { d.add(deletedFigureIndices[i], deletedFigures.get(i)); } addToSelection(deletedFigures); } @Override public void redo() throws CannotRedoException { super.redo(); for (int i = 0; i < deletedFigureIndices.length; i++) { drawing.remove(deletedFigures.get(i)); } } }); } @Override public void duplicate() { Collection<Figure> sorted = getDrawing().sort(getSelectedFigures()); HashMap<Figure, Figure> originalToDuplicateMap = new HashMap<Figure, Figure>(sorted.size()); clearSelection(); final ArrayList<Figure> duplicates = new ArrayList<Figure>(sorted.size()); AffineTransform tx = new AffineTransform(); tx.translate(5, 5); for (Figure f : sorted) { Figure d = (Figure) f.clone(); d.transform(tx); duplicates.add(d); originalToDuplicateMap.put(f, d); drawing.add(d); } for (Figure f : duplicates) { f.remap(originalToDuplicateMap, false); } addToSelection(duplicates); getDrawing().fireUndoableEditHappened(new AbstractUndoableEdit() { @Override public String getPresentationName() { ResourceBundleUtil labels = ResourceBundleUtil.getBundle("org.jhotdraw.draw.Labels"); return labels.getString("edit.duplicate.text"); } @Override public void undo() throws CannotUndoException { super.undo(); getDrawing().removeAll(duplicates); } @Override public void redo() throws CannotRedoException { super.redo(); getDrawing().addAll(duplicates); } }); } @Override public void removeNotify(DrawingEditor editor) { this.editor = null; repaint(); } @Override public void addNotify(DrawingEditor editor) { DrawingEditor oldValue = editor; this.editor = editor; firePropertyChange("editor", oldValue, editor); invalidateHandles(); repaint(); } @Override public void setVisibleConstrainer(Constrainer newValue) { Constrainer oldValue = visibleConstrainer; visibleConstrainer = newValue; firePropertyChange(VISIBLE_CONSTRAINER_PROPERTY, oldValue, newValue); } @Override public Constrainer getVisibleConstrainer() { return visibleConstrainer; } @Override public void setInvisibleConstrainer(Constrainer newValue) { Constrainer oldValue = invisibleConstrainer; invisibleConstrainer = newValue; firePropertyChange(INVISIBLE_CONSTRAINER_PROPERTY, oldValue, newValue); } @Override public Constrainer getInvisibleConstrainer() { return invisibleConstrainer; } @Override public void setConstrainerVisible(boolean newValue) { boolean oldValue = isConstrainerVisible; isConstrainerVisible = newValue; firePropertyChange(CONSTRAINER_VISIBLE_PROPERTY, oldValue, newValue); repaint(); } @Override public boolean isConstrainerVisible() { return isConstrainerVisible; } /** Returns true, if the the drawing is double buffered. */ public boolean isDrawingDoubleBuffered() { return isDrawingDoubleBuffered; } /** * Returns a paint for drawing the background of the drawing area. * @return Paint. */ protected Paint getBackgroundPaint( int x, int y) { if (backgroundTile == null) { backgroundTile = new BufferedImage(16, 16, BufferedImage.TYPE_INT_RGB); Graphics2D g = backgroundTile.createGraphics(); g.setColor(Color.white); g.fillRect(0, 0, 16, 16); g.setColor(new Color(0xdfdfdf)); g.fillRect(0, 0, 8, 8); g.fillRect(8, 8, 8, 8); g.dispose(); } return new TexturePaint(backgroundTile, new Rectangle(x, y, backgroundTile.getWidth(), backgroundTile.getHeight())); } @Override public DrawingEditor getEditor() { return editor; } // Variables declaration - do not modify//GEN-BEGIN:variables // End of variables declaration//GEN-END:variables @Override public void setActiveHandle(@Nullable Handle newValue) { Handle oldValue = activeHandle; if (oldValue != null) { repaint(oldValue.getDrawingArea()); } activeHandle = newValue; if (newValue != null) { repaint(newValue.getDrawingArea()); } firePropertyChange(ACTIVE_HANDLE_PROPERTY, oldValue, newValue); } @Override public Handle getActiveHandle() { return activeHandle; } }