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