1
ARCHITECTURE
Thibaud edited this page 2026-03-27 15:43:53 +01:00

Architecture Documentation — Java Swing Shape Editor

1. Overview

This application is an interactive 2D shape editor built with Java Swing, structured around the Model-View-Controller (MVC) architectural pattern. The model is a tree of shapes rooted at an SCollection; the view renders that tree onto a JPanel using a Visitor; the controller translates raw mouse and keyboard events into model mutations and triggers repaints.

The design deliberately separates concerns so that export targets (HTML, SVG) can reuse the same Visitor interface as the GUI renderer, and so that new shape types can be added with minimal changes to existing code.


2. Package Structure

ovh.gasser.newshapes
├── App.java                        — Entry point, builds JFrame, menu bar, initial model
├── Selection.java                  — Manages selected shapes, notifies listeners
├── ShapeVisitor.java               — Visitor interface for shape rendering
├── HTMLExporter.java               — Exports model to HTML/CSS files
├── SVGExporter.java                — Exports model to SVG file
├── attributes/
│   ├── Attributes.java             — Base interface (getID())
│   ├── ColorAttributes.java        — Fill/stroke colors and flags
│   └── SelectionAttributes.java    — Selection state
├── shapes/
│   ├── Shape.java                  — Core interface
│   ├── AbstractShape.java          — Base implementation (bounds, attributes map, translate, resize)
│   ├── SRectangle.java             — Rectangle shape
│   ├── SCircle.java                — Circle shape (radius-based)
│   ├── STriangle.java              — Equilateral triangle
│   ├── SText.java                  — Text shape with font properties
│   ├── SCollection.java            — Composite pattern: group of shapes
│   └── ResizeHandle.java           — Enum for 8 directional resize handles
├── ui/
│   ├── Controller.java             — Mouse/keyboard event handler (MVC controller)
│   ├── ShapeDraftman.java          — GUI rendering visitor (draws on Graphics2D)
│   ├── ShapesView.java             — JPanel view, owns Controller
│   ├── listeners/
│   │   ├── MenuAddListener.java    — Adds shapes from menu
│   │   ├── MenuEditListener.java   — Color/border/fill changes
│   │   └── SelectionListener.java  — Interface for selection change events
│   └── visitors/
│       ├── HTMLDraftman.java       — Generates HTML/CSS from shapes
│       └── SVGDraftman.java        — Generates SVG from shapes
└── util/
    └── Streamable.java             — Adds stream() to Iterables

3. MVC Breakdown

Roles

Layer Class(es) Responsibility
Model SCollection, Shape, AbstractShape, subclasses Shape tree; single source of truth for geometry and attributes
View ShapesView JPanel subclass; delegates all painting to ShapeDraftman
Controller Controller Receives Swing events; mutates model; triggers repaint

Data Flow

User input (mouse / keyboard)
        |
        v
  Controller
  (MouseListener, KeyListener)
        |
        |-- hit-test shapes in model
        |-- mutate model (translate, resize, add, remove)
        |-- update Selection
        |
        v
  Model mutation complete
        |
        v
  ShapesView.repaint()
        |
        v
  ShapesView.paintComponent(Graphics g)
        |
        v
  ShapeDraftman.visit*(shape)   [Visitor traversal of the shape tree]
        |
        v
  Graphics2D rendering on screen

The view never writes to the model. The model has no reference to the view or the controller. Communication from model to view is limited to the repaint trigger issued by the controller after each mutation.


4. Design Patterns

4.1 Visitor Pattern

ShapeVisitor is the central extension point for operations over the shape tree.

public interface ShapeVisitor {
    void visitRectangle(SRectangle rectangle);
    void visitCircle(SCircle circle);
    void visitTriangle(STriangle triangle);
    void visitText(SText text);
    void visitCollection(SCollection collection);
}

Each concrete shape implements accept(ShapeVisitor v) by calling the appropriate method on the visitor:

// SRectangle.java
@Override
public void accept(ShapeVisitor v) {
    v.visitRectangle(this);
}

Three concrete visitors are provided:

Visitor Purpose
ShapeDraftman Paints shapes onto a Graphics2D context for on-screen rendering
HTMLDraftman Emits HTML <div> elements with inline CSS for each shape
SVGDraftman Emits SVG primitives (<rect>, <circle>, <polygon>, <text>)

Adding a new rendering target requires only a new class implementing ShapeVisitor; no existing shape or visitor code changes.

4.2 Composite Pattern

SCollection extends AbstractShape and holds an ordered list of Shape children, allowing shapes to be grouped and treated uniformly.

Shape (interface)
  └── AbstractShape (abstract)
        ├── SRectangle
        ├── SCircle
        ├── STriangle
        ├── SText
        └── SCollection          <-- contains List<Shape> children
              ├── SRectangle
              ├── SCircle
              └── SCollection    <-- groups can be nested

Key overrides in SCollection:

  • translate(dx, dy) — propagates the translation to every child
  • getBounds() — returns the union of all children's bounding rectangles
  • clone() — performs a deep copy by cloning each child recursively
  • accept(ShapeVisitor) — calls visitCollection, which internally iterates children

SCollection also implements Streamable<Shape>, providing a stream() method that exposes the child list as a Stream<Shape> for functional-style iteration.

4.3 Observer Pattern

Selection maintains the set of currently selected shapes and a list of SelectionListener callbacks.

Selection
  ├── List<Shape> selectedShapes
  ├── List<SelectionListener> listeners
  ├── add(Shape)      → notifyListeners()
  ├── clear()         → notifyListeners()
  └── notifyListeners()

SelectionListener (interface)
  └── onSelectionChanged(Selection selection)

App  implements  SelectionListener
  └── updates menu checkbox state (fill / border toggles)

When the selection changes, App reads the ColorAttributes of the newly selected shapes and synchronises the menu checkboxes accordingly, so the UI always reflects the state of the model.

4.4 Factory Methods

Each concrete shape exposes a static create() method to centralise construction and attribute initialisation:

SRectangle.create(int x, int y, int width, int height, Color color)
SCircle.create(int x, int y, int radius)
STriangle.create(int x, int y, int side)
SText.create(int x, int y, String text, Font font, Color color)

This keeps instantiation logic off the caller and ensures every shape is created with a consistent default attribute set (a ColorAttributes and a SelectionAttributes are always added to the internal TreeMap during create()).


5. Selection and Interaction Model

Mouse Interactions

Gesture Effect
Click on shape Select that shape; clear previous selection
Shift + click on shape Add shape to current selection (multi-select)
Click on empty space Deselect all
Click on empty space + drag Rubber-band box selection: selects all shapes whose bounding rectangle intersects the drag rectangle
Drag selected shape(s) Translate all selected shapes by the drag delta

Keyboard Interactions

Key Effect
R Toggle resize mode for the current selection
Delete / Backspace Remove selected shapes from the model

Resize Mode

When resize mode is active, the ShapeDraftman renders eight handles around each selected shape, corresponding to the eight values of the ResizeHandle enum:

NW ---  N  --- NE
 |             |
 W             E
 |             |
SW ---  S  --- SE

Dragging a handle resizes the shape in the direction indicated by the handle, updating the bounding rectangle while keeping the opposite corner anchored.


6. Attributes System

Shapes store their attributes in a TreeMap<String, Attributes> where the key is the stable ID string returned by Attributes.getID(). This map lives in AbstractShape and is inherited by all concrete shapes.

AbstractShape
  └── TreeMap<String, Attributes> attributes
        ├── "COLOR_ATTRS"     → ColorAttributes
        └── "SELECTION_ATTRS" → SelectionAttributes

Defined Attribute Types

ColorAttributes (ID = "COLOR_ATTRS")

Field Type Description
fillColor Color Interior fill colour
strokeColor Color Border/outline colour
filled boolean Whether the shape is filled
stroked boolean Whether the shape has a visible border

SelectionAttributes (ID = "SELECTION_ATTRS")

Field Type Description
selected boolean Whether the shape is currently selected

Extensibility

The attribute system is open for extension. A new attribute type — for example, OpacityAttributes or ShadowAttributes — only needs to implement the Attributes interface and provide a unique ID string. No existing attribute class or shape class requires modification.

public interface Attributes {
    String getID();
}

Consumers retrieve attributes with a cast after a key lookup, making it straightforward to check for the presence of an optional attribute type without imposing it on all shapes.