diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 000000000..725c4b27a
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,29 @@
+name: CI
+
+on:
+ push:
+ branches:
+ - main
+ - master
+ pull_request:
+ branches:
+ - '**'
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Set up JDK 8
+ uses: actions/setup-java@v4
+ with:
+ java-version: '8'
+ distribution: 'temurin'
+
+ - name: Build and test with Maven
+ run: mvn clean install --no-transfer-progress -s .maven-settings.xml
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.gitignore b/.gitignore
index e99300712..8ac1cc002 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,3 +6,4 @@ nb-configuration.xml
*.ftf
/jhotdraw-samples/jhotdraw-samples-misc/nbproject/
*.iml
+docs
\ No newline at end of file
diff --git a/.maven-settings.xml b/.maven-settings.xml
new file mode 100644
index 000000000..b4927f1ec
--- /dev/null
+++ b/.maven-settings.xml
@@ -0,0 +1,12 @@
+
+
+
+ github
+ ${env.GITHUB_ACTOR}
+ ${env.GITHUB_TOKEN}
+
+
+
diff --git a/Labs/L2-ConceptLocation.md b/Labs/L2-ConceptLocation.md
new file mode 100644
index 000000000..1c9f8f404
--- /dev/null
+++ b/Labs/L2-ConceptLocation.md
@@ -0,0 +1,22 @@
+# Concept Location: The Selection Tool
+
+To find the classes that implement the Selection feature I located the concept dynamically rather than by reading code. I launched the runnable SVG sample (`org.jhotdraw.samples.svg.Main` in `jhotdraw-samples-misc`), set a breakpoint on the first line of `SelectionTool.mousePressed(MouseEvent)` (`SelectionTool.java:217`), and then exercised the feature three ways on the canvas: clicking a figure, clicking one of a figure's handles, and clicking empty space. Each click suspended the debugger at the same entry point, which confirmed that `SelectionTool` is the controller Tool the editor delegates mouse events to and the natural starting point of the concept.
+
+From that breakpoint I stepped into the body. `view.findHandle(anchor)` took me into `DrawingView`; when it returned null I followed `findTargetFigure(...)` into `Figure.isSelectable()`/`contains()` and `Drawing.findFigureBehind(...)`; then `resolveTracker(handle, figure, evt)` (`SelectionTool.java:318`) picked a tracker and `setTracker(...)` activated it. Stepping into `tracker.mousePressed(evt)` dropped me into one of the three Default trackers depending on which gesture I had performed. Holding Alt while clicking overlapping figures took the `isSelectBehindModifierHeld` branch and stepped into `findFigureBehindCurrentSelection`. Walking those call chains gave me the set of collaborating classes below.
+
+| Domain Class | Responsibility |
+| --- | --- |
+| `SelectionTool` | Controller/entry Tool the debugger lands in first; holds the current tracker and routes mouse/key events to it (Strategy context). |
+| `AbstractTool` | Superclass providing common Tool plumbing: the `anchor` point, view/editor accessors, and the `fire*` event helpers. |
+| `Tool` | Interface defining the Tool contract (`activate`, `mousePressed`, `draw`, listener methods) that `SelectionTool` and the trackers implement. |
+| `HandleTracker` / `DefaultHandleTracker` | Tracker state for manipulating a handle; default impl drags the handle and its compatible handles. |
+| `DragTracker` / `DefaultDragTracker` | Tracker state for dragging a selected figure; default impl moves the figure with the mouse. |
+| `SelectAreaTracker` / `DefaultSelectAreaTracker` | Tracker state for rubber-band area selection; default impl draws the selection rectangle and selects enclosed figures. |
+| `DrawingView` | The view clicked on; resolves handles and figures (`findHandle`, `findFigure`), converts coordinates (`viewToDrawing`), and manages selection state. |
+| `Drawing` | The figure container; `findFigureBehind(...)` walks the z-order for the select-behind gesture. |
+| `DrawingEditor` | Editor context passed to `activate`/`deactivate` when trackers are swapped. |
+| `Figure` | The drawable object being selected; queried via `isSelectable()` and `contains(point)`. |
+| `Handle` | A draggable control point on a selected figure; the target a `HandleTracker` operates on. |
+| `ToolEvent` / `ToolAdapter` | Event plumbing; the inner `TrackerHandler` extends `ToolAdapter` to catch tracker `toolDone` events and reset to the select-area tracker. |
+
+The initial concept set is therefore `SelectionTool` together with `AbstractTool`/`Tool`, the three tracker pairs (`HandleTracker`/`DefaultHandleTracker`, `DragTracker`/`DefaultDragTracker`, `SelectAreaTracker`/`DefaultSelectAreaTracker`), and the collaborators `DrawingView`, `Drawing`, `DrawingEditor`, `Figure`, `Handle`, and the `ToolEvent`/`ToolAdapter` event types.
diff --git a/Labs/L2-UserStory.md b/Labs/L2-UserStory.md
new file mode 100644
index 000000000..59827580d
--- /dev/null
+++ b/Labs/L2-UserStory.md
@@ -0,0 +1,29 @@
+# L2 — Change Request: User Story (Selection Tool)
+
+For my individual portfolio I picked the **Selection Tool** (`org.jhotdraw.draw.tool.SelectionTool`, in `jhotdraw-core`). It is the tool the user is in when they interact directly with the canvas to pick figures and move them around, so it is the most user-facing piece of behaviour I could attach a clear story to.
+
+## Story
+
+> **As a** person drawing a diagram in the SVG editor,
+> **I want** to select and manipulate the figures I have already placed on the canvas,
+> **so that** I can move, resize and rearrange my drawing without redrawing it from scratch.
+
+This is tracked on the team's GitHub backlog (we keep the backlog as GitHub issues) as **issue #12**, labelled **`user story`**, currently sitting in the **TODO** column: https://github.com/timadam03/JHotDraw/issues/12
+
+## Acceptance criteria
+
+These line up one-to-one with the behaviours I later drove out in the BDD scenarios (US1-US5).
+
+| # | Given / When | Then |
+|---|--------------|------|
+| US1 | A selectable figure exists and I click on it | The figure becomes the drag target and I can drag to move it (`DragTracker`) |
+| US2 | I click on an empty part of the canvas | The current selection is cleared |
+| US3 | I click directly on a handle of a selected figure | I can drag that handle to resize/transform the figure (`HandleTracker`) |
+| US4 | I press and drag starting on empty canvas | A rubber-band selection rectangle is drawn over the area (`SelectAreaTracker`) |
+| US5 | Two figures overlap and I hold Alt/Ctrl while clicking | The figure *behind* the front one is selected (select-behind) |
+
+## Notes / scope
+
+- The story is about the existing user-visible behaviour; I am not adding a new feature here. The selection of this story is what frames the later refactoring (branch `altan/develop`) and the unit + BDD tests on the same feature.
+- "Selectable" follows `Figure.isSelectable()`; deselect and select-behind only apply when the view is active, so the criteria assume an enabled `DrawingView`.
+- US5 is gated by the bound `selectBehindEnabled` property (default on), which is why the modifier-key behaviour is listed as a criterion rather than always-on.
diff --git a/Labs/L3-ImpactAnalysis.md b/Labs/L3-ImpactAnalysis.md
new file mode 100644
index 000000000..926417954
--- /dev/null
+++ b/Labs/L3-ImpactAnalysis.md
@@ -0,0 +1,51 @@
+# Lab 3 — Impact Analysis: SelectionTool
+
+## Starting point
+
+Concept location (Lab 2) ended on `org.jhotdraw.draw.tool.SelectionTool` as the class that implements the Selection Tool feature. Following the static + dynamic impact analysis process in Figure 7.9 (Rajlich), I mark `SelectionTool` as **CHANGED**, mark all of its direct neighbours **NEXT**, and then walk the graph: each time I take a class off the NEXT set I decide whether a change to `SelectionTool` would actually force a change in it (**CHANGED**), whether it would only relay the change further (**PROPAGATES**), or whether it would be left **UNCHANGED**. New neighbours of a CHANGED/PROPAGATES class get added to NEXT. I stop when NEXT is empty.
+
+I built the initial neighbour set from the imports and field/parameter types in `SelectionTool.java` plus the call sites I traced while reading `mousePressed`, `resolveTracker`, `findTargetFigure`, `findFigureBehindCurrentSelection`, `findFigureAtPoint` and `setTracker`. I sanity-checked the reverse direction with a `grep -rl "new SelectionTool"` / `extends SelectionTool` to see who depends *on* the tool — only `DelegationSelectionTool` and the editor wiring do, and neither is touched by the refactor.
+
+## Walking the graph
+
+Starting from `SelectionTool` (**CHANGED**), the first ring of NEXT nodes is: `AbstractTool`, `Tool`, the three tracker interfaces (`DragTracker`, `HandleTracker`, `SelectAreaTracker`) and their three default impls (`DefaultDragTracker`, `DefaultHandleTracker`, `DefaultSelectAreaTracker`), then `DrawingView`, `Drawing`, `DrawingEditor`, `Figure`, `Handle`, and the event plumbing `ToolEvent` / `ToolAdapter` / `ToolListener`.
+
+**`AbstractTool` (superclass) — UNCHANGED.** `SelectionTool` calls `super.mousePressed`, `getView`, `getEditor`, `firePropertyChange`, `fireToolDone`, `fireAreaInvalidated`, `fireBoundsInvalidated` and reads `anchor`. All of these are existing protected members; the refactor (commits `8243c188` and `35f97d94`) only re-organised code *inside* `SelectionTool` and added private helpers. It never changed how the superclass is called, so nothing propagates upward.
+
+**`Tool` interface — UNCHANGED.** The local field `tracker` is typed as `Tool` and `resolveTracker`/`setTracker` pass `Tool` around, but I never added a method to the interface — `setTracker` still uses `activate`, `deactivate`, `mousePressed`, `addToolListener`, `removeToolListener`, all pre-existing. Visited, left alone.
+
+**`DragTracker`, `HandleTracker`, `SelectAreaTracker` interfaces — UNCHANGED.** These are the State roles in the Strategy pattern. `getDragTracker`, `getHandleTracker`, `getSelectAreaTracker` still call `setDraggedFigure`, `setHandles`, etc. The whole point of the refactor was to keep their contracts intact, so a change to `SelectionTool` does not force a change here. They are the boundary where the walk stops on the tracker side.
+
+**`DefaultDragTracker`, `DefaultHandleTracker`, `DefaultSelectAreaTracker` — UNCHANGED.** Lazily instantiated by the `getXxxTracker` methods exactly as before. `DefaultHandleTracker` is mentioned in a Javadoc note about keeping the figure-search order consistent; I checked that order in `findFigureAtPoint` and it matches, so the note is honoured and the class does not need editing.
+
+**`DrawingView` — UNCHANGED (interface), PROPAGATES at most.** This is the busiest collaborator: `findHandle`, `findFigure`, `viewToDrawing`, `getDrawing`, `getSelectedFigures`, `getCompatibleHandles`, `clearSelection`, `setHandleDetailLevel`, `isEnabled`. Every one of these is an existing method on the `DrawingView` interface. The refactor moved the calls into helper methods but kept the same calls with the same arguments, so the interface is visited and left UNCHANGED.
+
+**`Drawing` — UNCHANGED.** Reached through `view.getDrawing()` inside `findFigureBehindCurrentSelection` and `findFigureAtPoint`, then `drawing.findFigureBehind(...)`. Pre-existing method, unchanged signature. Visited, left alone.
+
+**`DrawingEditor` — UNCHANGED.** Only used as the activate/deactivate context (`activate(DrawingEditor)`, `deactivate(DrawingEditor)`, `getEditor()`). No change.
+
+**`Figure` — UNCHANGED.** The tool reads `isSelectable()` and `contains(Point2D.Double)`. Both already existed; the select-behind loop and the "prefer current selection" loop use them as before. Visited, left alone.
+
+**`Handle` — UNCHANGED.** Returned by `view.findHandle` and passed into `getHandleTracker`. The tool only holds and forwards it; no member of `Handle` is touched.
+
+**`ToolEvent`, `ToolAdapter`, `ToolListener` — UNCHANGED.** The inner class `TrackerHandler extends ToolAdapter` overrides `toolDone`, `areaInvalidated`, `boundsInvalidated` and reads `ToolEvent.getInvalidatedArea()`. These overrides and that accessor are unchanged by the refactor, and `ToolListener` is only used through `addToolListener`/`removeToolListener`. Event plumbing is visited and left alone.
+
+After processing all of the above, NEXT is empty and the walk terminates.
+
+## Conclusion of the walk
+
+The change is contained inside the **single CHANGED class, `SelectionTool`**. Every neighbour I visited was either a collaborator interface (`Tool`, the three tracker interfaces, `DrawingView`, `Drawing`, `DrawingEditor`, `Figure`, `Handle`) or a class whose contract the refactor deliberately preserved (`AbstractTool`, the three `Default*` trackers, the three event classes). None of them had to change, because both refactor commits only restructured `SelectionTool`'s own body — extracting `findTargetFigure`/`resolveTracker`/`isViewActive`/`setTracker` (`8243c188`) and then splitting `findTargetFigure` into `isSelectBehindModifierHeld`/`findFigureBehindCurrentSelection`/`findFigureAtPoint` (`35f97d94`) — while keeping every external call unchanged.
+
+## Table 1 — Packages visited during impact analysis
+
+| Package name | # of classes | Comments |
+|---|---|---|
+| `org.jhotdraw.draw.tool` | 20 | Home package of the feature; holds `SelectionTool` (CHANGED) plus `Tool`/`AbstractTool` and the three tracker interfaces + three `Default*` impls (all visited, UNCHANGED). |
+| `org.jhotdraw.draw` | 23 | Provides the core collaborators `DrawingView`, `Drawing`, `DrawingEditor` that the tool queries and mutates; visited, UNCHANGED. |
+| `org.jhotdraw.draw.figure` | 27 | Supplies `Figure` (`isSelectable`, `contains`), the objects the tool selects and drags; visited, UNCHANGED. |
+| `org.jhotdraw.draw.handle` | 26 | Supplies `Handle`, the draggable control point returned by `findHandle` and fed to the handle tracker; visited, UNCHANGED. |
+| `org.jhotdraw.draw.event` | 26 | Event plumbing — `ToolEvent`, `ToolAdapter`, `ToolListener` used by the inner `TrackerHandler` to relay tool events; visited, UNCHANGED. |
+
+## Estimated impact set
+
+**{ `SelectionTool` }** — the only CHANGED class. All other classes in the five packages above were visited during the walk and marked UNCHANGED, because the refactor preserved the contracts of the interfaces (`Tool`, `DragTracker`, `HandleTracker`, `SelectAreaTracker`, `DrawingView`, `Drawing`, `DrawingEditor`, `Figure`, `Handle`) and of the collaborating classes (`AbstractTool`, the `Default*` trackers, the event classes). The test code (`SelectionToolTest`, the JGiven BDD stages) is also affected as a consumer, but it sits in `src/test` and follows the change rather than being part of the production impact set.
diff --git a/Labs/L4-Refactoring.md b/Labs/L4-Refactoring.md
new file mode 100644
index 000000000..7838a5708
--- /dev/null
+++ b/Labs/L4-Refactoring.md
@@ -0,0 +1,59 @@
+# Lab 4 — Refactoring the Selection Tool
+
+The feature I have been working on all semester is `org.jhotdraw.draw.tool.SelectionTool` (module `jhotdraw-core`). It extends `AbstractTool` and is the tool the user drives on the canvas: it decides, on every mouse press, whether the user is grabbing a handle, dragging a figure, or rubber-banding a selection rectangle, and delegates the rest of the gesture to one of three tracker strategies (`HandleTracker`, `DragTracker`, `SelectAreaTracker`).
+
+This lab covers the smells I found in that class, the plan I drew up, and the two behaviour-preserving commits I used to clean it up: `8243c188` ("Refactor SelectionTool: simplify tracker logic") and `35f97d94` ("Refactor SelectionTool target-figure lookup").
+
+## How I found the smells
+
+I read the class top to bottom first, then ran SonarLint over it in the IDE. SonarLint is an IDE plugin, so there is no ruleset committed to the repo — the warnings live in the editor, not in CI. The two it kept raising were:
+
+- **Cognitive Complexity too high** on `mousePressed`. The method nested an `if/else` (handle vs. no handle) inside another `if/else` (select-behind vs. normal lookup), with two `while` loops and a `for` loop underneath, plus a final tracker-swap block. It was around 80 lines.
+- **Duplicated blocks** across the key/mouse handlers — the `getView() != null && getView().isEnabled()` guard appeared verbatim in seven methods.
+
+My own reading flagged the same three things, which I list below.
+
+## Code smells (before)
+
+| # | Smell (Fowler) | Where | Symptom |
+|---|----------------|-------|---------|
+| 1 | Long Method | `mousePressed(MouseEvent)` | One method did handle lookup, select-behind logic, figure lookup, tracker selection and tracker swapping all inline. |
+| 2 | Duplicated Code | `keyPressed`, `keyReleased`, `keyTyped`, `mouseClicked`, `mouseDragged`, `mouseReleased`, `mousePressed` | The exact guard `getView() != null && getView().isEnabled()` was copy-pasted into seven event methods. |
+| 3 | Complicated / nested Conditional | the target-figure lookup inside `mousePressed` | A two-branch conditional (select-behind vs. normal) each holding its own loop-driven hit-test, mixing the *decision* with the two *searches*. |
+
+There was also a smaller duplication: the tracker swap (deactivate old → assign → activate new → re-subscribe the listener) existed both in `mousePressed` and inside `TrackerHandler.toolDone`, with subtly different null handling.
+
+## Plan
+
+Work in small steps, each one behaviour-preserving and verified against the existing JUnit 4 / Mockito suite (`SelectionToolTest`) and the JGiven scenarios (`SelectionToolBDDTest`). The target shape is *Compose Method*: every method should read at one level of abstraction, so `mousePressed` becomes a short narrative of named steps and the detail moves into helpers. I split the work across two commits so each diff stayed reviewable.
+
+## Refactorings applied
+
+### Commit `8243c188` — simplify tracker logic
+
+- **Extract Method** (Fowler) on the duplicated guard. The seven copies of `getView() != null && getView().isEnabled()` collapsed into one private `isViewActive()`, and each handler now reads `if (isViewActive())`. This is *Consolidate Duplicate Conditional Fragments* applied across methods, realised through Extract Method (smell #2).
+- **Extract Method** on the body of `mousePressed`. I pulled the figure search into `findTargetFigure(view, drawingPoint, evt)` and the tracker decision into `resolveTracker(handle, figure, evt)`. `resolveTracker` keeps the original priority order: handle → `getHandleTracker`; selectable figure → `getDragTracker`; otherwise clear the selection (unless Shift is held) and return `getSelectAreaTracker`.
+- **Compose Method** (Kerievsky) on `mousePressed` itself. After the extractions it reads as: guard, `super.mousePressed`, `findHandle`, `findTargetFigure`, `resolveTracker`, `setTracker`, delegate. Roughly 80 lines down to about 8 (smell #1).
+- **Extract Method** to remove the swap duplication: I centralised the deactivate/assign/activate/re-subscribe sequence in `setTracker(Tool)` and gave it a single null guard at the top (return early if `newTracker == null`). `TrackerHandler.toolDone` now just calls `setTracker(getSelectAreaTracker())` instead of repeating the swap.
+
+### Commit `35f97d94` — decompose the figure lookup
+
+- **Decompose Conditional** (Fowler) on `findTargetFigure`. The condition became `isSelectBehindModifierHeld(evt)`, and the two branches became `findFigureBehindCurrentSelection(view, drawingPoint)` and `findFigureAtPoint(view, drawingPoint)`. `findTargetFigure` is now a single ternary that names the decision and the two outcomes (smell #3):
+
+ ```java
+ return isSelectBehindModifierHeld(evt)
+ ? findFigureBehindCurrentSelection(view, drawingPoint)
+ : findFigureAtPoint(view, drawingPoint);
+ ```
+
+- The condition predicate (`isSelectBehindEnabled()` AND ALT/CTRL held) moved into `isSelectBehindModifierHeld`, so the modifier-mask bit-twiddling is no longer inline in the search. The original search order and select-behind semantics are preserved — I kept the comment that the sequence must stay consistent with `DefaultHandleTracker`, `DefaultSelectAreaTracker` and `DelegationSelectionTool`.
+
+## Strategy and verification
+
+Because `SelectionTool` is itself a Strategy host (it swaps trackers as states), I was careful that none of these refactorings touched the *observable* state machine — only the internal control flow. Every step was kept green:
+
+- After the Extract Method passes I re-ran `SelectionToolTest`. Its boundary section already covered the branches I was moving (null tracker, disabled view via `isViewActive`, and the handle/figure/empty arms of `resolveTracker`), so a regression in the extraction would have failed there immediately.
+- The JGiven scenarios pinned the user-visible behaviour end to end: US1 (click selectable figure → `DragTracker`), US2 (click empty → selection cleared), US3 (click handle → `HandleTracker`), US4 (click empty → `SelectAreaTracker`), US5 (alt-click overlapping → figure behind). US5 in particular guards the `findFigureBehindCurrentSelection` path I split out in the second commit.
+- CI (`.github/workflows/ci.yml`, `mvn clean install` on JDK 8) runs both suites on every PR with `enableAssertions=true`, so the invariant assertions (tracker never null, `selectBehindEnabled` defaults true, `supportsHandleInteraction` always true) also had to hold after each push.
+
+The net result: `mousePressed` now reads as a short sequence of intent-named calls, the view guard exists once, and the figure hit-test is three focused helpers (`isSelectBehindModifierHeld`, `findFigureBehindCurrentSelection`, `findFigureAtPoint`) instead of one nested block. SonarLint no longer flags cognitive complexity or duplicated blocks on the class.
diff --git a/Labs/L5-Actualization.md b/Labs/L5-Actualization.md
new file mode 100644
index 000000000..160d0e8d7
--- /dev/null
+++ b/Labs/L5-Actualization.md
@@ -0,0 +1,46 @@
+# Lab 5 — Actualization
+
+For this lab I went back over the Selection Tool work and checked it against the SOLID principles and Clean Architecture. My feature is `org.jhotdraw.draw.tool.SelectionTool` in `jhotdraw-core`, and the two refactoring commits I refer to are `8243c188` (simplify tracker logic) and `35f97d94` (decompose the target-figure lookup).
+
+## Part 1 — SOLID in the Selection Tool
+
+### Single Responsibility Principle
+
+The clearest example is the tracker split. `SelectionTool` is in one of three interaction modes — area selection, figure dragging, handle manipulation — and each mode is a separate class: `DefaultSelectAreaTracker`, `DefaultDragTracker`, `DefaultHandleTracker`. None of them knows about the other two. `SelectionTool` itself only decides *which* tracker is current and forwards events to it.
+
+My own refactors pushed SRP down to the method level. Before `35f97d94`, `findTargetFigure()` did three jobs in one body: check the modifier keys, walk behind the current selection, and find the topmost figure at the point. I split it into `isSelectBehindModifierHeld()` (lines 253-257), `findFigureBehindCurrentSelection()` (267-280) and `findFigureAtPoint()` (294-308). Each now does one thing, and `findTargetFigure()` (240-244) is a two-line router. The same idea drove `8243c188`: `resolveTracker()` only picks a tracker, `setTracker()` (332-343) only swaps one in and out, and `isViewActive()` (409-411) only answers whether the view is usable.
+
+### Open/Closed Principle
+
+The Strategy design lets me add new selection behaviour without touching `SelectionTool`. The three tracker roles are interfaces (`HandleTracker`, `DragTracker`, `SelectAreaTracker`), and the public setters `setHandleTracker()`, `setDragTracker()`, `setSelectAreaTracker()` (lines 384-402) let a caller inject a different implementation. `DelegationSelectionTool` in the SVG sample is the existing proof of this — it reuses `SelectionTool` and supplies its own behaviour rather than editing the base class. So the class is open for extension (new tracker impls) but closed for modification.
+
+### Liskov Substitution Principle
+
+Two substitutions hold here. First, the `Default*` trackers stand in wherever their interface is expected — `getDragTracker()` returns a `DragTracker`, and the field it is stored in (`tracker`) is typed as `Tool`, so the concrete type never leaks. Second, `SelectionTool extends AbstractTool` and is used everywhere a `Tool` is expected (the editor holds it as the `Tool` interface). My overrides keep the contract: `mousePressed`, `keyPressed`, etc. all behave as a `Tool` is expected to, just guarded by `isViewActive()` first. The unit tests lean on this — `TestableSelectionTool` is a subclass that I hand to code expecting a `SelectionTool`, and nothing downstream notices the difference.
+
+### Interface Segregation Principle
+
+The design uses several small interfaces instead of one fat `Tool` that does everything. `Tool` is the event-handling contract; `HandleTracker`, `DragTracker` and `SelectAreaTracker` each add only the one method their role needs (`setHandles`, `setDraggedFigure`, and nothing extra for the area tracker). `Handle` is its own focused interface for a draggable control point. Because the tracker interfaces are separate, a class that only drags figures never has to implement handle logic it does not use.
+
+### Dependency Inversion Principle
+
+`SelectionTool` depends on abstractions, not concretes, almost everywhere. The current tracker is held as `Tool`; collaborators come in as `DrawingView`, `Drawing`, `Figure`, `Handle` interfaces; the trackers it returns are typed by their interfaces. The only place it names a concrete class is the lazy default creation inside `getHandleTracker`/`getDragTracker`/`getSelectAreaTracker`, and the setters exist precisely so that default can be replaced. This is exactly what my tests exploit: in `SelectionToolTest` I inject Mockito mocks of `DrawingView`, `Drawing`, `Figure` and `Handle`, and BDD scenarios inject mock trackers through the setters, so I can verify routing without a real Swing canvas.
+
+| Principle | Where it shows up in the Selection Tool |
+|-----------|------------------------------------------|
+| SRP | One tracker per interaction mode; each private method (`isSelectBehindModifierHeld`, `findFigureAtPoint`, `setTracker`, `isViewActive`) does one job |
+| OCP | Strategy trackers + `setHandleTracker`/`setDragTracker`/`setSelectAreaTracker` add behaviour without editing `SelectionTool`; `DelegationSelectionTool` proves it |
+| LSP | `Default*` trackers substitute for their interfaces; `SelectionTool`/`TestableSelectionTool` substitute for `AbstractTool`/`Tool` |
+| ISP | Small focused interfaces: `Tool`, `HandleTracker`, `DragTracker`, `SelectAreaTracker`, `Handle` |
+| DIP | Depends on `Tool`/`*Tracker`/`DrawingView` abstractions; setters allow injecting mocks, which the tests use |
+
+## Part 2 — Clean Architecture in this case study
+
+The Maven module layout maps cleanly onto the Clean Architecture rings.
+
+- **Inner ring (domain / abstractions and logic):** `jhotdraw-api` and `jhotdraw-core`. This is where `Tool`, `DrawingView`, `Drawing`, `Figure`, `Handle`, the tracker interfaces, and `SelectionTool` itself live. These define *what* selection means with no knowledge of how the pixels get on screen.
+- **Outer ring (frameworks, UI, wiring):** `jhotdraw-gui`, `jhotdraw-app`, and `jhotdraw-samples` (e.g. the runnable SVG demo `org.jhotdraw.samples.svg.Main`). This is where Swing components, application menus and the concrete editor wiring sit.
+
+Dependencies point inward. The UI and sample modules depend on the core abstractions; the core does not depend on them. Swing is at the very outer edge — `SelectionTool` consumes `java.awt.event.MouseEvent`/`KeyEvent`, but it never reaches up into a `JPanel` or an application window. It talks to the framework only through the `DrawingView`/`Drawing` interfaces defined in the inner ring, which is the dependency-inversion boundary Clean Architecture asks for.
+
+This is the link to actualization. Every change I made — extracting `findTargetFigure`/`resolveTracker`, decomposing the figure lookup, centralising `setTracker` — stayed entirely inside `jhotdraw-core`. I never had to open a GUI or app module, and nothing in `jhotdraw-gui`/`jhotdraw-app` needed editing to keep the build green. The dependency direction is what makes that possible: because the UI depends on the core and not the other way round, I could refactor and re-test the core tool logic in isolation, with mocks standing in for the outer ring, and the CI build (`mvn clean install` on JDK 8) confirmed the outer modules still compiled against the unchanged interfaces.
diff --git a/Labs/L7-Testing.md b/Labs/L7-Testing.md
new file mode 100644
index 000000000..cd574862c
--- /dev/null
+++ b/Labs/L7-Testing.md
@@ -0,0 +1,80 @@
+# Lab 7 — Testing the Selection Tool
+
+This lab covers how I verified `org.jhotdraw.draw.tool.SelectionTool` with unit tests. After the two refactors on `altan/develop` (8243c188 and 35f97d94) I had small, named methods to target, so I wrote a JUnit 4 + Mockito suite that exercises them without ever opening a Swing window.
+
+## Test setup in the pom
+
+I added the test dependencies to `jhotdraw-core/pom.xml`. For this lab the ones that matter are JUnit `4.13.2` and `mockito-core` `3.12.4` (the JGiven/AssertJ entries are for the BDD suite in Lab 8). I also reconfigured surefire `3.2.5`:
+
+```xml
+
+ true
+
+ --add-opens java.base/java.lang=ALL-UNNAMED
+ --add-opens java.base/java.lang.reflect=ALL-UNNAMED
+
+
+```
+
+The `--add-opens` lines are needed because Mockito uses reflection to build its mocks. `enableAssertions=true` is the important one for the invariant section below — by default the JVM runs with `assert` disabled, so without this flag those checks would silently pass and test nothing.
+
+## Mocking the collaborators
+
+`SelectionTool` talks to `DrawingEditor`, `DrawingView`, `Drawing`, `Figure` and `Handle`. All of these are interfaces in the framework, and in production they are backed by live Swing components. Driving a real canvas in a unit test would be slow and flaky, so in `setUp()` I mock all five with `Mockito.mock(...)` and wire just enough behaviour to get the tool running:
+
+- `mockEditor.getActiveView()` / `findView(...)` return `mockView`
+- `mockView.isEnabled()` returns `true`, `getDrawing()` returns `mockDrawing`
+- `viewToDrawing(Point)` returns a fixed `Point2D.Double(100, 100)`
+- `findHandle(...)` and `findFigure(...)` return `null` by default, so each test only stubs the lookup it cares about
+
+This lets me assert on *which tracker the tool picks* purely at the domain level — no `JFrame`, no event-dispatch thread.
+
+To reach the `protected` factory and swap methods I added a small `TestableSelectionTool` subclass that exposes `getSelectAreaTracker()`, `getDragTracker(Figure)`, `getHandleTracker(Handle)` and `setTracker(Tool)`.
+
+## The three sections of `SelectionToolTest`
+
+I split the ~20 tests into three labelled groups.
+
+| Section | What it checks | Example tests |
+|---|---|---|
+| A. Best-case | Normal behaviour with valid inputs: property accessors, property-change firing, lazy creation of trackers, tracker activate/deactivate delegation, tracker swapping | `testDefaultSelectBehindEnabled`, `testGetSelectAreaTrackerLazyInit`, `testSetTrackerSwapsTrackers` |
+| B. Boundary | Edge inputs and each branch of `resolveTracker`: null tracker, disabled view, the handle / selectable-figure / empty-area branches, a no-op property set | `testSetTrackerWithNull`, `testMousePressedWithDisabledView`, `testResolveTrackerWith*`, `testSetSelectBehindEnabledNoChangeDoesNotFire` |
+| C. Invariant | Properties that must always hold on any instance, asserted with the Java `assert` keyword | `testInvariantTrackerNeverNull`, `testInvariantSelectBehindEnabledDefaultTrue`, `testInvariantSupportsHandleInteractionAlwaysTrue` |
+
+### A — best case
+
+These confirm the tool behaves under normal use. `testSelectBehindEnabledFiresPropertyChange` registers a mock `PropertyChangeListener`, flips `selectBehindEnabled` to `false`, and verifies the listener fired. The lazy-init tests call a factory twice and use `assertSame(first, second)` to prove the `Default*` tracker is created once and cached. `testSetTrackerSwapsTrackers` injects two mock `Tool`s in sequence and verifies the old one got `deactivate(editor)` and the new one `activate(editor)` — the centralised swap logic from 8243c188.
+
+### B — boundary and branch coverage
+
+The core of `mousePressed` is `resolveTracker(handle, figure, evt)`, which has three exits. I cover each one by stubbing the view's lookups and then asserting the matching tracker type is created:
+
+- **Handle branch** — `findHandle(...)` returns `mockHandle`, expect a `HandleTracker` (`testResolveTrackerWithHandleReturnsHandleTracker`).
+- **Selectable figure branch** — `findHandle` returns `null`, `findFigure` returns a `mockFigure` with `isSelectable()` true, expect a `DragTracker`.
+- **Empty area branch** — both lookups return `null`, expect a `SelectAreaTracker`.
+
+The edge cases:
+
+- `testSetTrackerWithNull` passes `null` into `setTracker`; the null guard added in 8243c188 means it is a no-op, no `NullPointerException`, and the tool still deactivates cleanly.
+- `testMousePressedWithDisabledView` stubs `isEnabled()` to `false`, then verifies the injected tracker's `mousePressed` is **never** called. This pins down the `isViewActive()` guard that the same commit pulled out of seven event handlers.
+- `testSetSelectBehindEnabledNoChangeDoesNotFire` sets the property to its current value and uses `verify(listener, never())` — `PropertyChangeSupport` skips the event when old equals new, and I wanted that documented as expected behaviour rather than a bug.
+
+When I first wrote the disabled-view test it failed because my `setUp()` default already stubbed `isEnabled()` to `true`; I had to re-stub it to `false` inside the test before `activate()`. A breakpoint in `isViewActive()` confirmed the guard was the thing short-circuiting `mousePressed`.
+
+### C — invariants with the `assert` keyword
+
+Three things should hold for any `SelectionTool` regardless of how it was constructed: the select-area tracker is never null, `selectBehindEnabled` defaults to `true`, and `supportsHandleInteraction()` is always `true`. I express these with the Java `assert` keyword on a freshly constructed instance, e.g.:
+
+```java
+assert freshTool.isSelectBehindEnabled() : "selectBehindEnabled must be true by default";
+```
+
+This is why `enableAssertions=true` in surefire matters — I checked that it actually runs by temporarily breaking one invariant and watching the test go red; with assertions disabled it had been passing regardless. I kept a parallel `assertTrue(...)` alongside each `assert` so the test still fails loudly even if someone runs it without `-ea`.
+
+## How to run
+
+```
+mvn -pl jhotdraw-core test
+```
+
+The suite also runs automatically in CI: `.github/workflows/ci.yml` executes `mvn clean install -s .maven-settings.xml` on JDK 8 for every pull request, so both the unit tests and the BDD scenarios from the next lab gate every merge into `develop`.
diff --git a/Labs/L8-BDD.md b/Labs/L8-BDD.md
new file mode 100644
index 000000000..864497e99
--- /dev/null
+++ b/Labs/L8-BDD.md
@@ -0,0 +1,59 @@
+# Lab 8 — Behaviour-Driven Development for the Selection Tool
+
+I kept working on `org.jhotdraw.draw.tool.SelectionTool` (module `jhotdraw-core`). For this lab I turned the user stories I had already captured on the team backlog (tracked as GitHub issue #12) into executable Given-When-Then scenarios and automated them with JGiven, so the behaviour the user expects on the canvas is checked on every CI run.
+
+## User stories mapped to scenarios
+
+| User story | Given | When | Then |
+|---|---|---|---|
+| US1 — click a selectable figure to select it | a drawing with a selectable figure | the user clicks the figure | the `DragTracker` is activated |
+| US2 — click empty area to deselect | an empty drawing area | the user presses on an empty area | the current selection is cleared |
+| US3 — grab a figure's handle to manipulate it | a drawing with a handle at the click point | the user presses on the handle | the `HandleTracker` is activated |
+| US4 — rubber-band select an area | an empty drawing area | the user presses on an empty area | the `SelectAreaTracker` is activated |
+| US5 — alt-click overlapping figures to reach the one behind | overlapping figures with select-behind enabled | the user clicks with the Alt modifier | the `DragTracker` is activated on the figure behind |
+
+US2 and US4 share the same Given/When (an empty-area press) but assert two different consequences of that one gesture: the previous selection is dropped, and the tool switches to area selection. I left them as separate scenarios because they map to separate stories and read more clearly that way.
+
+Each row corresponds to one `@Test` in `jhotdraw-core/src/test/java/org/jhotdraw/draw/tool/bdd/SelectionToolBDDTest.java`, e.g. `clicking_on_a_selectable_figure_activates_drag_tracker()` reads:
+
+```
+given().a_drawing_with_a_selectable_figure();
+when().the_user_clicks_on_the_figure();
+then().the_DragTracker_is_activated();
+```
+
+## JGiven structure
+
+The test class extends `ScenarioTest`, which gives me the `given()`, `when()`, `then()` factory methods. Each is backed by a stage class extending `com.tngtech.jgiven.Stage`:
+
+- **`GivenSelectionTool`** sets up the world. A `@BeforeStage` method (`setupMocks()`) builds the tool and the mocks; the step methods (`a_drawing_with_a_selectable_figure`, `a_drawing_with_a_handle_at_click_point`, `an_empty_drawing_area`, `a_disabled_drawing_view`, `overlapping_figures_with_select_behind_enabled`) stub the relevant `DrawingView`/`Drawing`/`Figure`/`Handle` behaviour, then `activate(mockEditor)`.
+- **`WhenSelectionTool`** performs the gesture. It synthesises a `MouseEvent` and calls `tool.mousePressed(evt)`. `the_user_clicks_with_alt_modifier()` passes `InputEvent.ALT_DOWN_MASK` so `isSelectBehindModifierHeld(evt)` is true and the lookup goes through `findFigureBehindCurrentSelection` (US5).
+- **`ThenSelectionTool`** asserts the outcome, e.g. `the_DragTracker_is_activated()`, `the_current_selection_is_cleared()`.
+
+Every step method returns `self()`, which is what lets the `given().a_drawing_with_a_selectable_figure()` calls chain fluently and keeps each scenario reading like a sentence.
+
+State flows between the three stages through scenario state, not method arguments. The Given fields (`tool`, `mockEditor`, `mockView`, `mockDrawing`, `mockFigure`, `mockHandle`, the dummy `JPanel`) are annotated `@ProvidedScenarioState`; the When and Then stages declare the same fields as `@ExpectedScenarioState` and JGiven injects the instances that the Given produced. The When stage also *provides* its own state — the `lastMouseEvent` and the two captured tracker instances (`firstTrackerInstance`, `secondTrackerInstance`, declared with `Resolution.NAME` so they're matched by field name rather than type, since both are `Tool`) — which the Then stage then consumes.
+
+## Assertions and test level
+
+The Then stage asserts with **AssertJ-core**, e.g. in `the_DragTracker_is_activated()`:
+
+```java
+Tool tracker = tool.callGetDragTracker(mockFigure);
+assertThat(tracker).isNotNull();
+assertThat(tracker).isInstanceOf(DragTracker.class);
+```
+
+For the side-effecting stories I verify against the Mockito mock instead — `the_current_selection_is_cleared()` does `verify(mockView).clearSelection()`, and the boundary scenario `no_tracker_action_is_performed()` does `verify(mockView, never()).findHandle(any(Point.class))` to confirm a disabled view short-circuits in `isViewActive()` before any tracker logic runs.
+
+I deliberately test at the domain level: the scenarios drive a real `SelectionTool` (a `TestableSelectionTool` subclass declared inside `GivenSelectionTool` that exposes the protected `getDragTracker`/`getHandleTracker`/`getSelectAreaTracker`/`setTracker` methods) wired to Mockito mocks of `DrawingEditor`, `DrawingView`, `Drawing`, `Figure` and `Handle`. I never start the live Swing GUI. This keeps the scenarios fast and deterministic — no event-dispatch thread, no real frame to pump — and it's exactly why I removed the unused `assertj-swing-junit` dependency from `jhotdraw-core/pom.xml`; I'm asserting on the tool's behaviour and its collaborator interactions, not pixels.
+
+## Extra scenarios beyond the stories
+
+Alongside the five story scenarios I added a few that protect non-functional expectations:
+
+- **Property change** — `setting_selectBehindEnabled_fires_property_change()` flips `selectBehindEnabled` and `then().selectBehindEnabled_is(false)` confirms the bound property updates. (The `the_property_change_event_is_fired()` step backed by `verify(mockListener)...` is in the Then stage for asserting the `PropertyChange` directly.)
+- **Lazy-init invariant** — `select_area_tracker_lazy_init_returns_same_instance()`, plus the drag- and handle-tracker variants, request a tracker twice and assert `firstTrackerInstance` `isSameAs` `secondTrackerInstance`, confirming the `getXxxTracker()` lazy initialisers cache rather than recreate.
+- **Handle-interaction invariant** — `selection_tool_always_supports_handle_interaction()` asserts `supportsHandleInteraction()` stays `true`.
+
+All of this runs under the existing CI job (`.github/workflows/ci.yml`, `mvn clean install -s .maven-settings.xml` on JDK 8), so the JGiven scenarios execute as ordinary JUnit tests on every pull request next to the unit suite in `SelectionToolTest.java`.
diff --git a/Labs/README.md b/Labs/README.md
new file mode 100644
index 000000000..b3ae612e0
--- /dev/null
+++ b/Labs/README.md
@@ -0,0 +1,19 @@
+# Portfolio — Software Maintenance
+
+Lab write-ups for my individual portfolio. The feature I worked on across all of the
+labs is the **Selection Tool** (`org.jhotdraw.draw.tool.SelectionTool`, in `jhotdraw-core`).
+
+| Lab | Document |
+|-----|----------|
+| L2 — Concept Location | [L2-ConceptLocation.md](L2-ConceptLocation.md) |
+| L2 — Change Request / User Story | [L2-UserStory.md](L2-UserStory.md) |
+| L3 — Impact Analysis | [L3-ImpactAnalysis.md](L3-ImpactAnalysis.md) |
+| L4 — Refactoring | [L4-Refactoring.md](L4-Refactoring.md) |
+| L5 — Actualization (SOLID / Clean Architecture) | [L5-Actualization.md](L5-Actualization.md) |
+| L7 — Unit Testing | [L7-Testing.md](L7-Testing.md) |
+| L8 — Behaviour-Driven Development | [L8-BDD.md](L8-BDD.md) |
+
+The user story is tracked on the backlog as issue
+[#12](https://github.com/timadam03/JHotDraw/issues/12). The unit tests
+(`SelectionToolTest`) and the JGiven scenarios (`bdd/`) live under
+`jhotdraw-core/src/test/java/org/jhotdraw/draw/tool/` and run in CI on every pull request.
diff --git a/jhotdraw-core/pom.xml b/jhotdraw-core/pom.xml
index 7c276da85..00b229e40 100644
--- a/jhotdraw-core/pom.xml
+++ b/jhotdraw-core/pom.xml
@@ -35,10 +35,70 @@
6.8.21test
+
+ junit
+ junit
+ 4.13.2
+ test
+
+
+ org.mockito
+ mockito-core
+ 3.12.4
+ test
+
+
+ com.tngtech.jgiven
+ jgiven-junit
+ 0.18.2
+ test
+
+
+ org.assertj
+ assertj-core
+ 3.24.2
+ test
+ ${project.groupId}jhotdraw-actions${project.version}
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 3.2.5
+
+ true
+
+
+
+
+
+
+
+ surefire-jdk9plus
+
+ [9,)
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+
+ --add-opens java.base/java.lang=ALL-UNNAMED
+ --add-opens java.base/java.lang.reflect=ALL-UNNAMED
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/SelectionTool.java b/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/SelectionTool.java
index 50f6c47e0..0f9eb703b 100644
--- a/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/SelectionTool.java
+++ b/jhotdraw-core/src/main/java/org/jhotdraw/draw/tool/SelectionTool.java
@@ -74,17 +74,7 @@ private class TrackerHandler extends ToolAdapter {
@Override
public void toolDone(ToolEvent event) {
- // Empty
- Tool newTracker = getSelectAreaTracker();
- if (newTracker != null) {
- if (tracker != null) {
- tracker.deactivate(getEditor());
- tracker.removeToolListener(this);
- }
- tracker = newTracker;
- tracker.activate(getEditor());
- tracker.addToolListener(this);
- }
+ setTracker(getSelectAreaTracker());
fireToolDone();
}
@@ -160,35 +150,35 @@ public void deactivate(DrawingEditor editor) {
@Override
public void keyPressed(KeyEvent e) {
- if (getView() != null && getView().isEnabled()) {
+ if (isViewActive()) {
tracker.keyPressed(e);
}
}
@Override
public void keyReleased(KeyEvent evt) {
- if (getView() != null && getView().isEnabled()) {
+ if (isViewActive()) {
tracker.keyReleased(evt);
}
}
@Override
public void keyTyped(KeyEvent evt) {
- if (getView() != null && getView().isEnabled()) {
+ if (isViewActive()) {
tracker.keyTyped(evt);
}
}
@Override
public void mouseClicked(MouseEvent evt) {
- if (getView() != null && getView().isEnabled()) {
+ if (isViewActive()) {
tracker.mouseClicked(evt);
}
}
@Override
public void mouseDragged(MouseEvent evt) {
- if (getView() != null && getView().isEnabled()) {
+ if (isViewActive()) {
tracker.mouseDragged(evt);
}
}
@@ -212,7 +202,7 @@ public void mouseMoved(MouseEvent evt) {
@Override
public void mouseReleased(MouseEvent evt) {
- if (getView() != null && getView().isEnabled()) {
+ if (isViewActive()) {
tracker.mouseReleased(evt);
}
}
@@ -224,82 +214,132 @@ public void draw(Graphics2D g) {
@Override
public void mousePressed(MouseEvent evt) {
- if (getView() != null && getView().isEnabled()) {
+ if (isViewActive()) {
super.mousePressed(evt);
DrawingView view = getView();
Handle handle = view.findHandle(anchor);
- Tool newTracker = null;
- if (handle != null) {
- newTracker = getHandleTracker(handle);
- } else {
- Figure figure;
- Drawing drawing = view.getDrawing();
- Point2D.Double p = view.viewToDrawing(anchor);
- if (isSelectBehindEnabled()
- && (evt.getModifiersEx()
- & (InputEvent.ALT_DOWN_MASK | InputEvent.CTRL_DOWN_MASK)) != 0) {
- // Select a figure behind the current selection
- figure = view.findFigure(anchor);
- while (figure != null && !figure.isSelectable()) {
- figure = drawing.findFigureBehind(p, figure);
- }
- HashSet ignoredFigures = new HashSet<>(view.getSelectedFigures());
- ignoredFigures.add(figure);
- Figure figureBehind = view.getDrawing().findFigureBehind(
- view.viewToDrawing(anchor), ignoredFigures);
- if (figureBehind != null) {
- figure = figureBehind;
- }
- } else {
- // Note: The search sequence used here, must be
- // consistent with the search sequence used by the
- // DefaultHandleTracker, the DefaultSelectAreaTracker and DelegationSelectionTool.
- // If possible, continue to work with the current selection
- figure = null;
- if (isSelectBehindEnabled()) {
- for (Figure f : view.getSelectedFigures()) {
- if (f.contains(p)) {
- figure = f;
- break;
- }
- }
- }
- // If the point is not contained in the current selection,
- // search for a figure in the drawing.
- if (figure == null) {
- figure = view.findFigure(anchor);
- while (figure != null && !figure.isSelectable()) {
- figure = drawing.findFigureBehind(p, figure);
- }
- }
- }
- if (figure != null && figure.isSelectable()) {
- newTracker = getDragTracker(figure);
- } else {
- if (!evt.isShiftDown()) {
- view.clearSelection();
- view.setHandleDetailLevel(0);
- }
- newTracker = getSelectAreaTracker();
+ Figure figure = (handle == null)
+ ? findTargetFigure(view, view.viewToDrawing(anchor), evt)
+ : null;
+ Tool newTracker = resolveTracker(handle, figure, evt);
+ setTracker(newTracker);
+ tracker.mousePressed(evt);
+ }
+ }
+
+ /**
+ * Finds the target figure under the given drawing point.
+ * Routes to select-behind logic when alt/ctrl modifiers are held,
+ * otherwise finds the topmost selectable figure at the point.
+ *
+ * @param view the current drawing view
+ * @param drawingPoint the point in drawing coordinates
+ * @param evt the mouse event
+ * @return the target figure, or null if none found
+ */
+ private Figure findTargetFigure(DrawingView view, Point2D.Double drawingPoint, MouseEvent evt) {
+ return isSelectBehindModifierHeld(evt)
+ ? findFigureBehindCurrentSelection(view, drawingPoint)
+ : findFigureAtPoint(view, drawingPoint);
+ }
+
+ /**
+ * Returns true when the user is holding the alt or ctrl modifier,
+ * indicating a select-behind gesture.
+ *
+ * @param evt the mouse event
+ * @return true if select-behind modifier is held
+ */
+ private boolean isSelectBehindModifierHeld(MouseEvent evt) {
+ return isSelectBehindEnabled()
+ && (evt.getModifiersEx()
+ & (InputEvent.ALT_DOWN_MASK | InputEvent.CTRL_DOWN_MASK)) != 0;
+ }
+
+ /**
+ * Finds the first selectable figure behind the current selection at the given point.
+ * Used when the user holds alt/ctrl to reach figures underneath the current selection.
+ *
+ * @param view the current drawing view
+ * @param drawingPoint the point in drawing coordinates
+ * @return the figure behind the current selection, or null if none found
+ */
+ private Figure findFigureBehindCurrentSelection(DrawingView view, Point2D.Double drawingPoint) {
+ Drawing drawing = view.getDrawing();
+ Figure figure = view.findFigure(anchor);
+ while (figure != null && !figure.isSelectable()) {
+ figure = drawing.findFigureBehind(drawingPoint, figure);
+ }
+ HashSet ignoredFigures = new HashSet<>(view.getSelectedFigures());
+ ignoredFigures.add(figure);
+ Figure figureBehind = drawing.findFigureBehind(drawingPoint, ignoredFigures);
+ if (figureBehind != null) {
+ figure = figureBehind;
+ }
+ return figure;
+ }
+
+ /**
+ * Finds the topmost selectable figure at the given point.
+ * Prefers a figure already in the current selection; falls back to a drawing search.
+ *