From dd59c7d51a52a00326bce13ab04731f61ac155e0 Mon Sep 17 00:00:00 2001 From: Thibaud Date: Thu, 19 Mar 2026 16:06:01 +0100 Subject: [PATCH] sync edit menu checkbox state with the selected shape --- src/main/java/ovh/gasser/newshapes/App.java | 49 +++++++++++++++++-- .../java/ovh/gasser/newshapes/Selection.java | 23 ++++++++- .../gasser/newshapes/shapes/SCollection.java | 30 ++++++++++-- .../ovh/gasser/newshapes/ui/Controller.java | 7 ++- .../ovh/gasser/newshapes/ui/ShapesView.java | 9 ++-- .../{menu => listeners}/MenuAddListener.java | 2 +- .../{menu => listeners}/MenuEditListener.java | 2 +- .../ui/listeners/SelectionListener.java | 7 +++ 8 files changed, 113 insertions(+), 16 deletions(-) rename src/main/java/ovh/gasser/newshapes/ui/{menu => listeners}/MenuAddListener.java (97%) rename src/main/java/ovh/gasser/newshapes/ui/{menu => listeners}/MenuEditListener.java (99%) create mode 100644 src/main/java/ovh/gasser/newshapes/ui/listeners/SelectionListener.java diff --git a/src/main/java/ovh/gasser/newshapes/App.java b/src/main/java/ovh/gasser/newshapes/App.java index e3b0732..8e3dc2c 100644 --- a/src/main/java/ovh/gasser/newshapes/App.java +++ b/src/main/java/ovh/gasser/newshapes/App.java @@ -2,21 +2,27 @@ package ovh.gasser.newshapes; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import ovh.gasser.newshapes.attributes.ColorAttributes; import ovh.gasser.newshapes.shapes.SCircle; import ovh.gasser.newshapes.shapes.SCollection; import ovh.gasser.newshapes.shapes.SRectangle; +import ovh.gasser.newshapes.shapes.Shape; import ovh.gasser.newshapes.ui.ShapesView; -import ovh.gasser.newshapes.ui.menu.MenuAddListener; -import ovh.gasser.newshapes.ui.menu.MenuEditListener; +import ovh.gasser.newshapes.ui.listeners.MenuAddListener; +import ovh.gasser.newshapes.ui.listeners.MenuEditListener; +import ovh.gasser.newshapes.ui.listeners.SelectionListener; import javax.swing.*; import java.awt.*; import java.io.FileNotFoundException; +import java.util.Optional; public class App { private final static Logger logger = LoggerFactory.getLogger(App.class); public static final Dimension WIN_SIZE = new Dimension(800, 600); private SCollection model; + private JCheckBoxMenuItem editFill; + private JCheckBoxMenuItem editBorder; private App() throws HeadlessException { final JFrame frame = new JFrame("Reactive shapes"); @@ -35,6 +41,13 @@ public class App { frame.setVisible(true); this.buildMenuBar(frame, view); + + view.addSelectionChangeListener(new SelectionListener() { + @Override + public void onSelectionChanged(Iterable selectedShapes) { + updateMenuState(selectedShapes); + } + }); } private void buildModel() { @@ -94,8 +107,8 @@ public class App { JMenuItem editColor = new JMenuItem("Change color"); JMenuItem editBorderColor = new JMenuItem("Change border color"); JMenuItem deleteItem = new JMenuItem("Delete"); - JCheckBoxMenuItem editFill = new JCheckBoxMenuItem("Fill Shape"); - JCheckBoxMenuItem editBorder = new JCheckBoxMenuItem("Draw border"); + editFill = new JCheckBoxMenuItem("Fill Shape"); + editBorder = new JCheckBoxMenuItem("Draw border"); editColor.addActionListener(editListener); editBorderColor.addActionListener(editListener); deleteItem.addActionListener(editListener); @@ -111,6 +124,34 @@ public class App { return menuEdit; } + private void updateMenuState(Iterable selectedShapes) { + boolean hasAttributes = false; + boolean allFilled = true; + boolean allStroked = true; + + for (Shape s : selectedShapes) { + ColorAttributes attrs = (ColorAttributes) s.getAttributes(ColorAttributes.ID); + if (attrs != null) { + hasAttributes = true; + allFilled = allFilled && attrs.filled; + allStroked = allStroked && attrs.stroked; + } + } + + updateMenuItem(editFill, hasAttributes, allFilled); + updateMenuItem(editBorder, hasAttributes, allStroked); + } + + private void updateMenuItem(JCheckBoxMenuItem menuItem, boolean hasAttributes, boolean allSelected) { + if (!hasAttributes) { + menuItem.setSelected(false); + menuItem.setEnabled(false); + } else { + menuItem.setSelected(allSelected); + menuItem.setEnabled(true); + } + } + public static void main(String[] args) { SwingUtilities.invokeLater(App::new); } diff --git a/src/main/java/ovh/gasser/newshapes/Selection.java b/src/main/java/ovh/gasser/newshapes/Selection.java index a3f6915..c6b8896 100644 --- a/src/main/java/ovh/gasser/newshapes/Selection.java +++ b/src/main/java/ovh/gasser/newshapes/Selection.java @@ -1,15 +1,22 @@ package ovh.gasser.newshapes; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import ovh.gasser.newshapes.attributes.SelectionAttributes; import ovh.gasser.newshapes.shapes.Shape; +import ovh.gasser.newshapes.ui.listeners.SelectionListener; import ovh.gasser.newshapes.util.Streamable; import java.util.ArrayList; +import java.util.Collections; import java.util.Iterator; import java.util.List; public class Selection implements Streamable { + private final static Logger logger = LoggerFactory.getLogger(Selection.class); + private final List selectedShapes = new ArrayList<>(); + private final List listeners = new ArrayList<>(); @Override public Iterator iterator() { @@ -20,12 +27,26 @@ public class Selection implements Streamable { for (Shape shape : selectedShapes) { shape.addAttributes(new SelectionAttributes(false)); } - selectedShapes.clear(); + notifyListeners(); } public void add(Shape s) { + logger.info("Selection.add"); selectedShapes.add(s); s.addAttributes(new SelectionAttributes(true)); + notifyListeners(); + } + + public List getSelectedShapes() { + return List.copyOf(selectedShapes); + } + + public void addListener(SelectionListener listener) { + this.listeners.add(listener); + } + + private void notifyListeners(){ + listeners.forEach(l -> l.onSelectionChanged(Collections.unmodifiableCollection(selectedShapes))); } } diff --git a/src/main/java/ovh/gasser/newshapes/shapes/SCollection.java b/src/main/java/ovh/gasser/newshapes/shapes/SCollection.java index 17aef7c..73828ab 100644 --- a/src/main/java/ovh/gasser/newshapes/shapes/SCollection.java +++ b/src/main/java/ovh/gasser/newshapes/shapes/SCollection.java @@ -4,14 +4,15 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ovh.gasser.newshapes.App; import ovh.gasser.newshapes.ShapeVisitor; +import ovh.gasser.newshapes.attributes.Attributes; +import ovh.gasser.newshapes.attributes.ColorAttributes; import ovh.gasser.newshapes.attributes.SelectionAttributes; import ovh.gasser.newshapes.util.Streamable; +import javax.swing.text.html.Option; import java.awt.*; -import java.util.ArrayList; -import java.util.Iterator; +import java.util.*; import java.util.List; -import java.util.Spliterator; public class SCollection extends AbstractShape implements Streamable { private final static Logger logger = LoggerFactory.getLogger(SCollection.class); @@ -38,7 +39,7 @@ public class SCollection extends AbstractShape implements Streamable { for (Shape s : children) bounds = bounds.union(s.getBounds()); return bounds; } catch (IndexOutOfBoundsException e){ - logger.error("getBounds(): {}"); + logger.error("getBounds(): {e}", e); throw new RuntimeException(e); } } @@ -76,6 +77,27 @@ public class SCollection extends AbstractShape implements Streamable { } } + @Override + public Attributes getAttributes(String key) { + if (key.equals(ColorAttributes.ID)) { + // If the shape is a collection, it does not support color attributes directly + // For now, use the attributes from the first child shape + Optional first = children.stream().findFirst(); + return first.map(shape -> shape.getAttributes(key)).orElse(null); + } + + return super.getAttributes(key); + } + + @Override + public void addAttributes(Attributes attrs) { + // Propagate color attributes to children + if (attrs.getID().equals(ColorAttributes.ID)) { + children.forEach(shape -> shape.addAttributes(attrs)); + } + super.addAttributes(attrs); + } + @Override public String toString() { StringBuilder sb = new StringBuilder("SCollection{"); diff --git a/src/main/java/ovh/gasser/newshapes/ui/Controller.java b/src/main/java/ovh/gasser/newshapes/ui/Controller.java index c1706e4..e4b7c36 100644 --- a/src/main/java/ovh/gasser/newshapes/ui/Controller.java +++ b/src/main/java/ovh/gasser/newshapes/ui/Controller.java @@ -7,6 +7,7 @@ import ovh.gasser.newshapes.Selection; import ovh.gasser.newshapes.attributes.ColorAttributes; import ovh.gasser.newshapes.shapes.SCollection; import ovh.gasser.newshapes.shapes.Shape; +import ovh.gasser.newshapes.ui.listeners.SelectionListener; import java.awt.*; import java.awt.event.KeyAdapter; @@ -21,6 +22,7 @@ public class Controller { private final ShapesView view; private final SCollection model; private final Selection selection; + private Point lastMousePos; Controller(ShapesView view, SCollection model) { @@ -139,10 +141,13 @@ public class Controller { selection.clear(); } + public void addSelectionChangeListener(SelectionListener listener) { + selection.addListener(listener); + } + private Optional getTarget(MouseEvent evt, SCollection sc) { return sc.stream() .filter(s -> s.getBounds().contains(evt.getPoint())) .findFirst(); } - } diff --git a/src/main/java/ovh/gasser/newshapes/ui/ShapesView.java b/src/main/java/ovh/gasser/newshapes/ui/ShapesView.java index b47781e..7881816 100644 --- a/src/main/java/ovh/gasser/newshapes/ui/ShapesView.java +++ b/src/main/java/ovh/gasser/newshapes/ui/ShapesView.java @@ -1,17 +1,14 @@ package ovh.gasser.newshapes.ui; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import ovh.gasser.newshapes.ShapeVisitor; import ovh.gasser.newshapes.shapes.SCollection; import ovh.gasser.newshapes.shapes.Shape; +import ovh.gasser.newshapes.ui.listeners.SelectionListener; import javax.swing.*; import java.awt.*; public class ShapesView extends JPanel { - final Logger logger = LoggerFactory.getLogger(ShapesView.class); - private final Shape model; private final Controller controller; private ShapeVisitor draftman; @@ -31,4 +28,8 @@ public class ShapesView extends JPanel { public Controller getController() { return controller; } + + public void addSelectionChangeListener(SelectionListener listener) { + controller.addSelectionChangeListener(listener); + } } diff --git a/src/main/java/ovh/gasser/newshapes/ui/menu/MenuAddListener.java b/src/main/java/ovh/gasser/newshapes/ui/listeners/MenuAddListener.java similarity index 97% rename from src/main/java/ovh/gasser/newshapes/ui/menu/MenuAddListener.java rename to src/main/java/ovh/gasser/newshapes/ui/listeners/MenuAddListener.java index 734c388..231542c 100644 --- a/src/main/java/ovh/gasser/newshapes/ui/menu/MenuAddListener.java +++ b/src/main/java/ovh/gasser/newshapes/ui/listeners/MenuAddListener.java @@ -1,4 +1,4 @@ -package ovh.gasser.newshapes.ui.menu; +package ovh.gasser.newshapes.ui.listeners; import ovh.gasser.newshapes.attributes.ColorAttributes; import ovh.gasser.newshapes.attributes.SelectionAttributes; diff --git a/src/main/java/ovh/gasser/newshapes/ui/menu/MenuEditListener.java b/src/main/java/ovh/gasser/newshapes/ui/listeners/MenuEditListener.java similarity index 99% rename from src/main/java/ovh/gasser/newshapes/ui/menu/MenuEditListener.java rename to src/main/java/ovh/gasser/newshapes/ui/listeners/MenuEditListener.java index 695c11e..f3a26ec 100644 --- a/src/main/java/ovh/gasser/newshapes/ui/menu/MenuEditListener.java +++ b/src/main/java/ovh/gasser/newshapes/ui/listeners/MenuEditListener.java @@ -1,4 +1,4 @@ -package ovh.gasser.newshapes.ui.menu; +package ovh.gasser.newshapes.ui.listeners; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/src/main/java/ovh/gasser/newshapes/ui/listeners/SelectionListener.java b/src/main/java/ovh/gasser/newshapes/ui/listeners/SelectionListener.java new file mode 100644 index 0000000..b760e9a --- /dev/null +++ b/src/main/java/ovh/gasser/newshapes/ui/listeners/SelectionListener.java @@ -0,0 +1,7 @@ +package ovh.gasser.newshapes.ui.listeners; + +import ovh.gasser.newshapes.shapes.Shape; + +public interface SelectionListener { + void onSelectionChanged(Iterable selectedShapes); +}