/*******************************************************************************
* Copyright (c) 2006-2012
* Software Technology Group, Dresden University of Technology
* DevBoost GmbH, Berlin, Amtsgericht Charlottenburg, HRB 140026
*
* 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:
* Software Technology Group - TU Dresden, Germany;
* DevBoost GmbH - Berlin, Germany
* - initial API and implementation
******************************************************************************/
/*
* @(#)DefaultDrawingView.java 4.6 2009-04-25
*
* Copyright (c) 1996-2009 by the original authors of JHotDraw
* and all its contributors.
* All rights reserved.
*
* The copyright of this software is owned by the authors and
* contributors of the JHotDraw project ("the copyright holders").
* You may not use, copy or modify this software, except in
* accordance with the license agreement you entered into with
* the copyright holders. For details see accompanying license terms.
*/
package org.jhotdraw.draw;
import javax.swing.event.*;
import javax.swing.undo.*;
import org.jhotdraw.util.*;
import java.awt.*;
import java.awt.geom.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.util.*;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import org.jhotdraw.app.EditableComponent;
import static org.jhotdraw.draw.AttributeKeys.*;
/**
* The DefaultDrawingView is suited for viewing drawings with a small number
* of Figures.
*
* FIXME - Implement clone Method.
* FIXME - Use double buffering for the drawing to improve performance.
*
* @author Werner Randelshofer
* @version 4.6 2009-04-25 Center drawing in view, if view is larger than
* the canvas size.
* <br>4.5.3 2008-09-01 Use an ordered set for the selected figures.
* <br>4.5.2 2008-06-09 A DrawingView must not create Handle's, if it
* has no DrawingEditor.
* <br>4.5.1 2008-05-18 Delete method did not preserve z-index on undo.
* <br>4.5 2008-05-18 Retrieve tooltip text from current tool.
* <br>4.4 2007-12-18 Reduced repaints of the drawing area.
* <br>4.3 2007-12-16 Retrieve canvasColor color from Drawing object.
* <br>4.2 2007-09-12 The DrawingView is now responsible for
* holding the Constrainer objects which affect editing on this view.
* <br>4.0 2007-07-23 DefaultDrawingView does not publicly extend anymore
* CompositeFigureListener and HandleListener.
* <br>3.5 2007-04-13 Implement clipboard functions using TransferHandler.
* <br>3.4 2007-04-09 Visualizes the canvas sgetChildCountof a Drawing by a filled
* white rectangle on the canvasColor.
* <br>3.3 2007-01-23 Only repaintDrawingArea handles on focus gained/lost.
* <br>3.2 2006-12-26 Rewrote storage and clipboard support.
* <br>3.1 2006-12-17 Added printing support.
* <br>3.0.2 2006-07-03 Constrainer must be a bound property.
* <br>3.0.1 2006-06-11 Draw handles when this DrawingView is the focused
* drawing view of the DrawingEditor.
* <br>3.0 2006-02-17 Reworked to support multiple drawing views in a
* DrawingEditor.
* <br>2.0 2006-01-14 Changed to support double precision coordinates.
* <br>1.0 2003-12-01 Derived from JHotDraw 5.4b1.
*/
public class DefaultDrawingView
extends JComponent
implements DrawingView, EditableComponent {
/**
* Set this to true to turn on debugging output on System.out.
*/
private final static boolean DEBUG = false;
private Drawing drawing;
private Set<Figure> dirtyFigures = new HashSet<Figure>();
/**
* Holds the selected figures in an ordered set. The ordering reflects
* the sequence that was used to select the figures.
*/
private Set<Figure> selectedFigures = new LinkedHashSet<Figure>();
//private int rainbow = 0;
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;
private Handle activeHandle;
private LinkedList<Handle> secondaryHandles = new LinkedList<Handle>();
private boolean handlesAreValid = true;
private transient Dimension cachedPreferredSize;
private double scaleFactor = 1;
private Point2D.Double translate = new Point2D.Double(0, 0);
private int detailLevel;
private DrawingEditor editor;
private JLabel emptyDrawingLabel;
private FigureListener handleInvalidator = new FigureAdapter() {
@Override
public void figureHandlesChanged(FigureEvent e) {
invalidateHandles();
}
};
private ChangeListener changeHandler = new ChangeListener() {
public void stateChanged(ChangeEvent evt) {
repaint();
}
};
private transient Rectangle2D.Double cachedDrawingArea;
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);
}
}
private class EventHandler implements FigureListener, CompositeFigureListener, HandleListener, FocusListener {
public void figureAdded(CompositeFigureEvent evt) {
if (drawing.getChildCount() == 1 && getEmptyDrawingMessage() != null) {
repaint();
} else {
repaintDrawingArea(evt.getInvalidatedArea());
}
invalidateDimension();
}
public void figureRemoved(CompositeFigureEvent evt) {
if (drawing.getChildCount() == 0 && getEmptyDrawingMessage() != null) {
repaint();
} else {
repaintDrawingArea(evt.getInvalidatedArea());
}
removeFromSelection(evt.getChildFigure());
invalidateDimension();
}
public void areaInvalidated(FigureEvent evt) {
repaintDrawingArea(evt.getInvalidatedArea());
invalidateDimension();
}
public void areaInvalidated(HandleEvent evt) {
repaint(evt.getInvalidatedArea());
invalidateDimension();
}
public void handleRequestSecondaryHandles(HandleEvent e) {
secondaryHandleOwner = e.getHandle();
secondaryHandles.clear();
secondaryHandles.addAll(secondaryHandleOwner.createSecondaryHandles());
for (Handle h : secondaryHandles) {
h.setView(DefaultDrawingView.this);
h.addHandleListener(eventHandler);
}
repaint();
}
public void focusGained(FocusEvent e) {
// repaintHandles();
if (editor != null) {
editor.setActiveView(DefaultDrawingView.this);
}
}
public void focusLost(FocusEvent e) {
// repaintHandles();
}
public void handleRequestRemove(HandleEvent e) {
selectionHandles.remove(e.getHandle());
e.getHandle().dispose();
invalidateHandles();
repaint(e.getInvalidatedArea());
}
public void attributeChanged(FigureEvent e) {
if (e.getSource() == drawing) {
if (e.getAttribute().equals(CANVAS_HEIGHT) || e.getAttribute().equals(CANVAS_WIDTH)) {
validateViewTranslation();
}
repaint();
} else {
repaintDrawingArea(e.getInvalidatedArea());
}
}
public void figureHandlesChanged(FigureEvent e) {
}
public void figureChanged(FigureEvent e) {
repaintDrawingArea(e.getInvalidatedArea());
}
public void figureAdded(FigureEvent e) {
}
public void figureRemoved(FigureEvent e) {
}
public void figureRequestRemove(FigureEvent e) {
}
}
private EventHandler eventHandler;
/** Creates new instance. */
public DefaultDrawingView() {
initComponents();
eventHandler = createEventHandler();
setToolTipText("dummy"); // Set a dummy tool tip text to turn tooltips on
setFocusable(true);
addFocusListener(eventHandler);
setTransferHandler(new DefaultDrawingViewTransferHandler());
//setBorder(new EmptyBorder(10,10,10,10));
}
protected EventHandler createEventHandler() {
return new EventHandler();
}
/** This method is called from within the constructor to
* initialize the form.
* WARNING: Do NOT modify this code. The content of this method is
* always regenerated by the Form Editor.
*/
private void initComponents() {//GEN-BEGIN:initComponents
buttonGroup1 = new javax.swing.ButtonGroup();
setLayout(null);
setBackground(new java.awt.Color(255, 255, 255));
}//GEN-END:initComponents
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();
}
/**
* 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;
// 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, (Options.isFractionalMetrics()) ? RenderingHints.VALUE_FRACTIONALMETRICS_ON : RenderingHints.VALUE_FRACTIONALMETRICS_OFF);
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, (Options.isTextAntialiased()) ? RenderingHints.VALUE_TEXT_ANTIALIAS_ON : RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
drawBackground(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, (Options.isFractionalMetrics()) ? RenderingHints.VALUE_FRACTIONALMETRICS_ON : RenderingHints.VALUE_FRACTIONALMETRICS_OFF);
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, (Options.isTextAntialiased()) ? RenderingHints.VALUE_TEXT_ANTIALIAS_ON : RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
drawDrawing(g);
}
protected void drawBackground(Graphics2D g) {
// Position of the zero coordinate point on the view
int x = (int) (-translate.x * scaleFactor);
int y = (int) (-translate.y * scaleFactor);
int w = getWidth();
int h = getHeight();
// Retrieve the canvasColor color from the drawing
Color canvasColor;
if (drawing == null) {
canvasColor = getBackground();
} else {
canvasColor = CANVAS_FILL_COLOR.get(drawing);
if (canvasColor != null) {
canvasColor = new Color((canvasColor.getRGB() & 0xffffff) | ((int) (CANVAS_FILL_OPACITY.get(drawing) * 255) << 24), true);
}
}
if (canvasColor == null || canvasColor.getAlpha() != 255) {
g.setPaint(getBackgroundPaint(x, y));
g.fillRect(x, y, w - x, h - y);
}
if (canvasColor != null) {
g.setColor(canvasColor);
g.fillRect(x, y, w - x, h - y);
}
// Draw a gray canvasColor for the area which is at
// negative view coordinates.
Color outerBackground = new Color(0xf0f0f0);
if (y > 0) {
g.setColor(outerBackground);
g.fillRect(0, 0, w, y);
}
if (x > 0) {
g.setColor(outerBackground);
g.fillRect(0, y, x, h - y);
}
if (getDrawing() != null) {
Double cw = CANVAS_WIDTH.get(getDrawing());
Double ch = CANVAS_HEIGHT.get(getDrawing());
if (cw != null && ch != null) {
Point lowerRight = drawingToView(
new Point2D.Double(cw, ch));
if (lowerRight.x < w) {
g.setColor(outerBackground);
g.fillRect(lowerRight.x, y, w - lowerRight.x, h - y);
}
if (lowerRight.y < h) {
g.setColor(outerBackground);
g.fillRect(x, lowerRight.y, w - x, h - lowerRight.y);
}
}
}
/*
//Fill canvasColor with alternating colors to debug clipping
rainbow = (rainbow + 10) % 360;
g.setColor(
new Color(Color.HSBtoRGB((float) (rainbow / 360f), 0.3f, 1.0f)));
g.fill(g.getClipBounds());*/
}
//int rainbow;
protected void drawConstrainer(Graphics2D g) {
getConstrainer().draw(g, this);
}
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.translate(-translate.x * scaleFactor, -translate.y * scaleFactor);
tx.scale(scaleFactor, scaleFactor);
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);
}
}
public void setDrawing(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);
}
invalidateDimension();
if (getParent() != null) {
getParent().validate();
if (getParent() instanceof JViewport) {
JViewport vp = (JViewport) getParent();
Rectangle2D.Double r = getDrawingArea();
vp.setViewPosition(drawingToView(new Point2D.Double(Math.min(0, -r.x), Math.min(0, -r.y))));
}
}
firePropertyChange(DRAWING_PROPERTY, oldValue, newValue);
validateViewTranslation();
revalidate();
repaint();
}
protected void repaintDrawingArea(Rectangle2D.Double r) {
Rectangle vr = drawingToView(r);
vr.grow(1, 1);
repaint(vr);
}
@Override
public void invalidate() {
invalidateDimension();
super.invalidate();
}
/**
* Adds a figure to the current selection.
*/
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.
*/
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.
*/
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.
*/
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.
*/
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.
*/
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);
}
//repaintDrawingArea();
}
/**
* Test whether a given figure is selected.
*/
public boolean isFigureSelected(Figure checkFigure) {
return selectedFigures.contains(checkFigure);
}
/**
* Gets the current selection as a FigureSelection. A FigureSelection
* can be cut, copied, pasted.
*/
public Set<Figure> getSelectedFigures() {
return Collections.unmodifiableSet(selectedFigures);
}
/**
* Gets the number of selected figures.
*/
public int getSelectionCount() {
return selectedFigures.size();
}
/**
* Gets the currently active selection handles.
*/
private java.util.List<Handle> getSelectionHandles() {
validateHandles();
return Collections.unmodifiableList(selectionHandles);
}
/**
* Gets the currently active secondary handles.
*/
private java.util.List<Handle> getSecondaryHandles() {
validateHandles();
return Collections.unmodifiableList(secondaryHandles);
}
/**
* Invalidates the handles.
*/
private 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.
*/
private 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;
int level = detailLevel;
do {
for (Figure figure : getSelectedFigures()) {
for (Handle handle : figure.createHandles(level)) {
handle.setView(this);
selectionHandles.add(handle);
handle.addHandleListener(eventHandler);
if (invalidatedArea == null) {
invalidatedArea = handle.getDrawingArea();
} else {
invalidatedArea.add(handle.getDrawingArea());
}
}
}
} while (level-- > 0 && selectionHandles.size() == 0);
detailLevel = level + 1;
if (invalidatedArea != null) {
repaint(invalidatedArea);
}
}
}
/**
* Finds a handle at a given coordinates.
* @return A handle, null if no handle is found.
*/
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.
*/
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.
*/
public Figure findFigure(Point p) {
return getDrawing().findFigure(viewToDrawing(p));
}
public Collection<Figure> findFigures(Rectangle r) {
return getDrawing().findFigures(viewToDrawing(r));
}
public Collection<Figure> findFiguresWithin(Rectangle r) {
return getDrawing().findFiguresWithin(viewToDrawing(r));
}
public void addFigureSelectionListener(FigureSelectionListener fsl) {
listenerList.add(FigureSelectionListener.class, fsl);
}
public void removeFigureSelectionListener(FigureSelectionListener fsl) {
listenerList.remove(FigureSelectionListener.class, fsl);
}
/**
* Notify all listenerList that have registered interest for
* notification on this event type.
*/
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);
}
}
}
}
protected void invalidateDimension() {
cachedPreferredSize = null;
cachedDrawingArea = null;
}
public Constrainer getConstrainer() {
return isConstrainerVisible() ? visibleConstrainer : invisibleConstrainer;
}
@Override
public Dimension getPreferredSize() {
if (cachedPreferredSize == null) {
Rectangle2D.Double r = getDrawingArea();
Double cw = getDrawing() == null ? null : CANVAS_WIDTH.get(getDrawing());
Double ch = getDrawing() == null ? null : CANVAS_HEIGHT.get(getDrawing());
Insets insets = getInsets();
if (cw == null || ch == null) {
cachedPreferredSize = new Dimension(
(int) ((Math.max(0,r.x) + r.width) * scaleFactor) + insets.left + insets.right,
(int) ((Math.max(0,r.y) + r.height) * scaleFactor) + insets.top + insets.bottom);
} else {
cachedPreferredSize = new Dimension(
(int) (Math.max((Math.max(0,r.x) + r.width), cw) * scaleFactor) + insets.left + insets.right,
(int) (Math.max((Math.max(0,r.y) + r.height), ch) * scaleFactor) + insets.top + insets.bottom);
}
validateViewTranslation();
}
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.
*/
private void validateViewTranslation() {
if (getDrawing() == null) {
translate.x = translate.y = 0;
return;
}
Point2D.Double oldTranslate = (Point2D.Double) translate.clone();
int width = getWidth();
int height = getHeight();
Insets insets = getInsets();
Rectangle2D.Double r = getDrawingArea();
Double cw = CANVAS_WIDTH.get(getDrawing());
Double ch = CANVAS_HEIGHT.get(getDrawing());
if (cw == null || ch == null) {
// The canvas size is not specified.
// Place the drawing at the top left corner.
translate.x = Math.min(0, r.x);
translate.y = Math.min(0, r.y);
} else {
// The canvas size is not specified.
//Place the canvas at the center
Dimension preferred = getPreferredSize();
if (cw != null && ch != null) {
if (cw * scaleFactor < width) {
translate.x = (width / scaleFactor - cw) / -2d;
}
if (ch * scaleFactor < height) {
translate.y = (height / scaleFactor - ch) / -2d;
}
}
if (r.y - translate.y < insets.top / scaleFactor) {
// We cut off the upper part of the drawing -> shift the canvas down
translate.y = r.y;
} else if (r.y - translate.y + r.height > (height-insets.bottom)/scaleFactor) {
// We cut off the lower part of the drawing -> shift the canvas up
translate.y = r.y + r.height - (height-insets.bottom)/scaleFactor;
}
if (r.x - translate.x < insets.left / scaleFactor) {
// We cut off the left part of the drawing -> shift the canvas right
translate.x = r.x;
} else if (r.x- translate.x + r.width > (width-insets.right)/scaleFactor) {
// We cut off the right part of the drawing -> shift the canvas left
translate.x = r.x + r.width - (width-insets.right)/scaleFactor;
}
}
// Move the canvas out of the center if needed
if (!oldTranslate.equals(translate)) {
fireViewTransformChanged();
repaint();
}
}
/**
* Converts drawing coordinates to view coordinates.
*/
public Point drawingToView(
Point2D.Double p) {
return new Point(
(int) ((p.x - translate.x) * scaleFactor),
(int) ((p.y - translate.y) * scaleFactor));
}
/**
* Converts view coordinates to drawing coordinates.
*/
public Point2D.Double viewToDrawing(Point p) {
return new Point2D.Double(
p.x / scaleFactor + translate.x,
p.y / scaleFactor + translate.y);
}
public Rectangle drawingToView(
Rectangle2D.Double r) {
return new Rectangle(
(int) ((r.x - translate.x) * scaleFactor),
(int) ((r.y - translate.y) * scaleFactor),
(int) (r.width * scaleFactor),
(int) (r.height * scaleFactor));
}
public Rectangle2D.Double viewToDrawing(Rectangle r) {
return new Rectangle2D.Double(
r.x / scaleFactor + translate.x,
r.y / scaleFactor + translate.y,
r.width / scaleFactor,
r.height / scaleFactor);
}
public JComponent getComponent() {
return this;
}
public double getScaleFactor() {
return scaleFactor;
}
public void setScaleFactor(double newValue) {
double oldValue = scaleFactor;
scaleFactor =
newValue;
//fireViewTransformChanged();
validateViewTranslation();
firePropertyChange("scaleFactor", oldValue, newValue);
invalidate();
invalidateHandles();
if (getParent() != null) {
getParent().validate();
}
repaint();
}
protected void fireViewTransformChanged() {
for (Handle handle : selectionHandles) {
handle.viewTransformChanged();
}
for (Handle handle : secondaryHandles) {
handle.viewTransformChanged();
}
}
public void setHandleDetailLevel(int newValue) {
if (newValue != detailLevel) {
detailLevel = newValue;
invalidateHandles();
validateHandles();
}
}
public int getHandleDetailLevel() {
return detailLevel;
}
public AffineTransform getDrawingToViewTransform() {
AffineTransform t = new AffineTransform();
t.scale(scaleFactor, scaleFactor);
t.translate(-translate.x, -translate.y);
return t;
}
public void delete() {
final LinkedList<CompositeFigureEvent> deletionEvents = new LinkedList<CompositeFigureEvent>();
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));
}
}
});
}
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);
}
});
}
public void removeNotify(DrawingEditor editor) {
this.editor = null;
repaint();
}
public void addNotify(DrawingEditor editor) {
DrawingEditor oldValue = editor;
this.editor = editor;
firePropertyChange("editor", oldValue, editor);
invalidateHandles();
repaint();
}
public void setVisibleConstrainer(Constrainer newValue) {
Constrainer oldValue = visibleConstrainer;
visibleConstrainer =
newValue;
firePropertyChange(VISIBLE_CONSTRAINER_PROPERTY, oldValue, newValue);
}
public Constrainer getVisibleConstrainer() {
return visibleConstrainer;
}
public void setInvisibleConstrainer(Constrainer newValue) {
Constrainer oldValue = invisibleConstrainer;
invisibleConstrainer =
newValue;
firePropertyChange(INVISIBLE_CONSTRAINER_PROPERTY, oldValue, newValue);
}
public Constrainer getInvisibleConstrainer() {
return invisibleConstrainer;
}
public void setConstrainerVisible(boolean newValue) {
boolean oldValue = isConstrainerVisible;
isConstrainerVisible =
newValue;
firePropertyChange(CONSTRAINER_VISIBLE_PROPERTY, oldValue, newValue);
repaint();
}
public boolean isConstrainerVisible() {
return isConstrainerVisible;
}
protected BufferedImage backgroundTile;
/**
* 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()));
}
public DrawingEditor getEditor() {
return editor;
}
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.ButtonGroup buttonGroup1;
// End of variables declaration//GEN-END:variables
public void setActiveHandle(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);
}
public Handle getActiveHandle() {
return activeHandle;
}
}