sync edit menu checkbox state with the selected shape

This commit is contained in:
2026-03-19 16:06:01 +01:00
parent ce6d0b0815
commit dd59c7d51a
8 changed files with 113 additions and 16 deletions

View File

@@ -2,21 +2,27 @@ package ovh.gasser.newshapes;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import ovh.gasser.newshapes.attributes.ColorAttributes;
import ovh.gasser.newshapes.shapes.SCircle; import ovh.gasser.newshapes.shapes.SCircle;
import ovh.gasser.newshapes.shapes.SCollection; import ovh.gasser.newshapes.shapes.SCollection;
import ovh.gasser.newshapes.shapes.SRectangle; import ovh.gasser.newshapes.shapes.SRectangle;
import ovh.gasser.newshapes.shapes.Shape;
import ovh.gasser.newshapes.ui.ShapesView; import ovh.gasser.newshapes.ui.ShapesView;
import ovh.gasser.newshapes.ui.menu.MenuAddListener; import ovh.gasser.newshapes.ui.listeners.MenuAddListener;
import ovh.gasser.newshapes.ui.menu.MenuEditListener; import ovh.gasser.newshapes.ui.listeners.MenuEditListener;
import ovh.gasser.newshapes.ui.listeners.SelectionListener;
import javax.swing.*; import javax.swing.*;
import java.awt.*; import java.awt.*;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.util.Optional;
public class App { public class App {
private final static Logger logger = LoggerFactory.getLogger(App.class); private final static Logger logger = LoggerFactory.getLogger(App.class);
public static final Dimension WIN_SIZE = new Dimension(800, 600); public static final Dimension WIN_SIZE = new Dimension(800, 600);
private SCollection model; private SCollection model;
private JCheckBoxMenuItem editFill;
private JCheckBoxMenuItem editBorder;
private App() throws HeadlessException { private App() throws HeadlessException {
final JFrame frame = new JFrame("Reactive shapes"); final JFrame frame = new JFrame("Reactive shapes");
@@ -35,6 +41,13 @@ public class App {
frame.setVisible(true); frame.setVisible(true);
this.buildMenuBar(frame, view); this.buildMenuBar(frame, view);
view.addSelectionChangeListener(new SelectionListener() {
@Override
public void onSelectionChanged(Iterable<Shape> selectedShapes) {
updateMenuState(selectedShapes);
}
});
} }
private void buildModel() { private void buildModel() {
@@ -94,8 +107,8 @@ public class App {
JMenuItem editColor = new JMenuItem("Change color"); JMenuItem editColor = new JMenuItem("Change color");
JMenuItem editBorderColor = new JMenuItem("Change border color"); JMenuItem editBorderColor = new JMenuItem("Change border color");
JMenuItem deleteItem = new JMenuItem("Delete"); JMenuItem deleteItem = new JMenuItem("Delete");
JCheckBoxMenuItem editFill = new JCheckBoxMenuItem("Fill Shape"); editFill = new JCheckBoxMenuItem("Fill Shape");
JCheckBoxMenuItem editBorder = new JCheckBoxMenuItem("Draw border"); editBorder = new JCheckBoxMenuItem("Draw border");
editColor.addActionListener(editListener); editColor.addActionListener(editListener);
editBorderColor.addActionListener(editListener); editBorderColor.addActionListener(editListener);
deleteItem.addActionListener(editListener); deleteItem.addActionListener(editListener);
@@ -111,6 +124,34 @@ public class App {
return menuEdit; return menuEdit;
} }
private void updateMenuState(Iterable<Shape> 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) { public static void main(String[] args) {
SwingUtilities.invokeLater(App::new); SwingUtilities.invokeLater(App::new);
} }

View File

@@ -1,15 +1,22 @@
package ovh.gasser.newshapes; package ovh.gasser.newshapes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ovh.gasser.newshapes.attributes.SelectionAttributes; import ovh.gasser.newshapes.attributes.SelectionAttributes;
import ovh.gasser.newshapes.shapes.Shape; import ovh.gasser.newshapes.shapes.Shape;
import ovh.gasser.newshapes.ui.listeners.SelectionListener;
import ovh.gasser.newshapes.util.Streamable; import ovh.gasser.newshapes.util.Streamable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
public class Selection implements Streamable<Shape> { public class Selection implements Streamable<Shape> {
private final static Logger logger = LoggerFactory.getLogger(Selection.class);
private final List<Shape> selectedShapes = new ArrayList<>(); private final List<Shape> selectedShapes = new ArrayList<>();
private final List<SelectionListener> listeners = new ArrayList<>();
@Override @Override
public Iterator<Shape> iterator() { public Iterator<Shape> iterator() {
@@ -20,12 +27,26 @@ public class Selection implements Streamable<Shape> {
for (Shape shape : selectedShapes) { for (Shape shape : selectedShapes) {
shape.addAttributes(new SelectionAttributes(false)); shape.addAttributes(new SelectionAttributes(false));
} }
selectedShapes.clear(); selectedShapes.clear();
notifyListeners();
} }
public void add(Shape s) { public void add(Shape s) {
logger.info("Selection.add");
selectedShapes.add(s); selectedShapes.add(s);
s.addAttributes(new SelectionAttributes(true)); s.addAttributes(new SelectionAttributes(true));
notifyListeners();
}
public List<Shape> 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)));
} }
} }

View File

@@ -4,14 +4,15 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import ovh.gasser.newshapes.App; import ovh.gasser.newshapes.App;
import ovh.gasser.newshapes.ShapeVisitor; 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.attributes.SelectionAttributes;
import ovh.gasser.newshapes.util.Streamable; import ovh.gasser.newshapes.util.Streamable;
import javax.swing.text.html.Option;
import java.awt.*; import java.awt.*;
import java.util.ArrayList; import java.util.*;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Spliterator;
public class SCollection extends AbstractShape implements Streamable<Shape> { public class SCollection extends AbstractShape implements Streamable<Shape> {
private final static Logger logger = LoggerFactory.getLogger(SCollection.class); private final static Logger logger = LoggerFactory.getLogger(SCollection.class);
@@ -38,7 +39,7 @@ public class SCollection extends AbstractShape implements Streamable<Shape> {
for (Shape s : children) bounds = bounds.union(s.getBounds()); for (Shape s : children) bounds = bounds.union(s.getBounds());
return bounds; return bounds;
} catch (IndexOutOfBoundsException e){ } catch (IndexOutOfBoundsException e){
logger.error("getBounds(): {}"); logger.error("getBounds(): {e}", e);
throw new RuntimeException(e); throw new RuntimeException(e);
} }
} }
@@ -76,6 +77,27 @@ public class SCollection extends AbstractShape implements Streamable<Shape> {
} }
} }
@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<Shape> 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 @Override
public String toString() { public String toString() {
StringBuilder sb = new StringBuilder("SCollection{"); StringBuilder sb = new StringBuilder("SCollection{");

View File

@@ -7,6 +7,7 @@ import ovh.gasser.newshapes.Selection;
import ovh.gasser.newshapes.attributes.ColorAttributes; import ovh.gasser.newshapes.attributes.ColorAttributes;
import ovh.gasser.newshapes.shapes.SCollection; import ovh.gasser.newshapes.shapes.SCollection;
import ovh.gasser.newshapes.shapes.Shape; import ovh.gasser.newshapes.shapes.Shape;
import ovh.gasser.newshapes.ui.listeners.SelectionListener;
import java.awt.*; import java.awt.*;
import java.awt.event.KeyAdapter; import java.awt.event.KeyAdapter;
@@ -21,6 +22,7 @@ public class Controller {
private final ShapesView view; private final ShapesView view;
private final SCollection model; private final SCollection model;
private final Selection selection; private final Selection selection;
private Point lastMousePos; private Point lastMousePos;
Controller(ShapesView view, SCollection model) { Controller(ShapesView view, SCollection model) {
@@ -139,10 +141,13 @@ public class Controller {
selection.clear(); selection.clear();
} }
public void addSelectionChangeListener(SelectionListener listener) {
selection.addListener(listener);
}
private Optional<Shape> getTarget(MouseEvent evt, SCollection sc) { private Optional<Shape> getTarget(MouseEvent evt, SCollection sc) {
return sc.stream() return sc.stream()
.filter(s -> s.getBounds().contains(evt.getPoint())) .filter(s -> s.getBounds().contains(evt.getPoint()))
.findFirst(); .findFirst();
} }
} }

View File

@@ -1,17 +1,14 @@
package ovh.gasser.newshapes.ui; package ovh.gasser.newshapes.ui;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ovh.gasser.newshapes.ShapeVisitor; import ovh.gasser.newshapes.ShapeVisitor;
import ovh.gasser.newshapes.shapes.SCollection; import ovh.gasser.newshapes.shapes.SCollection;
import ovh.gasser.newshapes.shapes.Shape; import ovh.gasser.newshapes.shapes.Shape;
import ovh.gasser.newshapes.ui.listeners.SelectionListener;
import javax.swing.*; import javax.swing.*;
import java.awt.*; import java.awt.*;
public class ShapesView extends JPanel { public class ShapesView extends JPanel {
final Logger logger = LoggerFactory.getLogger(ShapesView.class);
private final Shape model; private final Shape model;
private final Controller controller; private final Controller controller;
private ShapeVisitor draftman; private ShapeVisitor draftman;
@@ -31,4 +28,8 @@ public class ShapesView extends JPanel {
public Controller getController() { public Controller getController() {
return controller; return controller;
} }
public void addSelectionChangeListener(SelectionListener listener) {
controller.addSelectionChangeListener(listener);
}
} }

View File

@@ -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.ColorAttributes;
import ovh.gasser.newshapes.attributes.SelectionAttributes; import ovh.gasser.newshapes.attributes.SelectionAttributes;

View File

@@ -1,4 +1,4 @@
package ovh.gasser.newshapes.ui.menu; package ovh.gasser.newshapes.ui.listeners;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;

View File

@@ -0,0 +1,7 @@
package ovh.gasser.newshapes.ui.listeners;
import ovh.gasser.newshapes.shapes.Shape;
public interface SelectionListener {
void onSelectionChanged(Iterable<Shape> selectedShapes);
}