diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml
new file mode 100644
index 000000000..7381b9bc6
--- /dev/null
+++ b/.github/workflows/maven.yml
@@ -0,0 +1,39 @@
+name: Java CI with Maven
+
+on:
+ push:
+ branches: [ feature/arrange-send-to-front-or-back ]
+
+ pull_request:
+ branches: [ develop ]
+
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ packages: read
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Set up JDK 17
+ uses: actions/setup-java@v4
+ with:
+ java-version: '17'
+ distribution: 'temurin'
+ cache: maven
+
+ - name: Build with Maven
+ run: mvn -B -DskipTests package -s .maven-settings.xml
+ env:
+ GITHUB_ACTOR: ${{ github.actor }}
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Run tests
+ run: mvn -B test -s .maven-settings.xml
+ env:
+ GITHUB_ACTOR: ${{ github.actor }}
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.maven-settings.xml b/.maven-settings.xml
new file mode 100644
index 000000000..2edc8a0e9
--- /dev/null
+++ b/.maven-settings.xml
@@ -0,0 +1,11 @@
+
+
+
+ github
+ ${env.GITHUB_ACTOR}
+ ${env.GITHUB_TOKEN}
+
+
+
diff --git a/jhotdraw-core/pom.xml b/jhotdraw-core/pom.xml
index 7c276da85..7eb55661b 100644
--- a/jhotdraw-core/pom.xml
+++ b/jhotdraw-core/pom.xml
@@ -35,10 +35,28 @@
6.8.21
test
+
+ junit
+ junit
+ 4.13.2
+ test
+
+
+ com.tngtech.jgiven
+ jgiven-junit
+ 1.3.1
+ test
+
+
+ org.assertj
+ assertj-core
+ 3.26.3
+ test
+
${project.groupId}
jhotdraw-actions
${project.version}
-
\ No newline at end of file
+
diff --git a/jhotdraw-core/src/main/java/org/jhotdraw/draw/action/AbstractZOrderAction.java b/jhotdraw-core/src/main/java/org/jhotdraw/draw/action/AbstractZOrderAction.java
new file mode 100644
index 000000000..b60e9843e
--- /dev/null
+++ b/jhotdraw-core/src/main/java/org/jhotdraw/draw/action/AbstractZOrderAction.java
@@ -0,0 +1,87 @@
+/*
+ * @(#)AbstractZOrderAction.java
+ *
+ * Copyright (c) 2003-2008 The authors and contributors of JHotDraw.
+ * You may not use, copy or modify this file, except in compliance with the
+ * accompanying license terms.
+ */
+package org.jhotdraw.draw.action;
+
+import java.awt.event.ActionEvent;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Objects;
+import javax.swing.undo.AbstractUndoableEdit;
+import javax.swing.undo.CannotRedoException;
+import javax.swing.undo.CannotUndoException;
+import org.jhotdraw.draw.Drawing;
+import org.jhotdraw.draw.DrawingEditor;
+import org.jhotdraw.draw.DrawingView;
+import org.jhotdraw.draw.figure.Figure;
+import org.jhotdraw.util.ResourceBundleUtil;
+
+/**
+ * Defines the common workflow for z-order actions on selected figures.
+ */
+public abstract class AbstractZOrderAction extends AbstractSelectedAction {
+
+ private static final long serialVersionUID = 1L;
+ private final String id;
+
+ protected AbstractZOrderAction(DrawingEditor editor, String id) {
+ super(editor);
+ this.id = id;
+ ResourceBundleUtil labels
+ = ResourceBundleUtil.getBundle("org.jhotdraw.draw.Labels");
+ labels.configureAction(this, id);
+ updateEnabledState();
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ final DrawingView view = Objects.requireNonNull(
+ getView(),
+ "A z-order action requires an active drawing view.");
+ final Drawing drawing = Objects.requireNonNull(
+ view.getDrawing(),
+ "A z-order action requires an attached drawing.");
+ final LinkedList figures = new LinkedList<>(view.getSelectedFigures());
+ assert figures != null : "Selected figures collection must not be null.";
+ final List originalOrder = new ArrayList<>(drawing.getChildren());
+ reorder(view, figures);
+ final List reorderedOrder = new ArrayList<>(drawing.getChildren());
+ fireUndoableEditHappened(new AbstractUndoableEdit() {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public String getPresentationName() {
+ ResourceBundleUtil labels
+ = ResourceBundleUtil.getBundle("org.jhotdraw.draw.Labels");
+ return labels.getTextProperty(id);
+ }
+
+ @Override
+ public void redo() throws CannotRedoException {
+ super.redo();
+ restoreOrder(drawing, reorderedOrder);
+ }
+
+ @Override
+ public void undo() throws CannotUndoException {
+ super.undo();
+ restoreOrder(drawing, originalOrder);
+ }
+ });
+ }
+
+ private void restoreOrder(Drawing drawing, List order) {
+ drawing.basicRemoveAll(new ArrayList<>(drawing.getChildren()));
+ drawing.basicAddAll(0, order);
+ }
+
+ protected abstract void reorder(DrawingView view, Collection figures);
+
+ protected abstract void reverseReorder(DrawingView view, Collection figures);
+}
diff --git a/jhotdraw-core/src/main/java/org/jhotdraw/draw/action/BringToFrontAction.java b/jhotdraw-core/src/main/java/org/jhotdraw/draw/action/BringToFrontAction.java
index c54d29888..8762d47f2 100644
--- a/jhotdraw-core/src/main/java/org/jhotdraw/draw/action/BringToFrontAction.java
+++ b/jhotdraw-core/src/main/java/org/jhotdraw/draw/action/BringToFrontAction.java
@@ -9,9 +9,7 @@
import org.jhotdraw.draw.figure.Figure;
import java.util.*;
-import javax.swing.undo.*;
import org.jhotdraw.draw.*;
-import org.jhotdraw.util.ResourceBundleUtil;
/**
* ToFrontAction.
@@ -19,7 +17,7 @@
* @author Werner Randelshofer
* @version $Id$
*/
-public class BringToFrontAction extends AbstractSelectedAction {
+public class BringToFrontAction extends AbstractZOrderAction {
private static final long serialVersionUID = 1L;
public static final String ID = "edit.bringToFront";
@@ -28,44 +26,25 @@ public class BringToFrontAction extends AbstractSelectedAction {
* Creates a new instance.
*/
public BringToFrontAction(DrawingEditor editor) {
- super(editor);
- ResourceBundleUtil labels
- = ResourceBundleUtil.getBundle("org.jhotdraw.draw.Labels");
- labels.configureAction(this, ID);
- updateEnabledState();
+ super(editor, ID);
}
@Override
- public void actionPerformed(java.awt.event.ActionEvent e) {
- final DrawingView view = getView();
- final LinkedList figures = new LinkedList<>(view.getSelectedFigures());
+ protected void reorder(DrawingView view, Collection figures) {
bringToFront(view, figures);
- fireUndoableEditHappened(new AbstractUndoableEdit() {
- private static final long serialVersionUID = 1L;
-
- @Override
- public String getPresentationName() {
- ResourceBundleUtil labels
- = ResourceBundleUtil.getBundle("org.jhotdraw.draw.Labels");
- return labels.getTextProperty(ID);
- }
-
- @Override
- public void redo() throws CannotRedoException {
- super.redo();
- BringToFrontAction.bringToFront(view, figures);
- }
+ }
- @Override
- public void undo() throws CannotUndoException {
- super.undo();
- SendToBackAction.sendToBack(view, figures);
- }
- });
+ @Override
+ protected void reverseReorder(DrawingView view, Collection figures) {
+ SendToBackAction.sendToBack(view, figures);
}
public static void bringToFront(DrawingView view, Collection figures) {
- Drawing drawing = view.getDrawing();
+ Objects.requireNonNull(view, "DrawingView must not be null.");
+ Objects.requireNonNull(figures, "Figures collection must not be null.");
+ Drawing drawing = Objects.requireNonNull(
+ view.getDrawing(),
+ "DrawingView must provide a drawing.");
for (Figure figure : drawing.sort(figures)) {
drawing.bringToFront(figure);
}
diff --git a/jhotdraw-core/src/main/java/org/jhotdraw/draw/action/SendToBackAction.java b/jhotdraw-core/src/main/java/org/jhotdraw/draw/action/SendToBackAction.java
index be14fa523..364b9b35a 100644
--- a/jhotdraw-core/src/main/java/org/jhotdraw/draw/action/SendToBackAction.java
+++ b/jhotdraw-core/src/main/java/org/jhotdraw/draw/action/SendToBackAction.java
@@ -9,9 +9,8 @@
import org.jhotdraw.draw.figure.Figure;
import java.util.*;
-import javax.swing.undo.*;
import org.jhotdraw.draw.*;
-import org.jhotdraw.util.ResourceBundleUtil;
+import org.jhotdraw.util.ReversedList;
/**
* SendToBackAction.
@@ -19,7 +18,7 @@
* @author Werner Randelshofer
* @version $Id$
*/
-public class SendToBackAction extends AbstractSelectedAction {
+public class SendToBackAction extends AbstractZOrderAction {
private static final long serialVersionUID = 1L;
public static final String ID = "edit.sendToBack";
@@ -28,45 +27,27 @@ public class SendToBackAction extends AbstractSelectedAction {
* Creates a new instance.
*/
public SendToBackAction(DrawingEditor editor) {
- super(editor);
- ResourceBundleUtil labels
- = ResourceBundleUtil.getBundle("org.jhotdraw.draw.Labels");
- labels.configureAction(this, ID);
- updateEnabledState();
+ super(editor, ID);
}
@Override
- public void actionPerformed(java.awt.event.ActionEvent e) {
- final DrawingView view = getView();
- final LinkedList figures = new LinkedList<>(view.getSelectedFigures());
+ protected void reorder(DrawingView view, Collection figures) {
sendToBack(view, figures);
- fireUndoableEditHappened(new AbstractUndoableEdit() {
- private static final long serialVersionUID = 1L;
-
- @Override
- public String getPresentationName() {
- ResourceBundleUtil labels
- = ResourceBundleUtil.getBundle("org.jhotdraw.draw.Labels");
- return labels.getTextProperty(ID);
- }
-
- @Override
- public void redo() throws CannotRedoException {
- super.redo();
- SendToBackAction.sendToBack(view, figures);
- }
+ }
- @Override
- public void undo() throws CannotUndoException {
- super.undo();
- BringToFrontAction.bringToFront(view, figures);
- }
- });
+ @Override
+ protected void reverseReorder(DrawingView view, Collection figures) {
+ BringToFrontAction.bringToFront(view, figures);
}
public static void sendToBack(DrawingView view, Collection figures) {
- Drawing drawing = view.getDrawing();
- for (Figure figure : figures) { // XXX Shouldn't the figures be sorted here back to front?
+ Objects.requireNonNull(view, "DrawingView must not be null.");
+ Objects.requireNonNull(figures, "Figures collection must not be null.");
+ Drawing drawing = Objects.requireNonNull(
+ view.getDrawing(),
+ "DrawingView must provide a drawing.");
+ List sorted = drawing.sort(figures);
+ for (Figure figure : new ReversedList<>(sorted)) {
drawing.sendToBack(figure);
}
}
diff --git a/jhotdraw-core/src/test/java/org/jhotdraw/draw/ArrangeTestSupport.java b/jhotdraw-core/src/test/java/org/jhotdraw/draw/ArrangeTestSupport.java
new file mode 100644
index 000000000..7d1fc1a54
--- /dev/null
+++ b/jhotdraw-core/src/test/java/org/jhotdraw/draw/ArrangeTestSupport.java
@@ -0,0 +1,68 @@
+package org.jhotdraw.draw;
+
+import java.awt.geom.Point2D;
+import java.util.Arrays;
+import java.util.List;
+import javax.swing.event.UndoableEditEvent;
+import javax.swing.event.UndoableEditListener;
+import javax.swing.undo.UndoableEdit;
+import org.jhotdraw.draw.figure.Figure;
+import org.jhotdraw.draw.figure.RectangleFigure;
+import static org.junit.Assert.assertEquals;
+
+public final class ArrangeTestSupport {
+
+ private ArrangeTestSupport() {
+ }
+
+ public static DefaultDrawingView createDefaultView() {
+ return createView(new DefaultDrawing());
+ }
+
+ public static DefaultDrawingView createQuadTreeView() {
+ return createView(new QuadTreeDrawing());
+ }
+
+ public static DefaultDrawingView createView(Drawing drawing) {
+ DefaultDrawingView view = new DefaultDrawingView();
+ view.setDrawing(drawing);
+ return view;
+ }
+
+ public static Figure addFigure(DefaultDrawingView view, double x) {
+ RectangleFigure figure = new RectangleFigure();
+ figure.setBounds(new Point2D.Double(x, 0), new Point2D.Double(x + 5, 5));
+ view.getDrawing().add(figure);
+ return figure;
+ }
+
+ public static void select(DefaultDrawingView view, Figure... figures) {
+ view.clearSelection();
+ view.addToSelection(Arrays.asList(figures));
+ }
+
+ public static void assertOrder(DefaultDrawingView view, Figure... expected) {
+ List actual = view.getDrawing().sort(Arrays.asList(expected));
+ assertEquals(Arrays.asList(expected), actual);
+ }
+
+ public static UndoableEdit captureUndoableEdit(Drawing drawing, Runnable trigger) {
+ final UndoableEdit[] captured = new UndoableEdit[1];
+ UndoableEditListener listener = new UndoableEditListener() {
+ @Override
+ public void undoableEditHappened(UndoableEditEvent e) {
+ captured[0] = e.getEdit();
+ }
+ };
+ drawing.addUndoableEditListener(listener);
+ try {
+ trigger.run();
+ } finally {
+ drawing.removeUndoableEditListener(listener);
+ }
+ if (captured[0] == null) {
+ throw new AssertionError("Expected an undoable edit to be published.");
+ }
+ return captured[0];
+ }
+}
diff --git a/jhotdraw-core/src/test/java/org/jhotdraw/draw/QuadTreeDrawingArrangeTest.java b/jhotdraw-core/src/test/java/org/jhotdraw/draw/QuadTreeDrawingArrangeTest.java
new file mode 100644
index 000000000..fcf4a2323
--- /dev/null
+++ b/jhotdraw-core/src/test/java/org/jhotdraw/draw/QuadTreeDrawingArrangeTest.java
@@ -0,0 +1,88 @@
+package org.jhotdraw.draw;
+
+import java.util.Arrays;
+import java.util.List;
+import org.jhotdraw.draw.action.BringToFrontAction;
+import org.jhotdraw.draw.action.SendToBackAction;
+import org.jhotdraw.draw.figure.Figure;
+import static org.jhotdraw.draw.ArrangeTestSupport.addFigure;
+import static org.jhotdraw.draw.ArrangeTestSupport.assertOrder;
+import static org.jhotdraw.draw.ArrangeTestSupport.createQuadTreeView;
+import static org.junit.Assert.assertEquals;
+import org.junit.Test;
+
+public class QuadTreeDrawingArrangeTest {
+
+ @Test
+ public void sendToBackPreservesRelativeOrderForMultipleSelectedFiguresInQuadTreeDrawing() {
+ DefaultDrawingView view = createQuadTreeView();
+ Figure a = addFigure(view, 0);
+ Figure b = addFigure(view, 10);
+ Figure c = addFigure(view, 20);
+ Figure d = addFigure(view, 30);
+ Figure e = addFigure(view, 40);
+
+ SendToBackAction.sendToBack(view, Arrays.asList(b, d));
+
+ assertOrder(view, b, d, a, c, e);
+ }
+
+ @Test
+ public void bringToFrontPreservesRelativeOrderForMultipleSelectedFiguresInQuadTreeDrawing() {
+ DefaultDrawingView view = createQuadTreeView();
+ Figure a = addFigure(view, 0);
+ Figure b = addFigure(view, 10);
+ Figure c = addFigure(view, 20);
+ Figure d = addFigure(view, 30);
+ Figure e = addFigure(view, 40);
+
+ BringToFrontAction.bringToFront(view, Arrays.asList(b, d));
+
+ assertOrder(view, a, c, e, b, d);
+ }
+
+ @Test
+ public void sendToBackWithAllFiguresSelectedPreservesOrderInQuadTreeDrawing() {
+ DefaultDrawingView view = createQuadTreeView();
+ Figure a = addFigure(view, 0);
+ Figure b = addFigure(view, 10);
+ Figure c = addFigure(view, 20);
+ Figure d = addFigure(view, 30);
+
+ SendToBackAction.sendToBack(view, Arrays.asList(a, b, c, d));
+
+ assertOrder(view, a, b, c, d);
+ }
+
+ @Test
+ public void sortReturnsBackToFrontOrderAfterSendToBack() {
+ DefaultDrawingView view = createQuadTreeView();
+ Figure a = addFigure(view, 0);
+ Figure b = addFigure(view, 10);
+ Figure c = addFigure(view, 20);
+ Figure d = addFigure(view, 30);
+ Figure e = addFigure(view, 40);
+
+ SendToBackAction.sendToBack(view, Arrays.asList(b, d));
+
+ List actual = view.getDrawing().sort(Arrays.asList(e, b, c, d));
+
+ assertEquals(Arrays.asList(b, d, c, e), actual);
+ }
+
+ @Test
+ public void getFiguresFrontToBackMatchesInverseOfSortedOrderAfterBringToFront() {
+ DefaultDrawingView view = createQuadTreeView();
+ Figure a = addFigure(view, 0);
+ Figure b = addFigure(view, 10);
+ Figure c = addFigure(view, 20);
+ Figure d = addFigure(view, 30);
+ Figure e = addFigure(view, 40);
+
+ BringToFrontAction.bringToFront(view, Arrays.asList(b, d));
+
+ List actual = view.getDrawing().getFiguresFrontToBack();
+
+ assertEquals(Arrays.asList(d, b, e, c, a), actual);
+ }
+}
diff --git a/jhotdraw-core/src/test/java/org/jhotdraw/draw/action/AbstractZOrderActionIntegrationTest.java b/jhotdraw-core/src/test/java/org/jhotdraw/draw/action/AbstractZOrderActionIntegrationTest.java
new file mode 100644
index 000000000..6541404eb
--- /dev/null
+++ b/jhotdraw-core/src/test/java/org/jhotdraw/draw/action/AbstractZOrderActionIntegrationTest.java
@@ -0,0 +1,183 @@
+package org.jhotdraw.draw.action;
+
+import java.awt.event.ActionEvent;
+import javax.swing.undo.UndoableEdit;
+import org.jhotdraw.draw.DefaultDrawingEditor;
+import org.jhotdraw.draw.DefaultDrawingView;
+import org.jhotdraw.draw.figure.Figure;
+import static org.jhotdraw.draw.ArrangeTestSupport.addFigure;
+import static org.jhotdraw.draw.ArrangeTestSupport.assertOrder;
+import static org.jhotdraw.draw.ArrangeTestSupport.captureUndoableEdit;
+import static org.jhotdraw.draw.ArrangeTestSupport.createDefaultView;
+import static org.jhotdraw.draw.ArrangeTestSupport.select;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import org.junit.Test;
+
+public class AbstractZOrderActionIntegrationTest {
+
+ @Test
+ public void sendToBackActionPerformedUsesActiveViewSelection() {
+ DefaultDrawingEditor editor = new DefaultDrawingEditor();
+ DefaultDrawingView activeView = createDefaultView();
+ DefaultDrawingView inactiveView = createDefaultView();
+ editor.add(activeView);
+ editor.add(inactiveView);
+
+ Figure a = addFigure(activeView, 0);
+ Figure b = addFigure(activeView, 10);
+ Figure c = addFigure(activeView, 20);
+ Figure d = addFigure(activeView, 30);
+
+ Figure x = addFigure(inactiveView, 0);
+ Figure y = addFigure(inactiveView, 10);
+ Figure z = addFigure(inactiveView, 20);
+
+ select(activeView, c);
+ select(inactiveView, y);
+ editor.setActiveView(activeView);
+
+ SendToBackAction action = new SendToBackAction(editor);
+ action.actionPerformed(actionEvent());
+
+ assertOrder(activeView, c, a, b, d);
+ assertOrder(inactiveView, x, y, z);
+ }
+
+ @Test
+ public void bringToFrontActionPerformedUsesActiveViewSelection() {
+ DefaultDrawingEditor editor = new DefaultDrawingEditor();
+ DefaultDrawingView activeView = createDefaultView();
+ DefaultDrawingView inactiveView = createDefaultView();
+ editor.add(activeView);
+ editor.add(inactiveView);
+
+ Figure a = addFigure(activeView, 0);
+ Figure b = addFigure(activeView, 10);
+ Figure c = addFigure(activeView, 20);
+ Figure d = addFigure(activeView, 30);
+
+ Figure x = addFigure(inactiveView, 0);
+ Figure y = addFigure(inactiveView, 10);
+ Figure z = addFigure(inactiveView, 20);
+
+ select(activeView, b);
+ select(inactiveView, y);
+ editor.setActiveView(activeView);
+
+ BringToFrontAction action = new BringToFrontAction(editor);
+ action.actionPerformed(actionEvent());
+
+ assertOrder(activeView, a, c, d, b);
+ assertOrder(inactiveView, x, y, z);
+ }
+
+ @Test
+ public void sendToBackActionPublishesUndoableEditThatRestoresOriginalOrderOnUndo() {
+ DefaultDrawingEditor editor = new DefaultDrawingEditor();
+ DefaultDrawingView view = createDefaultView();
+ editor.add(view);
+ editor.setActiveView(view);
+
+ Figure a = addFigure(view, 0);
+ Figure b = addFigure(view, 10);
+ Figure c = addFigure(view, 20);
+ Figure d = addFigure(view, 30);
+
+ select(view, b, d);
+ SendToBackAction action = new SendToBackAction(editor);
+
+ UndoableEdit edit = captureUndoableEdit(
+ view.getDrawing(),
+ () -> action.actionPerformed(actionEvent()));
+
+ assertOrder(view, b, d, a, c);
+
+ edit.undo();
+
+ assertOrder(view, a, b, c, d);
+ }
+
+ @Test
+ public void bringToFrontActionPublishesUndoableEditThatRestoresOriginalOrderOnUndo() {
+ DefaultDrawingEditor editor = new DefaultDrawingEditor();
+ DefaultDrawingView view = createDefaultView();
+ editor.add(view);
+ editor.setActiveView(view);
+
+ Figure a = addFigure(view, 0);
+ Figure b = addFigure(view, 10);
+ Figure c = addFigure(view, 20);
+ Figure d = addFigure(view, 30);
+
+ select(view, a, c);
+ BringToFrontAction action = new BringToFrontAction(editor);
+
+ UndoableEdit edit = captureUndoableEdit(
+ view.getDrawing(),
+ () -> action.actionPerformed(actionEvent()));
+
+ assertOrder(view, b, d, a, c);
+
+ edit.undo();
+
+ assertOrder(view, a, b, c, d);
+ }
+
+ @Test
+ public void sendToBackUndoableEditReappliesOrderOnRedo() {
+ DefaultDrawingEditor editor = new DefaultDrawingEditor();
+ DefaultDrawingView view = createDefaultView();
+ editor.add(view);
+ editor.setActiveView(view);
+
+ Figure a = addFigure(view, 0);
+ Figure b = addFigure(view, 10);
+ Figure c = addFigure(view, 20);
+ Figure d = addFigure(view, 30);
+
+ select(view, b, d);
+ SendToBackAction action = new SendToBackAction(editor);
+
+ UndoableEdit edit = captureUndoableEdit(
+ view.getDrawing(),
+ () -> action.actionPerformed(actionEvent()));
+
+ edit.undo();
+ edit.redo();
+
+ assertOrder(view, b, d, a, c);
+ }
+
+ @Test
+ public void actionIsDisabledWhenSelectionIsEmpty() {
+ DefaultDrawingEditor editor = new DefaultDrawingEditor();
+ DefaultDrawingView view = createDefaultView();
+ editor.add(view);
+ editor.setActiveView(view);
+
+ addFigure(view, 0);
+ BringToFrontAction action = new BringToFrontAction(editor);
+
+ assertFalse(action.isEnabled());
+ }
+
+ @Test
+ public void actionBecomesEnabledWhenFiguresAreSelected() {
+ DefaultDrawingEditor editor = new DefaultDrawingEditor();
+ DefaultDrawingView view = createDefaultView();
+ editor.add(view);
+ editor.setActiveView(view);
+
+ Figure a = addFigure(view, 0);
+ BringToFrontAction action = new BringToFrontAction(editor);
+
+ select(view, a);
+
+ assertTrue(action.isEnabled());
+ }
+
+ private ActionEvent actionEvent() {
+ return new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "test");
+ }
+}
diff --git a/jhotdraw-core/src/test/java/org/jhotdraw/draw/action/ArrangeZOrderActionTest.java b/jhotdraw-core/src/test/java/org/jhotdraw/draw/action/ArrangeZOrderActionTest.java
new file mode 100644
index 000000000..4ded0df91
--- /dev/null
+++ b/jhotdraw-core/src/test/java/org/jhotdraw/draw/action/ArrangeZOrderActionTest.java
@@ -0,0 +1,79 @@
+package org.jhotdraw.draw.action;
+
+import java.util.Arrays;
+import java.util.Collections;
+import org.jhotdraw.draw.DefaultDrawingView;
+import org.jhotdraw.draw.figure.Figure;
+import static org.jhotdraw.draw.ArrangeTestSupport.addFigure;
+import static org.jhotdraw.draw.ArrangeTestSupport.assertOrder;
+import static org.jhotdraw.draw.ArrangeTestSupport.createDefaultView;
+import org.junit.Test;
+
+public class ArrangeZOrderActionTest {
+
+ @Test
+ public void sendToBackPreservesRelativeOrderForMultipleSelectedFigures() {
+ DefaultDrawingView view = createDefaultView();
+ Figure a = addFigure(view, 0);
+ Figure b = addFigure(view, 10);
+ Figure c = addFigure(view, 20);
+ Figure d = addFigure(view, 30);
+ Figure e = addFigure(view, 40);
+
+ SendToBackAction.sendToBack(view, Arrays.asList(b, d));
+
+ assertOrder(view, b, d, a, c, e);
+ }
+
+ @Test
+ public void bringToFrontPreservesRelativeOrderForMultipleSelectedFigures() {
+ DefaultDrawingView view = createDefaultView();
+ Figure a = addFigure(view, 0);
+ Figure b = addFigure(view, 10);
+ Figure c = addFigure(view, 20);
+ Figure d = addFigure(view, 30);
+ Figure e = addFigure(view, 40);
+
+ BringToFrontAction.bringToFront(view, Arrays.asList(b, d));
+
+ assertOrder(view, a, c, e, b, d);
+ }
+
+ @Test
+ public void sendToBackMovesSingleSelectedFigureToBack() {
+ DefaultDrawingView view = createDefaultView();
+ Figure a = addFigure(view, 0);
+ Figure b = addFigure(view, 10);
+ Figure c = addFigure(view, 20);
+ Figure d = addFigure(view, 30);
+
+ SendToBackAction.sendToBack(view, Collections.singletonList(c));
+
+ assertOrder(view, c, a, b, d);
+ }
+
+ @Test
+ public void sendToBackWithEmptySelectionDoesNotChangeOrder() {
+ DefaultDrawingView view = createDefaultView();
+ Figure a = addFigure(view, 0);
+ Figure b = addFigure(view, 10);
+ Figure c = addFigure(view, 20);
+
+ SendToBackAction.sendToBack(view, Collections.emptyList());
+
+ assertOrder(view, a, b, c);
+ }
+
+ @Test
+ public void sendToBackWithAllFiguresSelectedPreservesOrder() {
+ DefaultDrawingView view = createDefaultView();
+ Figure a = addFigure(view, 0);
+ Figure b = addFigure(view, 10);
+ Figure c = addFigure(view, 20);
+ Figure d = addFigure(view, 30);
+
+ SendToBackAction.sendToBack(view, Arrays.asList(a, b, c, d));
+
+ assertOrder(view, a, b, c, d);
+ }
+}
diff --git a/jhotdraw-core/src/test/java/org/jhotdraw/draw/action/bdd/ArrangeBehaviorTest.java b/jhotdraw-core/src/test/java/org/jhotdraw/draw/action/bdd/ArrangeBehaviorTest.java
new file mode 100644
index 000000000..4b5843bff
--- /dev/null
+++ b/jhotdraw-core/src/test/java/org/jhotdraw/draw/action/bdd/ArrangeBehaviorTest.java
@@ -0,0 +1,51 @@
+package org.jhotdraw.draw.action.bdd;
+
+import com.tngtech.jgiven.junit.ScenarioTest;
+import org.junit.Test;
+
+public class ArrangeBehaviorTest extends ScenarioTest<
+ ArrangeGivenStage,
+ ArrangeWhenStage,
+ ArrangeThenStage> {
+
+ @Test
+ public void send_selected_figures_to_back_preserving_relative_order() {
+ given().a_default_drawing_with_five_figures()
+ .and().the_figures_b_and_d_are_selected();
+
+ when().the_user_sends_the_selection_to_the_back();
+
+ then().the_order_is_b_d_a_c_e();
+ }
+
+ @Test
+ public void bring_selected_figures_to_front_preserving_relative_order() {
+ given().a_default_drawing_with_five_figures()
+ .and().the_figures_b_and_d_are_selected();
+
+ when().the_user_brings_the_selection_to_the_front();
+
+ then().the_order_is_a_c_e_b_d();
+ }
+
+ @Test
+ public void undo_restores_the_original_order_after_send_to_back() {
+ given().a_default_drawing_with_five_figures()
+ .and().the_figures_b_and_d_are_selected();
+
+ when().the_user_sends_the_selection_to_the_back()
+ .and().the_user_undoes_the_arrange_action();
+
+ then().the_order_is_a_b_c_d_e();
+ }
+
+ @Test
+ public void send_to_back_preserves_relative_order_in_a_quad_tree_drawing() {
+ given().a_quad_tree_drawing_with_five_figures()
+ .and().the_figures_b_and_d_are_selected();
+
+ when().the_user_sends_the_selection_to_the_back();
+
+ then().the_order_is_b_d_a_c_e();
+ }
+}
diff --git a/jhotdraw-core/src/test/java/org/jhotdraw/draw/action/bdd/ArrangeGivenStage.java b/jhotdraw-core/src/test/java/org/jhotdraw/draw/action/bdd/ArrangeGivenStage.java
new file mode 100644
index 000000000..90c33bd8d
--- /dev/null
+++ b/jhotdraw-core/src/test/java/org/jhotdraw/draw/action/bdd/ArrangeGivenStage.java
@@ -0,0 +1,51 @@
+package org.jhotdraw.draw.action.bdd;
+
+import com.tngtech.jgiven.Stage;
+import com.tngtech.jgiven.annotation.ProvidedScenarioState;
+import org.jhotdraw.draw.DefaultDrawingEditor;
+import static org.jhotdraw.draw.ArrangeTestSupport.addFigure;
+import static org.jhotdraw.draw.ArrangeTestSupport.createDefaultView;
+import static org.jhotdraw.draw.ArrangeTestSupport.createQuadTreeView;
+import static org.jhotdraw.draw.ArrangeTestSupport.select;
+
+public class ArrangeGivenStage extends Stage {
+
+ @ProvidedScenarioState
+ private final ArrangeScenarioState state = new ArrangeScenarioState();
+
+ public ArrangeGivenStage a_default_drawing_with_five_figures() {
+ state.editor = new DefaultDrawingEditor();
+ state.view = createDefaultView();
+ state.editor.add(state.view);
+ state.editor.setActiveView(state.view);
+ addFiveFigures();
+ return self();
+ }
+
+ public ArrangeGivenStage a_quad_tree_drawing_with_five_figures() {
+ state.editor = new DefaultDrawingEditor();
+ state.view = createQuadTreeView();
+ state.editor.add(state.view);
+ state.editor.setActiveView(state.view);
+ addFiveFigures();
+ return self();
+ }
+
+ public ArrangeGivenStage the_figures_b_and_d_are_selected() {
+ select(state.view, state.b, state.d);
+ return self();
+ }
+
+ public ArrangeGivenStage the_figures_a_and_c_are_selected() {
+ select(state.view, state.a, state.c);
+ return self();
+ }
+
+ private void addFiveFigures() {
+ state.a = addFigure(state.view, 0);
+ state.b = addFigure(state.view, 10);
+ state.c = addFigure(state.view, 20);
+ state.d = addFigure(state.view, 30);
+ state.e = addFigure(state.view, 40);
+ }
+}
diff --git a/jhotdraw-core/src/test/java/org/jhotdraw/draw/action/bdd/ArrangeScenarioState.java b/jhotdraw-core/src/test/java/org/jhotdraw/draw/action/bdd/ArrangeScenarioState.java
new file mode 100644
index 000000000..5209ce1d7
--- /dev/null
+++ b/jhotdraw-core/src/test/java/org/jhotdraw/draw/action/bdd/ArrangeScenarioState.java
@@ -0,0 +1,18 @@
+package org.jhotdraw.draw.action.bdd;
+
+import javax.swing.undo.UndoableEdit;
+import org.jhotdraw.draw.DefaultDrawingEditor;
+import org.jhotdraw.draw.DefaultDrawingView;
+import org.jhotdraw.draw.figure.Figure;
+
+final class ArrangeScenarioState {
+
+ DefaultDrawingEditor editor;
+ DefaultDrawingView view;
+ Figure a;
+ Figure b;
+ Figure c;
+ Figure d;
+ Figure e;
+ UndoableEdit capturedEdit;
+}
diff --git a/jhotdraw-core/src/test/java/org/jhotdraw/draw/action/bdd/ArrangeThenStage.java b/jhotdraw-core/src/test/java/org/jhotdraw/draw/action/bdd/ArrangeThenStage.java
new file mode 100644
index 000000000..889f6fc42
--- /dev/null
+++ b/jhotdraw-core/src/test/java/org/jhotdraw/draw/action/bdd/ArrangeThenStage.java
@@ -0,0 +1,34 @@
+package org.jhotdraw.draw.action.bdd;
+
+import com.tngtech.jgiven.Stage;
+import com.tngtech.jgiven.annotation.ExpectedScenarioState;
+import java.util.Arrays;
+import java.util.List;
+import org.jhotdraw.draw.figure.Figure;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class ArrangeThenStage extends Stage {
+
+ @ExpectedScenarioState
+ private ArrangeScenarioState state;
+
+ public ArrangeThenStage the_order_is_b_d_a_c_e() {
+ assertThat(currentOrder()).containsExactly(state.b, state.d, state.a, state.c, state.e);
+ return self();
+ }
+
+ public ArrangeThenStage the_order_is_a_c_e_b_d() {
+ assertThat(currentOrder()).containsExactly(state.a, state.c, state.e, state.b, state.d);
+ return self();
+ }
+
+ public ArrangeThenStage the_order_is_a_b_c_d_e() {
+ assertThat(currentOrder()).containsExactly(state.a, state.b, state.c, state.d, state.e);
+ return self();
+ }
+
+ private List currentOrder() {
+ return state.view.getDrawing().sort(
+ Arrays.asList(state.a, state.b, state.c, state.d, state.e));
+ }
+}
diff --git a/jhotdraw-core/src/test/java/org/jhotdraw/draw/action/bdd/ArrangeWhenStage.java b/jhotdraw-core/src/test/java/org/jhotdraw/draw/action/bdd/ArrangeWhenStage.java
new file mode 100644
index 000000000..43e74834a
--- /dev/null
+++ b/jhotdraw-core/src/test/java/org/jhotdraw/draw/action/bdd/ArrangeWhenStage.java
@@ -0,0 +1,39 @@
+package org.jhotdraw.draw.action.bdd;
+
+import com.tngtech.jgiven.Stage;
+import com.tngtech.jgiven.annotation.ExpectedScenarioState;
+import java.awt.event.ActionEvent;
+import org.jhotdraw.draw.action.BringToFrontAction;
+import org.jhotdraw.draw.action.SendToBackAction;
+import static org.jhotdraw.draw.ArrangeTestSupport.captureUndoableEdit;
+
+public class ArrangeWhenStage extends Stage {
+
+ @ExpectedScenarioState
+ private ArrangeScenarioState state;
+
+ public ArrangeWhenStage the_user_sends_the_selection_to_the_back() {
+ SendToBackAction action = new SendToBackAction(state.editor);
+ state.capturedEdit = captureUndoableEdit(
+ state.view.getDrawing(),
+ () -> action.actionPerformed(actionEvent()));
+ return self();
+ }
+
+ public ArrangeWhenStage the_user_brings_the_selection_to_the_front() {
+ BringToFrontAction action = new BringToFrontAction(state.editor);
+ state.capturedEdit = captureUndoableEdit(
+ state.view.getDrawing(),
+ () -> action.actionPerformed(actionEvent()));
+ return self();
+ }
+
+ public ArrangeWhenStage the_user_undoes_the_arrange_action() {
+ state.capturedEdit.undo();
+ return self();
+ }
+
+ private ActionEvent actionEvent() {
+ return new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "bdd");
+ }
+}