From 2485c0b8efacb7c1f565bfa06a692af24c3c5c69 Mon Sep 17 00:00:00 2001 From: Seralyne Date: Sat, 27 Dec 2025 19:27:01 +0100 Subject: [PATCH 01/27] Extract Tool interfaces to fix Interface Segregation Principle violation --- .../src/main/java/org/jhotdraw/draw/tool/BaseTool.java | 4 ++++ .../main/java/org/jhotdraw/draw/tool/ClickListeingTool.java | 4 ++++ .../src/main/java/org/jhotdraw/draw/tool/DragableTool.java | 4 ++++ .../main/java/org/jhotdraw/draw/tool/KeyListeningTool.java | 4 ++++ 4 files changed, 16 insertions(+) create mode 100644 jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/BaseTool.java create mode 100644 jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/ClickListeingTool.java create mode 100644 jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/DragableTool.java create mode 100644 jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/KeyListeningTool.java 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..92b93128b --- /dev/null +++ b/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/BaseTool.java @@ -0,0 +1,4 @@ +package org.jhotdraw.draw.tool; + +public interface BaseTool { +} diff --git a/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/ClickListeingTool.java b/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/ClickListeingTool.java new file mode 100644 index 000000000..4ece8e5ad --- /dev/null +++ b/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/ClickListeingTool.java @@ -0,0 +1,4 @@ +package org.jhotdraw.draw.tool; + +public interface ClickListeingTool { +} 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..741c08cfc --- /dev/null +++ b/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/DragableTool.java @@ -0,0 +1,4 @@ +package org.jhotdraw.draw.tool; + +public interface DragableTool { +} 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..2cb547537 --- /dev/null +++ b/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/KeyListeningTool.java @@ -0,0 +1,4 @@ +package org.jhotdraw.draw.tool; + +public interface KeyListeningTool { +} From 48a874466325f763efd56730e284735afc96c3ff Mon Sep 17 00:00:00 2001 From: Seralyne Date: Sat, 27 Dec 2025 19:30:31 +0100 Subject: [PATCH 02/27] Things didn't migrate properly --- .../jhotdraw/draw/AbstractDrawingView.java | 17 ++-- .../jhotdraw/draw/DefaultDrawingEditor.java | 5 +- .../java/org/jhotdraw/draw/DrawingEditor.java | 4 +- .../org/jhotdraw/draw/DrawingEditorProxy.java | 3 +- .../draw/action/AbstractSelectedAction.java | 2 + .../org/jhotdraw/draw/event/ToolEvent.java | 7 +- .../java/org/jhotdraw/draw/tool/BaseTool.java | 86 +++++++++++++++++++ .../jhotdraw/draw/tool/ClickListeingTool.java | 4 +- .../org/jhotdraw/draw/tool/DragableTool.java | 4 +- .../jhotdraw/draw/tool/KeyListeningTool.java | 4 +- .../jhotdraw/draw/tool/TextCreationTool.java | 20 ++++- .../java/org/jhotdraw/draw/tool/Tool.java | 83 +----------------- .../jhotdraw/gui/action/ButtonFactory.java | 3 +- 13 files changed, 141 insertions(+), 101 deletions(-) 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..bff7f438d 100644 --- a/jhotdraw-core/src/main/java/org/jhotdraw/draw/AbstractDrawingView.java +++ b/jhotdraw-core/src/main/java/org/jhotdraw/draw/AbstractDrawingView.java @@ -196,14 +196,14 @@ protected void drawBackground(Graphics2D g) { public boolean isSelectionEmpty() { return selectedFigures.isEmpty(); } - + // TODO: Extract Class OR move to top private class EventHandler implements FigureListener, CompositeFigureListener, HandleListener, FocusListener { @Override public void figureAdded(CompositeFigureEvent evt) { if (drawing.getChildCount() == 1 && getEmptyDrawingMessage() != null) { repaint(); - } else { + } else { // FIXME: what the fuck repaintDrawingArea(evt.getCompositeFigure().getDrawingArea(AttributeKeys.getScaleFactor(getDrawingToViewTransform()))); } } @@ -212,14 +212,14 @@ public void figureAdded(CompositeFigureEvent evt) { public void figureRemoved(CompositeFigureEvent evt) { if (drawing.getChildCount() == 0 && getEmptyDrawingMessage() != null) { repaint(); - } else { + } else { // FIXME: this is just as obtuse down here as it was up there,theres gotta be a more clear way repaintDrawingArea(evt.getCompositeFigure().getDrawingArea(AttributeKeys.getScaleFactor(getDrawingToViewTransform()))); } removeFromSelection(evt.getChildFigure()); } @Override - public void areaInvalidated(FigureEvent evt) { + public void areaInvalidated(FigureEvent evt) { // FIXME: Third Time I've seen this bullshit. Make a method that takes an event repaintDrawingArea(evt.getFigure().getDrawingArea(AttributeKeys.getScaleFactor(getDrawingToViewTransform()))); } @@ -283,7 +283,7 @@ public void figureHandlesChanged(FigureEvent e) { } @Override - public void figureChanged(FigureEvent e) { + public void figureChanged(FigureEvent e) { // FIXME: ANOTHER ONE repaintDrawingArea(e.getFigure().getDrawingArea(AttributeKeys.getScaleFactor(getDrawingToViewTransform()))); } @@ -299,6 +299,8 @@ public void figureRemoved(FigureEvent e) { public void figureRequestRemove(FigureEvent e) { } } + + // FIXME: Move to top private final EventHandler eventHandler = new EventHandler(); @@ -953,7 +955,8 @@ public void delete() { drawing.fireUndoableEditHappened(new AbstractUndoableEdit() { private static final long serialVersionUID = 1L; - @Override + @Override // FIXME: Why the fuck are we nesting functions???? + // FIXME: Apparently an extract Class Angle public String getPresentationName() { ResourceBundleUtil labels = ResourceBundleUtil.getBundle("org.jhotdraw.draw.Labels"); return labels.getString("edit.delete.text"); @@ -1001,6 +1004,8 @@ public void duplicate() { f.remap(originalToDuplicateMap, false); } addToSelection(duplicates); + + // FIXME: Extract Class 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..5553ed766 100644 --- a/jhotdraw-core/src/main/java/org/jhotdraw/draw/DefaultDrawingEditor.java +++ b/jhotdraw-core/src/main/java/org/jhotdraw/draw/DefaultDrawingEditor.java @@ -29,6 +29,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; /** @@ -115,7 +116,7 @@ public DefaultDrawingEditor() { @Override public void setTool(Tool newValue) { - Tool oldValue = tool; + BaseTool oldValue = tool; if (newValue == tool) { return; } @@ -155,7 +156,7 @@ public void setActiveView(DrawingView newValue) { } @Override - public Tool getTool() { + public BaseTool getTool() { return tool; } 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..04403ae9b 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; /** @@ -164,7 +166,7 @@ public interface DrawingEditor { *

* 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..e4228a145 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; /** @@ -110,7 +111,7 @@ public void setTool(Tool 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/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/tool/BaseTool.java b/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/BaseTool.java index 92b93128b..a481dca04 100644 --- a/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/BaseTool.java +++ b/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/BaseTool.java @@ -1,4 +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/ClickListeingTool.java b/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/ClickListeingTool.java index 4ece8e5ad..5ea0165e6 100644 --- a/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/ClickListeingTool.java +++ b/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/ClickListeingTool.java @@ -1,4 +1,6 @@ package org.jhotdraw.draw.tool; -public interface ClickListeingTool { +import java.awt.event.MouseListener; + +public interface ClickListeingTool extends MouseListener { } 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 index 741c08cfc..bff72358f 100644 --- a/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/DragableTool.java +++ b/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/DragableTool.java @@ -1,4 +1,6 @@ package org.jhotdraw.draw.tool; -public interface DragableTool { +import java.awt.event.MouseMotionListener; + +public interface DragableTool extends MouseMotionListener { } 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 index 2cb547537..9ae7269d8 100644 --- a/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/KeyListeningTool.java +++ b/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/KeyListeningTool.java @@ -1,4 +1,6 @@ package org.jhotdraw.draw.tool; -public interface KeyListeningTool { +import java.awt.event.KeyListener; + +public interface KeyListeningTool extends KeyListener { } 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..706c2d2ab 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 @@ -8,9 +8,11 @@ package org.jhotdraw.draw.tool; import org.jhotdraw.draw.figure.Figure; +import org.jhotdraw.draw.figure.TextFigure; import org.jhotdraw.draw.figure.TextHolderFigure; import java.awt.*; import java.awt.event.*; +import java.awt.geom.Point2D; import java.util.*; import javax.swing.undo.AbstractUndoableEdit; import javax.swing.undo.UndoableEdit; @@ -62,6 +64,8 @@ public class TextCreationTool extends CreationTool implements ActionListener { private FloatingTextField textField; private TextHolderFigure typingTarget; + + /** * Creates a new instance. */ @@ -107,9 +111,12 @@ public void mousePressed(MouseEvent e) { } } - @Override - public void mouseDragged(java.awt.event.MouseEvent e) { - } + //@Override + //public void mouseDragged(java.awt.event.MouseEvent e) { + // if (getCreatedFigure() != null && getCreatedFigure().isTransformable()) { + // super.mouseDragged(e); + // } + //} protected void beginEdit(TextHolderFigure textHolder) { if (textField == null) { @@ -126,6 +133,13 @@ protected void beginEdit(TextHolderFigure textHolder) { @Override public void mouseReleased(MouseEvent evt) { + TextFigure textFigure = (TextFigure) getCreatedFigure(); + + super.mouseReleased(evt); + + if (textField != null) { + beginEdit(textFigure); + } } protected void endEdit() { 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..63056b33b 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 @@ -73,85 +73,6 @@ * @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(); +public interface Tool extends BaseTool, ClickListeingTool, DragableTool, KeyListeningTool { + // Legacy God Tool to maintain compatibility } 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..c05139a76 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; @@ -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) { From 16452bc53fca3d7f29c21948543b78604f5169fb Mon Sep 17 00:00:00 2001 From: Seralyne Date: Sat, 27 Dec 2025 21:49:14 +0100 Subject: [PATCH 03/27] Refactoring Work: * Made classes that rely on Tool work with BaseTool * Segregated AbstractTool and CreationTool into legacy classes that other features may continue to use until they get refactored * Added new, simplified architecture for simpler creation tools (like TextCreationTool) * Fixed Typo in ClickListeningTool --- .../jhotdraw/draw/DefaultDrawingEditor.java | 34 +- .../java/org/jhotdraw/draw/DrawingEditor.java | 2 +- .../org/jhotdraw/draw/DrawingEditorProxy.java | 2 +- .../draw/tool/AbstractCreationTool.java | 202 ++++++++++ .../org/jhotdraw/draw/tool/AbstractTool.java | 337 +--------------- .../org/jhotdraw/draw/tool/BaseToolImpl.java | 361 ++++++++++++++++++ ...teingTool.java => ClickListeningTool.java} | 2 +- .../org/jhotdraw/draw/tool/CreationTool.java | 190 ++------- .../draw/tool/SimpleCreationTool.java | 33 ++ .../jhotdraw/draw/tool/TextCreationTool.java | 13 +- .../java/org/jhotdraw/draw/tool/Tool.java | 6 +- .../jhotdraw/gui/action/ButtonFactory.java | 6 +- .../samples/draw/DrawApplicationModel.java | 9 +- .../jhotdraw/samples/draw/DrawingPanel.java | 8 +- .../samples/svg/gui/ToolsToolBar.java | 15 +- 15 files changed, 686 insertions(+), 534 deletions(-) create mode 100644 jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/AbstractCreationTool.java create mode 100644 jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/BaseToolImpl.java rename jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/{ClickListeingTool.java => ClickListeningTool.java} (54%) create mode 100644 jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/SimpleCreationTool.java 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 5553ed766..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; @@ -45,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; @@ -115,16 +111,16 @@ public DefaultDrawingEditor() { } @Override - public void setTool(Tool newValue) { + 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); } @@ -136,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); } @@ -205,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) { @@ -222,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/DrawingEditor.java b/jhotdraw-core/src/main/java/org/jhotdraw/draw/DrawingEditor.java index 04403ae9b..9b5b7c588 100644 --- a/jhotdraw-core/src/main/java/org/jhotdraw/draw/DrawingEditor.java +++ b/jhotdraw-core/src/main/java/org/jhotdraw/draw/DrawingEditor.java @@ -159,7 +159,7 @@ public interface DrawingEditor { *

* This is a bound property. */ - void setTool(Tool t); + void setTool(BaseTool t); /** * Gets the current tool. 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 e4228a145..8d47d62e9 100644 --- a/jhotdraw-core/src/main/java/org/jhotdraw/draw/DrawingEditorProxy.java +++ b/jhotdraw-core/src/main/java/org/jhotdraw/draw/DrawingEditorProxy.java @@ -106,7 +106,7 @@ public DrawingView getFocusedView() { } @Override - public void setTool(Tool t) { + public void setTool(BaseTool t) { target.setTool(t); } 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..7b182f98d --- /dev/null +++ b/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/AbstractCreationTool.java @@ -0,0 +1,202 @@ +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 mouseClicked(MouseEvent e) { + + } + + @Override + public void mouseExited(MouseEvent e) { + + } + + @Override + public void mouseEntered(MouseEvent e) { + + } + + @Override + public void mouseReleased(MouseEvent e) { + isWorking = false; + } + + @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); + } + + @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/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/ClickListeingTool.java b/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/ClickListeningTool.java similarity index 54% rename from jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/ClickListeingTool.java rename to jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/ClickListeningTool.java index 5ea0165e6..54604cae6 100644 --- a/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/ClickListeingTool.java +++ b/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/ClickListeningTool.java @@ -2,5 +2,5 @@ import java.awt.event.MouseListener; -public interface ClickListeingTool extends MouseListener { +public interface ClickListeningTool extends MouseListener { } 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..2eadec9ab 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 AbstractCreationTool 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/SimpleCreationTool.java b/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/SimpleCreationTool.java new file mode 100644 index 000000000..67508995a --- /dev/null +++ b/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/SimpleCreationTool.java @@ -0,0 +1,33 @@ +package org.jhotdraw.draw.tool; + +import org.jhotdraw.draw.AttributeKey; +import org.jhotdraw.draw.figure.Figure; + +import java.awt.event.MouseEvent; +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 mouseReleased(MouseEvent event) { + super.mouseReleased(event); + if (getCreatedFigure() != null) { + creationFinished(getCreatedFigure()); + } + if (isToolDoneAfterCreation()) { + fireToolDone(); + } + } +} 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 706c2d2ab..de24a5376 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,7 +12,6 @@ import org.jhotdraw.draw.figure.TextHolderFigure; import java.awt.*; import java.awt.event.*; -import java.awt.geom.Point2D; import java.util.*; import javax.swing.undo.AbstractUndoableEdit; import javax.swing.undo.UndoableEdit; @@ -58,7 +57,7 @@ * @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; @@ -192,6 +191,16 @@ public void redo() { // view().checkDamage(); } + @Override + public void keyTyped(KeyEvent e) { + + } + + @Override + public void keyPressed(KeyEvent e) { + + } + @Override public void keyReleased(KeyEvent evt) { if (evt.getKeyCode() == KeyEvent.VK_ESCAPE || isToolDoneAfterCreation()) { 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 63056b33b..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,6 +72,7 @@ * @author Werner Randelshofer * @version $Id$ */ -public interface Tool extends BaseTool, ClickListeingTool, DragableTool, KeyListeningTool { +@Deprecated +public interface Tool extends BaseTool, ClickListeningTool, DragableTool, KeyListeningTool { // Legacy God Tool to maintain compatibility } 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 c05139a76..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 @@ -308,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; } @@ -410,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/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/gui/ToolsToolBar.java b/jhotdraw-samples/jhotdraw-samples-misc/src/main/java/org/jhotdraw/samples/svg/gui/ToolsToolBar.java index e5ac32bb3..137da7a98 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; @@ -91,16 +92,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 +119,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; From 2baee1e0ea8187a2b4da72d6e13b8590f560aaea Mon Sep 17 00:00:00 2001 From: Seralyne Date: Sat, 27 Dec 2025 23:06:56 +0100 Subject: [PATCH 04/27] More refactoring to Dependency Inversion Principle (Depending on abstractions rather than concrets) --- .../jhotdraw/draw/figure/AbstractFigure.java | 3 +- .../java/org/jhotdraw/draw/figure/Figure.java | 3 +- .../org/jhotdraw/draw/figure/LabelFigure.java | 3 +- .../org/jhotdraw/draw/figure/TextFigure.java | 3 +- .../draw/tool/AbstractCreationTool.java | 14 -------- .../draw/tool/ClickListeningTool.java | 6 ++++ .../draw/tool/DelegationSelectionTool.java | 5 +-- .../jhotdraw/draw/tool/KeyListeningTool.java | 16 ++++++++- .../org/jhotdraw/draw/tool/SelectionTool.java | 34 ++++++++++++------- .../jhotdraw/draw/tool/TextCreationTool.java | 24 ------------- .../jhotdraw/draw/tool/TextEditingTool.java | 10 ++---- .../samples/svg/figures/SVGTextFigure.java | 3 +- 12 files changed, 58 insertions(+), 66 deletions(-) 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..5ca1dc8a5 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,6 +11,7 @@ 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; @@ -61,7 +62,7 @@ public TextHolderFigure getLabelFor() { * Returns null, if no specialized tool is available. */ @Override - public Tool getTool(Point2D.Double p) { + public BaseTool getTool(Point2D.Double p) { return (target != null && contains(p)) ? new TextEditingTool(target) : null; } 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..0a684ec08 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 @@ -19,6 +19,7 @@ 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; @@ -284,7 +285,7 @@ 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; 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 index 7b182f98d..65529e048 100644 --- a/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/AbstractCreationTool.java +++ b/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/AbstractCreationTool.java @@ -102,20 +102,6 @@ public void deactivate(DrawingEditor editor) { } } - @Override - public void mouseClicked(MouseEvent e) { - - } - - @Override - public void mouseExited(MouseEvent e) { - - } - - @Override - public void mouseEntered(MouseEvent e) { - - } @Override public void mouseReleased(MouseEvent e) { 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 index 54604cae6..2b191c823 100644 --- a/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/ClickListeningTool.java +++ b/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/ClickListeningTool.java @@ -1,6 +1,12 @@ package org.jhotdraw.draw.tool; +import java.awt.event.MouseEvent; import java.awt.event.MouseListener; public interface ClickListeningTool extends MouseListener { + @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/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/KeyListeningTool.java b/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/KeyListeningTool.java index 9ae7269d8..b89f2514b 100644 --- a/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/KeyListeningTool.java +++ b/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/KeyListeningTool.java @@ -1,6 +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 { -} + @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/TextCreationTool.java b/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/TextCreationTool.java index de24a5376..0f89c41b4 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 @@ -110,12 +110,6 @@ public void mousePressed(MouseEvent e) { } } - //@Override - //public void mouseDragged(java.awt.event.MouseEvent e) { - // if (getCreatedFigure() != null && getCreatedFigure().isTransformable()) { - // super.mouseDragged(e); - // } - //} protected void beginEdit(TextHolderFigure textHolder) { if (textField == null) { @@ -130,16 +124,6 @@ protected void beginEdit(TextHolderFigure textHolder) { typingTarget = textHolder; } - @Override - public void mouseReleased(MouseEvent evt) { - TextFigure textFigure = (TextFigure) getCreatedFigure(); - - super.mouseReleased(evt); - - if (textField != null) { - beginEdit(textFigure); - } - } protected void endEdit() { if (typingTarget != null) { @@ -191,15 +175,7 @@ public void redo() { // view().checkDamage(); } - @Override - public void keyTyped(KeyEvent e) { - } - - @Override - public void keyPressed(KeyEvent e) { - - } @Override public void keyReleased(KeyEvent evt) { 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..f2620667d 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 @@ -44,7 +44,7 @@ * @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; @@ -87,9 +87,6 @@ protected void beginEdit(TextHolderFigure textHolder) { typingTarget = textHolder; } - @Override - public void mouseReleased(MouseEvent evt) { - } protected void endEdit() { if (typingTarget != null) { @@ -161,8 +158,5 @@ public void updateCursor(DrawingView view, Point p) { } } - @Override - public void mouseDragged(MouseEvent e) { - throw new UnsupportedOperationException("Not supported yet."); - } + } 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..8393ee2f6 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,6 +24,7 @@ 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; @@ -430,7 +431,7 @@ 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; From c793fb3e06591a1cbb23206275bfdfc7b99bda37 Mon Sep 17 00:00:00 2001 From: Seralyne Date: Sun, 28 Dec 2025 03:03:47 +0100 Subject: [PATCH 05/27] Fixed regression exposed by my refactor causing broken event loops. --- .../main/java/org/jhotdraw/draw/text/FloatingTextField.java | 1 + .../main/java/org/jhotdraw/draw/tool/ClickListeningTool.java | 2 +- .../src/main/java/org/jhotdraw/draw/tool/DragableTool.java | 2 +- .../main/java/org/jhotdraw/draw/tool/KeyListeningTool.java | 2 +- .../main/java/org/jhotdraw/draw/tool/TextCreationTool.java | 4 +++- 5 files changed, 7 insertions(+), 4 deletions(-) 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..1ade75c7d 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 @@ -80,6 +80,7 @@ public void createOverlay(DrawingView view, TextHolderFigure figure) { } protected void updateWidget() { + if (editedFigure == null) return; Font font = editedFigure.getFont(); font = font.deriveFont(font.getStyle(), (float) (editedFigure.getFontSize() * view.getScaleFactor())); textField.setFont(font); 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 index 2b191c823..b617e0354 100644 --- a/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/ClickListeningTool.java +++ b/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/ClickListeningTool.java @@ -3,7 +3,7 @@ import java.awt.event.MouseEvent; import java.awt.event.MouseListener; -public interface ClickListeningTool extends 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) {} 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 index bff72358f..f9a34e3bd 100644 --- a/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/DragableTool.java +++ b/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/DragableTool.java @@ -2,5 +2,5 @@ import java.awt.event.MouseMotionListener; -public interface DragableTool extends MouseMotionListener { +public interface DragableTool extends MouseMotionListener, BaseTool { } 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 index b89f2514b..5167251f2 100644 --- a/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/KeyListeningTool.java +++ b/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/KeyListeningTool.java @@ -4,7 +4,7 @@ import java.awt.event.KeyListener; import java.awt.event.MouseEvent; -public interface KeyListeningTool extends KeyListener { +public interface KeyListeningTool extends KeyListener, BaseTool { @Override default void keyTyped(KeyEvent e) { } 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 0f89c41b4..b8c7bdf03 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 @@ -85,6 +85,8 @@ public void deactivate(DrawingEditor editor) { super.deactivate(editor); } + + /** * Creates a new figure at the location where the mouse was pressed. */ @@ -169,8 +171,8 @@ public void redo() { }; getDrawing().fireUndoableEditHappened(edit); typingTarget.changed(); - typingTarget = null; textField.endOverlay(); + typingTarget = null; } // view().checkDamage(); } From d654e87987b1bf0ab8aebde1ce1fa444061663ab Mon Sep 17 00:00:00 2001 From: Seralyne Date: Sun, 28 Dec 2025 11:44:34 +0100 Subject: [PATCH 06/27] Changed TextCreationTool to work on mouseClicked since there was a regression with click and drag logic, but this tool does not need click and drag logic. --- .../draw/tool/SimpleCreationTool.java | 21 +++++++++ .../jhotdraw/draw/tool/TextCreationTool.java | 47 ++++++++++++++++++- 2 files changed, 67 insertions(+), 1 deletion(-) 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 index 67508995a..01d9d428b 100644 --- a/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/SimpleCreationTool.java +++ b/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/SimpleCreationTool.java @@ -1,9 +1,12 @@ 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 { @@ -20,6 +23,24 @@ public SimpleCreationTool(Figure prototype, Map, Object> attribu 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 b8c7bdf03..16172318e 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,6 +12,7 @@ import org.jhotdraw.draw.figure.TextHolderFigure; import java.awt.*; import java.awt.event.*; +import java.awt.geom.Point2D; import java.util.*; import javax.swing.undo.AbstractUndoableEdit; import javax.swing.undo.UndoableEdit; @@ -81,17 +82,55 @@ public TextCreationTool(TextHolderFigure prototype, Map, Object> @Override public void deactivate(DrawingEditor editor) { - endEdit(); + if (typingTarget != null) { + endEdit(); + } super.deactivate(editor); } + @Override + public void mouseReleased(MouseEvent event) { + boolean disabled = true; + if (disabled) return; + isWorking = false; + if (getCreatedFigure() instanceof TextHolderFigure) { + creationFinished(getCreatedFigure()); + TextHolderFigure createdFigure = (TextHolderFigure) getCreatedFigure(); + beginEdit(createdFigure); + return; + } + super.mouseReleased(event); + fireToolDone(); + } + + + @Override + public void mouseClicked(MouseEvent event) { + + if (typingTarget != null) { + endEdit(); + fireToolDone(); + return; + } + + super.mouseClicked(event); + + if (createdFigure instanceof TextHolderFigure) { + beginEdit((TextHolderFigure) createdFigure); + } + //isWorking = false; + } /** * Creates a new figure at the location where the mouse was pressed. */ @Override public void mousePressed(MouseEvent e) { + + // Disable this logic while adapting to mouseClicked + boolean disabled = true; + if (disabled) return; // Note: The search sequence used here, must be // consistent with the search sequence used by the // HandleTracker, SelectAreaTracker, DelegationSelectionTool, SelectionTool. @@ -113,6 +152,12 @@ public void mousePressed(MouseEvent e) { } + @Override + protected void fireToolDone() { + if (typingTarget != null) return; + super.fireToolDone(); + } + protected void beginEdit(TextHolderFigure textHolder) { if (textField == null) { textField = new FloatingTextField(); From fb1ec1bc17ef497f6d99fae77580d407a1d96a8f Mon Sep 17 00:00:00 2001 From: Seralyne Date: Sun, 28 Dec 2025 21:08:38 +0100 Subject: [PATCH 07/27] Work for today: Finished refactoring of TextCreationTool and TextEditingTool --- .../src/main/java/org/jhotdraw/draw/undo/TextEdit.java | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 jhotdraw-core/src/main/java/org/jhotdraw/draw/undo/TextEdit.java 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..3f8f1e30c --- /dev/null +++ b/jhotdraw-core/src/main/java/org/jhotdraw/draw/undo/TextEdit.java @@ -0,0 +1,4 @@ +package org.jhotdraw.draw.undo; + +public class TextEdit { +} From 4d48d789dc2978f10b14e3c3ac0db88739d377cb Mon Sep 17 00:00:00 2001 From: Seralyne Date: Mon, 29 Dec 2025 11:07:20 +0100 Subject: [PATCH 08/27] Some shit didn't add itself properly to last commit --- .../draw/tool/SimpleCreationTool.java | 9 +- .../jhotdraw/draw/tool/TextCreationTool.java | 107 +++--------------- .../jhotdraw/draw/tool/TextEditingTool.java | 34 +----- .../draw/tool/TextOverlayController.java | 1 + .../java/org/jhotdraw/draw/undo/TextEdit.java | 47 +++++++- 5 files changed, 72 insertions(+), 126 deletions(-) create mode 100644 jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/TextOverlayController.java 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 index 01d9d428b..e54b46e04 100644 --- a/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/SimpleCreationTool.java +++ b/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/SimpleCreationTool.java @@ -39,16 +39,11 @@ public void mouseClicked(MouseEvent event) { view.addToSelection(createdFigure); } - + @Override public void mousePressed(MouseEvent event) { + } @Override public void mouseReleased(MouseEvent event) { super.mouseReleased(event); - if (getCreatedFigure() != null) { - creationFinished(getCreatedFigure()); - } - if (isToolDoneAfterCreation()) { - fireToolDone(); - } } } 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 16172318e..918a20211 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 @@ -8,17 +8,13 @@ package org.jhotdraw.draw.tool; import org.jhotdraw.draw.figure.Figure; -import org.jhotdraw.draw.figure.TextFigure; import org.jhotdraw.draw.figure.TextHolderFigure; import java.awt.*; import java.awt.event.*; -import java.awt.geom.Point2D; 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} @@ -61,7 +57,7 @@ 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; @@ -88,22 +84,9 @@ public void deactivate(DrawingEditor editor) { super.deactivate(editor); } - - @Override - public void mouseReleased(MouseEvent event) { - boolean disabled = true; - if (disabled) return; - isWorking = false; - if (getCreatedFigure() instanceof TextHolderFigure) { - creationFinished(getCreatedFigure()); - TextHolderFigure createdFigure = (TextHolderFigure) getCreatedFigure(); - beginEdit(createdFigure); - return; - } - super.mouseReleased(event); - fireToolDone(); - } - + /** + * If the created figure is a TextHolderFigure it can be edited. + */ @Override public void mouseClicked(MouseEvent event) { @@ -119,36 +102,6 @@ public void mouseClicked(MouseEvent event) { if (createdFigure instanceof TextHolderFigure) { beginEdit((TextHolderFigure) createdFigure); } - //isWorking = false; - } - - /** - * Creates a new figure at the location where the mouse was pressed. - */ - @Override - public void mousePressed(MouseEvent e) { - - // Disable this logic while adapting to mouseClicked - boolean disabled = true; - if (disabled) return; - // Note: The search sequence used here, must be - // consistent with the search sequence used by the - // HandleTracker, SelectAreaTracker, DelegationSelectionTool, SelectionTool. - 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()); - } } @@ -158,6 +111,7 @@ protected void fireToolDone() { super.fireToolDone(); } + @SuppressWarnings("Duplicates") protected void beginEdit(TextHolderFigure textHolder) { if (textField == null) { textField = new FloatingTextField(); @@ -178,61 +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(); - textField.endOverlay(); 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 || isToolDoneAfterCreation()) { fireToolDone(); } } @Override - public void actionPerformed(ActionEvent event) { + public void actionPerformed(ActionEvent actionEvent) { endEdit(); if (isToolDoneAfterCreation()) { fireToolDone(); @@ -250,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 f2620667d..ff4d2ecb2 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 @@ -14,6 +14,7 @@ import javax.swing.undo.UndoableEdit; import org.jhotdraw.draw.*; import org.jhotdraw.draw.text.*; +import org.jhotdraw.draw.undo.TextEdit; import org.jhotdraw.util.ResourceBundleUtil; /** @@ -74,6 +75,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(); @@ -94,42 +98,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 diff --git a/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/TextOverlayController.java b/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/TextOverlayController.java new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/TextOverlayController.java @@ -0,0 +1 @@ + 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 index 3f8f1e30c..2a5c8929f 100644 --- a/jhotdraw-core/src/main/java/org/jhotdraw/draw/undo/TextEdit.java +++ b/jhotdraw-core/src/main/java/org/jhotdraw/draw/undo/TextEdit.java @@ -1,4 +1,49 @@ package org.jhotdraw.draw.undo; -public class TextEdit { +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(); + } } From 2008599f28b73de3208af45b1f5e86972d38fed9 Mon Sep 17 00:00:00 2001 From: Seralyne Date: Mon, 29 Dec 2025 11:09:20 +0100 Subject: [PATCH 09/27] Removed class created while testing --- .../main/java/org/jhotdraw/draw/tool/TextOverlayController.java | 1 - 1 file changed, 1 deletion(-) delete mode 100644 jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/TextOverlayController.java diff --git a/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/TextOverlayController.java b/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/TextOverlayController.java deleted file mode 100644 index 8b1378917..000000000 --- a/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/TextOverlayController.java +++ /dev/null @@ -1 +0,0 @@ - From 6619a11fd996c9083d128e216bf8f79db8ef9cc7 Mon Sep 17 00:00:00 2001 From: Seralyne Date: Tue, 30 Dec 2025 03:58:31 +0100 Subject: [PATCH 10/27] Done a bunch of refactoring --- .../jhotdraw/draw/AbstractDrawingView.java | 16 +- .../jhotdraw/draw/event/FigureListener.java | 14 +- .../org/jhotdraw/draw/figure/LabelFigure.java | 62 +++----- .../org/jhotdraw/draw/figure/TextFigure.java | 139 +++++++++++------- .../jhotdraw/draw/tool/TextEditingTool.java | 2 +- 5 files changed, 119 insertions(+), 114 deletions(-) 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 bff7f438d..709f8f72a 100644 --- a/jhotdraw-core/src/main/java/org/jhotdraw/draw/AbstractDrawingView.java +++ b/jhotdraw-core/src/main/java/org/jhotdraw/draw/AbstractDrawingView.java @@ -196,14 +196,15 @@ protected void drawBackground(Graphics2D g) { public boolean isSelectionEmpty() { return selectedFigures.isEmpty(); } - // TODO: Extract Class OR move to top + + private class EventHandler implements FigureListener, CompositeFigureListener, HandleListener, FocusListener { @Override public void figureAdded(CompositeFigureEvent evt) { if (drawing.getChildCount() == 1 && getEmptyDrawingMessage() != null) { repaint(); - } else { // FIXME: what the fuck + } else { repaintDrawingArea(evt.getCompositeFigure().getDrawingArea(AttributeKeys.getScaleFactor(getDrawingToViewTransform()))); } } @@ -212,14 +213,14 @@ public void figureAdded(CompositeFigureEvent evt) { public void figureRemoved(CompositeFigureEvent evt) { if (drawing.getChildCount() == 0 && getEmptyDrawingMessage() != null) { repaint(); - } else { // FIXME: this is just as obtuse down here as it was up there,theres gotta be a more clear way + } else { repaintDrawingArea(evt.getCompositeFigure().getDrawingArea(AttributeKeys.getScaleFactor(getDrawingToViewTransform()))); } removeFromSelection(evt.getChildFigure()); } @Override - public void areaInvalidated(FigureEvent evt) { // FIXME: Third Time I've seen this bullshit. Make a method that takes an event + public void areaInvalidated(FigureEvent evt) { repaintDrawingArea(evt.getFigure().getDrawingArea(AttributeKeys.getScaleFactor(getDrawingToViewTransform()))); } @@ -283,7 +284,7 @@ public void figureHandlesChanged(FigureEvent e) { } @Override - public void figureChanged(FigureEvent e) { // FIXME: ANOTHER ONE + public void figureChanged(FigureEvent e) { repaintDrawingArea(e.getFigure().getDrawingArea(AttributeKeys.getScaleFactor(getDrawingToViewTransform()))); } @@ -300,7 +301,6 @@ public void figureRequestRemove(FigureEvent e) { } } - // FIXME: Move to top private final EventHandler eventHandler = new EventHandler(); @@ -955,8 +955,7 @@ public void delete() { drawing.fireUndoableEditHappened(new AbstractUndoableEdit() { private static final long serialVersionUID = 1L; - @Override // FIXME: Why the fuck are we nesting functions???? - // FIXME: Apparently an extract Class Angle + @Override public String getPresentationName() { ResourceBundleUtil labels = ResourceBundleUtil.getBundle("org.jhotdraw.draw.Labels"); return labels.getString("edit.delete.text"); @@ -1005,7 +1004,6 @@ public void duplicate() { } addToSelection(duplicates); - // FIXME: Extract Class drawing.fireUndoableEditHappened(new AbstractUndoableEdit() { private static final long serialVersionUID = 1L; 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/figure/LabelFigure.java b/jhotdraw-core/src/main/java/org/jhotdraw/draw/figure/LabelFigure.java index 5ca1dc8a5..0fd6130ce 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 @@ -13,21 +13,33 @@ 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. @@ -41,15 +53,7 @@ public LabelFigure(String text) { setEditable(false); } - public void setLabelFor(TextHolderFigure target) { - if (this.target != null) { - this.target.removeFigureListener(this); - } - this.target = target; - if (this.target != null) { - this.target.addFigureListener(this); - } - } + @Override public TextHolderFigure getLabelFor() { @@ -62,37 +66,15 @@ public TextHolderFigure getLabelFor() { * Returns null, if no specialized tool is available. */ @Override - public BaseTool 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) { @@ -100,14 +82,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 0a684ec08..71c96c0ee 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; @@ -21,7 +22,6 @@ 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; @@ -41,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. @@ -61,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 @@ -103,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; } @@ -129,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 @@ -155,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; @@ -203,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; } /** @@ -242,7 +254,7 @@ public Color getFillColor() { @Override public void setFontSize(float size) { - set(FONT_SIZE, new Double(size)); + set(FONT_SIZE, (double) size); } @Override @@ -256,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; } @@ -287,8 +296,7 @@ public Collection createHandles(int detailLevel) { @Override public BaseTool getTool(Point2D.Double p) { if (isEditable() && contains(p)) { - TextEditingTool t = new TextEditingTool(this); - return t; + return new TextEditingTool(this); } return null; } @@ -310,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/tool/TextEditingTool.java b/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/TextEditingTool.java index ff4d2ecb2..fafbce25f 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 @@ -48,7 +48,7 @@ public class TextEditingTool extends BaseToolImpl implements ActionListener, ClickListeningTool, KeyListeningTool { private static final long serialVersionUID = 1L; - private FloatingTextField textField; + private transient FloatingTextField textField; private TextHolderFigure typingTarget; /** From edec18ed3c78d594d1fa93b3855a6ad4f5da5161 Mon Sep 17 00:00:00 2001 From: Seralyne Date: Tue, 30 Dec 2025 13:18:26 +0100 Subject: [PATCH 11/27] Finished refactor --- .../org/jhotdraw/draw/figure/TextFigure.java | 2 +- .../text/AbstractEditableFloatingText.java | 49 ++++++++ .../jhotdraw/draw/text/FloatingTextArea.java | 36 +++--- .../jhotdraw/draw/text/FloatingTextField.java | 66 +++++------ .../samples/svg/figures/SVGTextFigure.java | 109 ++++++++++-------- 5 files changed, 157 insertions(+), 105 deletions(-) create mode 100644 jhotdraw-core/src/main/java/org/jhotdraw/draw/text/AbstractEditableFloatingText.java 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 71c96c0ee..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 @@ -41,7 +41,7 @@ public class TextFigure extends AbstractAttributedDecoratedFigure implements TextHolderFigure { - private static final int MIN_COLUMN_COUNT = 4; +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; 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..3a2fd1454 --- /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; + + protected 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..8464fa72b 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 + protected JComponent getEditorComponent() { + return textArea; + } + + @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 1ade75c7d..be72f3d3d 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 + protected JComponent getEditorComponent() { + return textField; + } + + @Override + protected DrawingView getDrawingView() { + return view; + } + /** * Creates the overlay for the given Container using a * specific font. @@ -79,31 +89,32 @@ public void createOverlay(DrawingView view, TextHolderFigure figure) { updateWidget(); } + protected void updateWidget() { if (editedFigure == null) return; - Font font = editedFigure.getFont(); - font = font.deriveFont(font.getStyle(), (float) (editedFigure.getFontSize() * view.getScaleFactor())); - textField.setFont(font); + 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(); + 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) ); } @@ -140,20 +151,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-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 8393ee2f6..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 @@ -26,7 +26,6 @@ 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; @@ -50,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}; @@ -77,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 @@ -118,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); @@ -183,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); @@ -206,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); } @@ -297,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); @@ -326,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) { @@ -367,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) { @@ -375,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); } @@ -403,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; } @@ -433,8 +417,7 @@ public Collection createHandles(int detailLevel) { @Override public BaseTool getTool(Point2D.Double p) { if (isEditable() && contains(p)) { - TextEditingTool tool = new TextEditingTool(this); - return tool; + return new TextEditingTool(this); } return null; } @@ -462,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]; @@ -478,7 +489,7 @@ public SVGTextFigure clone() { @Override public boolean isEmpty() { - return getText() == null || getText().length() == 0; + return getText() == null || getText().isEmpty(); } @Override From 52dabd4a460b0a210854493c29a13a88c37be99b Mon Sep 17 00:00:00 2001 From: Seralyne Date: Wed, 31 Dec 2025 00:30:26 +0100 Subject: [PATCH 12/27] Add CI/CD and finish refactor truly. Now to write tests --- .github/workflows/maven-build.yml | 40 +++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 .github/workflows/maven-build.yml diff --git a/.github/workflows/maven-build.yml b/.github/workflows/maven-build.yml new file mode 100644 index 000000000..1708f7437 --- /dev/null +++ b/.github/workflows/maven-build.yml @@ -0,0 +1,40 @@ +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 25 + uses: actions/setup-java@v4 + with: + java-version: '25' + 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 + + - 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 From 4fc85d9ec71a48397a05a062736e3f842d477654 Mon Sep 17 00:00:00 2001 From: Seralyne Date: Wed, 31 Dec 2025 14:02:44 +0100 Subject: [PATCH 13/27] Added basic tests. See if it works in CI/CD too --- jhotdraw-core/pom.xml | 68 ++++++++++++++++++- .../jhotdraw/draw/tool/BaseToolImplTest.java | 61 +++++++++++++++++ .../draw/tool/TextCreationToolTest.java | 54 +++++++++++++++ 3 files changed, 182 insertions(+), 1 deletion(-) create mode 100644 jhotdraw-core/src/test/java/org/jhotdraw/draw/tool/BaseToolImplTest.java create mode 100644 jhotdraw-core/src/test/java/org/jhotdraw/draw/tool/TextCreationToolTest.java diff --git a/jhotdraw-core/pom.xml b/jhotdraw-core/pom.xml index 7c276da85..769cf4bf7 100644 --- a/jhotdraw-core/pom.xml +++ b/jhotdraw-core/pom.xml @@ -35,10 +35,76 @@ 6.8.21 test + + org.mockito + mockito-core + 5.11.0 + test + ${project.groupId} jhotdraw-actions ${project.version} - + + junit + junit + 4.13.2 + test + + + junit + junit + 4.13.2 + test + + + junit + junit + 4.13.2 + test + + + junit + junit + 4.13.2 + test + + + junit + junit + 4.13.2 + test + + + junit + junit + 4.13.2 + test + + + junit + junit + 4.13.2 + test + + + junit + junit + 4.13.2 + test + + + junit + junit + 4.13.2 + test + + + junit + junit + 4.13.2 + test + + \ No newline at end of file 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..407d3a2b7 --- /dev/null +++ b/jhotdraw-core/src/test/java/org/jhotdraw/draw/tool/TextCreationToolTest.java @@ -0,0 +1,54 @@ +package org.jhotdraw.draw.tool; + +import org.jhotdraw.draw.DrawingEditor; +import org.jhotdraw.draw.figure.TextHolderFigure; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.MockMakers; + +import java.awt.event.KeyListener; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.awt.event.MouseMotionListener; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +public class TextCreationToolTest { + private TextCreationTool tool; + private DrawingEditor editor; + private TextHolderFigure figure; + + @Before + public void setUp() throws Exception { + // Mockito cannot mock this under JDK 25 for whatever reason without this. + figure = mock(TextHolderFigure.class, withSettings().mockMaker(MockMakers.SUBCLASS)); + tool = new TextCreationTool(figure); + + editor = mock(DrawingEditor.class); + } + + @Test + public void testImplementsNecessaryContracts() { + // If this is not true, something has gone terribly wrong. + assertTrue(tool instanceof MouseListener); + assertTrue(tool instanceof KeyListener); + assertFalse(tool instanceof MouseMotionListener); + } + + @Test + public void testHandleMouseClicks() { + tool.activate(editor); + + MouseEvent event = mock(MouseEvent.class, withSettings().mockMaker(MockMakers.SUBCLASS)); + + tool.mouseClicked(event); + + verify(editor, atLeastOnce()).getActiveView(); + } + + @After + public void tearDown() throws Exception { + } +} \ No newline at end of file From e5560f4a7b5e9048b1343aabcf633fe45cc5ce96 Mon Sep 17 00:00:00 2001 From: Seralyne Date: Wed, 31 Dec 2025 17:44:41 +0100 Subject: [PATCH 14/27] Started work on test suite --- .../draw/tool/AbstractCreationTool.java | 18 -------- .../org/jhotdraw/draw/tool/CreationTool.java | 2 +- .../draw/tool/ExtendedMouseCreationTool.java | 41 +++++++++++++++++++ .../draw/tool/TextCreationToolTest.java | 30 +++++++++++++- 4 files changed, 71 insertions(+), 20 deletions(-) create mode 100644 jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/ExtendedMouseCreationTool.java 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 index 65529e048..821e736c3 100644 --- a/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/AbstractCreationTool.java +++ b/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/AbstractCreationTool.java @@ -108,25 +108,7 @@ public void mouseReleased(MouseEvent e) { isWorking = false; } - @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); - } @SuppressWarnings("unchecked") protected Figure createFigure() { 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 2eadec9ab..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 @@ -58,7 +58,7 @@ */ @Deprecated() -public class CreationTool extends AbstractCreationTool implements DragableTool { +public class CreationTool extends ExtendedMouseCreationTool implements DragableTool { private static final long serialVersionUID = 1L; /** 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/test/java/org/jhotdraw/draw/tool/TextCreationToolTest.java b/jhotdraw-core/src/test/java/org/jhotdraw/draw/tool/TextCreationToolTest.java index 407d3a2b7..7636586f3 100644 --- a/jhotdraw-core/src/test/java/org/jhotdraw/draw/tool/TextCreationToolTest.java +++ b/jhotdraw-core/src/test/java/org/jhotdraw/draw/tool/TextCreationToolTest.java @@ -1,12 +1,16 @@ package org.jhotdraw.draw.tool; +import org.jhotdraw.draw.DefaultDrawing; +import org.jhotdraw.draw.DefaultDrawingView; import org.jhotdraw.draw.DrawingEditor; +import org.jhotdraw.draw.DrawingView; import org.jhotdraw.draw.figure.TextHolderFigure; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.mockito.MockMakers; +import java.awt.*; import java.awt.event.KeyListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; @@ -19,14 +23,16 @@ public class TextCreationToolTest { private TextCreationTool tool; private DrawingEditor editor; private TextHolderFigure figure; + DrawingView view; @Before public void setUp() throws Exception { // Mockito cannot mock this under JDK 25 for whatever reason without this. figure = mock(TextHolderFigure.class, withSettings().mockMaker(MockMakers.SUBCLASS)); tool = new TextCreationTool(figure); - + view = mock(DrawingView.class, withSettings().mockMaker(MockMakers.SUBCLASS)); editor = mock(DrawingEditor.class); + when(editor.getActiveView()).thenReturn(view); } @Test @@ -48,6 +54,28 @@ public void testHandleMouseClicks() { verify(editor, atLeastOnce()).getActiveView(); } + @Test + public void testMouseClickedAddsFigureToDrawing() { + DefaultDrawing drawing = new DefaultDrawing(); + + DefaultDrawingView realView = new DefaultDrawingView(); + DefaultDrawing realDrawing = new DefaultDrawing(); + realView.setDrawing(realDrawing); + + when(editor.getActiveView()).thenReturn(realView); + + tool.activate(editor); + + MouseEvent click = new MouseEvent(realView, MouseEvent.MOUSE_CLICKED, + System.currentTimeMillis(), 0, 50, 50, 1, false); + + tool.mouseClicked(click); + + assertEquals("The drawing should now have the figure", 1, realDrawing.getChildCount()); + + + } + @After public void tearDown() throws Exception { } From b6edd6ca74a5570295ddcb3297fd1b1e8a51cf93 Mon Sep 17 00:00:00 2001 From: Seralyne Date: Thu, 1 Jan 2026 16:04:51 +0100 Subject: [PATCH 15/27] Test debugging --- .../draw/tool/TextCreationToolTest.java | 25 ++++++------------- 1 file changed, 8 insertions(+), 17 deletions(-) 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 index 7636586f3..800084003 100644 --- a/jhotdraw-core/src/test/java/org/jhotdraw/draw/tool/TextCreationToolTest.java +++ b/jhotdraw-core/src/test/java/org/jhotdraw/draw/tool/TextCreationToolTest.java @@ -4,11 +4,13 @@ import org.jhotdraw.draw.DefaultDrawingView; import org.jhotdraw.draw.DrawingEditor; import org.jhotdraw.draw.DrawingView; +import org.jhotdraw.draw.figure.Figure; import org.jhotdraw.draw.figure.TextHolderFigure; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.mockito.MockMakers; +import org.mockito.MockitoAnnotations; import java.awt.*; import java.awt.event.KeyListener; @@ -27,6 +29,7 @@ public class TextCreationToolTest { @Before public void setUp() throws Exception { + MockitoAnnotations.openMocks(this); // Mockito cannot mock this under JDK 25 for whatever reason without this. figure = mock(TextHolderFigure.class, withSettings().mockMaker(MockMakers.SUBCLASS)); tool = new TextCreationTool(figure); @@ -55,25 +58,13 @@ public void testHandleMouseClicks() { } @Test - public void testMouseClickedAddsFigureToDrawing() { - DefaultDrawing drawing = new DefaultDrawing(); - - DefaultDrawingView realView = new DefaultDrawingView(); - DefaultDrawing realDrawing = new DefaultDrawing(); - realView.setDrawing(realDrawing); - - when(editor.getActiveView()).thenReturn(realView); - + public void testCreatesNewInstanceEachTime() { tool.activate(editor); + MouseEvent click1 = new MouseEvent(realView, MouseEvent.MOUSE_CLICKED, ...); + tool.mouseClicked(click1); - MouseEvent click = new MouseEvent(realView, MouseEvent.MOUSE_CLICKED, - System.currentTimeMillis(), 0, 50, 50, 1, false); - - tool.mouseClicked(click); - - assertEquals("The drawing should now have the figure", 1, realDrawing.getChildCount()); - - + Figure firstFigure = realDrawing.getChild(0); + assertNotSame("The created figure should be a clone, not the prototype", figure, firstFigure); } @After From 2aa0671ea780f937b0d4f1096731139ff9a3b5b3 Mon Sep 17 00:00:00 2001 From: Seralyne Date: Thu, 1 Jan 2026 21:10:52 +0100 Subject: [PATCH 16/27] Changed JDK version due to incompatibilities between Mockito and JDK 25 --- .github/workflows/maven-build.yml | 4 ++-- .../draw/tool/SimpleCreationTool.java | 2 -- .../draw/tool/TextCreationToolTest.java | 22 ++++++++++++------- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/.github/workflows/maven-build.yml b/.github/workflows/maven-build.yml index 1708f7437..1fa8479e3 100644 --- a/.github/workflows/maven-build.yml +++ b/.github/workflows/maven-build.yml @@ -13,10 +13,10 @@ jobs: - name: Clone code to worker uses: actions/checkout@v4 - - name: Set up JDK 25 + - name: Set up JDK 21 uses: actions/setup-java@v4 with: - java-version: '25' + java-version: '21' distribution: temurin cache: maven 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 index e54b46e04..5c8175082 100644 --- a/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/SimpleCreationTool.java +++ b/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/SimpleCreationTool.java @@ -39,8 +39,6 @@ public void mouseClicked(MouseEvent event) { view.addToSelection(createdFigure); } - @Override public void mousePressed(MouseEvent event) { - } @Override public void mouseReleased(MouseEvent event) { 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 index 800084003..2a90b1595 100644 --- a/jhotdraw-core/src/test/java/org/jhotdraw/draw/tool/TextCreationToolTest.java +++ b/jhotdraw-core/src/test/java/org/jhotdraw/draw/tool/TextCreationToolTest.java @@ -5,6 +5,7 @@ import org.jhotdraw.draw.DrawingEditor; import org.jhotdraw.draw.DrawingView; 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; @@ -17,6 +18,7 @@ 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.*; @@ -29,12 +31,12 @@ public class TextCreationToolTest { @Before public void setUp() throws Exception { - MockitoAnnotations.openMocks(this); // Mockito cannot mock this under JDK 25 for whatever reason without this. + MockitoAnnotations.openMocks(this); figure = mock(TextHolderFigure.class, withSettings().mockMaker(MockMakers.SUBCLASS)); tool = new TextCreationTool(figure); view = mock(DrawingView.class, withSettings().mockMaker(MockMakers.SUBCLASS)); - editor = mock(DrawingEditor.class); + editor = mock(DrawingEditor.class, withSettings().mockMaker(MockMakers.SUBCLASS)); when(editor.getActiveView()).thenReturn(view); } @@ -57,16 +59,20 @@ public void testHandleMouseClicks() { verify(editor, atLeastOnce()).getActiveView(); } + @Test - public void testCreatesNewInstanceEachTime() { - tool.activate(editor); - MouseEvent click1 = new MouseEvent(realView, MouseEvent.MOUSE_CLICKED, ...); - tool.mouseClicked(click1); + public void testToolUsesCorrectPrototype() { + TextFigure figure = mock(TextFigure.class); + when(figure.getText()).thenReturn("Test Text"); + TextCreationTool tool = new TextCreationTool(figure); + + Figure prototype = tool.getPrototype(); - Figure firstFigure = realDrawing.getChild(0); - assertNotSame("The created figure should be a clone, not the prototype", figure, firstFigure); + assertTrue(prototype instanceof TextHolderFigure); + assertEquals("Test Text", ((TextHolderFigure) prototype).getText()); } + @After public void tearDown() throws Exception { } From 2e2a17ab02bcd514994c43493b98716ca6c02133 Mon Sep 17 00:00:00 2001 From: Seralyne Date: Thu, 1 Jan 2026 21:26:44 +0100 Subject: [PATCH 17/27] Fixed bug caused when refactoring endOverlay --- .../src/main/java/org/jhotdraw/draw/text/FloatingTextArea.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 8464fa72b..9029d6c7e 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 @@ -89,7 +89,7 @@ public void requestFocus() { @Override protected JComponent getEditorComponent() { - return textArea; + return editScrollContainer; } @Override From 7aecbf89461c09e9d01302f6de8ade0fb9801c05 Mon Sep 17 00:00:00 2001 From: Seralyne Date: Thu, 1 Jan 2026 21:35:56 +0100 Subject: [PATCH 18/27] Problems with worker --- .github/workflows/maven-build.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/maven-build.yml b/.github/workflows/maven-build.yml index 1fa8479e3..03d0ca7e3 100644 --- a/.github/workflows/maven-build.yml +++ b/.github/workflows/maven-build.yml @@ -27,7 +27,10 @@ jobs: run: mvn compile -Djava.awt.headless=true - name: Run All Tests - run: xvfb-run mvn -B verify + run: > + xvfb-run mvn -B verify + -DfailIfNoTests=false + -Dtest="*Test, *IT" - name: Get Test Results if: always() From 9422ecc08b04dc2c63b735a9c121dad1aa0428f1 Mon Sep 17 00:00:00 2001 From: Seralyne Date: Thu, 1 Jan 2026 21:39:58 +0100 Subject: [PATCH 19/27] Duplicate junit dependencies --- jhotdraw-core/pom.xml | 54 ------------------------------------------- 1 file changed, 54 deletions(-) diff --git a/jhotdraw-core/pom.xml b/jhotdraw-core/pom.xml index 769cf4bf7..98293745f 100644 --- a/jhotdraw-core/pom.xml +++ b/jhotdraw-core/pom.xml @@ -52,59 +52,5 @@ 4.13.2 test - - junit - junit - 4.13.2 - test - - - junit - junit - 4.13.2 - test - - - junit - junit - 4.13.2 - test - - - junit - junit - 4.13.2 - test - - - junit - junit - 4.13.2 - test - - - junit - junit - 4.13.2 - test - - - junit - junit - 4.13.2 - test - - - junit - junit - 4.13.2 - test - - - junit - junit - 4.13.2 - test - \ No newline at end of file From dd0323f7da0dcab9e528e8738e370ba83bdf427d Mon Sep 17 00:00:00 2001 From: Seralyne Date: Thu, 1 Jan 2026 21:41:25 +0100 Subject: [PATCH 20/27] Forgot to update worker code to not pattern match --- .github/workflows/maven-build.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/maven-build.yml b/.github/workflows/maven-build.yml index 03d0ca7e3..9c7abc483 100644 --- a/.github/workflows/maven-build.yml +++ b/.github/workflows/maven-build.yml @@ -30,7 +30,6 @@ jobs: run: > xvfb-run mvn -B verify -DfailIfNoTests=false - -Dtest="*Test, *IT" - name: Get Test Results if: always() From c9ad0602bee693654cfcaea4092f1d87c0c697fd Mon Sep 17 00:00:00 2001 From: Seralyne Date: Thu, 1 Jan 2026 21:52:10 +0100 Subject: [PATCH 21/27] Force maven to do both junit and testng for worker --- .github/workflows/maven-build.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/maven-build.yml b/.github/workflows/maven-build.yml index 9c7abc483..25bf90a63 100644 --- a/.github/workflows/maven-build.yml +++ b/.github/workflows/maven-build.yml @@ -30,6 +30,8 @@ jobs: run: > xvfb-run mvn -B verify -DfailIfNoTests=false + -Dsurefire.includeJUnit=true + -Dsurefire.includeTestNG=true - name: Get Test Results if: always() From 16bddbd500a477d18e9b3b6592bd7df28c0ade6f Mon Sep 17 00:00:00 2001 From: Seralyne Date: Thu, 1 Jan 2026 21:55:30 +0100 Subject: [PATCH 22/27] Test removal of testNG --- jhotdraw-core/pom.xml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/jhotdraw-core/pom.xml b/jhotdraw-core/pom.xml index 98293745f..737702cf7 100644 --- a/jhotdraw-core/pom.xml +++ b/jhotdraw-core/pom.xml @@ -29,12 +29,6 @@ jhotdraw-datatransfer ${project.version} - - org.testng - testng - 6.8.21 - test - org.mockito mockito-core From 6afcee55b60e9bc34279f899e22063ccd46c874c Mon Sep 17 00:00:00 2001 From: Seralyne Date: Thu, 1 Jan 2026 22:27:13 +0100 Subject: [PATCH 23/27] Okay so removing setLabelFor during my refactor was stupid --- .github/workflows/maven-build.yml | 2 - jhotdraw-core/pom.xml | 6 +++ .../org/jhotdraw/draw/figure/LabelFigure.java | 11 ++++ .../jhotdraw/draw/figure/LabelFigureTest.java | 23 ++++++++ .../jhotdraw/draw/figure/TextFigureTest.java | 54 +++++++++++++++++++ 5 files changed, 94 insertions(+), 2 deletions(-) create mode 100644 jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/LabelFigureTest.java create mode 100644 jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/TextFigureTest.java diff --git a/.github/workflows/maven-build.yml b/.github/workflows/maven-build.yml index 25bf90a63..9c7abc483 100644 --- a/.github/workflows/maven-build.yml +++ b/.github/workflows/maven-build.yml @@ -30,8 +30,6 @@ jobs: run: > xvfb-run mvn -B verify -DfailIfNoTests=false - -Dsurefire.includeJUnit=true - -Dsurefire.includeTestNG=true - name: Get Test Results if: always() diff --git a/jhotdraw-core/pom.xml b/jhotdraw-core/pom.xml index 737702cf7..98293745f 100644 --- a/jhotdraw-core/pom.xml +++ b/jhotdraw-core/pom.xml @@ -29,6 +29,12 @@ jhotdraw-datatransfer ${project.version} + + org.testng + testng + 6.8.21 + test + org.mockito mockito-core 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 0fd6130ce..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 @@ -54,6 +54,17 @@ public LabelFigure(String text) { } + public void setLabelFor(TextHolderFigure target) { + if (this.target != null) { + this.target.removeFigureListener(eventHandler); + } + this.target = target; + if (this.target != null) { + this.target.addFigureListener(eventHandler); + } + } + + @Override public TextHolderFigure getLabelFor() { diff --git a/jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/LabelFigureTest.java b/jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/LabelFigureTest.java new file mode 100644 index 000000000..248d519d3 --- /dev/null +++ b/jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/LabelFigureTest.java @@ -0,0 +1,23 @@ +package org.jhotdraw.draw.figure; + +import org.junit.After; +import org.junit.Before; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.mock; + +public class LabelFigureTest { + private LabelFigure figure; + private TextHolderFigure mockTarget; + + @Before + public void setUp() throws Exception { + figure = new LabelFigure(); + mockTarget = mock(TextHolderFigure.class); + figure.setLabelFor(figure); + } + + @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 From 0833829b05fb5c014b69cc0ded38583a385b06c9 Mon Sep 17 00:00:00 2001 From: Seralyne Date: Sat, 3 Jan 2026 02:20:56 +0100 Subject: [PATCH 24/27] Integration tests mostly done. Last one probably won't be finished unless I've got extra time on the 8th --- jhotdraw-core/pom.xml | 2 +- .../jhotdraw/draw/text/FloatingTextField.java | 1 + .../jhotdraw/draw/tool/TextEditingTool.java | 5 +- .../draw/DefaultDrawingEditorTest.java | 30 +++++++ .../jhotdraw/draw/figure/LabelFigureTest.java | 23 ----- .../draw/tool/TextCreationToolTest.java | 56 ++++++++++-- .../draw/tool/TextEditingToolTest.java | 88 +++++++++++++++++++ 7 files changed, 168 insertions(+), 37 deletions(-) create mode 100644 jhotdraw-core/src/test/java/org/jhotdraw/draw/DefaultDrawingEditorTest.java delete mode 100644 jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/LabelFigureTest.java create mode 100644 jhotdraw-core/src/test/java/org/jhotdraw/draw/tool/TextEditingToolTest.java diff --git a/jhotdraw-core/pom.xml b/jhotdraw-core/pom.xml index 98293745f..9618ec1a8 100644 --- a/jhotdraw-core/pom.xml +++ b/jhotdraw-core/pom.xml @@ -34,7 +34,7 @@ testng 6.8.21 test - + org.mockito mockito-core 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 be72f3d3d..9d38cf489 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 @@ -108,6 +108,7 @@ protected void updateWidget() { 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( 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 fafbce25f..9c708c597 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,12 +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.draw.undo.TextEdit; -import org.jhotdraw.util.ResourceBundleUtil; /** * A tool to edit figures which implement the {@code TextHolderFigure} interface, @@ -48,7 +45,7 @@ public class TextEditingTool extends BaseToolImpl implements ActionListener, ClickListeningTool, KeyListeningTool { private static final long serialVersionUID = 1L; - private transient FloatingTextField textField; + transient FloatingTextField textField; // Visibility slightly relaxed for integration tests. private TextHolderFigure typingTarget; /** 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/LabelFigureTest.java b/jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/LabelFigureTest.java deleted file mode 100644 index 248d519d3..000000000 --- a/jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/LabelFigureTest.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.jhotdraw.draw.figure; - -import org.junit.After; -import org.junit.Before; - -import static org.junit.Assert.*; -import static org.mockito.Mockito.mock; - -public class LabelFigureTest { - private LabelFigure figure; - private TextHolderFigure mockTarget; - - @Before - public void setUp() throws Exception { - figure = new LabelFigure(); - mockTarget = mock(TextHolderFigure.class); - figure.setLabelFor(figure); - } - - @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 index 2a90b1595..f716f0587 100644 --- a/jhotdraw-core/src/test/java/org/jhotdraw/draw/tool/TextCreationToolTest.java +++ b/jhotdraw-core/src/test/java/org/jhotdraw/draw/tool/TextCreationToolTest.java @@ -1,18 +1,17 @@ package org.jhotdraw.draw.tool; -import org.jhotdraw.draw.DefaultDrawing; -import org.jhotdraw.draw.DefaultDrawingView; -import org.jhotdraw.draw.DrawingEditor; -import org.jhotdraw.draw.DrawingView; +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; @@ -27,22 +26,26 @@ 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. - MockitoAnnotations.openMocks(this); - figure = mock(TextHolderFigure.class, withSettings().mockMaker(MockMakers.SUBCLASS)); + figure = mock(TextFigure.class); tool = new TextCreationTool(figure); - view = mock(DrawingView.class, withSettings().mockMaker(MockMakers.SUBCLASS)); - editor = mock(DrawingEditor.class, withSettings().mockMaker(MockMakers.SUBCLASS)); + 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); @@ -52,7 +55,7 @@ public void testImplementsNecessaryContracts() { public void testHandleMouseClicks() { tool.activate(editor); - MouseEvent event = mock(MouseEvent.class, withSettings().mockMaker(MockMakers.SUBCLASS)); + MouseEvent event = mock(MouseEvent.class); tool.mouseClicked(event); @@ -72,6 +75,41 @@ public void testToolUsesCorrectPrototype() { 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 { 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 From 3307bc09f5041971c28e615ba8c99dd9ca0576ee Mon Sep 17 00:00:00 2001 From: Seralyne Date: Sat, 3 Jan 2026 02:39:56 +0100 Subject: [PATCH 25/27] Fixed small regression in which pressing escape no longer finishes --- .../org/jhotdraw/draw/text/AbstractEditableFloatingText.java | 2 +- .../src/main/java/org/jhotdraw/draw/text/FloatingTextArea.java | 2 +- .../src/main/java/org/jhotdraw/draw/text/FloatingTextField.java | 2 +- .../src/main/java/org/jhotdraw/draw/tool/TextCreationTool.java | 2 +- .../src/main/java/org/jhotdraw/draw/tool/TextEditingTool.java | 1 + 5 files changed, 5 insertions(+), 4 deletions(-) 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 index 3a2fd1454..a245cb3d5 100644 --- a/jhotdraw-core/src/main/java/org/jhotdraw/draw/text/AbstractEditableFloatingText.java +++ b/jhotdraw-core/src/main/java/org/jhotdraw/draw/text/AbstractEditableFloatingText.java @@ -13,7 +13,7 @@ public abstract class AbstractEditableFloatingText { private DrawingView view; private TextHolderFigure editedFigure; - protected abstract JComponent getEditorComponent(); + public abstract JComponent getEditorComponent(); protected abstract void updateWidget(); protected abstract DrawingView getDrawingView(); 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 9029d6c7e..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 @@ -88,7 +88,7 @@ public void requestFocus() { } @Override - protected JComponent getEditorComponent() { + public JComponent getEditorComponent() { return editScrollContainer; } 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 9d38cf489..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 @@ -64,7 +64,7 @@ public void requestFocus() { } @Override - protected JComponent getEditorComponent() { + public JComponent getEditorComponent() { return textField; } 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 918a20211..e6f02a2b3 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 @@ -107,7 +107,6 @@ public void mouseClicked(MouseEvent event) { @Override protected void fireToolDone() { - if (typingTarget != null) return; super.fireToolDone(); } @@ -122,6 +121,7 @@ protected void beginEdit(TextHolderFigure textHolder) { } textField.createOverlay(getView(), textHolder); textField.requestFocus(); + textField.getEditorComponent().addKeyListener(this); typingTarget = textHolder; } 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 9c708c597..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 @@ -85,6 +85,7 @@ protected void beginEdit(TextHolderFigure textHolder) { } textField.createOverlay(getView(), textHolder); textField.requestFocus(); + textField.getEditorComponent().addKeyListener(this); typingTarget = textHolder; } From 7de0d0e4ffbf54b4f69b46b67d9a90b6fca4e104 Mon Sep 17 00:00:00 2001 From: Seralyne Date: Sat, 3 Jan 2026 04:34:40 +0100 Subject: [PATCH 26/27] Fixed regression that was caused by dumbassery early in refactor. Also started work on BDD tests --- jhotdraw-core/pom.xml | 36 +++++++++++++ .../org/jhotdraw/draw/DefaultDrawingView.java | 1 + .../jhotdraw/draw/tool/TextCreationTool.java | 2 +- .../draw/stages/GivenDrawingCanvas.java | 35 +++++++++++++ .../jhotdraw/draw/stages/JHotDrawStage.java | 23 ++++++++ .../draw/stages/ThenDrawingState.java | 52 +++++++++++++++++++ .../draw/tool/stages/WhenUserActs.java | 44 ++++++++++++++++ .../jhotdraw-samples-misc/pom.xml | 26 ++++++++++ .../samples/svg/gui/ToolsToolBar.java | 2 + .../samples/svg/TextToolScenarioTest.java | 27 ++++++++++ 10 files changed, 247 insertions(+), 1 deletion(-) create mode 100644 jhotdraw-core/src/test/java/org/jhotdraw/draw/stages/GivenDrawingCanvas.java create mode 100644 jhotdraw-core/src/test/java/org/jhotdraw/draw/stages/JHotDrawStage.java create mode 100644 jhotdraw-core/src/test/java/org/jhotdraw/draw/stages/ThenDrawingState.java create mode 100644 jhotdraw-core/src/test/java/org/jhotdraw/draw/tool/stages/WhenUserActs.java create mode 100644 jhotdraw-samples/jhotdraw-samples-misc/src/test/java/org/jhotdraw/samples/svg/TextToolScenarioTest.java diff --git a/jhotdraw-core/pom.xml b/jhotdraw-core/pom.xml index 9618ec1a8..ba7d55955 100644 --- a/jhotdraw-core/pom.xml +++ b/jhotdraw-core/pom.xml @@ -40,6 +40,26 @@ 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} @@ -53,4 +73,20 @@ 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/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/tool/TextCreationTool.java b/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/TextCreationTool.java index e6f02a2b3..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 @@ -153,7 +153,7 @@ protected void endEdit() { @Override public void keyReleased(KeyEvent keyEvent) { - if (keyEvent.getKeyCode() == KeyEvent.VK_ESCAPE || isToolDoneAfterCreation()) { + if (keyEvent.getKeyCode() == KeyEvent.VK_ESCAPE) { fireToolDone(); } } 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..e52b2a8cd --- /dev/null +++ b/jhotdraw-core/src/test/java/org/jhotdraw/draw/stages/GivenDrawingCanvas.java @@ -0,0 +1,35 @@ +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.JButton; +import java.awt.geom.Point2D; + +public class GivenDrawingCanvas extends JHotDrawStage { + + public GivenDrawingCanvas the_text_tool_is_selected() { + + window.button(new GenericTypeMatcher(JButton.class) { + @Override + protected boolean isMatching(JButton button) { + return "Text Tool".equals(button.getText()); + } + }).click(); + 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..03660f41d --- /dev/null +++ b/jhotdraw-core/src/test/java/org/jhotdraw/draw/stages/ThenDrawingState.java @@ -0,0 +1,52 @@ +package org.jhotdraw.draw.stages; + + +import org.jhotdraw.draw.Drawing; +import org.jhotdraw.draw.figure.Figure; +import org.jhotdraw.draw.figure.TextFigure; + +import java.util.ArrayList; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ThenDrawingState extends JHotDrawStage { + + 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_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 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 TextFigure, "Should be a TextFigure"); + + return self(); + } + + +} 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..facaf61ff --- /dev/null +++ b/jhotdraw-core/src/test/java/org/jhotdraw/draw/tool/stages/WhenUserActs.java @@ -0,0 +1,44 @@ +package org.jhotdraw.draw.tool.stages; + +import org.assertj.swing.core.MouseClickInfo; +import org.jhotdraw.draw.stages.JHotDrawStage; + +import javax.swing.*; +import java.awt.Point; +import java.awt.event.KeyEvent; + +import static org.assertj.swing.core.KeyPressInfo.keyCode; + +public class WhenUserActs extends JHotDrawStage { + + public WhenUserActs the_user_clicks_on_the_canvas_at(int x, int y) { + JPanel canvas = window.panel("drawingCanvas").target(); + window.robot().click(canvas, new Point(x,y)); + return self(); + } + + public WhenUserActs the_user_types(String text) { + window.robot().enterText(text); + return self(); + } + + public WhenUserActs the_user_presses_the_escape_key() { + window.pressAndReleaseKey(keyCode(KeyEvent.VK_ESCAPE)); + 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); + JPanel canvas = window.panel("drawingCanvas").target(); + window.robot().click(canvas, new Point(0,0)); + return self(); + } + + public WhenUserActs the_user_finishes_editing() { + JPanel canvas = window.panel("drawingCanvas").target(); + window.robot().click(canvas, new Point(1,1)); + return self(); + } + +} 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/svg/gui/ToolsToolBar.java b/jhotdraw-samples/jhotdraw-samples-misc/src/main/java/org/jhotdraw/samples/svg/gui/ToolsToolBar.java index 137da7a98..4db9135b2 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 @@ -139,6 +139,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..0267fc9a4 --- /dev/null +++ b/jhotdraw-samples/jhotdraw-samples-misc/src/test/java/org/jhotdraw/samples/svg/TextToolScenarioTest.java @@ -0,0 +1,27 @@ +package org.jhotdraw.samples.svg; + +import com.tngtech.jgiven.junit.ScenarioTest; +import org.assertj.swing.core.BasicRobot; +import org.assertj.swing.core.Robot; +import org.assertj.swing.edt.GuiActionRunner; +import org.assertj.swing.fixture.FrameFixture; +import org.assertj.swing.junit.testcase.AssertJSwingJUnitTestCase; +import org.jhotdraw.draw.stages.GivenDrawingCanvas; +import org.jhotdraw.draw.stages.ThenDrawingState; +import org.jhotdraw.draw.tool.stages.WhenUserActs; +import org.junit.Before; + +public class TextToolScenarioTest extends ScenarioTest { + + private FrameFixture window; + private Robot robot; + + @Before + public void setup() { + + robot = BasicRobot.robotWithNewAwtHierarchy(); + GuiActionRunner.execute(() -> org.jhotdraw.samples.svg.Main.main(new String[]{})); + + } + +} From 72510acb17cc7e45014206da9fdbd73769cb1182 Mon Sep 17 00:00:00 2001 From: Seralyne Date: Sat, 3 Jan 2026 06:36:08 +0100 Subject: [PATCH 27/27] FINAL COMMIT FUCKING FINALLY --- .../draw/stages/GivenDrawingCanvas.java | 22 +- .../draw/stages/ThenDrawingState.java | 82 +- .../draw/tool/stages/WhenUserActs.java | 81 +- ...draw.samples.svg.TextToolScenarioTest.json | 1340 +++++++++++++++++ .../samples/svg/gui/ToolsToolBar.java | 2 + .../samples/svg/TextToolScenarioTest.java | 102 +- 6 files changed, 1612 insertions(+), 17 deletions(-) create mode 100644 jhotdraw-samples/jhotdraw-samples-misc/jgiven-reports/org.jhotdraw.samples.svg.TextToolScenarioTest.json 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 index e52b2a8cd..e2612fcfc 100644 --- a/jhotdraw-core/src/test/java/org/jhotdraw/draw/stages/GivenDrawingCanvas.java +++ b/jhotdraw-core/src/test/java/org/jhotdraw/draw/stages/GivenDrawingCanvas.java @@ -4,19 +4,27 @@ import org.assertj.swing.edt.GuiActionRunner; import org.jhotdraw.draw.figure.TextFigure; -import javax.swing.JButton; +import javax.swing.*; import java.awt.geom.Point2D; public class GivenDrawingCanvas extends JHotDrawStage { public GivenDrawingCanvas the_text_tool_is_selected() { - window.button(new GenericTypeMatcher(JButton.class) { - @Override - protected boolean isMatching(JButton button) { - return "Text Tool".equals(button.getText()); - } - }).click(); + 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(); } 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 index 03660f41d..811ceb238 100644 --- a/jhotdraw-core/src/test/java/org/jhotdraw/draw/stages/ThenDrawingState.java +++ b/jhotdraw-core/src/test/java/org/jhotdraw/draw/stages/ThenDrawingState.java @@ -1,17 +1,33 @@ 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.assertThat; +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()) @@ -20,6 +36,15 @@ public ThenDrawingState a_new_text_figure_should_exist() { 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); @@ -29,6 +54,23 @@ public ThenDrawingState the_figure_at_index_should_have_text(int index, String e 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") @@ -43,8 +85,44 @@ public ThenDrawingState the_figure_should_be_selected() { assertThat(drawingView.getSelectedFigures()) .as("Check that there's only a text figure selected") .hasSize(1) - .allMatch(f -> f instanceof TextFigure, "Should be a TextFigure"); + .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/stages/WhenUserActs.java b/jhotdraw-core/src/test/java/org/jhotdraw/draw/tool/stages/WhenUserActs.java index facaf61ff..cb40ee2c6 100644 --- 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 @@ -1,44 +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 java.awt.Point; +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) { - JPanel canvas = window.panel("drawingCanvas").target(); + 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.pressAndReleaseKey(keyCode(KeyEvent.VK_ESCAPE)); + 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); - JPanel canvas = window.panel("drawingCanvas").target(); - window.robot().click(canvas, new Point(0,0)); - return self(); + Component canvas = window.robot().finder().findByName("drawingCanvas", true); + + return the_user_clicks_on_the_canvas_at(0,0); } public WhenUserActs the_user_finishes_editing() { - JPanel canvas = window.panel("drawingCanvas").target(); + 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-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/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 4db9135b2..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 @@ -84,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(); 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 index 0267fc9a4..5242a6371 100644 --- 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 @@ -1,27 +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[]{})); + 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(); + } + } + }