diff --git a/.github/workflows/maven-build.yml b/.github/workflows/maven-build.yml new file mode 100644 index 000000000..9c7abc483 --- /dev/null +++ b/.github/workflows/maven-build.yml @@ -0,0 +1,42 @@ +name: CI/CD with Maven and Tests + +on: + push: + branches: ["develop", "mia"] + pull_request: + branches: ["main", "mia"] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Clone code to worker + uses: actions/checkout@v4 + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: temurin + cache: maven + + - name: Install xvfb for BDD Tests + run: sudo apt-get update && sudo apt-get install -y xvfb + + - name: Compile + run: mvn compile -Djava.awt.headless=true + + - name: Run All Tests + run: > + xvfb-run mvn -B verify + -DfailIfNoTests=false + + - name: Get Test Results + if: always() + uses: actions/upload-artifact@v4 + with: + name: test-reports + path: | + target/jgiven-reports/html + target/surefire-reports + target/failsafe-reports diff --git a/jhotdraw-core/pom.xml b/jhotdraw-core/pom.xml index 7c276da85..ba7d55955 100644 --- a/jhotdraw-core/pom.xml +++ b/jhotdraw-core/pom.xml @@ -34,11 +34,59 @@ testng 6.8.21 test - + + + org.mockito + mockito-core + 5.11.0 + test + + + org.assertj + assertj-core + 3.25.3 + test + + + + org.assertj + assertj-swing-junit + 3.17.1 + test + + + + com.tngtech.jgiven + jgiven-junit + 1.3.1 + test + ${project.groupId} jhotdraw-actions ${project.version} - + + junit + junit + 4.13.2 + test + + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.4.1 + + + + test-jar + + + + + + \ No newline at end of file diff --git a/jhotdraw-core/src/main/java/org/jhotdraw/draw/AbstractDrawingView.java b/jhotdraw-core/src/main/java/org/jhotdraw/draw/AbstractDrawingView.java index 433dd3815..709f8f72a 100644 --- a/jhotdraw-core/src/main/java/org/jhotdraw/draw/AbstractDrawingView.java +++ b/jhotdraw-core/src/main/java/org/jhotdraw/draw/AbstractDrawingView.java @@ -197,6 +197,7 @@ public boolean isSelectionEmpty() { return selectedFigures.isEmpty(); } + private class EventHandler implements FigureListener, CompositeFigureListener, HandleListener, FocusListener { @Override @@ -299,6 +300,7 @@ public void figureRemoved(FigureEvent e) { public void figureRequestRemove(FigureEvent e) { } } + private final EventHandler eventHandler = new EventHandler(); @@ -1001,6 +1003,7 @@ public void duplicate() { f.remap(originalToDuplicateMap, false); } addToSelection(duplicates); + drawing.fireUndoableEditHappened(new AbstractUndoableEdit() { private static final long serialVersionUID = 1L; diff --git a/jhotdraw-core/src/main/java/org/jhotdraw/draw/DefaultDrawingEditor.java b/jhotdraw-core/src/main/java/org/jhotdraw/draw/DefaultDrawingEditor.java index 7510b6816..5249bcad6 100644 --- a/jhotdraw-core/src/main/java/org/jhotdraw/draw/DefaultDrawingEditor.java +++ b/jhotdraw-core/src/main/java/org/jhotdraw/draw/DefaultDrawingEditor.java @@ -9,11 +9,7 @@ import org.jhotdraw.draw.figure.Figure; import java.awt.*; -import java.awt.event.FocusEvent; -import java.awt.event.FocusListener; -import java.awt.event.InputEvent; -import java.awt.event.KeyEvent; -import java.awt.event.MouseWheelListener; +import java.awt.event.*; import java.util.*; import javax.swing.ActionMap; import javax.swing.InputMap; @@ -29,6 +25,7 @@ import org.jhotdraw.draw.action.*; import org.jhotdraw.draw.event.ToolAdapter; import org.jhotdraw.draw.event.ToolEvent; +import org.jhotdraw.draw.tool.BaseTool; import org.jhotdraw.draw.tool.Tool; /** @@ -44,7 +41,7 @@ public class DefaultDrawingEditor extends AbstractBean implements DrawingEditor private static final long serialVersionUID = 1L; private HashMap, Object> defaultAttributes = new HashMap<>(); private HashMap, Object> handleAttributes = new HashMap<>(); - private Tool tool; + private BaseTool tool; private HashSet views; private DrawingView activeView; private boolean isEnabled = true; @@ -114,16 +111,16 @@ public DefaultDrawingEditor() { } @Override - public void setTool(Tool newValue) { - Tool oldValue = tool; + public void setTool(BaseTool newValue) { + BaseTool oldValue = tool; if (newValue == tool) { return; } if (tool != null) { for (DrawingView v : views) { - v.removeMouseListener(tool); - v.removeMouseMotionListener(tool); - v.removeKeyListener(tool); + if (tool instanceof MouseListener) v.removeMouseListener((MouseListener) tool); + if (tool instanceof MouseMotionListener) v.removeMouseMotionListener((MouseMotionListener) tool); + if (tool instanceof KeyListener) v.removeKeyListener((KeyListener) tool); if (tool instanceof MouseWheelListener) { v.removeMouseWheelListener((MouseWheelListener) tool); } @@ -135,9 +132,9 @@ public void setTool(Tool newValue) { if (tool != null) { tool.activate(this); for (DrawingView v : views) { - v.addMouseListener(tool); - v.addMouseMotionListener(tool); - v.addKeyListener(tool); + if (tool instanceof MouseListener) v.addMouseListener((MouseListener) tool); + if (tool instanceof MouseMotionListener) v.addMouseMotionListener((MouseMotionListener) tool); + if (tool instanceof KeyListener) v.addKeyListener((KeyListener) tool); if (tool instanceof MouseWheelListener) { v.addMouseWheelListener((MouseWheelListener) tool); } @@ -155,7 +152,7 @@ public void setActiveView(DrawingView newValue) { } @Override - public Tool getTool() { + public BaseTool getTool() { return tool; } @@ -204,9 +201,9 @@ public void remove(DrawingView view) { view.getComponent().removeFocusListener(focusHandler); views.remove(view); if (tool != null) { - view.removeMouseListener(tool); - view.removeMouseMotionListener(tool); - view.removeKeyListener(tool); + if (tool instanceof MouseListener) view.removeMouseListener((MouseListener) tool); + if (tool instanceof MouseMotionListener) view.removeMouseMotionListener((MouseMotionListener) tool); + if (tool instanceof KeyListener) view.removeKeyListener((KeyListener) tool); } view.removeNotify(this); if (activeView == view) { @@ -221,9 +218,9 @@ public void add(DrawingView view) { view.addNotify(this); view.getComponent().addFocusListener(focusHandler); if (tool != null) { - view.addMouseListener(tool); - view.addMouseMotionListener(tool); - view.addKeyListener(tool); + if (tool instanceof MouseListener) view.addMouseListener((MouseListener) tool); + if (tool instanceof MouseMotionListener) view.addMouseMotionListener((MouseMotionListener) tool); + if (tool instanceof KeyListener) view.addKeyListener((KeyListener) tool); } updateActiveView(); } diff --git a/jhotdraw-core/src/main/java/org/jhotdraw/draw/DefaultDrawingView.java b/jhotdraw-core/src/main/java/org/jhotdraw/draw/DefaultDrawingView.java index de7e77022..64ee1f246 100644 --- a/jhotdraw-core/src/main/java/org/jhotdraw/draw/DefaultDrawingView.java +++ b/jhotdraw-core/src/main/java/org/jhotdraw/draw/DefaultDrawingView.java @@ -305,6 +305,7 @@ public DefaultDrawingView() { setTransferHandler(new DefaultDrawingViewTransferHandler()); setBackground(new Color(0xb0b0b0)); setOpaque(true); + setName("drawingCanvas"); // For BDD Testing } protected EventHandler createEventHandler() { diff --git a/jhotdraw-core/src/main/java/org/jhotdraw/draw/DrawingEditor.java b/jhotdraw-core/src/main/java/org/jhotdraw/draw/DrawingEditor.java index 2776955cd..9b5b7c588 100644 --- a/jhotdraw-core/src/main/java/org/jhotdraw/draw/DrawingEditor.java +++ b/jhotdraw-core/src/main/java/org/jhotdraw/draw/DrawingEditor.java @@ -13,6 +13,8 @@ import java.util.*; import javax.swing.ActionMap; import javax.swing.InputMap; + +import org.jhotdraw.draw.tool.BaseTool; import org.jhotdraw.draw.tool.Tool; /** @@ -157,14 +159,14 @@ public interface DrawingEditor { *

* This is a bound property. */ - void setTool(Tool t); + void setTool(BaseTool t); /** * Gets the current tool. *

* This is a bound property. */ - Tool getTool(); + BaseTool getTool(); /** * Sets the cursor on the view(s) of the drawing editor. diff --git a/jhotdraw-core/src/main/java/org/jhotdraw/draw/DrawingEditorProxy.java b/jhotdraw-core/src/main/java/org/jhotdraw/draw/DrawingEditorProxy.java index b7d65bafc..8d47d62e9 100644 --- a/jhotdraw-core/src/main/java/org/jhotdraw/draw/DrawingEditorProxy.java +++ b/jhotdraw-core/src/main/java/org/jhotdraw/draw/DrawingEditorProxy.java @@ -17,6 +17,7 @@ import javax.swing.ActionMap; import javax.swing.InputMap; import org.jhotdraw.beans.AbstractBean; +import org.jhotdraw.draw.tool.BaseTool; import org.jhotdraw.draw.tool.Tool; /** @@ -105,12 +106,12 @@ public DrawingView getFocusedView() { } @Override - public void setTool(Tool t) { + public void setTool(BaseTool t) { target.setTool(t); } @Override - public Tool getTool() { + public BaseTool getTool() { return target.getTool(); } diff --git a/jhotdraw-core/src/main/java/org/jhotdraw/draw/action/AbstractSelectedAction.java b/jhotdraw-core/src/main/java/org/jhotdraw/draw/action/AbstractSelectedAction.java index c6f23dc34..c4b8820b7 100644 --- a/jhotdraw-core/src/main/java/org/jhotdraw/draw/action/AbstractSelectedAction.java +++ b/jhotdraw-core/src/main/java/org/jhotdraw/draw/action/AbstractSelectedAction.java @@ -164,6 +164,8 @@ public void setUpdateEnabledState(boolean newValue) { * Returns true, if this action automatically updates its enabled * state to reflect the enabled state of the active {@code DrawingView}. */ + + //FIXME: Typo public boolean isUpdatEnabledState() { return eventHandler != null; } diff --git a/jhotdraw-core/src/main/java/org/jhotdraw/draw/event/FigureListener.java b/jhotdraw-core/src/main/java/org/jhotdraw/draw/event/FigureListener.java index 235f1157a..486ede776 100644 --- a/jhotdraw-core/src/main/java/org/jhotdraw/draw/event/FigureListener.java +++ b/jhotdraw-core/src/main/java/org/jhotdraw/draw/event/FigureListener.java @@ -36,12 +36,12 @@ public interface FigureListener extends EventListener { /** * Sent when the drawing area used by the figure needs to be repainted. */ - public void areaInvalidated(FigureEvent e); + default void areaInvalidated(FigureEvent e) {} /** * Sent when an attribute of the figure has changed. */ - public void attributeChanged(FigureEvent e); + default void attributeChanged(FigureEvent e) {} /** * Sent when handles of a Figure have been added, removed or replaced. @@ -51,25 +51,25 @@ public interface FigureListener extends EventListener { * A Figure should not fire this event, if just the state or the location * of Handle has changed. */ - public void figureHandlesChanged(FigureEvent e); + default void figureHandlesChanged(FigureEvent e) {} /** * Sent when the geometry (for example the bounds) of the figure has changed. */ - public void figureChanged(FigureEvent e); + default void figureChanged(FigureEvent e) {} /** * Sent when a figure was added to a drawing. */ - public void figureAdded(FigureEvent e); + default void figureAdded(FigureEvent e) {} /** * Sent when a figure was removed from a drawing. */ - public void figureRemoved(FigureEvent e); + void figureRemoved(FigureEvent e); /** * Sent when the figure requests to be removed from a drawing. */ - public void figureRequestRemove(FigureEvent e); + default void figureRequestRemove(FigureEvent e) {} } diff --git a/jhotdraw-core/src/main/java/org/jhotdraw/draw/event/ToolEvent.java b/jhotdraw-core/src/main/java/org/jhotdraw/draw/event/ToolEvent.java index 7ac75849d..b2c060af6 100644 --- a/jhotdraw-core/src/main/java/org/jhotdraw/draw/event/ToolEvent.java +++ b/jhotdraw-core/src/main/java/org/jhotdraw/draw/event/ToolEvent.java @@ -10,6 +10,7 @@ import java.awt.*; import java.util.*; import org.jhotdraw.draw.*; +import org.jhotdraw.draw.tool.BaseTool; import org.jhotdraw.draw.tool.Tool; /** @@ -38,7 +39,7 @@ public class ToolEvent extends EventObject { /** * Creates a new instance. */ - public ToolEvent(Tool src, DrawingView view, Rectangle invalidatedArea) { + public ToolEvent(BaseTool src, DrawingView view, Rectangle invalidatedArea) { super(src); this.view = view; this.invalidatedArea = invalidatedArea; @@ -47,8 +48,8 @@ public ToolEvent(Tool src, DrawingView view, Rectangle invalidatedArea) { /** * Gets the tool which is the source of the event. */ - public Tool getTool() { - return (Tool) getSource(); + public BaseTool getTool() { + return (BaseTool) getSource(); } /** diff --git a/jhotdraw-core/src/main/java/org/jhotdraw/draw/figure/AbstractFigure.java b/jhotdraw-core/src/main/java/org/jhotdraw/draw/figure/AbstractFigure.java index 7602ca32f..ee74a0067 100644 --- a/jhotdraw-core/src/main/java/org/jhotdraw/draw/figure/AbstractFigure.java +++ b/jhotdraw-core/src/main/java/org/jhotdraw/draw/figure/AbstractFigure.java @@ -27,6 +27,7 @@ import org.jhotdraw.draw.handle.BoundsOutlineHandle; import org.jhotdraw.draw.handle.Handle; import org.jhotdraw.draw.handle.ResizeHandleKit; +import org.jhotdraw.draw.tool.BaseTool; import org.jhotdraw.draw.tool.Tool; import org.jhotdraw.geom.Dimension2DDouble; @@ -460,7 +461,7 @@ public Collection getActions(Point2D.Double p) { * Returns null, if no specialized tool is available. */ @Override - public Tool getTool(Point2D.Double p) { + public BaseTool getTool(Point2D.Double p) { return null; } diff --git a/jhotdraw-core/src/main/java/org/jhotdraw/draw/figure/Figure.java b/jhotdraw-core/src/main/java/org/jhotdraw/draw/figure/Figure.java index 2e9b9ab11..12e6c8aa5 100644 --- a/jhotdraw-core/src/main/java/org/jhotdraw/draw/figure/Figure.java +++ b/jhotdraw-core/src/main/java/org/jhotdraw/draw/figure/Figure.java @@ -20,6 +20,7 @@ import org.jhotdraw.draw.connector.Connector; import org.jhotdraw.draw.event.FigureListener; import org.jhotdraw.draw.handle.Handle; +import org.jhotdraw.draw.tool.BaseTool; import org.jhotdraw.draw.tool.Tool; import org.jhotdraw.geom.Dimension2DDouble; @@ -411,7 +412,7 @@ public interface Figure extends Cloneable, Serializable { *

* Returns null, if no specialized tool is available. */ - public Tool getTool(Point2D.Double p); + public BaseTool getTool(Point2D.Double p); /** * Returns a tooltip for the specified location on the figure. diff --git a/jhotdraw-core/src/main/java/org/jhotdraw/draw/figure/LabelFigure.java b/jhotdraw-core/src/main/java/org/jhotdraw/draw/figure/LabelFigure.java index 76b13bbab..f2235e9fa 100644 --- a/jhotdraw-core/src/main/java/org/jhotdraw/draw/figure/LabelFigure.java +++ b/jhotdraw-core/src/main/java/org/jhotdraw/draw/figure/LabelFigure.java @@ -11,22 +11,35 @@ import java.util.*; import org.jhotdraw.draw.event.FigureEvent; import org.jhotdraw.draw.event.FigureListener; +import org.jhotdraw.draw.tool.BaseTool; import org.jhotdraw.draw.tool.TextEditingTool; -import org.jhotdraw.draw.tool.Tool; + /** * A LabelFigure can be used to provide more double clickable area for a * TextHolderFigure. * - * FIXME - Move FigureListener into inner class. * * @author Werner Randelshofer * @version $Id$ */ -public class LabelFigure extends TextFigure implements FigureListener { +public class LabelFigure extends TextFigure { private static final long serialVersionUID = 1L; private TextHolderFigure target; + private final transient EventHandler eventHandler = new EventHandler(); + + + private class EventHandler implements FigureListener { + @Override + public void figureRemoved(FigureEvent e) { + if (e.getFigure() == target) { + target.removeFigureListener(this); + target = null; + } + } + } + /** * Creates a new instance. @@ -40,16 +53,19 @@ public LabelFigure(String text) { setEditable(false); } + public void setLabelFor(TextHolderFigure target) { if (this.target != null) { - this.target.removeFigureListener(this); + this.target.removeFigureListener(eventHandler); } this.target = target; if (this.target != null) { - this.target.addFigureListener(this); + this.target.addFigureListener(eventHandler); } } + + @Override public TextHolderFigure getLabelFor() { return (target == null) ? this : target; @@ -61,37 +77,15 @@ public TextHolderFigure getLabelFor() { * Returns null, if no specialized tool is available. */ @Override - public Tool getTool(Point2D.Double p) { - return (target != null && contains(p)) ? new TextEditingTool(target) : null; + public BaseTool getTool(Point2D.Double coordinate) { + return (target != null && contains(coordinate)) ? new TextEditingTool(target) : null; } - @Override - public void areaInvalidated(FigureEvent e) { - } - @Override - public void attributeChanged(FigureEvent e) { - } - @Override - public void figureAdded(FigureEvent e) { - } - @Override - public void figureChanged(FigureEvent e) { - } - @Override - public void figureRemoved(FigureEvent e) { - if (e.getFigure() == target) { - target.removeFigureListener(this); - target = null; - } - } - @Override - public void figureRequestRemove(FigureEvent e) { - } @Override public void remap(Map oldToNew, boolean disconnectIfNotInMap) { @@ -99,14 +93,12 @@ public void remap(Map oldToNew, boolean disconnectIfNotInMap) { if (target != null) { Figure newTarget = oldToNew.get(target); if (newTarget != null) { - target.removeFigureListener(this); + target.removeFigureListener(eventHandler); target = (TextHolderFigure) newTarget; - newTarget.addFigureListener(this); + newTarget.addFigureListener(eventHandler); } } } - @Override - public void figureHandlesChanged(FigureEvent e) { - } + } diff --git a/jhotdraw-core/src/main/java/org/jhotdraw/draw/figure/TextFigure.java b/jhotdraw-core/src/main/java/org/jhotdraw/draw/figure/TextFigure.java index 43be4010e..9318aa64f 100644 --- a/jhotdraw-core/src/main/java/org/jhotdraw/draw/figure/TextFigure.java +++ b/jhotdraw-core/src/main/java/org/jhotdraw/draw/figure/TextFigure.java @@ -12,6 +12,7 @@ import java.awt.geom.*; import java.io.*; import java.util.*; + import org.jhotdraw.draw.AttributeKeys; import static org.jhotdraw.draw.AttributeKeys.*; import org.jhotdraw.draw.handle.BoundsOutlineHandle; @@ -19,8 +20,8 @@ import org.jhotdraw.draw.handle.Handle; import org.jhotdraw.draw.handle.MoveHandle; import org.jhotdraw.draw.locator.RelativeLocator; +import org.jhotdraw.draw.tool.BaseTool; import org.jhotdraw.draw.tool.TextEditingTool; -import org.jhotdraw.draw.tool.Tool; import org.jhotdraw.geom.Dimension2DDouble; import org.jhotdraw.geom.Geom; import org.jhotdraw.geom.Insets2D; @@ -40,11 +41,12 @@ public class TextFigure extends AbstractAttributedDecoratedFigure implements TextHolderFigure { +private static final int MIN_COLUMN_COUNT = 4; private static final long serialVersionUID = 1L; protected Point2D.Double origin = new Point2D.Double(); protected boolean editable = true; // cache of the TextFigure's layout - transient protected TextLayout textLayout; + protected transient TextLayout textLayout; /** * Creates a new instance. @@ -60,39 +62,41 @@ public TextFigure(String text) { // DRAWING @Override - protected void drawStroke(java.awt.Graphics2D g) { + protected void drawStroke(java.awt.Graphics2D graphics2D) { + // Text Figures are treated as primitives, they do not support adding strokes. } @Override - protected void drawFill(java.awt.Graphics2D g) { + protected void drawFill(java.awt.Graphics2D graphics2D) { + // Text Figures do not have a fill area to draw. } @Override - protected void drawText(java.awt.Graphics2D g) { + protected void drawText(java.awt.Graphics2D canvas) { if (getText() != null || isEditable()) { TextLayout layout = getTextLayout(); - Graphics2D g2 = (Graphics2D) g.create(); + Graphics2D localGraphics = (Graphics2D) canvas.create(); try { //Test if world to screen transformation mirrors the text. If so it tries to //unmirror it. - if (g2.getTransform().getScaleY() * g2.getTransform().getScaleX() < 0) { + if (localGraphics.getTransform().getScaleY() * localGraphics.getTransform().getScaleX() < 0) { AffineTransform at = new AffineTransform(); at.translate(0, origin.y + layout.getAscent() / 2); at.scale(1, -1); at.translate(0, -origin.y - layout.getAscent() / 2); - g2.transform(at); + localGraphics.transform(at); } - layout.draw(g2, (float) origin.x, (float) (origin.y + layout.getAscent())); + layout.draw(localGraphics, (float) origin.x, (float) (origin.y + layout.getAscent())); } finally { - g2.dispose(); + localGraphics.dispose(); } } } // SHAPE AND BOUNDS @Override - public void transform(AffineTransform tx) { - tx.transform(origin, origin); + public void transform(AffineTransform affineTransform) { + affineTransform.transform(origin, origin); } @Override @@ -102,25 +106,22 @@ public void setBounds(Point2D.Double anchor, Point2D.Double lead) { @Override public boolean figureContains(Point2D.Double p) { - if (getBounds().contains(p)) { - return true; - } - return false; + return getBounds().contains(p); } protected TextLayout getTextLayout() { if (textLayout == null) { String text = getText(); - if (text == null || text.length() == 0) { + if (text == null || text.isEmpty()) { text = " "; } - FontRenderContext frc = getFontRenderContext(); + FontRenderContext fontRenderContext = getFontRenderContext(); HashMap textAttributes = new HashMap<>(); textAttributes.put(TextAttribute.FONT, getFont()); - if (get(FONT_UNDERLINE)) { + if (get(FONT_UNDERLINE) != null && get(FONT_UNDERLINE)) { textAttributes.put(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_LOW_ONE_PIXEL); } - textLayout = new TextLayout(text, textAttributes, frc); + textLayout = new TextLayout(text, textAttributes, fontRenderContext); } return textLayout; } @@ -128,15 +129,17 @@ protected TextLayout getTextLayout() { @Override public Rectangle2D.Double getBounds() { TextLayout layout = getTextLayout(); - Rectangle2D.Double r = new Rectangle2D.Double(origin.x, origin.y, layout.getAdvance(), - layout.getAscent() + layout.getDescent()); - return r; + return new Rectangle2D.Double( + origin.x, origin.y, + layout.getAdvance(), + layout.getAscent() + layout.getDescent() + ); } @Override public Dimension2DDouble getPreferredSize() { - Rectangle2D.Double b = getBounds(); - return new Dimension2DDouble(b.width, b.height); + Rectangle2D.Double bounds = getBounds(); + return new Dimension2DDouble(bounds.width, bounds.height); } @Override @@ -154,23 +157,34 @@ protected Rectangle2D.Double getFigureDrawingArea() { return getBounds(); } else { TextLayout layout = getTextLayout(); - Rectangle2D.Double r = new Rectangle2D.Double( + Rectangle2D.Double rectangle = new Rectangle2D.Double( origin.x, origin.y, layout.getAdvance(), layout.getAscent()); Rectangle2D lBounds = layout.getBounds(); if (!lBounds.isEmpty() && !Double.isNaN(lBounds.getX())) { - r.add(new Rectangle2D.Double( + rectangle.add(new Rectangle2D.Double( lBounds.getX() + origin.x, (lBounds.getY() + origin.y + layout.getAscent()), lBounds.getWidth(), lBounds.getHeight())); } // grow by two pixels to take antialiasing into account - Geom.grow(r, 2d, 2d); - return r; + Geom.grow(rectangle, 2d, 2d); + return rectangle; } } + public void restoreTransformTo(Point2D.Double geometry) { + origin.x = geometry.x; + origin.y = geometry.y; + } + + /** + * @deprecated + * Old implementation is not typesafe. + * Use {@link #restoreTransformTo(Point2D.Double)} instead. + */ @Override + @Deprecated public void restoreTransformTo(Object geometry) { Point2D.Double p = (Point2D.Double) geometry; origin.x = p.x; @@ -202,8 +216,7 @@ public void setText(String newText) { @Override public int getTextColumns() { - //return (getText() == null) ? 4 : Math.max(getText().length(), 4); - return 4; + return MIN_COLUMN_COUNT; } /** @@ -241,7 +254,7 @@ public Color getFillColor() { @Override public void setFontSize(float size) { - set(FONT_SIZE, new Double(size)); + set(FONT_SIZE, (double) size); } @Override @@ -255,25 +268,22 @@ public boolean isEditable() { return editable; } - public void setEditable(boolean b) { - this.editable = b; + public void setEditable(boolean editable) { + this.editable = editable; } @Override public Collection createHandles(int detailLevel) { LinkedList handles = new LinkedList<>(); - switch (detailLevel) { - case -1: - handles.add(new BoundsOutlineHandle(this, false, true)); - break; - case 0: - handles.add(new BoundsOutlineHandle(this)); - handles.add(new MoveHandle(this, RelativeLocator.northWest())); - handles.add(new MoveHandle(this, RelativeLocator.northEast())); - handles.add(new MoveHandle(this, RelativeLocator.southWest())); - handles.add(new MoveHandle(this, RelativeLocator.southEast())); - handles.add(new FontSizeHandle(this)); - break; + + if (detailLevel == -1) handles.add(new BoundsOutlineHandle(this, false, true)); + else if (detailLevel == 0) { + handles.add(new BoundsOutlineHandle(this)); + handles.add(new MoveHandle(this, RelativeLocator.northWest())); + handles.add(new MoveHandle(this, RelativeLocator.northEast())); + handles.add(new MoveHandle(this, RelativeLocator.southWest())); + handles.add(new MoveHandle(this, RelativeLocator.southEast())); + handles.add(new FontSizeHandle(this)); } return handles; } @@ -284,10 +294,9 @@ public Collection createHandles(int detailLevel) { * Returns null, if no specialized tool is available. */ @Override - public Tool getTool(Point2D.Double p) { + public BaseTool getTool(Point2D.Double p) { if (isEditable() && contains(p)) { - TextEditingTool t = new TextEditingTool(this); - return t; + return new TextEditingTool(this); } return null; } @@ -309,25 +318,44 @@ protected void validate() { } @Override - public void read(DOMInput in) throws IOException { + public void read(DOMInput input) throws IOException { setBounds( - new Point2D.Double(in.getAttribute("x", 0d), in.getAttribute("y", 0d)), + new Point2D.Double(input.getAttribute("x", 0d), input.getAttribute("y", 0d)), new Point2D.Double(0, 0)); - readAttributes(in); - readDecorator(in); + readAttributes(input); + readDecorator(input); invalidate(); } @Override public void write(DOMOutput out) throws IOException { - Rectangle2D.Double b = getBounds(); - out.addAttribute("x", b.x); - out.addAttribute("y", b.y); + Rectangle2D.Double bounds = getBounds(); + out.addAttribute("x", bounds.x); + out.addAttribute("y", bounds.y); writeAttributes(out); writeDecorator(out); } - @Override + + public static TextFigure createFrom(TextFigure figureToDuplicate) { + TextFigure duplicate = new TextFigure(); + duplicate.setText(figureToDuplicate.getText()); + duplicate.origin = (figureToDuplicate.origin == null) ? null : (Point2D.Double) figureToDuplicate.origin; + duplicate.setAttributes(figureToDuplicate.getAttributes()); + duplicate.textLayout = figureToDuplicate.textLayout; + return duplicate; + } + + + /** + * @deprecated Use {@link #createFrom(TextFigure)} instead. + * This method is kept around for framework compatibility. + * */ + // Using .clone is inherently flawed but unfortunately legacy concerns keep it around. + // I've deprecated it with reference to the copy factory above. + @Deprecated + @Override + @SuppressWarnings("java:S2975") public TextFigure clone() { TextFigure that = (TextFigure) super.clone(); that.origin = (Point2D.Double) this.origin.clone(); diff --git a/jhotdraw-core/src/main/java/org/jhotdraw/draw/text/AbstractEditableFloatingText.java b/jhotdraw-core/src/main/java/org/jhotdraw/draw/text/AbstractEditableFloatingText.java new file mode 100644 index 000000000..a245cb3d5 --- /dev/null +++ b/jhotdraw-core/src/main/java/org/jhotdraw/draw/text/AbstractEditableFloatingText.java @@ -0,0 +1,49 @@ +package org.jhotdraw.draw.text; + +import org.jhotdraw.draw.DrawingView; +import org.jhotdraw.draw.event.FigureAdapter; +import org.jhotdraw.draw.event.FigureEvent; +import org.jhotdraw.draw.event.FigureListener; +import org.jhotdraw.draw.figure.TextHolderFigure; + +import javax.swing.*; +import java.awt.*; + +public abstract class AbstractEditableFloatingText { + private DrawingView view; + private TextHolderFigure editedFigure; + + public abstract JComponent getEditorComponent(); + protected abstract void updateWidget(); + protected abstract DrawingView getDrawingView(); + + + + private FigureListener figureHandler = new FigureAdapter() { + @Override + public void attributeChanged(FigureEvent e) { + updateWidget(); + } + }; + + + /** + * Removes the overlay. + */ + public void endOverlay() { + view = getDrawingView(); + view.getComponent().requestFocus(); + JComponent component = getEditorComponent(); + if (component != null) { + component.setVisible(false); + view.getComponent().remove(component); + Rectangle bounds = component.getBounds(); + view.getComponent().repaint(bounds.x, bounds.y, bounds.width, bounds.height); + } + if (editedFigure != null) { + editedFigure.removeFigureListener(figureHandler); + editedFigure = null; + } + } + +} diff --git a/jhotdraw-core/src/main/java/org/jhotdraw/draw/text/FloatingTextArea.java b/jhotdraw-core/src/main/java/org/jhotdraw/draw/text/FloatingTextArea.java index 57d3ac765..e596e083c 100644 --- a/jhotdraw-core/src/main/java/org/jhotdraw/draw/text/FloatingTextArea.java +++ b/jhotdraw-core/src/main/java/org/jhotdraw/draw/text/FloatingTextArea.java @@ -10,9 +10,8 @@ import org.jhotdraw.draw.figure.TextHolderFigure; import java.awt.*; import java.awt.geom.*; -import javax.swing.BorderFactory; -import javax.swing.JScrollPane; -import javax.swing.JTextArea; +import javax.swing.*; + import org.jhotdraw.draw.*; import org.jhotdraw.draw.event.FigureAdapter; import org.jhotdraw.draw.event.FigureEvent; @@ -39,7 +38,7 @@ * @author Werner Randelshofer * @version $Id: FloatingTextArea.java -1 $ */ -public class FloatingTextArea { +public class FloatingTextArea extends AbstractEditableFloatingText { /** * A scroll pane to allow for vertical scrolling while editing @@ -88,6 +87,17 @@ public void requestFocus() { textArea.requestFocus(); } + @Override + public JComponent getEditorComponent() { + return editScrollContainer; + } + + @Override + protected DrawingView getDrawingView() { + return view; + } + + /** * Creates the overlay for the given Container using a * specific font. @@ -105,6 +115,8 @@ public void createOverlay(DrawingView view, TextHolderFigure figure) { } } + + protected void updateWidget() { Font f = editedFigure.getFont(); // FIXME - Should scale with fractional value! @@ -148,20 +160,4 @@ public Dimension getPreferredSize(int cols) { return new Dimension(textArea.getWidth(), textArea.getHeight()); } - /** - * Removes the overlay. - */ - public void endOverlay() { - view.getComponent().requestFocus(); - if (editScrollContainer != null) { - editScrollContainer.setVisible(false); - view.getComponent().remove(editScrollContainer); - Rectangle bounds = editScrollContainer.getBounds(); - view.getComponent().repaint(bounds.x, bounds.y, bounds.width, bounds.height); - } - if (editedFigure != null) { - editedFigure.removeFigureListener(figureHandler); - editedFigure = null; - } - } } diff --git a/jhotdraw-core/src/main/java/org/jhotdraw/draw/text/FloatingTextField.java b/jhotdraw-core/src/main/java/org/jhotdraw/draw/text/FloatingTextField.java index e2c423d94..d84815ddd 100644 --- a/jhotdraw-core/src/main/java/org/jhotdraw/draw/text/FloatingTextField.java +++ b/jhotdraw-core/src/main/java/org/jhotdraw/draw/text/FloatingTextField.java @@ -43,7 +43,7 @@ * @author Werner Randelshofer * @version $Id: FloatingTextField.java -1 $ */ -public class FloatingTextField { +public class FloatingTextField extends AbstractEditableFloatingText { private TextHolderFigure editedFigure; private JTextField textField; @@ -63,6 +63,16 @@ public void requestFocus() { textField.requestFocus(); } + @Override + public JComponent getEditorComponent() { + return textField; + } + + @Override + protected DrawingView getDrawingView() { + return view; + } + /** * Creates the overlay for the given Container using a * specific font. @@ -79,30 +89,33 @@ public void createOverlay(DrawingView view, TextHolderFigure figure) { updateWidget(); } + protected void updateWidget() { - Font font = editedFigure.getFont(); - font = font.deriveFont(font.getStyle(), (float) (editedFigure.getFontSize() * view.getScaleFactor())); - textField.setFont(font); + if (editedFigure == null) return; + Font fontOnFigure = editedFigure.getFont(); + fontOnFigure = fontOnFigure.deriveFont(fontOnFigure.getStyle(), (float) (editedFigure.getFontSize() * view.getScaleFactor())); + textField.setFont(fontOnFigure); textField.setForeground(editedFigure.getTextColor()); textField.setBackground(editedFigure.getFillColor()); - Rectangle2D.Double fDrawBounds = editedFigure.getBounds(); - Point2D.Double fDrawLoc = new Point2D.Double(fDrawBounds.getX(), fDrawBounds.getY()); + Rectangle2D.Double fieldDrawBounds = editedFigure.getBounds(); + Point2D.Double fieldDrawLocation = new Point2D.Double(fieldDrawBounds.getX(), fieldDrawBounds.getY()); if (editedFigure.get(TRANSFORM) != null) { - editedFigure.get(TRANSFORM).transform(fDrawLoc, fDrawLoc); + editedFigure.get(TRANSFORM).transform(fieldDrawLocation, fieldDrawLocation); } - Point fViewLoc = view.drawingToView(fDrawLoc); - Rectangle fViewBounds = view.drawingToView(fDrawBounds); - fViewBounds.x = fViewLoc.x; - fViewBounds.y = fViewLoc.y; - Dimension tfDim = textField.getPreferredSize(); - Insets tfInsets = textField.getInsets(); - float fontBaseline = textField.getGraphics().getFontMetrics(font).getMaxAscent(); - double fBaseline = editedFigure.getBaseline() * view.getScaleFactor(); + Point fieldViewLocation = view.drawingToView(fieldDrawLocation); + Rectangle fieldViewBounds = view.drawingToView(fieldDrawBounds); + fieldViewBounds.x = fieldViewLocation.x; + fieldViewBounds.y = fieldViewLocation.y; + Dimension textFieldDimensions = textField.getPreferredSize(); + Insets textFieldInsets = textField.getInsets(); + if (textField.getGraphics() == null) return; + float fontBaseline = textField.getGraphics().getFontMetrics(fontOnFigure).getMaxAscent(); + double fieldBaseline = editedFigure.getBaseline() * view.getScaleFactor(); textField.setBounds( - fViewBounds.x - tfInsets.left, - fViewBounds.y - tfInsets.top - (int) (fontBaseline - fBaseline), - Math.max(fViewBounds.width + tfInsets.left + tfInsets.right, tfDim.width), - Math.max(fViewBounds.height + tfInsets.top + tfInsets.bottom, tfDim.height) + fieldViewBounds.x - textFieldInsets.left, + fieldViewBounds.y - textFieldInsets.top - (int) (fontBaseline - fieldBaseline), + Math.max(fieldViewBounds.width + textFieldInsets.left + textFieldInsets.right, textFieldDimensions.width), + Math.max(fieldViewBounds.height + textFieldInsets.top + textFieldInsets.bottom, textFieldDimensions.height) ); } @@ -139,20 +152,5 @@ public Dimension getPreferredSize(int cols) { return textField.getPreferredSize(); } - /** - * Removes the overlay. - */ - public void endOverlay() { - view.getComponent().requestFocus(); - if (textField != null) { - textField.setVisible(false); - view.getComponent().remove(textField); - Rectangle bounds = textField.getBounds(); - view.getComponent().repaint(bounds.x, bounds.y, bounds.width, bounds.height); - } - if (editedFigure != null) { - editedFigure.removeFigureListener(figureHandler); - editedFigure = null; - } - } + } diff --git a/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/AbstractCreationTool.java b/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/AbstractCreationTool.java new file mode 100644 index 000000000..821e736c3 --- /dev/null +++ b/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/AbstractCreationTool.java @@ -0,0 +1,170 @@ +package org.jhotdraw.draw.tool; + +import org.jhotdraw.draw.AttributeKey; +import org.jhotdraw.draw.DrawingEditor; +import org.jhotdraw.draw.DrawingView; +import org.jhotdraw.draw.figure.CompositeFigure; +import org.jhotdraw.draw.figure.Figure; +import org.jhotdraw.util.ResourceBundleUtil; + +import java.awt.*; +import java.awt.event.MouseEvent; +import java.awt.geom.Point2D; +import java.util.Map; + + + +public abstract class AbstractCreationTool extends BaseToolImpl implements ClickListeningTool { + + protected boolean isWorking; + protected Point anchor = new Point(); + protected String presentationName; + + + /** + * Attributes to be applied to the created ConnectionFigure. These attributes override the + * default attributes of the DrawingEditor. + */ + protected Map, Object> prototypeAttributes; + /** + * The prototype for new figures. + */ + protected Figure prototype; + /** + * The created figure. + */ + protected Figure createdFigure; + /** + * If this is set to false, the CreationTool does not fire toolDone after a new Figure has been + * created. This allows to create multiple figures consecutively. + */ + private boolean isToolDoneAfterCreation = true; + + public AbstractCreationTool(Figure prototype, Map, Object> prototypeAttributes, String name) { + super(); + this.prototype = prototype; + this.prototypeAttributes = prototypeAttributes; + + if (name == null) { + ResourceBundleUtil labels = ResourceBundleUtil.getBundle("org.jhotdraw.draw.Labels"); + name = labels.getString("edit.createFigure.text"); + } + } + + public AbstractCreationTool(Figure prototype) { + this(prototype, null, null); + } + + + public AbstractCreationTool(String prototypeClassName, Map, Object> attributes, String name) { + try { + this.prototype = (Figure) Class.forName(prototypeClassName).newInstance(); + } catch (Exception e) { + InternalError error = new InternalError("Unable to create Figure from " + prototypeClassName); + error.initCause(e); + throw error; + } + this.prototypeAttributes = attributes; + if (name == null) { + ResourceBundleUtil labels = ResourceBundleUtil.getBundle("org.jhotdraw.draw.Labels"); + name = labels.getString("edit.createFigure.text"); + } + this.presentationName = name; + } + + public AbstractCreationTool(Figure prototype, Map, Object> attributes) { + this(prototype, attributes, null); + } + + public Figure getPrototype() { + return prototype; + } + + @Override + public void activate(DrawingEditor editor) { + super.activate(editor); + if (getView() != null) { + getView().setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR)); + } + } + + @Override + public void deactivate(DrawingEditor editor) { + super.deactivate(editor); + if (getView() != null) { + getView().setCursor(Cursor.getDefaultCursor()); + } + if (createdFigure != null) { + if (createdFigure instanceof CompositeFigure) { + ((CompositeFigure) createdFigure).layout(); + } + createdFigure = null; + } + } + + + @Override + public void mouseReleased(MouseEvent e) { + isWorking = false; + } + + + + @SuppressWarnings("unchecked") + protected Figure createFigure() { + Figure f = prototype.clone(); + getEditor().applyDefaultAttributesTo(f); + if (prototypeAttributes != null) { + for (Map.Entry, Object> entry : prototypeAttributes.entrySet()) { + f.set((AttributeKey) entry.getKey(), entry.getValue()); + } + } + return f; + } + + protected Figure getCreatedFigure() { + return createdFigure; + } + + protected Figure getAddedFigure() { + return createdFigure; + } + + /** + * This method allows subclasses to do perform additonal user interactions after the new figure + * has been created. The implementation of this class just invokes fireToolDone. + */ + protected void creationFinished(Figure createdFigure) { + if (createdFigure.isSelectable()) { + getView().addToSelection(createdFigure); + } + if (isToolDoneAfterCreation()) { + fireToolDone(); + } + } + + /** + * If this is set to false, the CreationTool does not fire toolDone after a new Figure has been + * created. This allows to create multiple figures consecutively. + */ + public void setToolDoneAfterCreation(boolean newValue) { + boolean oldValue = isToolDoneAfterCreation; + isToolDoneAfterCreation = newValue; + } + + /** + * Returns true, if this tool fires toolDone immediately after a new figure has been created. + */ + public boolean isToolDoneAfterCreation() { + return isToolDoneAfterCreation; + } + + @Override + public void updateCursor(DrawingView view, Point p) { + if (view.isEnabled()) { + view.setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR)); + } else { + view.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + } + } +} diff --git a/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/AbstractTool.java b/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/AbstractTool.java index a9599de68..1e3fe67eb 100644 --- a/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/AbstractTool.java +++ b/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/AbstractTool.java @@ -36,175 +36,29 @@ * @author Werner Randelshofer * @version $Id$ */ -public abstract class AbstractTool extends AbstractBean implements Tool { +public abstract class AbstractTool extends BaseToolImpl implements Tool { private static final long serialVersionUID = 1L; - /** - * This is set to true, if this is the active tool of the editor. - */ - private boolean isActive; /** * This is set to true, while the tool is doing some work. This prevents the currentView from * being changed when a mouseEnter event is received. */ protected boolean isWorking; - protected DrawingEditor editor; protected Point anchor = new Point(); - protected EventListenerList listenerList = new EventListenerList(); - private DrawingEditorProxy editorProxy; /* private PropertyChangeListener editorHandler; private PropertyChangeListener viewHandler; */ - /** - * The input map of the tool. - */ - private InputMap inputMap; - /** - * The action map of the tool. - */ - private ActionMap actionMap; /** * Creates a new instance. */ public AbstractTool() { - editorProxy = new DrawingEditorProxy(); + super(); setInputMap(createInputMap()); setActionMap(createActionMap()); } - public void addUndoableEditListener(UndoableEditListener l) { - listenerList.add(UndoableEditListener.class, l); - } - - public void removeUndoableEditListener(UndoableEditListener l) { - listenerList.remove(UndoableEditListener.class, l); - } - - @Override - public void activate(DrawingEditor editor) { - this.editor = editor; - editorProxy.setTarget(editor); - isActive = true; - // Repaint all handles - for (DrawingView v : editor.getDrawingViews()) { - v.repaintHandles(); - } - } - - @Override - public void deactivate(DrawingEditor editor) { - this.editor = editor; - editorProxy.setTarget(null); - isActive = false; - } - - public boolean isActive() { - return isActive; - } - - protected DrawingView getView() { - return editor.getActiveView(); - } - - protected DrawingEditor getEditor() { - return editor; - } - - protected Drawing getDrawing() { - return getView().getDrawing(); - } - - protected Point2D.Double viewToDrawing(Point p) { - return constrainPoint(getView().viewToDrawing(p)); - } - - protected Point2D.Double constrainPoint(Point p, Figure... figure) { - return constrainPoint(getView().viewToDrawing(p), figure); - } - - protected Point2D.Double constrainPoint(Point2D.Double p, Figure... figure) { - if (getView() == null) { - return p; - } - return getView().getConstrainer() == null ? p : getView().getConstrainer().constrainPoint(p, figure); - } - - /** - * Sets the InputMap for the Tool. - * - * @see #keyPressed - * @see #setActionMap - */ - public void setInputMap(InputMap newValue) { - inputMap = newValue; - } - - /** - * Gets the input map of the Tool - */ - public InputMap getInputMap() { - return inputMap; - } - - /** - * Sets the ActionMap for the Tool. - * - * @see #keyPressed - */ - public void setActionMap(ActionMap newValue) { - actionMap = newValue; - } - - /** - * Gets the action map of the Tool - */ - public ActionMap getActionMap() { - return actionMap; - } - - /** - * Deletes the selection. Depending on the tool, this could be selected figures, selected points - * or selected text. - */ - @Override - public void editDelete() { - getView().getDrawing().removeAll(getView().getSelectedFigures()); - } - - /** - * Cuts the selection into the clipboard. Depending on the tool, this could be selected figures, - * selected points or selected text. - */ - @Override - public void editCut() { - } - - /** - * Copies the selection into the clipboard. Depending on the tool, this could be selected - * figures, selected points or selected text. - */ - @Override - public void editCopy() { - } - - /** - * Duplicates the selection. Depending on the tool, this could be selected figures, selected - * points or selected text. - */ - @Override - public void editDuplicate() { - } - - /** - * Pastes the contents of the clipboard. Depending on the tool, this could be selected figures, - * selected points or selected text. - */ - @Override - public void editPaste() { - } - @Override public void keyReleased(KeyEvent evt) { fireToolDone(); @@ -259,26 +113,6 @@ public void keyPressed(KeyEvent evt) { } } - /** - * Override this method to create a tool-specific input map, which overrides the input map of - * the drawing edtior. - *

- * The implementation of this class returns null. - */ - protected InputMap createInputMap() { - return null; - } - - /** - * Override this method to create a tool-specific action map, which overrides the action map of - * the drawing edtior. - *

- * The implementation of this class returns null. - */ - protected ActionMap createActionMap() { - return null; - } - @Override public void mouseClicked(MouseEvent evt) { } @@ -312,171 +146,4 @@ public void mouseReleased(MouseEvent evt) { isWorking = false; } - @Override - public void addToolListener(ToolListener l) { - listenerList.add(ToolListener.class, l); - } - - @Override - public void removeToolListener(ToolListener l) { - listenerList.remove(ToolListener.class, l); - } - - /** - * Notify all listenerList that have registered interest for notification on this event type. - */ - protected void fireToolStarted(DrawingView view) { - ToolEvent 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] == ToolListener.class) { - // Lazily create the event: - if (event == null) { - event = new ToolEvent(this, view, new Rectangle(0, 0, -1, -1)); - } - ((ToolListener) listeners[i + 1]).toolStarted(event); - } - } - } - - /** - * Notify all listenerList that have registered interest for notification on this event type. - */ - protected void fireToolDone() { - ToolEvent 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] == ToolListener.class) { - // Lazily create the event: - if (event == null) { - event = new ToolEvent(this, getView(), new Rectangle(0, 0, -1, -1)); - } - ((ToolListener) listeners[i + 1]).toolDone(event); - } - } - } - - /** - * Notify all listenerList that have registered interest for notification on this event type. - */ - protected void fireAreaInvalidated(Rectangle2D.Double r) { - Point p1 = getView().drawingToView(new Point2D.Double(r.x, r.y)); - Point p2 = getView().drawingToView(new Point2D.Double(r.x + r.width, r.y + r.height)); - fireAreaInvalidated( - new Rectangle(p1.x, p1.y, p2.x - p1.x, p2.y - p1.y)); - } - - /** - * Notify all listenerList that have registered interest for notification on this event type. - */ - protected void fireAreaInvalidated(Rectangle invalidatedArea) { - ToolEvent 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] == ToolListener.class) { - // Lazily create the event: - if (event == null) { - event = new ToolEvent(this, getView(), invalidatedArea); - } - ((ToolListener) listeners[i + 1]).areaInvalidated(event); - } - } - } - - /** - * Notify all listenerList that have registered interest for notification on this event type. - * - * Note: This method only fires an event, if the invalidated area is outside of the canvas - * bounds. - */ - protected void maybeFireBoundsInvalidated(Rectangle invalidatedArea) { - Drawing d = getDrawing(); - Rectangle2D.Double canvasBounds = new Rectangle2D.Double(0, 0, 0, 0); - if (d.get(CANVAS_WIDTH) != null) { - canvasBounds.width += d.get(CANVAS_WIDTH); - } - if (d.get(CANVAS_HEIGHT) != null) { - canvasBounds.height += d.get(CANVAS_HEIGHT); - } - if (!canvasBounds.contains(invalidatedArea)) { - fireBoundsInvalidated(invalidatedArea); - } - } - - /** - * Notify all listenerList that have registered interest for notification on this event type. - */ - protected void fireBoundsInvalidated(Rectangle invalidatedArea) { - ToolEvent 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] == ToolListener.class) { - // Lazily create the event: - if (event == null) { - event = new ToolEvent(this, getView(), invalidatedArea); - } - ((ToolListener) listeners[i + 1]).boundsInvalidated(event); - } - } - } - - @Override - public void draw(Graphics2D g) { - } - - public void updateCursor(DrawingView view, Point p) { - if (view.isEnabled()) { - Handle handle = view.findHandle(p); - if (handle != null) { - view.setCursor(handle.getCursor()); - } else { - Figure figure = view.findFigure(p); - Point2D.Double point = view.viewToDrawing(p); - Drawing drawing = view.getDrawing(); - while (figure != null && !figure.isSelectable()) { - figure = drawing.findFigureBehind(point, figure); - } - if (figure != null) { - view.setCursor(figure.getCursor(view.viewToDrawing(p))); - } else { - view.setCursor(Cursor.getDefaultCursor()); - } - } - } else { - view.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - } - } - - @Override - public String getToolTipText(DrawingView view, MouseEvent evt) { - return null; - } - - /** - * Returns true, if this tool lets the user interact with handles. - *

- * Handles may draw differently, if interaction is not possible. - * - * @return True, if this tool supports interaction with the handles. - */ - @Override - public boolean supportsHandleInteraction() { - return false; - } } diff --git a/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/BaseTool.java b/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/BaseTool.java new file mode 100644 index 000000000..a481dca04 --- /dev/null +++ b/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/BaseTool.java @@ -0,0 +1,90 @@ +package org.jhotdraw.draw.tool; + +import org.jhotdraw.draw.DrawingEditor; +import org.jhotdraw.draw.DrawingView; +import org.jhotdraw.draw.event.ToolListener; + +import java.awt.*; +import java.awt.event.MouseEvent; + +public interface BaseTool { + /** + * Activates the tool for the given editor. This method is called + * whenever the user switches to this tool. + */ + void activate(DrawingEditor editor); + + /** + * Deactivates the tool. This method is called whenever the user + * switches to another tool. + */ + void deactivate(DrawingEditor editor); + + /** + * Adds a listener for this tool. + */ + void addToolListener(ToolListener l); + + /** + * Removes a listener for this tool. + */ + void removeToolListener(ToolListener l); + + /** + * Draws the tool. + */ + void draw(Graphics2D g); + + /** + * Deletes the selection. + * Depending on the tool, this could be selected figures, selected points + * or selected text. + */ + void editDelete(); + + /** + * Cuts the selection into the clipboard. + * Depending on the tool, this could be selected figures, selected points + * or selected text. + */ + void editCut(); + + /** + * Copies the selection into the clipboard. + * Depending on the tool, this could be selected figures, selected points + * or selected text. + */ + void editCopy(); + + /** + * Duplicates the selection. + * Depending on the tool, this could be selected figures, selected points + * or selected text. + */ + void editDuplicate(); + + /** + * Pastes the contents of the clipboard. + * Depending on the tool, this could be selected figures, selected points + * or selected text. + */ + void editPaste(); + + /** + * Returns the tooltip text for a mouse event on a drawing view. + * + * @param view A drawing view. + * @param evt A mouse event. + * @return A tooltip text or null. + */ + String getToolTipText(DrawingView view, MouseEvent evt); + + /** + * Returns true, if this tool lets the user interact with handles. + *

+ * Handles may draw differently, if interaction is not possible. + * + * @return True, if this tool supports interaction with the handles. + */ + boolean supportsHandleInteraction(); +} diff --git a/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/BaseToolImpl.java b/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/BaseToolImpl.java new file mode 100644 index 000000000..fd8a2c9a3 --- /dev/null +++ b/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/BaseToolImpl.java @@ -0,0 +1,361 @@ +package org.jhotdraw.draw.tool; + +import org.jhotdraw.beans.AbstractBean; +import org.jhotdraw.draw.Drawing; +import org.jhotdraw.draw.DrawingEditor; +import org.jhotdraw.draw.DrawingEditorProxy; +import org.jhotdraw.draw.DrawingView; +import org.jhotdraw.draw.event.ToolEvent; +import org.jhotdraw.draw.event.ToolListener; +import org.jhotdraw.draw.figure.Figure; +import org.jhotdraw.draw.handle.Handle; + +import javax.swing.*; +import javax.swing.event.EventListenerList; +import javax.swing.event.UndoableEditListener; +import java.awt.*; +import java.awt.event.MouseEvent; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; + +import static org.jhotdraw.draw.AttributeKeys.CANVAS_HEIGHT; +import static org.jhotdraw.draw.AttributeKeys.CANVAS_WIDTH; + +public class BaseToolImpl extends AbstractBean implements BaseTool { + protected DrawingEditor editor; + protected EventListenerList listenerList = new EventListenerList(); + protected DrawingEditorProxy editorProxy; + /** + * The input map of the tool. + */ + protected InputMap inputMap; + /** + * The action map of the tool. + */ + protected ActionMap actionMap; + /** + * This is set to true, if this is the active tool of the editor. + */ + private boolean isActive; + + public BaseToolImpl() { + editorProxy = new DrawingEditorProxy(); + } + + public void addUndoableEditListener(UndoableEditListener l) { + listenerList.add(UndoableEditListener.class, l); + } + + public void removeUndoableEditListener(UndoableEditListener l) { + listenerList.remove(UndoableEditListener.class, l); + } + + public void activate(DrawingEditor editor) { + this.editor = editor; + editorProxy.setTarget(editor); + isActive = true; + // Repaint all handles + for (DrawingView v : editor.getDrawingViews()) { + v.repaintHandles(); + } + } + + public void deactivate(DrawingEditor editor) { + this.editor = editor; + editorProxy.setTarget(null); + isActive = false; + } + + public boolean isActive() { + return isActive; + } + + protected DrawingView getView() { + return editor.getActiveView(); + } + + protected DrawingEditor getEditor() { + return editor; + } + + protected Drawing getDrawing() { + return getView().getDrawing(); + } + + protected Point2D.Double viewToDrawing(Point p) { + return constrainPoint(getView().viewToDrawing(p)); + } + + protected Point2D.Double constrainPoint(Point p, Figure... figure) { + return constrainPoint(getView().viewToDrawing(p), figure); + } + + protected Point2D.Double constrainPoint(Point2D.Double p, Figure... figure) { + if (getView() == null) { + return p; + } + return getView().getConstrainer() == null ? p : getView().getConstrainer().constrainPoint(p, figure); + } + + /** + * Sets the InputMap for the Tool. + * + * @see #keyPressed + * @see #setActionMap + */ + public void setInputMap(InputMap newValue) { + inputMap = newValue; + } + + /** + * Gets the input map of the Tool + */ + public InputMap getInputMap() { + return inputMap; + } + + /** + * Sets the ActionMap for the Tool. + * + * @see #keyPressed + */ + public void setActionMap(ActionMap newValue) { + actionMap = newValue; + } + + /** + * Gets the action map of the Tool + */ + public ActionMap getActionMap() { + return actionMap; + } + + /** + * Deletes the selection. Depending on the tool, this could be selected figures, selected points + * or selected text. + */ + public void editDelete() { + getView().getDrawing().removeAll(getView().getSelectedFigures()); + } + + /** + * Cuts the selection into the clipboard. Depending on the tool, this could be selected figures, + * selected points or selected text. + */ + public void editCut() { + } + + + protected DrawingView prepareView(MouseEvent event) { + DrawingView view = editor.findView((Container) event.getSource()); + if (view != null) { + view.requestFocus(); + fireToolStarted(view); + } + return view; + } + + /** + * Copies the selection into the clipboard. Depending on the tool, this could be selected + * figures, selected points or selected text. + */ + public void editCopy() { + } + + /** + * Duplicates the selection. Depending on the tool, this could be selected figures, selected + * points or selected text. + */ + public void editDuplicate() { + } + + /** + * Pastes the contents of the clipboard. Depending on the tool, this could be selected figures, + * selected points or selected text. + */ + public void editPaste() { + } + + /** + * Override this method to create a tool-specific input map, which overrides the input map of + * the drawing edtior. + *

+ * The implementation of this class returns null. + */ + protected InputMap createInputMap() { + return null; + } + + /** + * Override this method to create a tool-specific action map, which overrides the action map of + * the drawing edtior. + *

+ * The implementation of this class returns null. + */ + protected ActionMap createActionMap() { + return null; + } + + public void addToolListener(ToolListener l) { + listenerList.add(ToolListener.class, l); + } + + public void removeToolListener(ToolListener l) { + listenerList.remove(ToolListener.class, l); + } + + /** + * Notify all listenerList that have registered interest for notification on this event type. + */ + protected void fireToolStarted(DrawingView view) { + ToolEvent 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] == ToolListener.class) { + // Lazily create the event: + if (event == null) { + event = new ToolEvent(this, view, new Rectangle(0, 0, -1, -1)); + } + ((ToolListener) listeners[i + 1]).toolStarted(event); + } + } + } + + /** + * Notify all listenerList that have registered interest for notification on this event type. + */ + protected void fireToolDone() { + ToolEvent 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] == ToolListener.class) { + // Lazily create the event: + if (event == null) { + event = new ToolEvent(this, getView(), new Rectangle(0, 0, -1, -1)); + } + ((ToolListener) listeners[i + 1]).toolDone(event); + } + } + } + + /** + * Notify all listenerList that have registered interest for notification on this event type. + */ + protected void fireAreaInvalidated(Rectangle2D.Double r) { + Point p1 = getView().drawingToView(new Point2D.Double(r.x, r.y)); + Point p2 = getView().drawingToView(new Point2D.Double(r.x + r.width, r.y + r.height)); + fireAreaInvalidated( + new Rectangle(p1.x, p1.y, p2.x - p1.x, p2.y - p1.y)); + } + + /** + * Notify all listenerList that have registered interest for notification on this event type. + */ + protected void fireAreaInvalidated(Rectangle invalidatedArea) { + ToolEvent 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] == ToolListener.class) { + // Lazily create the event: + if (event == null) { + event = new ToolEvent(this, getView(), invalidatedArea); + } + ((ToolListener) listeners[i + 1]).areaInvalidated(event); + } + } + } + + /** + * Notify all listenerList that have registered interest for notification on this event type. + *

+ * Note: This method only fires an event, if the invalidated area is outside of the canvas + * bounds. + */ + protected void maybeFireBoundsInvalidated(Rectangle invalidatedArea) { + Drawing d = getDrawing(); + Rectangle2D.Double canvasBounds = new Rectangle2D.Double(0, 0, 0, 0); + if (d.get(CANVAS_WIDTH) != null) { + canvasBounds.width += d.get(CANVAS_WIDTH); + } + if (d.get(CANVAS_HEIGHT) != null) { + canvasBounds.height += d.get(CANVAS_HEIGHT); + } + if (!canvasBounds.contains(invalidatedArea)) { + fireBoundsInvalidated(invalidatedArea); + } + } + + /** + * Notify all listenerList that have registered interest for notification on this event type. + */ + protected void fireBoundsInvalidated(Rectangle invalidatedArea) { + ToolEvent 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] == ToolListener.class) { + // Lazily create the event: + if (event == null) { + event = new ToolEvent(this, getView(), invalidatedArea); + } + ((ToolListener) listeners[i + 1]).boundsInvalidated(event); + } + } + } + + public void draw(Graphics2D g) { + } + + public void updateCursor(DrawingView view, Point p) { + if (view.isEnabled()) { + Handle handle = view.findHandle(p); + if (handle != null) { + view.setCursor(handle.getCursor()); + } else { + Figure figure = view.findFigure(p); + Point2D.Double point = view.viewToDrawing(p); + Drawing drawing = view.getDrawing(); + while (figure != null && !figure.isSelectable()) { + figure = drawing.findFigureBehind(point, figure); + } + if (figure != null) { + view.setCursor(figure.getCursor(view.viewToDrawing(p))); + } else { + view.setCursor(Cursor.getDefaultCursor()); + } + } + } else { + view.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + } + } + + public String getToolTipText(DrawingView view, MouseEvent evt) { + return null; + } + + /** + * Returns true, if this tool lets the user interact with handles. + *

+ * Handles may draw differently, if interaction is not possible. + * + * @return True, if this tool supports interaction with the handles. + */ + public boolean supportsHandleInteraction() { + return false; + } +} diff --git a/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/ClickListeningTool.java b/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/ClickListeningTool.java new file mode 100644 index 000000000..b617e0354 --- /dev/null +++ b/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/ClickListeningTool.java @@ -0,0 +1,12 @@ +package org.jhotdraw.draw.tool; + +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; + +public interface ClickListeningTool extends MouseListener, BaseTool { + @Override default void mousePressed(MouseEvent e) {} + @Override default void mouseReleased(MouseEvent e) {} + @Override default void mouseClicked(MouseEvent e) {} + @Override default void mouseEntered(MouseEvent e) {} + @Override default void mouseExited(MouseEvent e) {} +} diff --git a/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/CreationTool.java b/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/CreationTool.java index c7e07706f..cde8b5d1f 100644 --- a/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/CreationTool.java +++ b/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/CreationTool.java @@ -12,12 +12,15 @@ import java.awt.*; import java.awt.event.*; import java.awt.geom.*; +import java.lang.reflect.InvocationTargetException; import java.util.*; import javax.swing.undo.*; import org.jhotdraw.draw.*; +import org.jhotdraw.draw.figure.ImageHolderFigure; import org.jhotdraw.util.*; /** + * @deprecated * A {@link Tool} to create a new figure by drawing its bounds. The figure to be created is * specified by a prototype. *

@@ -53,14 +56,11 @@ * @author Werner Randelshofer * @version $Id$ */ -public class CreationTool extends AbstractTool { + +@Deprecated() +public class CreationTool extends ExtendedMouseCreationTool implements DragableTool { private static final long serialVersionUID = 1L; - /** - * Attributes to be applied to the created ConnectionFigure. These attributes override the - * default attributes of the DrawingEditor. - */ - protected Map, Object> prototypeAttributes; /** * A localized name for this tool. The presentationName is displayed by the UndoableEdit. */ @@ -73,19 +73,6 @@ public class CreationTool extends AbstractTool { * We set the figure to this minimal size, if it is smaller than the minimal size treshold. */ protected Dimension minimalSize = new Dimension(40, 40); - /** - * The prototype for new figures. - */ - protected Figure prototype; - /** - * The created figure. - */ - protected Figure createdFigure; - /** - * If this is set to false, the CreationTool does not fire toolDone after a new Figure has been - * created. This allows to create multiple figures consecutively. - */ - private boolean isToolDoneAfterCreation = true; /** * Creates a new instance. @@ -98,20 +85,38 @@ public CreationTool(String prototypeClassName, Map, Object> attr this(prototypeClassName, attributes, null); } + public CreationTool(String prototypeClassName, Map, Object> attributes, String name) { + super(createInstance(prototypeClassName),attributes, name); + } + + public CreationTool(Figure prototype, Map, Object> attributes) { + super(prototype, attributes); + } + + private static Figure createInstance(String className) { try { - this.prototype = (Figure) Class.forName(prototypeClassName).newInstance(); - } catch (Exception e) { - InternalError error = new InternalError("Unable to create Figure from " + prototypeClassName); - error.initCause(e); - throw error; - } - this.prototypeAttributes = attributes; - if (name == null) { - ResourceBundleUtil labels = ResourceBundleUtil.getBundle("org.jhotdraw.draw.Labels"); - name = labels.getString("edit.createFigure.text"); + return (Figure) Class.forName(className).getDeclaredConstructor().newInstance(); + } catch (ClassNotFoundException e) { + throw new IllegalArgumentException("Class " + className + " not found"); + } catch (InstantiationException | IllegalAccessException e) { + throw new IllegalStateException("Failed to instantiate figure: " + className, e); + } catch (ClassCastException e) { + throw new IllegalArgumentException("Class " + className + " is not a figure"); + } catch (NoSuchMethodException e) { + throw new IllegalArgumentException("No such constructor: " + className, e); + } catch (InvocationTargetException e) { + + Throwable cause = e.getCause(); + + if (cause instanceof RuntimeException) + throw (RuntimeException) cause; + if (cause instanceof Error) + throw (Error) cause; + + // TODO: Make Tool Exception class + throw new RuntimeException("Failed to instantiate figure: " + className, e); } - this.presentationName = name; } /** @@ -123,7 +128,7 @@ public CreationTool(String prototypeClassName, Map, Object> attr * @param prototype The prototype used to create a new Figure. */ public CreationTool(Figure prototype) { - this(prototype, null, null); + super(prototype, null, null); } /** @@ -136,70 +141,9 @@ public CreationTool(Figure prototype) { * @param attributes The CreationTool applies these attributes to the prototype after having * applied the default attributes from the DrawingEditor. */ - public CreationTool(Figure prototype, Map, Object> attributes) { - this(prototype, attributes, null); - } - /** - * Creates a new instance with the specified prototype and attribute set. - * - * @param prototype The prototype used to create a new Figure. - * @param attributes The CreationTool applies these attributes to the prototype after having - * applied the default attributes from the DrawingEditor. - * @param name The name parameter is currently not used. - * @deprecated This constructor might go away, because the name parameter is not used. - */ - @Deprecated - public CreationTool(Figure prototype, Map, Object> attributes, String name) { - this.prototype = prototype; - this.prototypeAttributes = attributes; - if (name == null) { - ResourceBundleUtil labels = ResourceBundleUtil.getBundle("org.jhotdraw.draw.Labels"); - name = labels.getString("edit.createFigure.text"); - } - this.presentationName = name; - } - public Figure getPrototype() { - return prototype; - } - - @Override - public void activate(DrawingEditor editor) { - super.activate(editor); - if (getView() != null) { - getView().setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR)); - } - } - - @Override - public void deactivate(DrawingEditor editor) { - super.deactivate(editor); - if (getView() != null) { - getView().setCursor(Cursor.getDefaultCursor()); - } - if (createdFigure != null) { - if (createdFigure instanceof CompositeFigure) { - ((CompositeFigure) createdFigure).layout(); - } - createdFigure = null; - } - } - @Override - public void mousePressed(MouseEvent evt) { - super.mousePressed(evt); - if (getView() == null) { - return; - } - getView().clearSelection(); - createdFigure = createFigure(); - Point2D.Double p = constrainPoint(viewToDrawing(anchor), createdFigure); - anchor.x = evt.getX(); - anchor.y = evt.getY(); - createdFigure.setBounds(p, p); - getDrawing().add(createdFigure); - } @Override public void mouseDragged(MouseEvent evt) { @@ -213,6 +157,11 @@ public void mouseDragged(MouseEvent evt) { } } + @Override + public void mouseMoved(MouseEvent e) { + // Intentional No-Op: Legacy Code. Legacy parent implemented this, new parent does not. + } + @Override public void mouseReleased(MouseEvent evt) { if (createdFigure != null) { @@ -272,61 +221,4 @@ public void redo() throws CannotRedoException { } } - @SuppressWarnings("unchecked") - protected Figure createFigure() { - Figure f = prototype.clone(); - getEditor().applyDefaultAttributesTo(f); - if (prototypeAttributes != null) { - for (Map.Entry, Object> entry : prototypeAttributes.entrySet()) { - f.set((AttributeKey) entry.getKey(), entry.getValue()); - } - } - return f; - } - - protected Figure getCreatedFigure() { - return createdFigure; - } - - protected Figure getAddedFigure() { - return createdFigure; - } - - /** - * This method allows subclasses to do perform additonal user interactions after the new figure - * has been created. The implementation of this class just invokes fireToolDone. - */ - protected void creationFinished(Figure createdFigure) { - if (createdFigure.isSelectable()) { - getView().addToSelection(createdFigure); - } - if (isToolDoneAfterCreation()) { - fireToolDone(); - } - } - - /** - * If this is set to false, the CreationTool does not fire toolDone after a new Figure has been - * created. This allows to create multiple figures consecutively. - */ - public void setToolDoneAfterCreation(boolean newValue) { - boolean oldValue = isToolDoneAfterCreation; - isToolDoneAfterCreation = newValue; - } - - /** - * Returns true, if this tool fires toolDone immediately after a new figure has been created. - */ - public boolean isToolDoneAfterCreation() { - return isToolDoneAfterCreation; - } - - @Override - public void updateCursor(DrawingView view, Point p) { - if (view.isEnabled()) { - view.setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR)); - } else { - view.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - } - } } diff --git a/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/DelegationSelectionTool.java b/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/DelegationSelectionTool.java index 9490be884..32aee7808 100644 --- a/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/DelegationSelectionTool.java +++ b/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/DelegationSelectionTool.java @@ -298,7 +298,7 @@ protected void handleDoubleClick(MouseEvent evt) { if (DEBUG) { System.out.println("DelegationSelectionTool.handleDoubleClick by figure"); } - Tool figureTool = figure.getTool(p); + BaseTool figureTool = figure.getTool(p); if (figureTool == null) { figure = getDrawing().findFigureInside(p); if (figure != null) { @@ -307,7 +307,8 @@ protected void handleDoubleClick(MouseEvent evt) { } if (figureTool != null) { setTracker(figureTool); - figureTool.mousePressed(evt); + if (figureTool instanceof ClickListeningTool) + ((ClickListeningTool) figureTool).mousePressed(evt); } else { if (outerFigure.handleMouseClick(p, evt, getView())) { v.clearSelection(); diff --git a/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/DragableTool.java b/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/DragableTool.java new file mode 100644 index 000000000..f9a34e3bd --- /dev/null +++ b/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/DragableTool.java @@ -0,0 +1,6 @@ +package org.jhotdraw.draw.tool; + +import java.awt.event.MouseMotionListener; + +public interface DragableTool extends MouseMotionListener, BaseTool { +} diff --git a/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/ExtendedMouseCreationTool.java b/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/ExtendedMouseCreationTool.java new file mode 100644 index 000000000..aed57816b --- /dev/null +++ b/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/ExtendedMouseCreationTool.java @@ -0,0 +1,41 @@ +package org.jhotdraw.draw.tool; + +import org.jhotdraw.draw.AttributeKey; +import org.jhotdraw.draw.DrawingView; +import org.jhotdraw.draw.figure.Figure; + +import java.awt.*; +import java.awt.event.MouseEvent; +import java.awt.geom.Point2D; +import java.util.Map; + +public abstract class ExtendedMouseCreationTool extends AbstractCreationTool { + + @Override + public void mousePressed(MouseEvent evt) { + DrawingView view = prepareView(evt); + if (view == null) return; + if (getView() == null) { + return; + } + + anchor = new Point(evt.getX(), evt.getY()); + isWorking = true; + + getView().clearSelection(); + createdFigure = createFigure(); + Point2D.Double p = constrainPoint(viewToDrawing(anchor), createdFigure); + anchor.x = evt.getX(); + anchor.y = evt.getY(); + createdFigure.setBounds(p, p); + getDrawing().add(createdFigure); + } + + public ExtendedMouseCreationTool(Figure prototype, Map, Object> prototypeAttributes, String name) { + super(prototype, prototypeAttributes, name); + } + + public ExtendedMouseCreationTool(Figure prototype, Map, Object> attributes) { + super(prototype, attributes); + } +} diff --git a/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/KeyListeningTool.java b/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/KeyListeningTool.java new file mode 100644 index 000000000..5167251f2 --- /dev/null +++ b/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/KeyListeningTool.java @@ -0,0 +1,20 @@ +package org.jhotdraw.draw.tool; + +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.awt.event.MouseEvent; + +public interface KeyListeningTool extends KeyListener, BaseTool { + @Override + default void keyTyped(KeyEvent e) { + } + + @Override + default void keyPressed(KeyEvent e) { + } + + @Override + default void keyReleased(KeyEvent e) { + } + +} \ No newline at end of file diff --git a/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/SelectionTool.java b/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/SelectionTool.java index 50f6c47e0..cbdfce4b7 100644 --- a/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/SelectionTool.java +++ b/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/SelectionTool.java @@ -56,7 +56,7 @@ public class SelectionTool extends AbstractTool { /** * The tracker encapsulates the current state of the SelectionTool. */ - private Tool tracker; + private BaseTool tracker; /** * The tracker encapsulates the current state of the SelectionTool. */ @@ -161,59 +161,68 @@ public void deactivate(DrawingEditor editor) { @Override public void keyPressed(KeyEvent e) { if (getView() != null && getView().isEnabled()) { - tracker.keyPressed(e); + if (tracker instanceof KeyListeningTool) + ((KeyListeningTool) tracker).keyPressed(e); } } @Override public void keyReleased(KeyEvent evt) { if (getView() != null && getView().isEnabled()) { - tracker.keyReleased(evt); + if (tracker instanceof KeyListeningTool) + ((KeyListeningTool) tracker).keyReleased(evt); } } @Override public void keyTyped(KeyEvent evt) { if (getView() != null && getView().isEnabled()) { - tracker.keyTyped(evt); + if (tracker instanceof KeyListeningTool) + ((KeyListeningTool) tracker).keyTyped(evt); } } @Override public void mouseClicked(MouseEvent evt) { if (getView() != null && getView().isEnabled()) { - tracker.mouseClicked(evt); + if (tracker instanceof ClickListeningTool) + ((ClickListeningTool) tracker).mouseClicked(evt); } } @Override public void mouseDragged(MouseEvent evt) { if (getView() != null && getView().isEnabled()) { - tracker.mouseDragged(evt); + if (tracker instanceof DragableTool) + ((DragableTool) tracker).mouseDragged(evt); } } @Override public void mouseEntered(MouseEvent evt) { super.mouseEntered(evt); - tracker.mouseEntered(evt); + if (tracker instanceof ClickListeningTool) + ((ClickListeningTool) tracker).mouseEntered(evt); } @Override public void mouseExited(MouseEvent evt) { super.mouseExited(evt); - tracker.mouseExited(evt); + if (tracker instanceof ClickListeningTool) + ((ClickListeningTool) tracker).mouseExited(evt); } @Override public void mouseMoved(MouseEvent evt) { - tracker.mouseMoved(evt); + if (tracker instanceof DragableTool) + ((DragableTool) tracker).mouseMoved(evt); } @Override public void mouseReleased(MouseEvent evt) { if (getView() != null && getView().isEnabled()) { - tracker.mouseReleased(evt); + if (tracker instanceof ClickListeningTool) + ((ClickListeningTool) tracker).mouseReleased(evt); } } @@ -286,11 +295,12 @@ public void mousePressed(MouseEvent evt) { if (newTracker != null) { setTracker(newTracker); } - tracker.mousePressed(evt); + if (tracker instanceof ClickListeningTool) + ((ClickListeningTool) tracker).mousePressed(evt); } } - protected void setTracker(Tool newTracker) { + protected void setTracker(BaseTool newTracker) { if (tracker != null) { tracker.deactivate(getEditor()); tracker.removeToolListener(trackerHandler); diff --git a/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/SimpleCreationTool.java b/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/SimpleCreationTool.java new file mode 100644 index 000000000..5c8175082 --- /dev/null +++ b/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/SimpleCreationTool.java @@ -0,0 +1,47 @@ +package org.jhotdraw.draw.tool; + +import org.jhotdraw.draw.AttributeKey; +import org.jhotdraw.draw.DrawingView; +import org.jhotdraw.draw.figure.Figure; + +import java.awt.*; +import java.awt.event.MouseEvent; +import java.awt.geom.Point2D; +import java.util.Map; + +public class SimpleCreationTool extends AbstractCreationTool { + + public SimpleCreationTool(Figure prototype) { + super(prototype); + } + + public SimpleCreationTool(Figure prototype, Map, Object> attributes, String name) { + super(prototype, attributes, name); + } + + public SimpleCreationTool(Figure prototype, Map, Object> attributes) { + super(prototype, attributes); + } + + @Override + public void mouseClicked(MouseEvent event) { + DrawingView view = (getView() == null) ? null : prepareView(event); + if (view == null) return; + + Point anchor = new Point(event.getX(), event.getY()); + createdFigure = createFigure(); + + Point2D.Double p = constrainPoint(viewToDrawing(anchor), createdFigure); + createdFigure.setBounds(p,p); + + getDrawing().add(createdFigure); + view.clearSelection(); + view.addToSelection(createdFigure); + } + + + @Override + public void mouseReleased(MouseEvent event) { + super.mouseReleased(event); + } +} diff --git a/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/TextCreationTool.java b/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/TextCreationTool.java index d6477493c..a4cb8b52d 100644 --- a/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/TextCreationTool.java +++ b/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/TextCreationTool.java @@ -12,11 +12,9 @@ import java.awt.*; import java.awt.event.*; import java.util.*; -import javax.swing.undo.AbstractUndoableEdit; -import javax.swing.undo.UndoableEdit; import org.jhotdraw.draw.*; import org.jhotdraw.draw.text.*; -import org.jhotdraw.util.ResourceBundleUtil; +import org.jhotdraw.draw.undo.TextEdit; /** * A tool to create figures which implement the {@code TextHolderFigure} @@ -56,12 +54,14 @@ * @author Werner Randelshofer * @version $Id$ */ -public class TextCreationTool extends CreationTool implements ActionListener { +public class TextCreationTool extends SimpleCreationTool implements ActionListener, ClickListeningTool, KeyListeningTool { private static final long serialVersionUID = 1L; - private FloatingTextField textField; + private transient FloatingTextField textField; private TextHolderFigure typingTarget; + + /** * Creates a new instance. */ @@ -78,39 +78,39 @@ public TextCreationTool(TextHolderFigure prototype, Map, Object> @Override public void deactivate(DrawingEditor editor) { - endEdit(); + if (typingTarget != null) { + endEdit(); + } super.deactivate(editor); } /** - * Creates a new figure at the location where the mouse was pressed. + * If the created figure is a TextHolderFigure it can be edited. */ + @Override - public void mousePressed(MouseEvent e) { - // Note: The search sequence used here, must be - // consistent with the search sequence used by the - // HandleTracker, SelectAreaTracker, DelegationSelectionTool, SelectionTool. + public void mouseClicked(MouseEvent event) { + if (typingTarget != null) { endEdit(); - if (isToolDoneAfterCreation()) { - fireToolDone(); - } - } else { - super.mousePressed(e); - // update view so the created figure is drawn before the floating text - // figure is overlaid. - TextHolderFigure textHolder = (TextHolderFigure) getCreatedFigure(); - getView().clearSelection(); - getView().addToSelection(textHolder); - beginEdit(textHolder); - updateCursor(getView(), e.getPoint()); + fireToolDone(); + return; + } + + super.mouseClicked(event); + + if (createdFigure instanceof TextHolderFigure) { + beginEdit((TextHolderFigure) createdFigure); } } + @Override - public void mouseDragged(java.awt.event.MouseEvent e) { + protected void fireToolDone() { + super.fireToolDone(); } + @SuppressWarnings("Duplicates") protected void beginEdit(TextHolderFigure textHolder) { if (textField == null) { textField = new FloatingTextField(); @@ -121,12 +121,10 @@ protected void beginEdit(TextHolderFigure textHolder) { } textField.createOverlay(getView(), textHolder); textField.requestFocus(); + textField.getEditorComponent().addKeyListener(this); typingTarget = textHolder; } - @Override - public void mouseReleased(MouseEvent evt) { - } protected void endEdit() { if (typingTarget != null) { @@ -134,59 +132,34 @@ protected void endEdit() { final TextHolderFigure editedFigure = typingTarget; final String oldText = typingTarget.getText(); final String newText = textField.getText(); - if (newText.length() > 0) { + if (!newText.isEmpty()) { typingTarget.setText(newText); } else { - if (createdFigure != null) { getDrawing().remove(getAddedFigure()); // XXX - Fire undoable edit here!! - } else { + typingTarget.willChange(); typingTarget.setText(""); typingTarget.changed(); - } } - UndoableEdit edit = new AbstractUndoableEdit() { - private static final long serialVersionUID = 1L; - - @Override - public String getPresentationName() { - ResourceBundleUtil labels = ResourceBundleUtil.getBundle("org.jhotdraw.draw.Labels"); - return labels.getString("attribute.text.text"); - } - - @Override - public void undo() { - super.undo(); - editedFigure.willChange(); - editedFigure.setText(oldText); - editedFigure.changed(); - } - - @Override - public void redo() { - super.redo(); - editedFigure.willChange(); - editedFigure.setText(newText); - editedFigure.changed(); - } - }; - getDrawing().fireUndoableEditHappened(edit); + + TextEdit.createAndFireEditHappened(getDrawing(), editedFigure, oldText, newText); typingTarget.changed(); typingTarget = null; textField.endOverlay(); } - // view().checkDamage(); } + + @Override - public void keyReleased(KeyEvent evt) { - if (evt.getKeyCode() == KeyEvent.VK_ESCAPE || isToolDoneAfterCreation()) { + public void keyReleased(KeyEvent keyEvent) { + if (keyEvent.getKeyCode() == KeyEvent.VK_ESCAPE) { fireToolDone(); } } @Override - public void actionPerformed(ActionEvent event) { + public void actionPerformed(ActionEvent actionEvent) { endEdit(); if (isToolDoneAfterCreation()) { fireToolDone(); @@ -204,11 +177,11 @@ public boolean isEditing() { } @Override - public void updateCursor(DrawingView view, Point p) { - if (view.isEnabled()) { - view.setCursor(Cursor.getPredefinedCursor(isEditing() ? Cursor.DEFAULT_CURSOR : Cursor.CROSSHAIR_CURSOR)); + public void updateCursor(DrawingView drawingView, Point point) { + if (drawingView.isEnabled()) { + drawingView.setCursor(Cursor.getPredefinedCursor(isEditing() ? Cursor.DEFAULT_CURSOR : Cursor.CROSSHAIR_CURSOR)); } else { - view.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + drawingView.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); } } } diff --git a/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/TextEditingTool.java b/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/TextEditingTool.java index b9455715c..7976752ae 100644 --- a/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/TextEditingTool.java +++ b/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/TextEditingTool.java @@ -10,11 +10,9 @@ import org.jhotdraw.draw.figure.TextHolderFigure; import java.awt.*; import java.awt.event.*; -import javax.swing.undo.AbstractUndoableEdit; -import javax.swing.undo.UndoableEdit; import org.jhotdraw.draw.*; import org.jhotdraw.draw.text.*; -import org.jhotdraw.util.ResourceBundleUtil; +import org.jhotdraw.draw.undo.TextEdit; /** * A tool to edit figures which implement the {@code TextHolderFigure} interface, @@ -44,10 +42,10 @@ * @author Werner Randelshofer * @version $Id$ */ -public class TextEditingTool extends AbstractTool implements ActionListener { +public class TextEditingTool extends BaseToolImpl implements ActionListener, ClickListeningTool, KeyListeningTool { private static final long serialVersionUID = 1L; - private FloatingTextField textField; + transient FloatingTextField textField; // Visibility slightly relaxed for integration tests. private TextHolderFigure typingTarget; /** @@ -74,6 +72,9 @@ public void mousePressed(MouseEvent e) { } } + @SuppressWarnings("Duplicates") + // "Duplication is far cheaper than the wrong abstraction." - Sandi Metz + // Both have different reasons to change, so we keep both. protected void beginEdit(TextHolderFigure textHolder) { if (textField == null) { textField = new FloatingTextField(); @@ -84,12 +85,10 @@ protected void beginEdit(TextHolderFigure textHolder) { } textField.createOverlay(getView(), textHolder); textField.requestFocus(); + textField.getEditorComponent().addKeyListener(this); typingTarget = textHolder; } - @Override - public void mouseReleased(MouseEvent evt) { - } protected void endEdit() { if (typingTarget != null) { @@ -97,42 +96,16 @@ protected void endEdit() { final TextHolderFigure editedFigure = typingTarget; final String oldText = typingTarget.getText(); final String newText = textField.getText(); - if (newText.length() > 0) { + if (!newText.isEmpty()) { typingTarget.willChange(); typingTarget.setText(newText); typingTarget.changed(); } - UndoableEdit edit = new AbstractUndoableEdit() { - private static final long serialVersionUID = 1L; - - @Override - public String getPresentationName() { - ResourceBundleUtil labels = ResourceBundleUtil.getBundle("org.jhotdraw.draw.Labels"); - return labels.getString("attribute.text.text"); - } - - @Override - public void undo() { - super.undo(); - editedFigure.willChange(); - editedFigure.setText(oldText); - editedFigure.changed(); - } - - @Override - public void redo() { - super.redo(); - editedFigure.willChange(); - editedFigure.setText(newText); - editedFigure.changed(); - } - }; - getDrawing().fireUndoableEditHappened(edit); + TextEdit.createAndFireEditHappened(getDrawing(), editedFigure, oldText, newText); typingTarget.changed(); typingTarget = null; textField.endOverlay(); } - // view().checkDamage(); } @Override @@ -161,8 +134,5 @@ public void updateCursor(DrawingView view, Point p) { } } - @Override - public void mouseDragged(MouseEvent e) { - throw new UnsupportedOperationException("Not supported yet."); - } + } diff --git a/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/Tool.java b/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/Tool.java index 2fed1c394..a130f84b1 100644 --- a/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/Tool.java +++ b/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/Tool.java @@ -7,12 +7,11 @@ */ package org.jhotdraw.draw.tool; -import java.awt.*; -import java.awt.event.*; import org.jhotdraw.draw.*; import org.jhotdraw.draw.event.ToolListener; /** + * @deprecated * A tool defines an editing mode of a {@link DrawingEditor}. *

* Tools are used for user interaction. Unlike figures, a tool works with @@ -73,85 +72,7 @@ * @author Werner Randelshofer * @version $Id$ */ -public interface Tool extends MouseListener, MouseMotionListener, KeyListener { - - /** - * Activates the tool for the given editor. This method is called - * whenever the user switches to this tool. - */ - public void activate(DrawingEditor editor); - - /** - * Deactivates the tool. This method is called whenever the user - * switches to another tool. - */ - public void deactivate(DrawingEditor editor); - - /** - * Adds a listener for this tool. - */ - void addToolListener(ToolListener l); - - /** - * Removes a listener for this tool. - */ - void removeToolListener(ToolListener l); - - /** - * Draws the tool. - */ - void draw(Graphics2D g); - - /** - * Deletes the selection. - * Depending on the tool, this could be selected figures, selected points - * or selected text. - */ - public void editDelete(); - - /** - * Cuts the selection into the clipboard. - * Depending on the tool, this could be selected figures, selected points - * or selected text. - */ - public void editCut(); - - /** - * Copies the selection into the clipboard. - * Depending on the tool, this could be selected figures, selected points - * or selected text. - */ - public void editCopy(); - - /** - * Duplicates the selection. - * Depending on the tool, this could be selected figures, selected points - * or selected text. - */ - public void editDuplicate(); - - /** - * Pastes the contents of the clipboard. - * Depending on the tool, this could be selected figures, selected points - * or selected text. - */ - public void editPaste(); - - /** - * Returns the tooltip text for a mouse event on a drawing view. - * - * @param view A drawing view. - * @param evt A mouse event. - * @return A tooltip text or null. - */ - public String getToolTipText(DrawingView view, MouseEvent evt); - - /** - * Returns true, if this tool lets the user interact with handles. - *

- * Handles may draw differently, if interaction is not possible. - * - * @return True, if this tool supports interaction with the handles. - */ - public boolean supportsHandleInteraction(); +@Deprecated +public interface Tool extends BaseTool, ClickListeningTool, DragableTool, KeyListeningTool { + // Legacy God Tool to maintain compatibility } diff --git a/jhotdraw-core/src/main/java/org/jhotdraw/draw/undo/TextEdit.java b/jhotdraw-core/src/main/java/org/jhotdraw/draw/undo/TextEdit.java new file mode 100644 index 000000000..2a5c8929f --- /dev/null +++ b/jhotdraw-core/src/main/java/org/jhotdraw/draw/undo/TextEdit.java @@ -0,0 +1,49 @@ +package org.jhotdraw.draw.undo; + +import org.jhotdraw.draw.Drawing; +import org.jhotdraw.draw.figure.TextHolderFigure; +import org.jhotdraw.util.ResourceBundleUtil; + +import javax.swing.undo.AbstractUndoableEdit; + +public class TextEdit extends AbstractUndoableEdit { + + private TextHolderFigure editedFigure; + private String oldText; + private String newText; + + public TextEdit(TextHolderFigure figure, String oldText, String newText) { + this.editedFigure = figure; + this.oldText = oldText; + this.newText = newText; + } + + private static final long serialVersionUID = 1L; + + @Override + public String getPresentationName() { + ResourceBundleUtil labels = ResourceBundleUtil.getBundle("org.jhotdraw.draw.Labels"); + return labels.getString("attribute.text.text"); + } + + public static void createAndFireEditHappened(Drawing drawing, TextHolderFigure figure, String oldText, String newText) { + TextEdit edit = new TextEdit(figure, oldText, newText); + drawing.fireUndoableEditHappened(edit); + } + + @Override + public void undo() { + super.undo(); + editedFigure.willChange(); + editedFigure.setText(oldText); + editedFigure.changed(); + } + + @Override + public void redo() { + super.redo(); + editedFigure.willChange(); + editedFigure.setText(newText); + editedFigure.changed(); + } +} diff --git a/jhotdraw-core/src/test/java/org/jhotdraw/draw/DefaultDrawingEditorTest.java b/jhotdraw-core/src/test/java/org/jhotdraw/draw/DefaultDrawingEditorTest.java new file mode 100644 index 000000000..538f416ee --- /dev/null +++ b/jhotdraw-core/src/test/java/org/jhotdraw/draw/DefaultDrawingEditorTest.java @@ -0,0 +1,30 @@ +package org.jhotdraw.draw; + +import org.junit.After; +import org.junit.Before; + +import java.awt.event.KeyListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseListener; +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +public class DefaultDrawingEditorTest { + private MouseListener mouseListener; + private KeyListener keyListener; + private MouseListener combinedInterface; + + + + @Before + public void setUp() throws Exception { + mouseListener = mock(MouseListener.class); + + } + + + + @After + public void tearDown() throws Exception { + } +} \ No newline at end of file diff --git a/jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/TextFigureTest.java b/jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/TextFigureTest.java new file mode 100644 index 000000000..2da085fac --- /dev/null +++ b/jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/TextFigureTest.java @@ -0,0 +1,54 @@ +package org.jhotdraw.draw.figure; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.awt.font.TextLayout; +import java.awt.geom.Point2D; + +import static org.junit.Assert.*; + +public class TextFigureTest { + private TextFigure textFigure; + + @Before + public void setUp() throws Exception { + textFigure = new TextFigure(); + } + + @After + public void tearDown() throws Exception { + } + + + @Test + public void testDuplicationFactory() { + TextFigure original = new TextFigure(); + original.setText("Tell me. For whom do you fight?"); + + TextLayout layout = original.getTextLayout(); + + TextFigure duplicate = TextFigure.createFrom(original); + + assertEquals("Text should be the same","Tell me. For whom do you fight?", duplicate.getText()); + assertSame("Layout should be the same", duplicate.getTextLayout(), layout); + } + + + @Test + public void testTypesafeRestoreTransformTo() { + + Point2D.Double point = new Point2D.Double(3760, 3760); + + textFigure.restoreTransformTo(point); + + assertEquals(3760, textFigure.origin.x, 0.00001); + assertEquals(3760, textFigure.origin.y, 0.00001); + + } + + + + +} \ No newline at end of file diff --git a/jhotdraw-core/src/test/java/org/jhotdraw/draw/stages/GivenDrawingCanvas.java b/jhotdraw-core/src/test/java/org/jhotdraw/draw/stages/GivenDrawingCanvas.java new file mode 100644 index 000000000..e2612fcfc --- /dev/null +++ b/jhotdraw-core/src/test/java/org/jhotdraw/draw/stages/GivenDrawingCanvas.java @@ -0,0 +1,43 @@ +package org.jhotdraw.draw.stages; + +import org.assertj.swing.core.GenericTypeMatcher; +import org.assertj.swing.edt.GuiActionRunner; +import org.jhotdraw.draw.figure.TextFigure; + +import javax.swing.*; +import java.awt.geom.Point2D; + +public class GivenDrawingCanvas extends JHotDrawStage { + + public GivenDrawingCanvas the_text_tool_is_selected() { + + AbstractButton textButton = (AbstractButton) window.robot().finder().findByName("textToolButton",true); + + window.robot().click(textButton); + + return self(); + + } + + public GivenDrawingCanvas the_selection_tool_is_selected() { + + AbstractButton textButton = (AbstractButton) window.robot().finder().findByName("selectionToolButton",true); + + window.robot().click(textButton); + + return self(); + + } + + public GivenDrawingCanvas a_text_figure_exists_on_the_canvas() { + TextFigure figure = new TextFigure("Text"); + + figure.setBounds(new Point2D.Double(100,100), new Point2D.Double(100,100)); + + GuiActionRunner.execute(() -> { + drawingView.getDrawing().add(figure); + }); + return self(); + } + +} diff --git a/jhotdraw-core/src/test/java/org/jhotdraw/draw/stages/JHotDrawStage.java b/jhotdraw-core/src/test/java/org/jhotdraw/draw/stages/JHotDrawStage.java new file mode 100644 index 000000000..32480c921 --- /dev/null +++ b/jhotdraw-core/src/test/java/org/jhotdraw/draw/stages/JHotDrawStage.java @@ -0,0 +1,23 @@ +package org.jhotdraw.draw.stages; + +import com.tngtech.jgiven.Stage; +import org.assertj.swing.core.Robot; +import org.assertj.swing.fixture.FrameFixture; +import org.jhotdraw.draw.DrawingEditor; +import org.jhotdraw.draw.DrawingView; + +public class JHotDrawStage> extends Stage { + + protected static FrameFixture window; + protected static Robot robot; + protected static DrawingEditor drawingEditor; + protected static DrawingView drawingView; + + public void setFixtures(FrameFixture fixture, Robot robotInstance, DrawingEditor drawingEditor, DrawingView drawingView) { + window = fixture; + robot = robotInstance; + drawingEditor = drawingEditor; + drawingView = drawingView; + } + +} diff --git a/jhotdraw-core/src/test/java/org/jhotdraw/draw/stages/ThenDrawingState.java b/jhotdraw-core/src/test/java/org/jhotdraw/draw/stages/ThenDrawingState.java new file mode 100644 index 000000000..811ceb238 --- /dev/null +++ b/jhotdraw-core/src/test/java/org/jhotdraw/draw/stages/ThenDrawingState.java @@ -0,0 +1,130 @@ +package org.jhotdraw.draw.stages; + + +import com.tngtech.jgiven.annotation.ExpectedScenarioState; +import com.tngtech.jgiven.annotation.ScenarioState; +import org.assertj.swing.exception.ComponentLookupException; +import org.jhotdraw.draw.Drawing; +import org.jhotdraw.draw.DrawingView; +import org.jhotdraw.draw.figure.Figure; +import org.jhotdraw.draw.figure.TextFigure; +import org.jhotdraw.draw.figure.TextHolderFigure; + +import javax.swing.text.JTextComponent; +import java.awt.*; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; +import java.util.ArrayList; +import java.util.List; + +import static org.assertj.core.api.Assertions.*; + +public class ThenDrawingState extends JHotDrawStage { + + private DrawingView drawingView; + + public ThenDrawingState setView(DrawingView view) { + this.drawingView = view; + return self(); + } + + public ThenDrawingState a_new_text_figure_should_exist() { + Drawing drawing = drawingView.getDrawing(); + assertThat(drawing.getChildCount()) + .as("Check that at least one figure exists on the canvas") + .isGreaterThan(0); + return self(); + } + + public ThenDrawingState the_figure_text_should_be(String expectedText) { + Figure f = drawingView.getSelectedFigures().iterator().next(); + if (f instanceof TextHolderFigure) + assertThat(((TextHolderFigure) f).getText()).isEqualTo(expectedText); + else + fail("Selected figure does not hold text"); + return self(); + } + + public ThenDrawingState the_figure_at_index_should_have_text(int index, String expectedText) { + TextFigure figure = (TextFigure) drawingView.getDrawing().getChild(index); + + assertThat(figure.getText()) + .as("Verify content of figure") + .isEqualTo(expectedText); + return self(); + } + + public Point2D.Double get_figure_anchor() { + Figure figure = drawingView.getDrawing().getChildren().get(0); + return figure.getStartPoint(); + } + + public ThenDrawingState the_figure_should_exist_at(int x, int y) { + List

children = drawingView.getDrawing().getChildren(); + Figure figure = children.get(children.size() - 1); + + Point2D.Double currentPoint = figure.getStartPoint(); + + assertThat(currentPoint.x).isCloseTo(x, within(1.0)); + assertThat(currentPoint.y).isCloseTo(y, within(1.0)); + + return self(); + } + + public ThenDrawingState the_canvas_should_be_empty() { + assertThat(drawingView.getDrawing().getChildCount()) + .as("Check that no figures exist") + .isEqualTo(0); + return self(); + } + + public ThenDrawingState the_figure_should_be_selected() { + List
figures = new ArrayList<>(drawingView.getDrawing().getChildren()); + Figure newestFigure = figures.get(figures.size() - 1); + + assertThat(drawingView.getSelectedFigures()) + .as("Check that there's only a text figure selected") + .hasSize(1) + .allMatch(f -> f instanceof TextHolderFigure, "Should be a TextFigure"); + + return self(); + } + + public ThenDrawingState the_figure_should_be_in_edit_mode() { + Container viewContainer = (Container) drawingView.getComponent(); + + + window.robot().waitForIdle(); + + Component canvas = window.robot().finder().findByName("drawingCanvas", true); + + try { + window.robot().finder().find((Container) canvas, c -> + c instanceof JTextComponent && c.isShowing() + ); + } catch (ComponentLookupException e) { + fail("There should be a floating text editor visible on the canvas"); + } + + return self(); + } + + public ThenDrawingState the_figure_should_not_be_in_edit_mode() { + window.robot().waitForIdle(); + Container canvas = (Container) window.robot().finder().findByName("drawingCanvas", true); + + // Verify NO JTextComponent is currently showing on the canvas + boolean found = false; + for (Component c : canvas.getComponents()) { + if (c instanceof javax.swing.text.JTextComponent && c.isShowing()) { + found = true; + break; + } + } + + assertThat(found).as("The floating text editor should be closed").isFalse(); + return self(); + } + + +} diff --git a/jhotdraw-core/src/test/java/org/jhotdraw/draw/tool/BaseToolImplTest.java b/jhotdraw-core/src/test/java/org/jhotdraw/draw/tool/BaseToolImplTest.java new file mode 100644 index 000000000..9b42c9994 --- /dev/null +++ b/jhotdraw-core/src/test/java/org/jhotdraw/draw/tool/BaseToolImplTest.java @@ -0,0 +1,61 @@ +package org.jhotdraw.draw.tool; + +import org.jhotdraw.draw.DefaultDrawingEditor; +import org.jhotdraw.draw.DrawingEditor; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.awt.event.KeyListener; +import java.awt.event.MouseListener; +import java.awt.event.MouseMotionListener; + + +import static org.junit.Assert.*; +import static org.mockito.Mockito.mock; + +public class BaseToolImplTest { + private BaseToolImpl tool; + private DrawingEditor editor; + + @Before + public void setUp() throws Exception { + tool = new BaseToolImpl(); + editor = mock(DrawingEditor.class); + } + + @Test + public void testIsInitiallyInactive() { + assertFalse(tool.isActive()); + } + + @Test + public void testIsNotFatInterface() { + + + assertFalse("BaseTool should not be a MouseListener", tool instanceof MouseListener); + assertFalse("BaseTool should not be a MouseMotionListener", tool instanceof MouseMotionListener); + assertFalse("BaseTool should not be a KeyListener", tool instanceof KeyListener); + } + + @Test + public void testActivationLifecycle() { + + assertFalse("Tool should start inactive", tool.isActive()); + + tool.activate(editor); + assertTrue("Tool should be active after being activated", tool.isActive()); + + assertEquals("Tool should know its editor", editor, tool.getEditor()); + + tool.deactivate(editor); + assertFalse("Tool should be inactive after being deactivated", tool.isActive()); + + + } + + + @After + public void tearDown() throws Exception { + } +} \ No newline at end of file diff --git a/jhotdraw-core/src/test/java/org/jhotdraw/draw/tool/TextCreationToolTest.java b/jhotdraw-core/src/test/java/org/jhotdraw/draw/tool/TextCreationToolTest.java new file mode 100644 index 000000000..f716f0587 --- /dev/null +++ b/jhotdraw-core/src/test/java/org/jhotdraw/draw/tool/TextCreationToolTest.java @@ -0,0 +1,117 @@ +package org.jhotdraw.draw.tool; + +import org.jhotdraw.draw.*; +import org.jhotdraw.draw.figure.Figure; +import org.jhotdraw.draw.figure.TextFigure; +import org.jhotdraw.draw.figure.TextHolderFigure; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.MockMakers; +import org.mockito.MockitoAnnotations; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.KeyListener; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.awt.event.MouseMotionListener; +import java.awt.geom.Point2D; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +public class TextCreationToolTest { + private TextCreationTool tool; + private DrawingEditor editor; + private TextHolderFigure figure; + private Drawing drawing; + DrawingView view; + + @Before + public void setUp() throws Exception { + // Mockito cannot mock this under JDK 25 for whatever reason without this. + figure = mock(TextFigure.class); + tool = new TextCreationTool(figure); + view = mock(DefaultDrawingView.class); + editor = mock(DrawingEditor.class); + drawing = mock(Drawing.class); + when(editor.getActiveView()).thenReturn(view); + when(view.getDrawing()).thenReturn(drawing); + when(view.viewToDrawing(any(Point.class))).thenReturn(new Point2D.Double(10,10)); + } + + @Test + public void testImplementsNecessaryContracts() { + // If this is not true, something has gone terribly wrong. + assertNotNull(tool); + assertTrue(tool instanceof MouseListener); + assertTrue(tool instanceof KeyListener); + assertFalse(tool instanceof MouseMotionListener); + } + + @Test + public void testHandleMouseClicks() { + tool.activate(editor); + + MouseEvent event = mock(MouseEvent.class); + + tool.mouseClicked(event); + + verify(editor, atLeastOnce()).getActiveView(); + } + + + @Test + public void testToolUsesCorrectPrototype() { + TextFigure figure = mock(TextFigure.class); + when(figure.getText()).thenReturn("Test Text"); + TextCreationTool tool = new TextCreationTool(figure); + + Figure prototype = tool.getPrototype(); + + assertTrue(prototype instanceof TextHolderFigure); + assertEquals("Test Text", ((TextHolderFigure) prototype).getText()); + } + + @Test + public void testMouseClickedCreatesAndAddsFigure() { + + // One could argue whether this would have been better with a real instance + // rather than mocking this much surface area, but it would also introduce involving Swing a lot more. + // The focus is on testing the text creation tool, not on accounting for Swing. + MouseEvent event = mock(MouseEvent.class); + when(event.getPoint()).thenReturn(new Point(10,10)); + when(figure.clone()).thenReturn(mock(TextFigure.class)); + when(event.getClickCount()).thenReturn(1); + when(event.getSource()).thenReturn(view); + when(view.getComponent()).thenReturn((JComponent) view); + when(view.viewToDrawing(any(Point.class))).thenReturn(new Point2D.Double(10,10)); + when(view.isEnabled()).thenReturn(true); + when(editor.findView(any(Container.class))).thenReturn(view); + + TextCreationTool spyTool = spy(tool); + // This integration test is about proving the figure is being created and added. Not whether it begins editing. + doNothing().when(spyTool).beginEdit(any(TextHolderFigure.class)); + + + + spyTool.activate(editor); + + spyTool.mouseClicked(event); + + verify(figure, atLeastOnce()).clone(); + verify(drawing).add(any(TextHolderFigure.class)); + + spyTool.deactivate(editor); + + + + } + + + @After + public void tearDown() throws Exception { + } +} \ No newline at end of file diff --git a/jhotdraw-core/src/test/java/org/jhotdraw/draw/tool/TextEditingToolTest.java b/jhotdraw-core/src/test/java/org/jhotdraw/draw/tool/TextEditingToolTest.java new file mode 100644 index 000000000..26f2c2cd2 --- /dev/null +++ b/jhotdraw-core/src/test/java/org/jhotdraw/draw/tool/TextEditingToolTest.java @@ -0,0 +1,88 @@ +package org.jhotdraw.draw.tool; + +import org.jhotdraw.draw.DefaultDrawingView; +import org.jhotdraw.draw.Drawing; +import org.jhotdraw.draw.DrawingEditor; +import org.jhotdraw.draw.DrawingView; +import org.jhotdraw.draw.figure.TextFigure; +import org.jhotdraw.draw.figure.TextHolderFigure; +import org.jhotdraw.draw.text.FloatingTextArea; +import org.jhotdraw.draw.text.FloatingTextField; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.MouseEvent; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; + +import static org.junit.Assert.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +public class TextEditingToolTest { + private TextEditingTool textEditingTool; + private DrawingEditor editor; + private DrawingView view; + private Drawing drawing; + private TextHolderFigure textFigure; + + + @Before + public void setUp() throws Exception { + view = mock(DefaultDrawingView.class); + editor = mock(DrawingEditor.class); + drawing = mock(Drawing.class); + + textFigure = new TextFigure("OG Text"); + + textEditingTool = new TextEditingTool(textFigure); + + when(editor.getActiveView()).thenReturn(view); + when(editor.findView(any(Container.class))).thenReturn(view); + when(view.getDrawing()).thenReturn(drawing); + when(view.getComponent()).thenReturn((JComponent) view); + + } + + @Test + public void testMouseClickStartsEditing() { + Point2D.Double drawingPoint = new Point2D.Double(10, 10); + when(view.viewToDrawing(any(Point.class))).thenReturn(drawingPoint); + when(drawing.findFigureInside(drawingPoint)).thenReturn(textFigure); + when(view.getComponent()).thenReturn(new JPanel()); + + + MouseEvent event = mock(MouseEvent.class); + when(event.getSource()).thenReturn(view); + when(event.getPoint()).thenReturn(new Point(10,10)); + when(event.getClickCount()).thenReturn(1); + when(view.drawingToView(any(Point2D.Double.class))).thenReturn(new Point(10,10)); + when(view.drawingToView(any(Rectangle2D.Double.class))).thenReturn(new Rectangle(10, 10, 100, 20)); + + FloatingTextArea mockEditor = mock(FloatingTextArea.class); + when(mockEditor.getText()).thenReturn("New Text"); + + textEditingTool.activate(editor); + textEditingTool.beginEdit(textFigure); + + assertNotNull("Internal textfield should not be null", textEditingTool.textField); + + FloatingTextField spyField = spy(textEditingTool.textField); + + doReturn("New Text").when(spyField).getText(); + + assertEquals("New Text", textFigure.getText()); + + + + + + } + + @After + public void tearDown() throws Exception { + } +} \ No newline at end of file diff --git a/jhotdraw-core/src/test/java/org/jhotdraw/draw/tool/stages/WhenUserActs.java b/jhotdraw-core/src/test/java/org/jhotdraw/draw/tool/stages/WhenUserActs.java new file mode 100644 index 000000000..cb40ee2c6 --- /dev/null +++ b/jhotdraw-core/src/test/java/org/jhotdraw/draw/tool/stages/WhenUserActs.java @@ -0,0 +1,111 @@ +package org.jhotdraw.draw.tool.stages; + +import org.assertj.swing.core.MouseButton; +import org.assertj.swing.core.MouseClickInfo; +import org.assertj.swing.fixture.JTextComponentFixture; +import org.jhotdraw.draw.DrawingView; +import org.jhotdraw.draw.figure.Figure; +import org.jhotdraw.draw.stages.JHotDrawStage; +import org.jhotdraw.draw.stages.ThenDrawingState; + +import javax.swing.*; +import javax.swing.text.JTextComponent; +import java.awt.*; +import java.awt.event.KeyEvent; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; +import java.util.List; + +import static org.assertj.swing.core.KeyPressInfo.keyCode; +import static org.assertj.core.api.Assertions.fail; + + +public class WhenUserActs extends JHotDrawStage { + + private DrawingView drawingView; + + public WhenUserActs the_user_clicks_on_the_canvas_at(int x, int y) { + Component canvas = window.robot().finder().findByName("drawingCanvas", true); + window.robot().click(canvas, new Point(x,y)); + return self(); + } + + public WhenUserActs the_user_double_clicks_on_the_canvas_at(int x, int y) { + Component canvas = window.robot().finder().findByName("drawingCanvas", true); + window.robot().click(canvas, new Point(x,y), MouseButton.LEFT_BUTTON, 2); + return self(); + } + + + + + public WhenUserActs the_user_double_clicks_the_figure_at(int x, int y) { + Figure figure = drawingView.getDrawing().findFigure(new Point2D.Double(x,y)); + + + // As a fallback, choose the newest + if (figure == null) { + List
children = drawingView.getDrawing().getChildren(); + if (!children.isEmpty()) { + figure = children.get(children.size() - 1); + } + } + + if (figure == null) { + fail("No figure found at " + x + " " + y + " and drawing is empty."); + } + + Rectangle2D.Double bounds = figure.getBounds(); + + int centerX = (int) bounds.getCenterX(); + int centerY = (int) bounds.getCenterY(); + + Component canvas = window.robot().finder().findByName("drawingCanvas", true); + window.robot().click(canvas, new Point(centerX,centerY), MouseButton.LEFT_BUTTON, 2); + return self(); + } + + public WhenUserActs the_user_types(String text) { + window.robot().enterText(text); + window.robot().waitForIdle(); + return self(); + } + + public WhenUserActs the_user_presses_the_escape_key() { + window.robot().waitForIdle(); + + + Component canvas = window.robot().finder().findByName("drawingCanvas", true); + JTextComponent editor = (JTextComponent) window.robot().finder().find((Container) canvas, + c -> c instanceof JTextComponent && c.isShowing()); + + window.robot().focusAndWaitForFocusGain(editor); + + new JTextComponentFixture(window.robot(), editor).pressAndReleaseKey(keyCode(KeyEvent.VK_ESCAPE)); + + window.robot().waitForIdle(); + return self(); + } + + + public WhenUserActs the_user_clears_the_text_and_deselects() { + window.robot().pressAndReleaseKey(KeyEvent.VK_A, KeyEvent.CTRL_DOWN_MASK); + window.robot().pressAndReleaseKey(KeyEvent.VK_BACK_SPACE); + Component canvas = window.robot().finder().findByName("drawingCanvas", true); + + return the_user_clicks_on_the_canvas_at(0,0); + } + + public WhenUserActs the_user_finishes_editing() { + Component canvas = window.robot().finder().findByName("drawingCanvas", true); + window.robot().click(canvas, new Point(1,1)); + return self(); + } + + + public WhenUserActs setView(DrawingView view) { + this.drawingView = view; + return self(); + } + +} diff --git a/jhotdraw-gui/src/main/java/org/jhotdraw/gui/action/ButtonFactory.java b/jhotdraw-gui/src/main/java/org/jhotdraw/gui/action/ButtonFactory.java index 1470a011b..b765ee6be 100644 --- a/jhotdraw-gui/src/main/java/org/jhotdraw/gui/action/ButtonFactory.java +++ b/jhotdraw-gui/src/main/java/org/jhotdraw/gui/action/ButtonFactory.java @@ -99,6 +99,7 @@ import org.jhotdraw.draw.event.ToolAdapter; import org.jhotdraw.draw.event.ToolEvent; import org.jhotdraw.draw.event.ToolListener; +import org.jhotdraw.draw.tool.BaseTool; import org.jhotdraw.draw.tool.DelegationSelectionTool; import org.jhotdraw.draw.tool.Tool; import org.jhotdraw.geom.DoubleStroke; @@ -307,10 +308,10 @@ public class ButtonFactory { private static class ToolButtonListener implements ItemListener { - private Tool tool; + private BaseTool tool; private DrawingEditor editor; - public ToolButtonListener(Tool t, DrawingEditor editor) { + public ToolButtonListener(BaseTool t, DrawingEditor editor) { this.tool = t; this.editor = editor; } @@ -370,7 +371,7 @@ public static JToggleButton addSelectionToolTo(JToolBar tb, final DrawingEditor public static JToggleButton addSelectionToolTo(JToolBar tb, final DrawingEditor editor, Tool selectionTool) { ResourceBundleUtil labels = ResourceBundleUtil.getBundle("org.jhotdraw.draw.Labels"); JToggleButton t; - Tool tool; + BaseTool tool; HashMap attributes; ButtonGroup group; if (tb.getClientProperty("toolButtonGroup") instanceof ButtonGroup) { @@ -409,7 +410,7 @@ public void toolDone(ToolEvent event) { * */ public static JToggleButton addToolTo(JToolBar tb, DrawingEditor editor, - Tool tool, String labelKey, + BaseTool tool, String labelKey, ResourceBundleUtil labels) { ButtonGroup group = (ButtonGroup) tb.getClientProperty("toolButtonGroup"); ToolListener toolHandler = (ToolListener) tb.getClientProperty("toolHandler"); diff --git a/jhotdraw-samples/jhotdraw-samples-misc/jgiven-reports/org.jhotdraw.samples.svg.TextToolScenarioTest.json b/jhotdraw-samples/jhotdraw-samples-misc/jgiven-reports/org.jhotdraw.samples.svg.TextToolScenarioTest.json new file mode 100644 index 000000000..8df501b9e --- /dev/null +++ b/jhotdraw-samples/jhotdraw-samples-misc/jgiven-reports/org.jhotdraw.samples.svg.TextToolScenarioTest.json @@ -0,0 +1,1340 @@ +{ + "className": "org.jhotdraw.samples.svg.TextToolScenarioTest", + "name": "Text Tool Scenario", + "scenarios": [ + { + "className": "org.jhotdraw.samples.svg.TextToolScenarioTest", + "testMethodName": "pressing_escape_exits_edit_mode", + "description": "pressing escape exits edit mode", + "tagIds": [], + "explicitParameters": [], + "derivedParameters": [], + "scenarioCases": [ + { + "caseNr": 1, + "steps": [ + { + "name": "set fixtures", + "words": [ + { + "value": "Given", + "isIntroWord": true + }, + { + "value": "set fixtures" + }, + { + "value": "org.assertj.swing.fixture.FrameFixture@75a2673b", + "argumentInfo": { + "argumentName": "fixture", + "formattedValue": "org.assertj.swing.fixture.FrameFixture@75a2673b" + } + }, + { + "value": "org.assertj.swing.core.BasicRobot@1e8823d2", + "argumentInfo": { + "argumentName": "robotInstance", + "formattedValue": "org.assertj.swing.core.BasicRobot@1e8823d2" + } + }, + { + "value": "org.jhotdraw.draw.DefaultDrawingEditor@3b42d73b", + "argumentInfo": { + "argumentName": "drawingEditor", + "formattedValue": "org.jhotdraw.draw.DefaultDrawingEditor@3b42d73b" + } + }, + { + "value": "org.jhotdraw.draw.DefaultDrawingView[drawingCanvas,0,0,1920x887,alignmentX\u003d0.0,alignmentY\u003d0.0,border\u003d,flags\u003d16777224,maximumSize\u003d,minimumSize\u003d,preferredSize\u003d]", + "argumentInfo": { + "argumentName": "drawingView", + "formattedValue": "org.jhotdraw.draw.DefaultDrawingView[drawingCanvas,0,0,1920x887,alignmentX\u003d0.0,alignmentY\u003d0.0,border\u003d,flags\u003d16777224,maximumSize\u003d,minimumSize\u003d,preferredSize\u003d]" + } + } + ], + "status": "PASSED", + "durationInNanos": 34309800, + "depth": 0, + "parentFailed": false + }, + { + "name": "set fixtures", + "words": [ + { + "value": "When", + "isIntroWord": true + }, + { + "value": "set fixtures" + }, + { + "value": "org.assertj.swing.fixture.FrameFixture@75a2673b", + "argumentInfo": { + "argumentName": "fixture", + "formattedValue": "org.assertj.swing.fixture.FrameFixture@75a2673b" + } + }, + { + "value": "org.assertj.swing.core.BasicRobot@1e8823d2", + "argumentInfo": { + "argumentName": "robotInstance", + "formattedValue": "org.assertj.swing.core.BasicRobot@1e8823d2" + } + }, + { + "value": "org.jhotdraw.draw.DefaultDrawingEditor@3b42d73b", + "argumentInfo": { + "argumentName": "drawingEditor", + "formattedValue": "org.jhotdraw.draw.DefaultDrawingEditor@3b42d73b" + } + }, + { + "value": "org.jhotdraw.draw.DefaultDrawingView[drawingCanvas,0,0,1920x887,alignmentX\u003d0.0,alignmentY\u003d0.0,border\u003d,flags\u003d16777224,maximumSize\u003d,minimumSize\u003d,preferredSize\u003d]", + "argumentInfo": { + "argumentName": "drawingView", + "formattedValue": "org.jhotdraw.draw.DefaultDrawingView[drawingCanvas,0,0,1920x887,alignmentX\u003d0.0,alignmentY\u003d0.0,border\u003d,flags\u003d16777224,maximumSize\u003d,minimumSize\u003d,preferredSize\u003d]" + } + } + ], + "status": "PASSED", + "durationInNanos": 1414800, + "depth": 0, + "parentFailed": false + }, + { + "name": "set view", + "words": [ + { + "value": "When", + "isIntroWord": true + }, + { + "value": "set view" + }, + { + "value": "org.jhotdraw.draw.DefaultDrawingView[drawingCanvas,0,0,1920x887,alignmentX\u003d0.0,alignmentY\u003d0.0,border\u003d,flags\u003d16777224,maximumSize\u003d,minimumSize\u003d,preferredSize\u003d]", + "argumentInfo": { + "argumentName": "view", + "formattedValue": "org.jhotdraw.draw.DefaultDrawingView[drawingCanvas,0,0,1920x887,alignmentX\u003d0.0,alignmentY\u003d0.0,border\u003d,flags\u003d16777224,maximumSize\u003d,minimumSize\u003d,preferredSize\u003d]" + } + } + ], + "status": "PASSED", + "durationInNanos": 1119000, + "depth": 0, + "parentFailed": false + }, + { + "name": "set view", + "words": [ + { + "value": "Then", + "isIntroWord": true + }, + { + "value": "set view" + }, + { + "value": "org.jhotdraw.draw.DefaultDrawingView[drawingCanvas,0,0,1920x887,alignmentX\u003d0.0,alignmentY\u003d0.0,border\u003d,flags\u003d16777224,maximumSize\u003d,minimumSize\u003d,preferredSize\u003d]", + "argumentInfo": { + "argumentName": "view", + "formattedValue": "org.jhotdraw.draw.DefaultDrawingView[drawingCanvas,0,0,1920x887,alignmentX\u003d0.0,alignmentY\u003d0.0,border\u003d,flags\u003d16777224,maximumSize\u003d,minimumSize\u003d,preferredSize\u003d]" + } + } + ], + "status": "PASSED", + "durationInNanos": 1138200, + "depth": 0, + "parentFailed": false + }, + { + "name": "the text tool is selected", + "words": [ + { + "value": "Given", + "isIntroWord": true + }, + { + "value": "the text tool is selected" + } + ], + "status": "PASSED", + "durationInNanos": 371494900, + "depth": 0, + "parentFailed": false + }, + { + "name": "the user clicks on the canvas at", + "words": [ + { + "value": "When", + "isIntroWord": true + }, + { + "value": "the user clicks on the canvas at" + }, + { + "value": "200", + "argumentInfo": { + "argumentName": "x", + "formattedValue": "200" + } + }, + { + "value": "200", + "argumentInfo": { + "argumentName": "y", + "formattedValue": "200" + } + } + ], + "status": "PASSED", + "durationInNanos": 291831100, + "depth": 0, + "parentFailed": false + }, + { + "name": "the user types", + "words": [ + { + "value": "and", + "isIntroWord": true + }, + { + "value": "the user types" + }, + { + "value": "Pass Rot, Pop Green, Watch The Rots", + "argumentInfo": { + "argumentName": "text", + "formattedValue": "Pass Rot, Pop Green, Watch The Rots" + } + } + ], + "status": "PASSED", + "durationInNanos": 6250877400, + "depth": 0, + "parentFailed": false + }, + { + "name": "the user presses the escape key", + "words": [ + { + "value": "and", + "isIntroWord": true + }, + { + "value": "the user presses the escape key" + } + ], + "status": "PASSED", + "durationInNanos": 339768500, + "depth": 0, + "parentFailed": false + }, + { + "name": "the figure should not be in edit mode", + "words": [ + { + "value": "Then", + "isIntroWord": true + }, + { + "value": "the figure should not be in edit mode" + } + ], + "status": "PASSED", + "durationInNanos": 165131500, + "depth": 0, + "parentFailed": false + } + ], + "explicitArguments": [], + "derivedArguments": [], + "status": "SUCCESS", + "durationInNanos": 9556947500 + } + ], + "casesAsTable": false, + "durationInNanos": 9556947500 + }, + { + "className": "org.jhotdraw.samples.svg.TextToolScenarioTest", + "testMethodName": "figure_stays_at_original_location", + "description": "figure stays at original location", + "tagIds": [], + "explicitParameters": [], + "derivedParameters": [], + "scenarioCases": [ + { + "caseNr": 1, + "steps": [ + { + "name": "set fixtures", + "words": [ + { + "value": "Given", + "isIntroWord": true + }, + { + "value": "set fixtures" + }, + { + "value": "org.assertj.swing.fixture.FrameFixture@7a306bb2", + "argumentInfo": { + "argumentName": "fixture", + "formattedValue": "org.assertj.swing.fixture.FrameFixture@7a306bb2" + } + }, + { + "value": "org.assertj.swing.core.BasicRobot@971e903", + "argumentInfo": { + "argumentName": "robotInstance", + "formattedValue": "org.assertj.swing.core.BasicRobot@971e903" + } + }, + { + "value": "org.jhotdraw.draw.DefaultDrawingEditor@1e56c8e6", + "argumentInfo": { + "argumentName": "drawingEditor", + "formattedValue": "org.jhotdraw.draw.DefaultDrawingEditor@1e56c8e6" + } + }, + { + "value": "org.jhotdraw.draw.DefaultDrawingView[drawingCanvas,0,0,1920x887,alignmentX\u003d0.0,alignmentY\u003d0.0,border\u003d,flags\u003d16777224,maximumSize\u003d,minimumSize\u003d,preferredSize\u003d]", + "argumentInfo": { + "argumentName": "drawingView", + "formattedValue": "org.jhotdraw.draw.DefaultDrawingView[drawingCanvas,0,0,1920x887,alignmentX\u003d0.0,alignmentY\u003d0.0,border\u003d,flags\u003d16777224,maximumSize\u003d,minimumSize\u003d,preferredSize\u003d]" + } + } + ], + "status": "PASSED", + "durationInNanos": 724800, + "depth": 0, + "parentFailed": false + }, + { + "name": "set fixtures", + "words": [ + { + "value": "When", + "isIntroWord": true + }, + { + "value": "set fixtures" + }, + { + "value": "org.assertj.swing.fixture.FrameFixture@7a306bb2", + "argumentInfo": { + "argumentName": "fixture", + "formattedValue": "org.assertj.swing.fixture.FrameFixture@7a306bb2" + } + }, + { + "value": "org.assertj.swing.core.BasicRobot@971e903", + "argumentInfo": { + "argumentName": "robotInstance", + "formattedValue": "org.assertj.swing.core.BasicRobot@971e903" + } + }, + { + "value": "org.jhotdraw.draw.DefaultDrawingEditor@1e56c8e6", + "argumentInfo": { + "argumentName": "drawingEditor", + "formattedValue": "org.jhotdraw.draw.DefaultDrawingEditor@1e56c8e6" + } + }, + { + "value": "org.jhotdraw.draw.DefaultDrawingView[drawingCanvas,0,0,1920x887,alignmentX\u003d0.0,alignmentY\u003d0.0,border\u003d,flags\u003d16777224,maximumSize\u003d,minimumSize\u003d,preferredSize\u003d]", + "argumentInfo": { + "argumentName": "drawingView", + "formattedValue": "org.jhotdraw.draw.DefaultDrawingView[drawingCanvas,0,0,1920x887,alignmentX\u003d0.0,alignmentY\u003d0.0,border\u003d,flags\u003d16777224,maximumSize\u003d,minimumSize\u003d,preferredSize\u003d]" + } + } + ], + "status": "PASSED", + "durationInNanos": 428400, + "depth": 0, + "parentFailed": false + }, + { + "name": "set view", + "words": [ + { + "value": "When", + "isIntroWord": true + }, + { + "value": "set view" + }, + { + "value": "org.jhotdraw.draw.DefaultDrawingView[drawingCanvas,0,0,1920x887,alignmentX\u003d0.0,alignmentY\u003d0.0,border\u003d,flags\u003d16777224,maximumSize\u003d,minimumSize\u003d,preferredSize\u003d]", + "argumentInfo": { + "argumentName": "view", + "formattedValue": "org.jhotdraw.draw.DefaultDrawingView[drawingCanvas,0,0,1920x887,alignmentX\u003d0.0,alignmentY\u003d0.0,border\u003d,flags\u003d16777224,maximumSize\u003d,minimumSize\u003d,preferredSize\u003d]" + } + } + ], + "status": "PASSED", + "durationInNanos": 445100, + "depth": 0, + "parentFailed": false + }, + { + "name": "set view", + "words": [ + { + "value": "Then", + "isIntroWord": true + }, + { + "value": "set view" + }, + { + "value": "org.jhotdraw.draw.DefaultDrawingView[drawingCanvas,0,0,1920x887,alignmentX\u003d0.0,alignmentY\u003d0.0,border\u003d,flags\u003d16777224,maximumSize\u003d,minimumSize\u003d,preferredSize\u003d]", + "argumentInfo": { + "argumentName": "view", + "formattedValue": "org.jhotdraw.draw.DefaultDrawingView[drawingCanvas,0,0,1920x887,alignmentX\u003d0.0,alignmentY\u003d0.0,border\u003d,flags\u003d16777224,maximumSize\u003d,minimumSize\u003d,preferredSize\u003d]" + } + } + ], + "status": "PASSED", + "durationInNanos": 442800, + "depth": 0, + "parentFailed": false + }, + { + "name": "the text tool is selected", + "words": [ + { + "value": "Given", + "isIntroWord": true + }, + { + "value": "the text tool is selected" + } + ], + "status": "PASSED", + "durationInNanos": 359124600, + "depth": 0, + "parentFailed": false + }, + { + "name": "the user clicks on the canvas at", + "words": [ + { + "value": "When", + "isIntroWord": true + }, + { + "value": "the user clicks on the canvas at" + }, + { + "value": "200", + "argumentInfo": { + "argumentName": "x", + "formattedValue": "200" + } + }, + { + "value": "200", + "argumentInfo": { + "argumentName": "y", + "formattedValue": "200" + } + } + ], + "status": "PASSED", + "durationInNanos": 262065600, + "depth": 0, + "parentFailed": false + }, + { + "name": "the user types", + "words": [ + { + "value": "and", + "isIntroWord": true + }, + { + "value": "the user types" + }, + { + "value": "Red is defamation", + "argumentInfo": { + "argumentName": "text", + "formattedValue": "Red is defamation" + } + } + ], + "status": "PASSED", + "durationInNanos": 2447705400, + "depth": 0, + "parentFailed": false + }, + { + "name": "get figure anchor", + "words": [ + { + "value": "Then", + "isIntroWord": true + }, + { + "value": "get figure anchor" + } + ], + "status": "PASSED", + "durationInNanos": 71800, + "depth": 0, + "parentFailed": false + }, + { + "name": "the user finishes editing", + "words": [ + { + "value": "When", + "isIntroWord": true + }, + { + "value": "the user finishes editing" + } + ], + "status": "PASSED", + "durationInNanos": 247323400, + "depth": 0, + "parentFailed": false + }, + { + "name": "the figure should exist at", + "words": [ + { + "value": "Then", + "isIntroWord": true + }, + { + "value": "the figure should exist at" + }, + { + "value": "200", + "argumentInfo": { + "argumentName": "x", + "formattedValue": "200" + } + }, + { + "value": "187", + "argumentInfo": { + "argumentName": "y", + "formattedValue": "187" + } + } + ], + "status": "PASSED", + "durationInNanos": 4357900, + "depth": 0, + "parentFailed": false + } + ], + "explicitArguments": [], + "derivedArguments": [], + "status": "SUCCESS", + "durationInNanos": 4308424000 + } + ], + "casesAsTable": false, + "durationInNanos": 4308424000 + }, + { + "className": "org.jhotdraw.samples.svg.TextToolScenarioTest", + "testMethodName": "user_can_create_and_type_text", + "description": "user can create and type text", + "tagIds": [], + "explicitParameters": [], + "derivedParameters": [], + "scenarioCases": [ + { + "caseNr": 1, + "steps": [ + { + "name": "set fixtures", + "words": [ + { + "value": "Given", + "isIntroWord": true + }, + { + "value": "set fixtures" + }, + { + "value": "org.assertj.swing.fixture.FrameFixture@4b7cd802", + "argumentInfo": { + "argumentName": "fixture", + "formattedValue": "org.assertj.swing.fixture.FrameFixture@4b7cd802" + } + }, + { + "value": "org.assertj.swing.core.BasicRobot@715d6168", + "argumentInfo": { + "argumentName": "robotInstance", + "formattedValue": "org.assertj.swing.core.BasicRobot@715d6168" + } + }, + { + "value": "org.jhotdraw.draw.DefaultDrawingEditor@29c4fdbe", + "argumentInfo": { + "argumentName": "drawingEditor", + "formattedValue": "org.jhotdraw.draw.DefaultDrawingEditor@29c4fdbe" + } + }, + { + "value": "org.jhotdraw.draw.DefaultDrawingView[drawingCanvas,0,0,1920x887,alignmentX\u003d0.0,alignmentY\u003d0.0,border\u003d,flags\u003d16777224,maximumSize\u003d,minimumSize\u003d,preferredSize\u003d]", + "argumentInfo": { + "argumentName": "drawingView", + "formattedValue": "org.jhotdraw.draw.DefaultDrawingView[drawingCanvas,0,0,1920x887,alignmentX\u003d0.0,alignmentY\u003d0.0,border\u003d,flags\u003d16777224,maximumSize\u003d,minimumSize\u003d,preferredSize\u003d]" + } + } + ], + "status": "PASSED", + "durationInNanos": 929700, + "depth": 0, + "parentFailed": false + }, + { + "name": "set fixtures", + "words": [ + { + "value": "When", + "isIntroWord": true + }, + { + "value": "set fixtures" + }, + { + "value": "org.assertj.swing.fixture.FrameFixture@4b7cd802", + "argumentInfo": { + "argumentName": "fixture", + "formattedValue": "org.assertj.swing.fixture.FrameFixture@4b7cd802" + } + }, + { + "value": "org.assertj.swing.core.BasicRobot@715d6168", + "argumentInfo": { + "argumentName": "robotInstance", + "formattedValue": "org.assertj.swing.core.BasicRobot@715d6168" + } + }, + { + "value": "org.jhotdraw.draw.DefaultDrawingEditor@29c4fdbe", + "argumentInfo": { + "argumentName": "drawingEditor", + "formattedValue": "org.jhotdraw.draw.DefaultDrawingEditor@29c4fdbe" + } + }, + { + "value": "org.jhotdraw.draw.DefaultDrawingView[drawingCanvas,0,0,1920x887,alignmentX\u003d0.0,alignmentY\u003d0.0,border\u003d,flags\u003d16777224,maximumSize\u003d,minimumSize\u003d,preferredSize\u003d]", + "argumentInfo": { + "argumentName": "drawingView", + "formattedValue": "org.jhotdraw.draw.DefaultDrawingView[drawingCanvas,0,0,1920x887,alignmentX\u003d0.0,alignmentY\u003d0.0,border\u003d,flags\u003d16777224,maximumSize\u003d,minimumSize\u003d,preferredSize\u003d]" + } + } + ], + "status": "PASSED", + "durationInNanos": 1179800, + "depth": 0, + "parentFailed": false + }, + { + "name": "set view", + "words": [ + { + "value": "When", + "isIntroWord": true + }, + { + "value": "set view" + }, + { + "value": "org.jhotdraw.draw.DefaultDrawingView[drawingCanvas,0,0,1920x887,alignmentX\u003d0.0,alignmentY\u003d0.0,border\u003d,flags\u003d16777224,maximumSize\u003d,minimumSize\u003d,preferredSize\u003d]", + "argumentInfo": { + "argumentName": "view", + "formattedValue": "org.jhotdraw.draw.DefaultDrawingView[drawingCanvas,0,0,1920x887,alignmentX\u003d0.0,alignmentY\u003d0.0,border\u003d,flags\u003d16777224,maximumSize\u003d,minimumSize\u003d,preferredSize\u003d]" + } + } + ], + "status": "PASSED", + "durationInNanos": 696600, + "depth": 0, + "parentFailed": false + }, + { + "name": "set view", + "words": [ + { + "value": "Then", + "isIntroWord": true + }, + { + "value": "set view" + }, + { + "value": "org.jhotdraw.draw.DefaultDrawingView[drawingCanvas,0,0,1920x887,alignmentX\u003d0.0,alignmentY\u003d0.0,border\u003d,flags\u003d16777224,maximumSize\u003d,minimumSize\u003d,preferredSize\u003d]", + "argumentInfo": { + "argumentName": "view", + "formattedValue": "org.jhotdraw.draw.DefaultDrawingView[drawingCanvas,0,0,1920x887,alignmentX\u003d0.0,alignmentY\u003d0.0,border\u003d,flags\u003d16777224,maximumSize\u003d,minimumSize\u003d,preferredSize\u003d]" + } + } + ], + "status": "PASSED", + "durationInNanos": 681400, + "depth": 0, + "parentFailed": false + }, + { + "name": "the text tool is selected", + "words": [ + { + "value": "Given", + "isIntroWord": true + }, + { + "value": "the text tool is selected" + } + ], + "status": "PASSED", + "durationInNanos": 311449200, + "depth": 0, + "parentFailed": false + }, + { + "name": "the user clicks on the canvas at", + "words": [ + { + "value": "When", + "isIntroWord": true + }, + { + "value": "the user clicks on the canvas at" + }, + { + "value": "200", + "argumentInfo": { + "argumentName": "x", + "formattedValue": "200" + } + }, + { + "value": "200", + "argumentInfo": { + "argumentName": "y", + "formattedValue": "200" + } + } + ], + "status": "PASSED", + "durationInNanos": 253738100, + "depth": 0, + "parentFailed": false + }, + { + "name": "the user types", + "words": [ + { + "value": "and", + "isIntroWord": true + }, + { + "value": "the user types" + }, + { + "value": "Hello Near World", + "argumentInfo": { + "argumentName": "text", + "formattedValue": "Hello Near World" + } + } + ], + "status": "PASSED", + "durationInNanos": 2744810300, + "depth": 0, + "parentFailed": false + }, + { + "name": "a new text figure should exist", + "words": [ + { + "value": "Then", + "isIntroWord": true + }, + { + "value": "a new text figure should exist" + } + ], + "status": "PASSED", + "durationInNanos": 3601600, + "depth": 0, + "parentFailed": false + }, + { + "name": "the figure should be selected", + "words": [ + { + "value": "and", + "isIntroWord": true + }, + { + "value": "the figure should be selected" + } + ], + "status": "PASSED", + "durationInNanos": 8048500, + "depth": 0, + "parentFailed": false + } + ], + "explicitArguments": [], + "derivedArguments": [], + "status": "SUCCESS", + "durationInNanos": 4254722000 + } + ], + "casesAsTable": false, + "durationInNanos": 4254722000 + }, + { + "className": "org.jhotdraw.samples.svg.TextToolScenarioTest", + "testMethodName": "clicking_existing_text_with_selection_tool_enters_edit_mode", + "description": "clicking existing text with selection tool enters edit mode", + "tagIds": [], + "explicitParameters": [], + "derivedParameters": [], + "scenarioCases": [ + { + "caseNr": 1, + "steps": [ + { + "name": "set fixtures", + "words": [ + { + "value": "Given", + "isIntroWord": true + }, + { + "value": "set fixtures" + }, + { + "value": "org.assertj.swing.fixture.FrameFixture@286026f9", + "argumentInfo": { + "argumentName": "fixture", + "formattedValue": "org.assertj.swing.fixture.FrameFixture@286026f9" + } + }, + { + "value": "org.assertj.swing.core.BasicRobot@4ac86d6a", + "argumentInfo": { + "argumentName": "robotInstance", + "formattedValue": "org.assertj.swing.core.BasicRobot@4ac86d6a" + } + }, + { + "value": "org.jhotdraw.draw.DefaultDrawingEditor@27109b22", + "argumentInfo": { + "argumentName": "drawingEditor", + "formattedValue": "org.jhotdraw.draw.DefaultDrawingEditor@27109b22" + } + }, + { + "value": "org.jhotdraw.draw.DefaultDrawingView[drawingCanvas,0,0,1920x887,alignmentX\u003d0.0,alignmentY\u003d0.0,border\u003d,flags\u003d16777224,maximumSize\u003d,minimumSize\u003d,preferredSize\u003d]", + "argumentInfo": { + "argumentName": "drawingView", + "formattedValue": "org.jhotdraw.draw.DefaultDrawingView[drawingCanvas,0,0,1920x887,alignmentX\u003d0.0,alignmentY\u003d0.0,border\u003d,flags\u003d16777224,maximumSize\u003d,minimumSize\u003d,preferredSize\u003d]" + } + } + ], + "status": "PASSED", + "durationInNanos": 874100, + "depth": 0, + "parentFailed": false + }, + { + "name": "set fixtures", + "words": [ + { + "value": "When", + "isIntroWord": true + }, + { + "value": "set fixtures" + }, + { + "value": "org.assertj.swing.fixture.FrameFixture@286026f9", + "argumentInfo": { + "argumentName": "fixture", + "formattedValue": "org.assertj.swing.fixture.FrameFixture@286026f9" + } + }, + { + "value": "org.assertj.swing.core.BasicRobot@4ac86d6a", + "argumentInfo": { + "argumentName": "robotInstance", + "formattedValue": "org.assertj.swing.core.BasicRobot@4ac86d6a" + } + }, + { + "value": "org.jhotdraw.draw.DefaultDrawingEditor@27109b22", + "argumentInfo": { + "argumentName": "drawingEditor", + "formattedValue": "org.jhotdraw.draw.DefaultDrawingEditor@27109b22" + } + }, + { + "value": "org.jhotdraw.draw.DefaultDrawingView[drawingCanvas,0,0,1920x887,alignmentX\u003d0.0,alignmentY\u003d0.0,border\u003d,flags\u003d16777224,maximumSize\u003d,minimumSize\u003d,preferredSize\u003d]", + "argumentInfo": { + "argumentName": "drawingView", + "formattedValue": "org.jhotdraw.draw.DefaultDrawingView[drawingCanvas,0,0,1920x887,alignmentX\u003d0.0,alignmentY\u003d0.0,border\u003d,flags\u003d16777224,maximumSize\u003d,minimumSize\u003d,preferredSize\u003d]" + } + } + ], + "status": "PASSED", + "durationInNanos": 561600, + "depth": 0, + "parentFailed": false + }, + { + "name": "set view", + "words": [ + { + "value": "When", + "isIntroWord": true + }, + { + "value": "set view" + }, + { + "value": "org.jhotdraw.draw.DefaultDrawingView[drawingCanvas,0,0,1920x887,alignmentX\u003d0.0,alignmentY\u003d0.0,border\u003d,flags\u003d16777224,maximumSize\u003d,minimumSize\u003d,preferredSize\u003d]", + "argumentInfo": { + "argumentName": "view", + "formattedValue": "org.jhotdraw.draw.DefaultDrawingView[drawingCanvas,0,0,1920x887,alignmentX\u003d0.0,alignmentY\u003d0.0,border\u003d,flags\u003d16777224,maximumSize\u003d,minimumSize\u003d,preferredSize\u003d]" + } + } + ], + "status": "PASSED", + "durationInNanos": 550600, + "depth": 0, + "parentFailed": false + }, + { + "name": "set view", + "words": [ + { + "value": "Then", + "isIntroWord": true + }, + { + "value": "set view" + }, + { + "value": "org.jhotdraw.draw.DefaultDrawingView[drawingCanvas,0,0,1920x887,alignmentX\u003d0.0,alignmentY\u003d0.0,border\u003d,flags\u003d16777224,maximumSize\u003d,minimumSize\u003d,preferredSize\u003d]", + "argumentInfo": { + "argumentName": "view", + "formattedValue": "org.jhotdraw.draw.DefaultDrawingView[drawingCanvas,0,0,1920x887,alignmentX\u003d0.0,alignmentY\u003d0.0,border\u003d,flags\u003d16777224,maximumSize\u003d,minimumSize\u003d,preferredSize\u003d]" + } + } + ], + "status": "PASSED", + "durationInNanos": 980200, + "depth": 0, + "parentFailed": false + }, + { + "name": "the text tool is selected", + "words": [ + { + "value": "Given", + "isIntroWord": true + }, + { + "value": "the text tool is selected" + } + ], + "status": "PASSED", + "durationInNanos": 300273000, + "depth": 0, + "parentFailed": false + }, + { + "name": "the user clicks on the canvas at", + "words": [ + { + "value": "When", + "isIntroWord": true + }, + { + "value": "the user clicks on the canvas at" + }, + { + "value": "200", + "argumentInfo": { + "argumentName": "x", + "formattedValue": "200" + } + }, + { + "value": "200", + "argumentInfo": { + "argumentName": "y", + "formattedValue": "200" + } + } + ], + "status": "PASSED", + "durationInNanos": 248783700, + "depth": 0, + "parentFailed": false + }, + { + "name": "the user types", + "words": [ + { + "value": "and", + "isIntroWord": true + }, + { + "value": "the user types" + }, + { + "value": "Hello Far World", + "argumentInfo": { + "argumentName": "text", + "formattedValue": "Hello Far World" + } + } + ], + "status": "PASSED", + "durationInNanos": 2611925500, + "depth": 0, + "parentFailed": false + }, + { + "name": "the user finishes editing", + "words": [ + { + "value": "and", + "isIntroWord": true + }, + { + "value": "the user finishes editing" + } + ], + "status": "PASSED", + "durationInNanos": 249152600, + "depth": 0, + "parentFailed": false + }, + { + "name": "the selection tool is selected", + "words": [ + { + "value": "Given", + "isIntroWord": true + }, + { + "value": "the selection tool is selected" + } + ], + "status": "PASSED", + "durationInNanos": 296281100, + "depth": 0, + "parentFailed": false + }, + { + "name": "the user double clicks the figure at", + "words": [ + { + "value": "When", + "isIntroWord": true + }, + { + "value": "the user double clicks the figure at" + }, + { + "value": "200", + "argumentInfo": { + "argumentName": "x", + "formattedValue": "200" + } + }, + { + "value": "200", + "argumentInfo": { + "argumentName": "y", + "formattedValue": "200" + } + } + ], + "status": "PASSED", + "durationInNanos": 125254300, + "depth": 0, + "parentFailed": false + }, + { + "name": "the figure should be in edit mode", + "words": [ + { + "value": "Then", + "isIntroWord": true + }, + { + "value": "the figure should be in edit mode" + } + ], + "status": "PASSED", + "durationInNanos": 62053600, + "depth": 0, + "parentFailed": false + } + ], + "explicitArguments": [], + "derivedArguments": [], + "status": "SUCCESS", + "durationInNanos": 4813757300 + } + ], + "casesAsTable": false, + "durationInNanos": 4813757300 + }, + { + "className": "org.jhotdraw.samples.svg.TextToolScenarioTest", + "testMethodName": "text_figure_is_removed_if_empty_on_deselect", + "description": "text figure is removed if empty on deselect", + "tagIds": [], + "explicitParameters": [], + "derivedParameters": [], + "scenarioCases": [ + { + "caseNr": 1, + "steps": [ + { + "name": "set fixtures", + "words": [ + { + "value": "Given", + "isIntroWord": true + }, + { + "value": "set fixtures" + }, + { + "value": "org.assertj.swing.fixture.FrameFixture@7f685c37", + "argumentInfo": { + "argumentName": "fixture", + "formattedValue": "org.assertj.swing.fixture.FrameFixture@7f685c37" + } + }, + { + "value": "org.assertj.swing.core.BasicRobot@3eb3232b", + "argumentInfo": { + "argumentName": "robotInstance", + "formattedValue": "org.assertj.swing.core.BasicRobot@3eb3232b" + } + }, + { + "value": "org.jhotdraw.draw.DefaultDrawingEditor@2929538b", + "argumentInfo": { + "argumentName": "drawingEditor", + "formattedValue": "org.jhotdraw.draw.DefaultDrawingEditor@2929538b" + } + }, + { + "value": "org.jhotdraw.draw.DefaultDrawingView[drawingCanvas,0,0,1920x887,alignmentX\u003d0.0,alignmentY\u003d0.0,border\u003d,flags\u003d16777224,maximumSize\u003d,minimumSize\u003d,preferredSize\u003d]", + "argumentInfo": { + "argumentName": "drawingView", + "formattedValue": "org.jhotdraw.draw.DefaultDrawingView[drawingCanvas,0,0,1920x887,alignmentX\u003d0.0,alignmentY\u003d0.0,border\u003d,flags\u003d16777224,maximumSize\u003d,minimumSize\u003d,preferredSize\u003d]" + } + } + ], + "status": "PASSED", + "durationInNanos": 876700, + "depth": 0, + "parentFailed": false + }, + { + "name": "set fixtures", + "words": [ + { + "value": "When", + "isIntroWord": true + }, + { + "value": "set fixtures" + }, + { + "value": "org.assertj.swing.fixture.FrameFixture@7f685c37", + "argumentInfo": { + "argumentName": "fixture", + "formattedValue": "org.assertj.swing.fixture.FrameFixture@7f685c37" + } + }, + { + "value": "org.assertj.swing.core.BasicRobot@3eb3232b", + "argumentInfo": { + "argumentName": "robotInstance", + "formattedValue": "org.assertj.swing.core.BasicRobot@3eb3232b" + } + }, + { + "value": "org.jhotdraw.draw.DefaultDrawingEditor@2929538b", + "argumentInfo": { + "argumentName": "drawingEditor", + "formattedValue": "org.jhotdraw.draw.DefaultDrawingEditor@2929538b" + } + }, + { + "value": "org.jhotdraw.draw.DefaultDrawingView[drawingCanvas,0,0,1920x887,alignmentX\u003d0.0,alignmentY\u003d0.0,border\u003d,flags\u003d16777224,maximumSize\u003d,minimumSize\u003d,preferredSize\u003d]", + "argumentInfo": { + "argumentName": "drawingView", + "formattedValue": "org.jhotdraw.draw.DefaultDrawingView[drawingCanvas,0,0,1920x887,alignmentX\u003d0.0,alignmentY\u003d0.0,border\u003d,flags\u003d16777224,maximumSize\u003d,minimumSize\u003d,preferredSize\u003d]" + } + } + ], + "status": "PASSED", + "durationInNanos": 527600, + "depth": 0, + "parentFailed": false + }, + { + "name": "set view", + "words": [ + { + "value": "When", + "isIntroWord": true + }, + { + "value": "set view" + }, + { + "value": "org.jhotdraw.draw.DefaultDrawingView[drawingCanvas,0,0,1920x887,alignmentX\u003d0.0,alignmentY\u003d0.0,border\u003d,flags\u003d16777224,maximumSize\u003d,minimumSize\u003d,preferredSize\u003d]", + "argumentInfo": { + "argumentName": "view", + "formattedValue": "org.jhotdraw.draw.DefaultDrawingView[drawingCanvas,0,0,1920x887,alignmentX\u003d0.0,alignmentY\u003d0.0,border\u003d,flags\u003d16777224,maximumSize\u003d,minimumSize\u003d,preferredSize\u003d]" + } + } + ], + "status": "PASSED", + "durationInNanos": 435300, + "depth": 0, + "parentFailed": false + }, + { + "name": "set view", + "words": [ + { + "value": "Then", + "isIntroWord": true + }, + { + "value": "set view" + }, + { + "value": "org.jhotdraw.draw.DefaultDrawingView[drawingCanvas,0,0,1920x887,alignmentX\u003d0.0,alignmentY\u003d0.0,border\u003d,flags\u003d16777224,maximumSize\u003d,minimumSize\u003d,preferredSize\u003d]", + "argumentInfo": { + "argumentName": "view", + "formattedValue": "org.jhotdraw.draw.DefaultDrawingView[drawingCanvas,0,0,1920x887,alignmentX\u003d0.0,alignmentY\u003d0.0,border\u003d,flags\u003d16777224,maximumSize\u003d,minimumSize\u003d,preferredSize\u003d]" + } + } + ], + "status": "PASSED", + "durationInNanos": 501100, + "depth": 0, + "parentFailed": false + }, + { + "name": "the text tool is selected", + "words": [ + { + "value": "Given", + "isIntroWord": true + }, + { + "value": "the text tool is selected" + } + ], + "status": "PASSED", + "durationInNanos": 299522300, + "depth": 0, + "parentFailed": false + }, + { + "name": "the user clicks on the canvas at", + "words": [ + { + "value": "When", + "isIntroWord": true + }, + { + "value": "the user clicks on the canvas at" + }, + { + "value": "200", + "argumentInfo": { + "argumentName": "x", + "formattedValue": "200" + } + }, + { + "value": "200", + "argumentInfo": { + "argumentName": "y", + "formattedValue": "200" + } + } + ], + "status": "PASSED", + "durationInNanos": 244351300, + "depth": 0, + "parentFailed": false + }, + { + "name": "the user clears the text and deselects", + "words": [ + { + "value": "and", + "isIntroWord": true + }, + { + "value": "the user clears the text and deselects" + } + ], + "status": "PASSED", + "durationInNanos": 607333600, + "depth": 0, + "parentFailed": false + }, + { + "name": "the canvas should be empty", + "words": [ + { + "value": "Then", + "isIntroWord": true + }, + { + "value": "the canvas should be empty" + } + ], + "status": "PASSED", + "durationInNanos": 193300, + "depth": 0, + "parentFailed": false + } + ], + "explicitArguments": [], + "derivedArguments": [], + "status": "SUCCESS", + "durationInNanos": 2079950300 + } + ], + "casesAsTable": false, + "durationInNanos": 2079950300 + } + ], + "tagMap": {} +} \ No newline at end of file diff --git a/jhotdraw-samples/jhotdraw-samples-misc/pom.xml b/jhotdraw-samples/jhotdraw-samples-misc/pom.xml index ca8104ee5..6d4cae29f 100644 --- a/jhotdraw-samples/jhotdraw-samples-misc/pom.xml +++ b/jhotdraw-samples/jhotdraw-samples-misc/pom.xml @@ -14,6 +14,13 @@ jhotdraw-core ${project.version} + + org.jhotdraw + jhotdraw-core + ${project.version} + tests + test + org.devzendo Quaqua @@ -40,6 +47,25 @@ 4.13.2 test + + com.tngtech.jgiven + jgiven-junit + 1.3.1 test + + + + org.assertj + assertj-core + 3.25.3 + test + + + + org.assertj + assertj-swing-junit + 3.17.1 + test + diff --git a/jhotdraw-samples/jhotdraw-samples-misc/src/main/java/org/jhotdraw/samples/draw/DrawApplicationModel.java b/jhotdraw-samples/jhotdraw-samples-misc/src/main/java/org/jhotdraw/samples/draw/DrawApplicationModel.java index ae9f128a2..45eeca61f 100644 --- a/jhotdraw-samples/jhotdraw-samples-misc/src/main/java/org/jhotdraw/samples/draw/DrawApplicationModel.java +++ b/jhotdraw-samples/jhotdraw-samples-misc/src/main/java/org/jhotdraw/samples/draw/DrawApplicationModel.java @@ -34,12 +34,7 @@ import org.jhotdraw.draw.decoration.ArrowTip; import org.jhotdraw.draw.liner.CurvedLiner; import org.jhotdraw.draw.liner.ElbowLiner; -import org.jhotdraw.draw.tool.BezierTool; -import org.jhotdraw.draw.tool.ConnectionTool; -import org.jhotdraw.draw.tool.CreationTool; -import org.jhotdraw.draw.tool.ImageTool; -import org.jhotdraw.draw.tool.TextAreaCreationTool; -import org.jhotdraw.draw.tool.TextCreationTool; +import org.jhotdraw.draw.tool.*; import org.jhotdraw.gui.JFileURIChooser; import org.jhotdraw.gui.action.ButtonFactory; import org.jhotdraw.util.*; @@ -124,7 +119,7 @@ public void addDefaultCreationButtonsTo(JToolBar tb, final DrawingEditor editor, ButtonFactory.addSelectionToolTo(tb, editor, drawingActions, selectionActions); tb.addSeparator(); AbstractAttributedFigure af; - CreationTool ct; + AbstractCreationTool ct; ConnectionTool cnt; ConnectionFigure lc; ButtonFactory.addToolTo(tb, editor, new CreationTool(new RectangleFigure()), "edit.createRectangle", labels); diff --git a/jhotdraw-samples/jhotdraw-samples-misc/src/main/java/org/jhotdraw/samples/draw/DrawingPanel.java b/jhotdraw-samples/jhotdraw-samples-misc/src/main/java/org/jhotdraw/samples/draw/DrawingPanel.java index 418c02d46..ea9f83ad1 100644 --- a/jhotdraw-samples/jhotdraw-samples-misc/src/main/java/org/jhotdraw/samples/draw/DrawingPanel.java +++ b/jhotdraw-samples/jhotdraw-samples-misc/src/main/java/org/jhotdraw/samples/draw/DrawingPanel.java @@ -36,11 +36,7 @@ import org.jhotdraw.draw.decoration.ArrowTip; import org.jhotdraw.draw.liner.CurvedLiner; import org.jhotdraw.draw.liner.ElbowLiner; -import org.jhotdraw.draw.tool.BezierTool; -import org.jhotdraw.draw.tool.ConnectionTool; -import org.jhotdraw.draw.tool.CreationTool; -import org.jhotdraw.draw.tool.TextAreaCreationTool; -import org.jhotdraw.draw.tool.TextCreationTool; +import org.jhotdraw.draw.tool.*; import org.jhotdraw.gui.JPopupButton; import org.jhotdraw.gui.action.ButtonFactory; import org.jhotdraw.undo.UndoRedoManager; @@ -192,7 +188,7 @@ public void addDefaultCreationButtonsTo(JToolBar tb, final DrawingEditor editor, ButtonFactory.addSelectionToolTo(tb, editor, drawingActions, selectionActions); tb.addSeparator(); AbstractAttributedFigure af; - CreationTool ct; + AbstractCreationTool ct; ConnectionTool cnt; ConnectionFigure lc; ButtonFactory.addToolTo(tb, editor, new CreationTool(new RectangleFigure()), "edit.createRectangle", labels); diff --git a/jhotdraw-samples/jhotdraw-samples-misc/src/main/java/org/jhotdraw/samples/svg/figures/SVGTextFigure.java b/jhotdraw-samples/jhotdraw-samples-misc/src/main/java/org/jhotdraw/samples/svg/figures/SVGTextFigure.java index a96bb5211..465a9cba8 100644 --- a/jhotdraw-samples/jhotdraw-samples-misc/src/main/java/org/jhotdraw/samples/svg/figures/SVGTextFigure.java +++ b/jhotdraw-samples/jhotdraw-samples-misc/src/main/java/org/jhotdraw/samples/svg/figures/SVGTextFigure.java @@ -24,8 +24,8 @@ import org.jhotdraw.draw.handle.MoveHandle; import org.jhotdraw.draw.handle.TransformHandleKit; import org.jhotdraw.draw.locator.RelativeLocator; +import org.jhotdraw.draw.tool.BaseTool; import org.jhotdraw.draw.tool.TextEditingTool; -import org.jhotdraw.draw.tool.Tool; import org.jhotdraw.geom.Dimension2DDouble; import org.jhotdraw.geom.Geom; import org.jhotdraw.geom.Insets2D; @@ -49,6 +49,7 @@ public class SVGTextFigure extends SVGAttributedFigure implements TextHolderFigure, SVGFigure { + private static final int MIN_COLUMN_COUNT = 4; private static final long serialVersionUID = 1L; protected Point2D.Double[] coordinates = new Point2D.Double[]{new Point2D.Double()}; protected double[] rotates = new double[]{0}; @@ -76,6 +77,8 @@ public SVGTextFigure(String text) { // DRAWING @Override protected void drawText(java.awt.Graphics2D g) { + // SVGs have no concept of text per se, so it is being drawn in drawFill and drawStroke instead. + // This is probably a fat interface code smell, but fixing that hierarchy is out of scope. } @Override @@ -117,13 +120,13 @@ public Rectangle2D.Double getBounds() { cachedBounds = new Rectangle2D.Double(); cachedBounds.setRect(getTextShape().getBounds2D()); String text = getText(); - if (text == null || text.length() == 0) { + if (text == null || text.isEmpty()) { text = " "; } FontRenderContext frc = getFontRenderContext(); - HashMap textAttributes = new HashMap(); + HashMap textAttributes = new HashMap<>(); textAttributes.put(TextAttribute.FONT, getFont()); - if (get(FONT_UNDERLINE)) { + if (get(FONT_UNDERLINE) != null && get(FONT_UNDERLINE)) { textAttributes.put(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON); } TextLayout textLayout = new TextLayout(text, textAttributes, frc); @@ -182,13 +185,13 @@ public boolean contains(Point2D.Double p) { private Shape getTextShape() { if (cachedTextShape == null) { String text = getText(); - if (text == null || text.length() == 0) { + if (text == null || text.isEmpty()) { text = " "; } FontRenderContext frc = getFontRenderContext(); - HashMap textAttributes = new HashMap(); + HashMap textAttributes = new HashMap<>(); textAttributes.put(TextAttribute.FONT, getFont()); - if (get(FONT_UNDERLINE)) { + if (get(FONT_UNDERLINE) != null && get(FONT_UNDERLINE)) { textAttributes.put(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON); } TextLayout textLayout = new TextLayout(text, textAttributes, frc); @@ -205,10 +208,6 @@ private Shape getTextShape() { break; } tx.rotate(rotates[0]); - /* - if (get(TRANSFORM) != null) { - tx.preConcatenate(get(TRANSFORM)); - }*/ cachedTextShape = tx.createTransformedShape(textLayout.getOutline(tx)); cachedTextShape = textLayout.getOutline(tx); } @@ -296,11 +295,11 @@ public String getText() { @Override public void set(AttributeKey key, T newValue) { - if (key.equals(SVGAttributeKeys.TRANSFORM) - || key.equals(SVGAttributeKeys.FONT_FACE) - || key.equals(SVGAttributeKeys.FONT_BOLD) - || key.equals(SVGAttributeKeys.FONT_ITALIC) - || key.equals(SVGAttributeKeys.FONT_SIZE)) { + if (key.equals(AttributeKeys.TRANSFORM) + || key.equals(AttributeKeys.FONT_FACE) + || key.equals(AttributeKeys.FONT_BOLD) + || key.equals(AttributeKeys.FONT_ITALIC) + || key.equals(AttributeKeys.FONT_SIZE)) { invalidate(); } super.set(key, newValue); @@ -325,30 +324,26 @@ public void setEditable(boolean b) { @Override public int getTextColumns() { - //return (getText() == null) ? 4 : Math.min(getText().length(), 4); - return 4; + return MIN_COLUMN_COUNT; } @Override public Font getFont() { - return SVGAttributeKeys.getFont(this); + return AttributeKeys.getFont(this); } @Override public Color getTextColor() { return get(FILL_COLOR); - // return get(TEXT_COLOR); } @Override public Color getFillColor() { return get(FILL_COLOR) == null || get(FILL_COLOR).equals(Color.white) ? Color.black : Color.WHITE; - // return get(FILL_COLOR); } @Override public void setFontSize(float size) { - // put(FONT_SIZE, new Double(size)); Point2D.Double p = new Point2D.Double(0, size); AffineTransform tx = get(TRANSFORM); if (tx != null) { @@ -366,7 +361,6 @@ public void setFontSize(float size) { @Override public float getFontSize() { - // return get(FONT_SIZE).floatValue(); Point2D.Double p = new Point2D.Double(0, get(FONT_SIZE)); AffineTransform tx = get(TRANSFORM); if (tx != null) { @@ -374,12 +368,7 @@ public float getFontSize() { Point2D.Double p0 = new Point2D.Double(0, 0); tx.transform(p0, p0); p.y -= p0.y; - /* - try { - tx.inverseTransform(p, p); - } catch (NoninvertibleTransformException ex) { - ex.printStackTrace(); - }*/ + } return (float) Math.abs(p.y); } @@ -402,24 +391,20 @@ public Dimension2DDouble getPreferredSize() { @Override public Collection createHandles(int detailLevel) { - LinkedList handles = new LinkedList(); - switch (detailLevel % 2) { - case -1: // Mouse hover handles - handles.add(new BoundsOutlineHandle(this, false, true)); - break; - case 0: - handles.add(new BoundsOutlineHandle(this)); - handles.add(new MoveHandle(this, RelativeLocator.northWest())); - handles.add(new MoveHandle(this, RelativeLocator.northEast())); - handles.add(new MoveHandle(this, RelativeLocator.southWest())); - handles.add(new MoveHandle(this, RelativeLocator.southEast())); - handles.add(new FontSizeHandle(this)); - handles.add(new LinkHandle(this)); - break; - case 1: - TransformHandleKit.addTransformHandles(this, handles); - break; + LinkedList handles = new LinkedList<>(); + int parity = detailLevel % 2; + if (parity == -1) handles.add(new BoundsOutlineHandle(this, false, true)); // Adds mouse hover handles + if (parity == 1) TransformHandleKit.addTransformHandles(this, handles); + if (parity == 0) { + handles.add(new BoundsOutlineHandle(this)); + handles.add(new MoveHandle(this, RelativeLocator.northWest())); + handles.add(new MoveHandle(this, RelativeLocator.northEast())); + handles.add(new MoveHandle(this, RelativeLocator.southWest())); + handles.add(new MoveHandle(this, RelativeLocator.southEast())); + handles.add(new FontSizeHandle(this)); + handles.add(new LinkHandle(this)); } + return handles; } @@ -430,10 +415,9 @@ public Collection createHandles(int detailLevel) { * Returns null, if no specialized tool is available. */ @Override - public Tool getTool(Point2D.Double p) { + public BaseTool getTool(Point2D.Double p) { if (isEditable() && contains(p)) { - TextEditingTool tool = new TextEditingTool(this); - return tool; + return new TextEditingTool(this); } return null; } @@ -461,7 +445,35 @@ public Insets2D.Double getInsets() { return new Insets2D.Double(); } + public static SVGTextFigure createFrom(SVGTextFigure original) { + SVGTextFigure clone = new SVGTextFigure(); + + clone.setAttributes(original.getAttributes()); + + if (original.coordinates != null) { + clone.coordinates = Arrays.copyOf(original.coordinates, original.coordinates.length); + } + + if (original.rotates != null) { + clone.rotates = Arrays.copyOf(original.rotates, original.rotates.length); + } + + clone.cachedBounds = null; + clone.cachedDrawingArea = null; + clone.cachedTextShape = null; + + return clone; + } + + /** + * @deprecated Use {@link #createFrom(SVGTextFigure)} instead. + * This method is kept around for framework compatibility. + * */ + // Using .clone is inherently flawed but unfortunately legacy concerns keep it around. + // I've deprecated it with reference to the copy factory above. + @Deprecated @Override + @SuppressWarnings("java:S2975") public SVGTextFigure clone() { SVGTextFigure that = (SVGTextFigure) super.clone(); that.coordinates = new Point2D.Double[this.coordinates.length]; @@ -477,7 +489,7 @@ public SVGTextFigure clone() { @Override public boolean isEmpty() { - return getText() == null || getText().length() == 0; + return getText() == null || getText().isEmpty(); } @Override diff --git a/jhotdraw-samples/jhotdraw-samples-misc/src/main/java/org/jhotdraw/samples/svg/gui/ToolsToolBar.java b/jhotdraw-samples/jhotdraw-samples-misc/src/main/java/org/jhotdraw/samples/svg/gui/ToolsToolBar.java index e5ac32bb3..f104c0fe2 100644 --- a/jhotdraw-samples/jhotdraw-samples-misc/src/main/java/org/jhotdraw/samples/svg/gui/ToolsToolBar.java +++ b/jhotdraw-samples/jhotdraw-samples-misc/src/main/java/org/jhotdraw/samples/svg/gui/ToolsToolBar.java @@ -20,6 +20,7 @@ import org.jhotdraw.draw.DrawingEditor; import org.jhotdraw.draw.DrawingView; import org.jhotdraw.draw.action.*; +import org.jhotdraw.draw.tool.AbstractCreationTool; import org.jhotdraw.draw.tool.CreationTool; import org.jhotdraw.draw.tool.TextAreaCreationTool; import org.jhotdraw.draw.tool.TextCreationTool; @@ -74,7 +75,7 @@ protected JComponent createDisclosedComponent(int state) { p.setLayout(layout); GridBagConstraints gbc; AbstractButton btn; - CreationTool creationTool; + AbstractCreationTool abstractCreationTool; PathTool pathTool; TextCreationTool textTool; TextAreaCreationTool textAreaTool; @@ -83,6 +84,8 @@ protected JComponent createDisclosedComponent(int state) { btn = ButtonFactory.addSelectionToolTo(this, editor, ButtonFactory.createDrawingActions(editor, disposables), createSelectionActions(editor)); + btn.setName("selectionToolButton"); + btn.setToolTipText("Select Tool"); btn.setUI((PaletteButtonUI) PaletteButtonUI.createUI(btn)); btn.addMouseListener(new SelectionToolButtonHandler(editor)); gbc = new GridBagConstraints(); @@ -91,16 +94,16 @@ protected JComponent createDisclosedComponent(int state) { p.add(btn, gbc); labels.configureToolBarButton(btn, "selectionTool"); attributes = new HashMap, Object>(); - btn = ButtonFactory.addToolTo(this, editor, creationTool = new CreationTool(new SVGRectFigure(), attributes), "createRectangle", labels); - creationTool.setToolDoneAfterCreation(false); + btn = ButtonFactory.addToolTo(this, editor, abstractCreationTool = new CreationTool(new SVGRectFigure(), attributes), "createRectangle", labels); + abstractCreationTool.setToolDoneAfterCreation(false); btn.setUI((PaletteButtonUI) PaletteButtonUI.createUI(btn)); gbc = new GridBagConstraints(); gbc.gridx = 0; gbc.gridy = 1; gbc.insets = new Insets(3, 0, 0, 0); p.add(btn, gbc); - btn = ButtonFactory.addToolTo(this, editor, creationTool = new CreationTool(new SVGEllipseFigure(), attributes), "createEllipse", labels); - creationTool.setToolDoneAfterCreation(false); + btn = ButtonFactory.addToolTo(this, editor, abstractCreationTool = new CreationTool(new SVGEllipseFigure(), attributes), "createEllipse", labels); + abstractCreationTool.setToolDoneAfterCreation(false); btn.setUI((PaletteButtonUI) PaletteButtonUI.createUI(btn)); gbc = new GridBagConstraints(); gbc.gridx = 1; @@ -118,8 +121,8 @@ protected JComponent createDisclosedComponent(int state) { attributes = new HashMap, Object>(); attributes.put(AttributeKeys.FILL_COLOR, null); attributes.put(PATH_CLOSED, false); - btn = ButtonFactory.addToolTo(this, editor, creationTool = new CreationTool(new SVGPathFigure(), attributes), "createLine", labels); - creationTool.setToolDoneAfterCreation(false); + btn = ButtonFactory.addToolTo(this, editor, abstractCreationTool = new CreationTool(new SVGPathFigure(), attributes), "createLine", labels); + abstractCreationTool.setToolDoneAfterCreation(false); btn.setUI((PaletteButtonUI) PaletteButtonUI.createUI(btn)); gbc = new GridBagConstraints(); gbc.gridx = 1; @@ -138,6 +141,8 @@ protected JComponent createDisclosedComponent(int state) { attributes.put(AttributeKeys.FILL_COLOR, Color.black); attributes.put(AttributeKeys.STROKE_COLOR, null); btn = ButtonFactory.addToolTo(this, editor, textTool = new TextCreationTool(new SVGTextFigure(), attributes), "createText", labels); + btn.setName("textToolButton"); // For BDD + btn.setToolTipText("Text Tool"); textTool.setToolDoneAfterCreation(true); btn.setUI((PaletteButtonUI) PaletteButtonUI.createUI(btn)); gbc = new GridBagConstraints(); diff --git a/jhotdraw-samples/jhotdraw-samples-misc/src/test/java/org/jhotdraw/samples/svg/TextToolScenarioTest.java b/jhotdraw-samples/jhotdraw-samples-misc/src/test/java/org/jhotdraw/samples/svg/TextToolScenarioTest.java new file mode 100644 index 000000000..5242a6371 --- /dev/null +++ b/jhotdraw-samples/jhotdraw-samples-misc/src/test/java/org/jhotdraw/samples/svg/TextToolScenarioTest.java @@ -0,0 +1,127 @@ +package org.jhotdraw.samples.svg; + +import com.tngtech.jgiven.annotation.ScenarioState; +import com.tngtech.jgiven.junit.ScenarioTest; +import org.assertj.swing.core.BasicRobot; +import org.assertj.swing.core.GenericTypeMatcher; +import org.assertj.swing.core.Robot; +import org.assertj.swing.edt.GuiActionRunner; +import org.assertj.swing.finder.WindowFinder; +import org.assertj.swing.fixture.FrameFixture; +import org.assertj.swing.junit.testcase.AssertJSwingJUnitTestCase; +import org.jhotdraw.draw.DrawingEditor; +import org.jhotdraw.draw.DrawingView; +import org.jhotdraw.draw.stages.GivenDrawingCanvas; +import org.jhotdraw.draw.stages.ThenDrawingState; +import org.jhotdraw.draw.tool.stages.WhenUserActs; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import javax.swing.*; +import java.awt.*; +import java.awt.geom.Point2D; + +public class TextToolScenarioTest extends ScenarioTest { + + private FrameFixture window; + private Robot robot; + + private DrawingView view; + + @Before + public void setup() { + + robot = BasicRobot.robotWithNewAwtHierarchy(); + GuiActionRunner.execute(() -> org.jhotdraw.samples.svg.Main.main(new String[0])); + + window = WindowFinder.findFrame(new GenericTypeMatcher(JFrame.class) { + @Override + protected boolean isMatching(JFrame frame) { + return frame.isShowing() && frame.getTitle().toLowerCase().contains("svg"); + } + }).withTimeout(5000).using(robot); + + window.maximize(); + + this.view = (DrawingView) window.robot().finder().findByName("drawingCanvas"); + DrawingEditor editor = view.getEditor(); + + given().setFixtures(window, robot, editor, view); + when().setFixtures(window, robot, editor, view); + when().setView(view); + then().setView(view); + + + } + + @Test + public void user_can_create_and_type_text() { + given().the_text_tool_is_selected(); + + when().the_user_clicks_on_the_canvas_at(200,200) + .and().the_user_types("Hello Near World"); + then().a_new_text_figure_should_exist() + .and().the_figure_should_be_selected(); + } + + @Test + public void text_figure_is_removed_if_empty_on_deselect() { + given().the_text_tool_is_selected(); + + when().the_user_clicks_on_the_canvas_at(200,200) + .and().the_user_clears_the_text_and_deselects(); + + then().the_canvas_should_be_empty(); + } + + @Test + public void clicking_existing_text_with_selection_tool_enters_edit_mode() { + // Create figure + given().the_text_tool_is_selected(); + when().the_user_clicks_on_the_canvas_at(200,200) + .and().the_user_types("Hello Far World") + .and().the_user_finishes_editing(); + + given().the_selection_tool_is_selected(); + when().the_user_double_clicks_the_figure_at(200,200); + then().the_figure_should_be_in_edit_mode(); + + } + + @Test + public void figure_stays_at_original_location() { + int x = 200; + int y = 200; + given().the_text_tool_is_selected(); + when().the_user_clicks_on_the_canvas_at(x,y) + .and().the_user_types("Red is defamation"); + + // Find where JHotDraw actually positioned it + Point2D.Double initialPosition = then().get_figure_anchor(); + + when().the_user_finishes_editing(); + + then().the_figure_should_exist_at( (int) initialPosition.x, (int) initialPosition.y); + } + + @Test + public void pressing_escape_exits_edit_mode() { + given().the_text_tool_is_selected(); + when().the_user_clicks_on_the_canvas_at(200,200) + .and().the_user_types("Pass Rot, Pop Green, Watch The Rots") + .and().the_user_presses_the_escape_key(); + + then().the_figure_should_not_be_in_edit_mode(); + + } + + @After + public void tearDown() { + if (window != null) { + window.cleanUp(); + } + } + + +}