diff --git a/jhotdraw-actions/jgiven-reports/org.jhotdraw.action.edit.AbstractUndoRedoActionBDDTest.json b/jhotdraw-actions/jgiven-reports/org.jhotdraw.action.edit.AbstractUndoRedoActionBDDTest.json new file mode 100644 index 000000000..406648cd6 --- /dev/null +++ b/jhotdraw-actions/jgiven-reports/org.jhotdraw.action.edit.AbstractUndoRedoActionBDDTest.json @@ -0,0 +1,161 @@ +{ + "className": "org.jhotdraw.action.edit.AbstractUndoRedoActionBDDTest", + "name": "Abstract Undo Redo Action BDD", + "scenarios": [ + { + "className": "org.jhotdraw.action.edit.AbstractUndoRedoActionBDDTest", + "testMethodName": "undo_action_should_delegate_to_real_action", + "description": "undo action should delegate to real action", + "tagIds": [], + "explicitParameters": [], + "derivedParameters": [], + "scenarioCases": [ + { + "caseNr": 1, + "steps": [ + { + "name": "an application is running", + "words": [ + { + "value": "Given", + "isIntroWord": true + }, + { + "value": "an application is running" + } + ], + "status": "PASSED", + "durationInNanos": 914291900, + "depth": 0, + "parentFailed": false + }, + { + "name": "a real action is set", + "words": [ + { + "value": "and", + "isIntroWord": true + }, + { + "value": "a real action is set" + } + ], + "status": "PASSED", + "durationInNanos": 4300600, + "depth": 0, + "parentFailed": false + }, + { + "name": "the undo action is performed", + "words": [ + { + "value": "When", + "isIntroWord": true + }, + { + "value": "the undo action is performed" + } + ], + "status": "PASSED", + "durationInNanos": 323900, + "depth": 0, + "parentFailed": false + }, + { + "name": "the real action should be invoked", + "words": [ + { + "value": "Then", + "isIntroWord": true + }, + { + "value": "the real action should be invoked" + } + ], + "status": "PASSED", + "durationInNanos": 9822700, + "depth": 0, + "parentFailed": false + } + ], + "explicitArguments": [], + "derivedArguments": [], + "status": "SUCCESS", + "durationInNanos": 952868100 + } + ], + "casesAsTable": false, + "durationInNanos": 952868100 + }, + { + "className": "org.jhotdraw.action.edit.AbstractUndoRedoActionBDDTest", + "testMethodName": "undo_action_should_do_nothing_if_no_real_action", + "description": "undo action should do nothing if no real action", + "tagIds": [], + "explicitParameters": [], + "derivedParameters": [], + "scenarioCases": [ + { + "caseNr": 1, + "steps": [ + { + "name": "an application is running", + "words": [ + { + "value": "Given", + "isIntroWord": true + }, + { + "value": "an application is running" + } + ], + "status": "PASSED", + "durationInNanos": 1127300, + "depth": 0, + "parentFailed": false + }, + { + "name": "the undo action is performed", + "words": [ + { + "value": "When", + "isIntroWord": true + }, + { + "value": "the undo action is performed" + } + ], + "status": "PASSED", + "durationInNanos": 391000, + "depth": 0, + "parentFailed": false + }, + { + "name": "the real action should not be invoked", + "words": [ + { + "value": "Then", + "isIntroWord": true + }, + { + "value": "the real action should not be invoked" + } + ], + "status": "PASSED", + "durationInNanos": 197300, + "depth": 0, + "parentFailed": false + } + ], + "explicitArguments": [], + "derivedArguments": [], + "status": "SUCCESS", + "durationInNanos": 2158700 + } + ], + "casesAsTable": false, + "durationInNanos": 2158700 + } + ], + "tagMap": {} +} \ No newline at end of file diff --git a/jhotdraw-actions/pom.xml b/jhotdraw-actions/pom.xml index 5cc33e1c6..824d8bc90 100644 --- a/jhotdraw-actions/pom.xml +++ b/jhotdraw-actions/pom.xml @@ -24,5 +24,35 @@ jhotdraw-datatransfer ${project.version} + + org.mockito + mockito-core + 5.7.0 + test + + + org.junit.jupiter + junit-jupiter + RELEASE + test + + + org.assertj + assertj-core + 3.24.2 + test + + + com.tngtech.jgiven + jgiven-junit + 1.3.0 + test + + + junit + junit + 4.13.1 + test + \ No newline at end of file diff --git a/jhotdraw-actions/src/main/java/org/jhotdraw/action/AbstractViewAction.java b/jhotdraw-actions/src/main/java/org/jhotdraw/action/AbstractViewAction.java index b072f9be8..84382a7ff 100644 --- a/jhotdraw-actions/src/main/java/org/jhotdraw/action/AbstractViewAction.java +++ b/jhotdraw-actions/src/main/java/org/jhotdraw/action/AbstractViewAction.java @@ -8,6 +8,7 @@ package org.jhotdraw.action; import java.beans.*; +import java.util.Objects; import javax.swing.*; import org.jhotdraw.api.app.Application; import org.jhotdraw.api.app.View; @@ -44,24 +45,18 @@ public abstract class AbstractViewAction extends AbstractAction { * the enabled state of the view and the application. */ private boolean combinedEnabled = true; - private PropertyChangeListener applicationListener = new PropertyChangeListener() { - @Override - public void propertyChange(PropertyChangeEvent evt) { - if ((evt.getPropertyName() == null && Application.ACTIVE_VIEW_PROPERTY == null) || (evt.getPropertyName() != null && evt.getPropertyName().equals(Application.ACTIVE_VIEW_PROPERTY))) { // Strings get interned - updateView((View) evt.getOldValue(), (View) evt.getNewValue()); - } + private final PropertyChangeListener applicationListener = evt -> { + if (Objects.equals(evt.getPropertyName(), Application.ACTIVE_VIEW_PROPERTY)) { + updateView((View) evt.getOldValue(), (View) evt.getNewValue()); } }; - private PropertyChangeListener viewListener = new PropertyChangeListener() { - @Override - public void propertyChange(PropertyChangeEvent evt) { + private final PropertyChangeListener viewListener = evt -> { String name = evt.getPropertyName(); - if ("enabled".equals(name)) { + if (Objects.equals(name, "enabled")) { updateEnabled(); - } else if ((name == null && propertyName == null) || (name != null && name.equals(propertyName))) { + } else if (Objects.equals(name, propertyName)) { updateView(); } - } }; /** diff --git a/jhotdraw-actions/src/main/java/org/jhotdraw/action/edit/AbstractUndoRedoAction.java b/jhotdraw-actions/src/main/java/org/jhotdraw/action/edit/AbstractUndoRedoAction.java new file mode 100644 index 000000000..34af5cebc --- /dev/null +++ b/jhotdraw-actions/src/main/java/org/jhotdraw/action/edit/AbstractUndoRedoAction.java @@ -0,0 +1,101 @@ +package org.jhotdraw.action.edit; + +import org.jhotdraw.action.AbstractViewAction; +import org.jhotdraw.api.app.Application; +import org.jhotdraw.api.app.View; +import org.jhotdraw.util.ResourceBundleUtil; + +import javax.swing.*; +import java.awt.event.ActionEvent; +import java.beans.PropertyChangeListener; +import java.util.Objects; + +/** + * An abstract base class for undo and redo actions that centralizes common logic. + *

+ * This class uses an {@code actionID} to differentiate between various actions, + * configures resource bundles, manages property change listeners, and delegates + * action invocation to the corresponding real action from the view. + *

+ */ +public abstract class AbstractUndoRedoAction extends AbstractViewAction { + protected final String actionID; + protected final ResourceBundleUtil labels = ResourceBundleUtil.getBundle("org.jhotdraw.action.Labels"); + private final PropertyChangeListener actionPropertyListener = evt -> { + String name = evt.getPropertyName(); + if (Objects.equals(name, AbstractAction.NAME)) { + putValue(AbstractAction.NAME, evt.getNewValue()); + } else if ("enabled".equals(name)) { + updateEnabledState(); + } + }; + + protected AbstractUndoRedoAction(Application app, View view, String id) { + super(app, view); + this.actionID = id; + labels.configureAction(this, id); + } + + private Action getRealAction() { + return (getActiveView() == null) ? null : getActiveView().getActionMap().get(actionID); + } + + protected void updateEnabledState() { + boolean isEnabled = false; + Action realAction = getRealAction(); + if (realAction != null && realAction != this) { + isEnabled = realAction.isEnabled(); + } + setEnabled(isEnabled); + } + + @Override + public void actionPerformed(ActionEvent e) { + Action realAction = getRealAction(); + if (realAction != null && realAction != this) { + realAction.actionPerformed(e); + } + } + + /** + * Installs listeners on the view object. + */ + @Override + protected void installViewListeners(View p) { + super.installViewListeners(p); + Action actionInView = p.getActionMap().get(actionID); + if (actionInView != null && actionInView != this) { + actionInView.addPropertyChangeListener(actionPropertyListener); + } + } + + + /** + * Installs listeners on the view object. + */ + @Override + protected void uninstallViewListeners(View p) { + super.uninstallViewListeners(p); + Action actionInView = p.getActionMap().get(actionID); + if (actionInView != null && actionInView != this) { + actionInView.removePropertyChangeListener(actionPropertyListener); + } + } + + + @Override + protected void updateView(View oldValue, View newValue) { + super.updateView(oldValue, newValue); + if (newValue == null || newValue.getActionMap().get(actionID) == null) { + return; + } + Action actionInView = newValue.getActionMap().get(actionID); + if (actionInView != this) { + putValue(AbstractAction.NAME, actionInView.getValue(AbstractAction.NAME)); + updateEnabledState(); + } + } + + +} + diff --git a/jhotdraw-actions/src/main/java/org/jhotdraw/action/edit/RedoAction.java b/jhotdraw-actions/src/main/java/org/jhotdraw/action/edit/RedoAction.java index 794ab08c8..aa1b8d1fa 100644 --- a/jhotdraw-actions/src/main/java/org/jhotdraw/action/edit/RedoAction.java +++ b/jhotdraw-actions/src/main/java/org/jhotdraw/action/edit/RedoAction.java @@ -7,13 +7,9 @@ */ package org.jhotdraw.action.edit; -import java.awt.event.*; -import java.beans.*; -import javax.swing.*; -import org.jhotdraw.action.AbstractViewAction; import org.jhotdraw.api.app.Application; import org.jhotdraw.api.app.View; -import org.jhotdraw.util.*; + /** * Redoes the last user action on the active view. @@ -32,85 +28,15 @@ * @author Werner Randelshofer * @version $Id$ */ -public class RedoAction extends AbstractViewAction { - +public class RedoAction extends AbstractUndoRedoAction { private static final long serialVersionUID = 1L; public static final String ID = "edit.redo"; - private ResourceBundleUtil labels = ResourceBundleUtil.getBundle("org.jhotdraw.action.Labels"); - private PropertyChangeListener redoActionPropertyListener = new PropertyChangeListener() { - @Override - public void propertyChange(PropertyChangeEvent evt) { - String name = evt.getPropertyName(); - if ((name == null && AbstractAction.NAME == null) || (name != null && name.equals(AbstractAction.NAME))) { - putValue(AbstractAction.NAME, evt.getNewValue()); - } else if ("enabled".equals(name)) { - updateEnabledState(); - } - } - }; /** * Creates a new instance. */ public RedoAction(Application app, View view) { - super(app, view); - labels.configureAction(this, ID); + super(app, view, ID); } - protected void updateEnabledState() { - boolean isEnabled = false; - Action realRedoAction = getRealRedoAction(); - if (realRedoAction != null && realRedoAction != this) { - isEnabled = realRedoAction.isEnabled(); - } - setEnabled(isEnabled); - } - - @Override - protected void updateView(View oldValue, View newValue) { - super.updateView(oldValue, newValue); - if (newValue != null - && newValue.getActionMap().get(ID) != null - && newValue.getActionMap().get(ID) != this) { - putValue(AbstractAction.NAME, newValue.getActionMap().get(ID). - getValue(AbstractAction.NAME)); - updateEnabledState(); - } - } - - /** - * Installs listeners on the view object. - */ - @Override - protected void installViewListeners(View p) { - super.installViewListeners(p); - Action redoActionInView = p.getActionMap().get(ID); - if (redoActionInView != null && redoActionInView != this) { - redoActionInView.addPropertyChangeListener(redoActionPropertyListener); - } - } - - /** - * Installs listeners on the view object. - */ - @Override - protected void uninstallViewListeners(View p) { - super.uninstallViewListeners(p); - Action redoActionInView = p.getActionMap().get(ID); - if (redoActionInView != null && redoActionInView != this) { - redoActionInView.removePropertyChangeListener(redoActionPropertyListener); - } - } - - @Override - public void actionPerformed(ActionEvent e) { - Action realAction = getRealRedoAction(); - if (realAction != null && realAction != this) { - realAction.actionPerformed(e); - } - } - - private Action getRealRedoAction() { - return (getActiveView() == null) ? null : getActiveView().getActionMap().get(ID); - } } diff --git a/jhotdraw-actions/src/main/java/org/jhotdraw/action/edit/UndoAction.java b/jhotdraw-actions/src/main/java/org/jhotdraw/action/edit/UndoAction.java index 74c4f2cea..206b0d3f6 100644 --- a/jhotdraw-actions/src/main/java/org/jhotdraw/action/edit/UndoAction.java +++ b/jhotdraw-actions/src/main/java/org/jhotdraw/action/edit/UndoAction.java @@ -7,13 +7,9 @@ */ package org.jhotdraw.action.edit; -import java.awt.event.*; -import java.beans.*; -import javax.swing.*; -import org.jhotdraw.action.AbstractViewAction; import org.jhotdraw.api.app.Application; import org.jhotdraw.api.app.View; -import org.jhotdraw.util.*; + /** * Undoes the last user action. @@ -31,85 +27,15 @@ * @author Werner Randelshofer * @version $Id$ */ -public class UndoAction extends AbstractViewAction { +public class UndoAction extends AbstractUndoRedoAction { private static final long serialVersionUID = 1L; public static final String ID = "edit.undo"; - private ResourceBundleUtil labels = ResourceBundleUtil.getBundle("org.jhotdraw.action.Labels"); - private PropertyChangeListener redoActionPropertyListener = new PropertyChangeListener() { - @Override - public void propertyChange(PropertyChangeEvent evt) { - String name = evt.getPropertyName(); - if ((name == null && AbstractAction.NAME == null) || (name != null && name.equals(AbstractAction.NAME))) { - putValue(AbstractAction.NAME, evt.getNewValue()); - } else if ("enabled".equals(name)) { - updateEnabledState(); - } - } - }; /** * Creates a new instance. */ public UndoAction(Application app, View view) { - super(app, view); - labels.configureAction(this, ID); - } - - protected void updateEnabledState() { - boolean isEnabled = false; - Action realAction = getRealUndoAction(); - if (realAction != null && realAction != this) { - isEnabled = realAction.isEnabled(); - } - setEnabled(isEnabled); - } - - @Override - protected void updateView(View oldValue, View newValue) { - super.updateView(oldValue, newValue); - if (newValue != null - && newValue.getActionMap().get(ID) != null - && newValue.getActionMap().get(ID) != this) { - putValue(AbstractAction.NAME, newValue.getActionMap().get(ID). - getValue(AbstractAction.NAME)); - updateEnabledState(); - } - } - - /** - * Installs listeners on the view object. - */ - @Override - protected void installViewListeners(View p) { - super.installViewListeners(p); - Action undoActionInView = p.getActionMap().get(ID); - if (undoActionInView != null && undoActionInView != this) { - undoActionInView.addPropertyChangeListener(redoActionPropertyListener); - } - } - - /** - * Installs listeners on the view object. - */ - @Override - protected void uninstallViewListeners(View p) { - super.uninstallViewListeners(p); - Action undoActionInView = p.getActionMap().get(ID); - if (undoActionInView != null && undoActionInView != this) { - undoActionInView.removePropertyChangeListener(redoActionPropertyListener); - } - } - - @Override - public void actionPerformed(ActionEvent e) { - Action realUndoAction = getRealUndoAction(); - if (realUndoAction != null && realUndoAction != this) { - realUndoAction.actionPerformed(e); - } - } - - private Action getRealUndoAction() { - return (getActiveView() == null) ? null : getActiveView().getActionMap().get(ID); + super(app, view, ID); } } diff --git a/jhotdraw-actions/src/test/java/org/jhotdraw/action/edit/AbstractUndoRedoActionBDDTest.java b/jhotdraw-actions/src/test/java/org/jhotdraw/action/edit/AbstractUndoRedoActionBDDTest.java new file mode 100644 index 000000000..1215d4151 --- /dev/null +++ b/jhotdraw-actions/src/test/java/org/jhotdraw/action/edit/AbstractUndoRedoActionBDDTest.java @@ -0,0 +1,21 @@ +package org.jhotdraw.action.edit; + +import com.tngtech.jgiven.junit.ScenarioTest; +import org.junit.Test; + +public class AbstractUndoRedoActionBDDTest extends ScenarioTest { + + @Test + public void undo_action_should_delegate_to_real_action() { + given().an_application_is_running().and().a_real_action_is_set(); + when().the_undo_action_is_performed(); + then().the_real_action_should_be_invoked(); + } + + @Test + public void undo_action_should_do_nothing_if_no_real_action() { + given().an_application_is_running(); + when().the_undo_action_is_performed(); + then().the_real_action_should_not_be_invoked(); + } +} diff --git a/jhotdraw-actions/src/test/java/org/jhotdraw/action/edit/AbstractUndoRedoActionTest.java b/jhotdraw-actions/src/test/java/org/jhotdraw/action/edit/AbstractUndoRedoActionTest.java new file mode 100644 index 000000000..e0bba9b5b --- /dev/null +++ b/jhotdraw-actions/src/test/java/org/jhotdraw/action/edit/AbstractUndoRedoActionTest.java @@ -0,0 +1,77 @@ +package org.jhotdraw.action.edit; + +import static org.mockito.Mockito.*; +import static org.junit.jupiter.api.Assertions.*; + +import org.jhotdraw.action.edit.AbstractUndoRedoAction; +import org.junit.jupiter.api.BeforeEach; +import org.jhotdraw.api.app.Application; +import org.jhotdraw.api.app.View; +import org.junit.jupiter.api.Test; + +import javax.swing.*; +import java.awt.event.ActionEvent; +import java.beans.PropertyChangeListener; + +class AbstractUndoRedoActionTest { + private Application mockApp; + private View mockView; + private AbstractUndoRedoAction action; + private ActionEvent mockEvent; + private Action mockRealAction; + + @BeforeEach + void setUp() { + mockApp = mock(Application.class); + mockView = mock(View.class); + mockEvent = mock(ActionEvent.class); + mockRealAction = mock(Action.class); + when(mockView.getActionMap()).thenReturn(new ActionMap()); + action = new AbstractUndoRedoAction(mockApp, mockView, "undo") {}; + } + + @Test + void actionPerformed_delegatesToRealAction() { + mockView.getActionMap().put("undo", mockRealAction); + action.updateView(null, mockView); + action.actionPerformed(mockEvent); + verify(mockRealAction).actionPerformed(mockEvent); + } + + @Test + void actionPerformed_noRealAction_doesNothing() { + action.updateView(null, mockView); + action.actionPerformed(mockEvent); + verify(mockRealAction, never()).actionPerformed(mockEvent); + } + + @Test + void updateEnabledState_realActionEnabled_setsEnabledTrue() { + when(mockRealAction.isEnabled()).thenReturn(true); + when(mockApp.isEnabled()).thenReturn(true); + when(mockView.isEnabled()).thenReturn(true); + mockView.getActionMap().put("undo", mockRealAction); + action.updateView(null, mockView); + action.updateEnabledState(); + assertTrue(action.isEnabled()); + } + @Test + void updateEnabledState_noRealAction_setsEnabledFalse() { + action.updateView(null, mockView); + action.updateEnabledState(); + assertFalse(action.isEnabled()); + } + + @Test + void installViewListeners_addsPropertyChangeListener() { + mockView.getActionMap().put("undo", mockRealAction); + action.installViewListeners(mockView); + verify(mockRealAction).addPropertyChangeListener(any(PropertyChangeListener.class)); + } + @Test + void uninstallViewListeners_removesPropertyChangeListener() { + mockView.getActionMap().put("undo", mockRealAction); + action.uninstallViewListeners(mockView); + verify(mockRealAction).removePropertyChangeListener(any(PropertyChangeListener.class)); + } +} \ No newline at end of file diff --git a/jhotdraw-actions/src/test/java/org/jhotdraw/action/edit/GivenInitialState.java b/jhotdraw-actions/src/test/java/org/jhotdraw/action/edit/GivenInitialState.java new file mode 100644 index 000000000..bc207ee32 --- /dev/null +++ b/jhotdraw-actions/src/test/java/org/jhotdraw/action/edit/GivenInitialState.java @@ -0,0 +1,48 @@ +package org.jhotdraw.action.edit; + +import com.tngtech.jgiven.Stage; +import com.tngtech.jgiven.annotation.ProvidedScenarioState; +import org.jhotdraw.api.app.Application; +import org.jhotdraw.api.app.View; +import org.mockito.Mockito; +import org.mockito.Mockito.*; + +import javax.swing.*; +import java.awt.event.ActionEvent; + +import static org.mockito.Mockito.mock; + +public class GivenInitialState extends Stage { + @ProvidedScenarioState + Application mockApp; + + @ProvidedScenarioState + View mockView; + + @ProvidedScenarioState + AbstractUndoRedoAction action; + + @ProvidedScenarioState + ActionEvent mockEvent; + + @ProvidedScenarioState + Action mockRealAction; + + + public GivenInitialState an_application_is_running() { + mockApp = mock(Application.class); + mockView = mock(View.class); + mockEvent = mock(ActionEvent.class); + mockRealAction = mock(Action.class); + Mockito.when(mockView.getActionMap()).thenReturn(new ActionMap()); + action = new AbstractUndoRedoAction(mockApp, mockView, "undo") { + }; + return self(); + } + + public GivenInitialState a_real_action_is_set() { + mockView.getActionMap().put("undo", mockRealAction); + action.updateView(null, mockView); + return self(); + } +} diff --git a/jhotdraw-actions/src/test/java/org/jhotdraw/action/edit/ThenUndoActionIsPerformed.java b/jhotdraw-actions/src/test/java/org/jhotdraw/action/edit/ThenUndoActionIsPerformed.java new file mode 100644 index 000000000..57e3595a1 --- /dev/null +++ b/jhotdraw-actions/src/test/java/org/jhotdraw/action/edit/ThenUndoActionIsPerformed.java @@ -0,0 +1,27 @@ +package org.jhotdraw.action.edit; + +import com.tngtech.jgiven.Stage; +import com.tngtech.jgiven.annotation.ProvidedScenarioState; + +import static org.mockito.Mockito.*; + +import javax.swing.*; +import java.awt.event.ActionEvent; + +public class ThenUndoActionIsPerformed extends Stage { + @ProvidedScenarioState + Action mockRealAction; + + public ThenUndoActionIsPerformed the_real_action_should_be_invoked() { + verify(mockRealAction).actionPerformed(any(ActionEvent.class)); + return self(); + } + + public ThenUndoActionIsPerformed the_real_action_should_not_be_invoked() { + verify(mockRealAction, never()).actionPerformed(any(ActionEvent.class)); + return self(); + } + } + + + diff --git a/jhotdraw-actions/src/test/java/org/jhotdraw/action/edit/WhenUndoRedoActionIsPerformed.java b/jhotdraw-actions/src/test/java/org/jhotdraw/action/edit/WhenUndoRedoActionIsPerformed.java new file mode 100644 index 000000000..d27efbbad --- /dev/null +++ b/jhotdraw-actions/src/test/java/org/jhotdraw/action/edit/WhenUndoRedoActionIsPerformed.java @@ -0,0 +1,19 @@ +package org.jhotdraw.action.edit; + +import com.tngtech.jgiven.Stage; +import com.tngtech.jgiven.annotation.ProvidedScenarioState; + +import java.awt.event.ActionEvent; + +public class WhenUndoRedoActionIsPerformed extends Stage { + @ProvidedScenarioState + AbstractUndoRedoAction action; + + @ProvidedScenarioState + ActionEvent mockEvent; + + public WhenUndoRedoActionIsPerformed the_undo_action_is_performed() { + action.actionPerformed(mockEvent); + return self(); + } + } 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..25af20ddd 100644 --- a/jhotdraw-core/src/main/java/org/jhotdraw/draw/DefaultDrawingView.java +++ b/jhotdraw-core/src/main/java/org/jhotdraw/draw/DefaultDrawingView.java @@ -55,7 +55,7 @@ public class DefaultDrawingView * used to select the figures. */ private Set
selectedFigures = new LinkedHashSet<>(); - private LinkedList selectionHandles = new LinkedList<>(); + LinkedList selectionHandles = new LinkedList<>(); private boolean isConstrainerVisible = false; private Constrainer visibleConstrainer = new GridConstrainer(8, 8); private Constrainer invisibleConstrainer = new GridConstrainer(); @@ -182,8 +182,34 @@ public boolean isSelectionEmpty() { return selectedFigures.isEmpty(); } + public void setSecondaryHandleOwner(Handle handle) { + this.secondaryHandleOwner = handle; + } + + public void handleSecondaryHandlesRequest(HandleEvent e) { + secondaryHandleOwner = e.getHandle(); + secondaryHandles.clear(); + secondaryHandles.addAll(secondaryHandleOwner.createSecondaryHandles()); + for (Handle h : secondaryHandles) { + h.setView(DefaultDrawingView.this); + h.addHandleListener(eventHandler); + } + repaint(); + } + protected void setFocusGained() { + if (editor != null) { + editor.setActiveView(DefaultDrawingView.this); + } + } + private class EventHandler implements FigureListener, CompositeFigureListener, HandleListener, FocusListener { + private Drawing drawing; + + protected EventHandler(Drawing drawing1) { + this.drawing = drawing1; + } + @Override public void figureAdded(CompositeFigureEvent evt) { if (drawing.getChildCount() == 1 && getEmptyDrawingMessage() != null) { @@ -291,7 +317,7 @@ public void figureRemoved(FigureEvent e) { public void figureRequestRemove(FigureEvent e) { } } - private EventHandler eventHandler; + private IDrawingViewEventHandler eventHandler; /** * Creates new instance. @@ -307,8 +333,8 @@ public DefaultDrawingView() { setOpaque(true); } - protected EventHandler createEventHandler() { - return new EventHandler(); + protected IDrawingViewEventHandler createEventHandler() { + return new DefaultDrawingViewEventHandler(this); } /** @@ -913,7 +939,7 @@ private java.util.List getSecondaryHandles() { /** * Invalidates the handles. */ - private void invalidateHandles() { + void invalidateHandles() { if (handlesAreValid) { handlesAreValid = false; Rectangle invalidatedArea = null; @@ -1138,7 +1164,7 @@ public void setBounds(int x, int y, int width, int height) { * Updates the view translation taking into account the current dimension of the view * JComponent, the size of the drawing, and the scale factor. */ - private void validateViewTranslation() { + void validateViewTranslation() { if (getDrawing() == null) { translation.x = translation.y = 0; return; diff --git a/jhotdraw-core/src/main/java/org/jhotdraw/draw/DefaultDrawingViewEventHandler.java b/jhotdraw-core/src/main/java/org/jhotdraw/draw/DefaultDrawingViewEventHandler.java new file mode 100644 index 000000000..2ac51b3b0 --- /dev/null +++ b/jhotdraw-core/src/main/java/org/jhotdraw/draw/DefaultDrawingViewEventHandler.java @@ -0,0 +1,113 @@ +package org.jhotdraw.draw; + +import org.jhotdraw.draw.event.*; + +import java.awt.event.FocusEvent; + +import static org.jhotdraw.draw.AttributeKeys.CANVAS_HEIGHT; +import static org.jhotdraw.draw.AttributeKeys.CANVAS_WIDTH; + +public class DefaultDrawingViewEventHandler implements IDrawingViewEventHandler { + private DefaultDrawingView view; + + protected DefaultDrawingViewEventHandler(DefaultDrawingView drawingView) { + this.view = drawingView; + } + + @Override + public void figureAdded(CompositeFigureEvent evt) { + if (view.getDrawing().getChildCount() == 1 && view.getEmptyDrawingMessage() != null) { + view.repaint(); + } else { + view.repaintDrawingArea(evt.getInvalidatedArea()); + } + view.invalidateDimension(); + } + + @Override + public void figureRemoved(CompositeFigureEvent evt) { + if (view.getDrawing().getChildCount() == 0 && view.getEmptyDrawingMessage() != null) { + view.repaint(); + } else { + view.repaintDrawingArea(evt.getInvalidatedArea()); + } + view.removeFromSelection(evt.getChildFigure()); + view.invalidateDimension(); + } + + @Override + public void areaInvalidated(FigureEvent evt) { + view.repaintDrawingArea(evt.getInvalidatedArea()); + view.invalidateDimension(); + } + + @Override + public void areaInvalidated(HandleEvent evt) { + view.repaint(evt.getInvalidatedArea()); + view.invalidateDimension(); + } + + @Override + public void handleRequestSecondaryHandles(HandleEvent e) { + view.handleSecondaryHandlesRequest(e); + } + + @Override + public void focusGained(FocusEvent e) { + view.setFocusGained(); + } + + @Override + public void focusLost(FocusEvent e) { + // repaintHandles(); + } + + @Override + public void handleRequestRemove(HandleEvent e) { + view.selectionHandles.remove(e.getHandle()); + e.getHandle().dispose(); + view.invalidateHandles(); + view.repaint(e.getInvalidatedArea()); + } + + @Override + public void attributeChanged(FigureEvent e) { + if (e.getSource() == view.getDrawing()) { + AttributeKey a = e.getAttribute(); + if (a.equals(CANVAS_HEIGHT) || a.equals(CANVAS_WIDTH)) { + view.validateViewTranslation(); + view.repaint(); // must repaint everything + } + if (e.getInvalidatedArea() != null) { + view.repaintDrawingArea(e.getInvalidatedArea()); + } else { + view.repaintDrawingArea(view.viewToDrawing(view.getCanvasViewBounds())); + } + } else { + if (e.getInvalidatedArea() != null) { + view.repaintDrawingArea(e.getInvalidatedArea()); + } + } + } + + @Override + public void figureHandlesChanged(FigureEvent e) { + } + + @Override + public void figureChanged(FigureEvent e) { + view.repaintDrawingArea(e.getInvalidatedArea()); + } + + @Override + public void figureAdded(FigureEvent e) { + } + + @Override + public void figureRemoved(FigureEvent e) { + } + + @Override + public void figureRequestRemove(FigureEvent e) { + } +} diff --git a/jhotdraw-core/src/main/java/org/jhotdraw/draw/IDrawingViewEventHandler.java b/jhotdraw-core/src/main/java/org/jhotdraw/draw/IDrawingViewEventHandler.java new file mode 100644 index 000000000..d815f20dd --- /dev/null +++ b/jhotdraw-core/src/main/java/org/jhotdraw/draw/IDrawingViewEventHandler.java @@ -0,0 +1,14 @@ +package org.jhotdraw.draw; + +import org.jhotdraw.draw.event.CompositeFigureListener; +import org.jhotdraw.draw.event.FigureListener; +import org.jhotdraw.draw.event.HandleEvent; +import org.jhotdraw.draw.event.HandleListener; + +import java.awt.event.FocusListener; + +public interface IDrawingViewEventHandler extends FigureListener, CompositeFigureListener, HandleListener, FocusListener { + void handleRequestSecondaryHandles(HandleEvent e); + + void handleRequestRemove(HandleEvent e); +} diff --git a/jhotdraw-samples/jhotdraw-samples-misc/pom.xml b/jhotdraw-samples/jhotdraw-samples-misc/pom.xml index ca8104ee5..8b3ddcec3 100644 --- a/jhotdraw-samples/jhotdraw-samples-misc/pom.xml +++ b/jhotdraw-samples/jhotdraw-samples-misc/pom.xml @@ -19,11 +19,7 @@ Quaqua 7.3.4 - - net.sourceforge.htmlunit - htmlunit - 2.37.0 - + org.aspectj aspectjweaver @@ -40,6 +36,12 @@ 4.13.2 test + + org.assertj + assertj-core + 3.24.2 + test + diff --git a/pom.xml b/pom.xml index 5f6c7eef5..39eeb55a7 100644 --- a/pom.xml +++ b/pom.xml @@ -62,11 +62,11 @@ - jhotdraw-core - jhotdraw-samples - jhotdraw-xml - jhotdraw-api - jhotdraw-utils + jhotdraw-core + jhotdraw-samples + jhotdraw-xml + jhotdraw-api + jhotdraw-utils jhotdraw-gui jhotdraw-app jhotdraw-datatransfer