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

Visitor Pattern and Exporters

Overview

The project uses the Visitor pattern to separate shape data from rendering and export logic. This allows adding new output formats without modifying shape classes.


ShapeVisitor Interface

public interface ShapeVisitor {
    void visitRectangle(SRectangle sRectangle);
    void visitCollection(SCollection collection);
    void visitCircle(SCircle sCircle);
    void visitTriangle(STriangle sTriangle);
    void visitText(SText sText);
}

How It Works

Each Shape implements accept(ShapeVisitor visitor) by calling the appropriate visit* method on the visitor, passing itself:

// In SRectangle:
@Override
public void accept(ShapeVisitor visitor) {
    visitor.visitRectangle(this);
}

For SCollection, the visitor iterates over children to traverse the tree:

// Inside a visitor's visitCollection():
for (Shape child : collection) {
    child.accept(this);
}

This double-dispatch mechanism lets each visitor decide how to handle each shape type without the shapes knowing anything about rendering.


Visitor Implementations

1. ShapeDraftman -- GUI Rendering

Location: ovh.gasser.newshapes.ui.ShapeDraftman

Renders shapes onto a Graphics2D context for the Swing view (ShapesView).

Responsibilities:

  • Draws rectangles (fillRect/drawRect), circles (fillOval/drawOval), triangles (fillPolygon/drawPolygon), and text (drawString) using Graphics2D.
  • Reads ColorAttributes to determine fill/stroke colors and whether to fill or stroke.
  • Draws selection handles (small squares) when a shape is selected.
  • In resize mode, renders all 8 directional handles (NW, N, NE, E, SE, S, SW, W) on the shape bounds.
  • In normal mode, shows only the SE corner handle for selected shapes.

2. HTMLDraftman -- HTML/CSS Export

Location: ovh.gasser.newshapes.ui.visitors.HTMLDraftman

Generates HTML <div> elements with CSS for absolute positioning.

Output format:

Shape HTML Representation
Rectangle <div> with position: absolute, left, top, width, height
Circle <div> with border-radius: 50%
Triangle <div> using CSS border tricks
Text Styled <div> with font properties
Collection Recursively visits children

Colors are mapped to CSS background-color and border properties.

Used by: HTMLExporter, which writes out.html and style.css.

3. SVGDraftman -- SVG Export

Location: ovh.gasser.newshapes.ui.visitors.SVGDraftman

Generates SVG XML elements.

Output format:

Shape SVG Element
Rectangle <rect x="..." y="..." width="..." height="..." fill="..." stroke="...">
Circle <circle cx="..." cy="..." r="..." fill="..." stroke="...">
Triangle <polygon points="..."> with computed vertex coordinates
Text <text> with font attributes
Collection <g> group containing child elements

Color attributes are converted to SVG fill and stroke attributes using rgb(r,g,b) format.

Used by: SVGExporter, which writes out.svg.


Exporters

HTMLExporter

new HTMLExporter(model).export();  // Writes out.html + style.css

Creates an HTMLDraftman, visits the entire model tree, then writes the accumulated HTML structure and CSS styles to separate files.

SVGExporter

new SVGExporter(model).export();  // Writes out.svg

Creates an SVGDraftman, visits the entire model tree, wraps the output in SVG boilerplate (<svg> root element with namespace), and writes to a single file.


Adding a New Visitor / Exporter

Step 1: Create the Visitor

Create a class implementing ShapeVisitor (typically in ovh.gasser.newshapes.ui.visitors):

public class MyFormatDraftman implements ShapeVisitor {
    @Override
    public void visitRectangle(SRectangle rect) {
        Rectangle bounds = rect.getBounds();
        ColorAttributes colors = (ColorAttributes) rect.getAttributes(ColorAttributes.ID);
        // Generate your format output...
    }

    @Override
    public void visitCollection(SCollection collection) {
        for (Shape child : collection) {
            child.accept(this);  // Recursive traversal
        }
    }

    // ... implement visitCircle, visitTriangle, visitText
}

Step 2: Create the Exporter

public class MyFormatExporter {
    private final SCollection model;

    public MyFormatExporter(SCollection model) {
        this.model = model;
    }

    public void export() throws FileNotFoundException {
        MyFormatDraftman draftman = new MyFormatDraftman();
        model.accept(draftman);
        // Write draftman output to file(s)
    }
}

Step 3: Wire It Up

Add a menu item in App.buildFileMenu():

JMenuItem myExportItem = new JMenuItem("Export to MyFormat");
myExportItem.addActionListener(evt -> {
    try {
        new MyFormatExporter(model).export();
    } catch (FileNotFoundException e) {
        logger.error("Could not export: {}", e.getMessage());
    }
});
menuFile.add(myExportItem);

Known Issues

  • HTMLDraftman.visitTriangle() uses this.hashCode() instead of sTriangle.hashCode() for CSS class naming. This is a bug that could cause CSS class collisions when multiple triangles are exported.