Files
new-shapes/src/main/java/ovh/gasser/newshapes/ui/Controller.java
2026-03-19 22:32:49 +01:00

257 lines
7.9 KiB
Java

package ovh.gasser.newshapes.ui;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ovh.gasser.newshapes.HTMLExporter;
import ovh.gasser.newshapes.Selection;
import ovh.gasser.newshapes.attributes.ColorAttributes;
import ovh.gasser.newshapes.shapes.ResizeHandle;
import ovh.gasser.newshapes.shapes.SCollection;
import ovh.gasser.newshapes.shapes.SText;
import ovh.gasser.newshapes.shapes.Shape;
import ovh.gasser.newshapes.ui.listeners.SelectionListener;
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.FileNotFoundException;
import java.util.Optional;
public class Controller {
private final static Logger logger = LoggerFactory.getLogger(Controller.class);
private final ShapesView view;
private final SCollection model;
private final Selection selection;
private Point lastMousePos;
private boolean addingText;
private boolean resizing;
private ResizeHandle activeHandle;
private Point resizeOrigin;
private boolean resizeMode;
Controller(ShapesView view, SCollection model) {
this.view = view;
this.model = model;
this.selection = new Selection();
var adapter = new MouseAdapter() {
@Override
public void mousePressed(MouseEvent evt) {
handleMousePressed(evt);
}
@Override
public void mouseDragged(MouseEvent evt) {
handleMouseDragged(evt);
}
@Override
public void mouseReleased(MouseEvent evt) {
resizing = false;
activeHandle = null;
resizeOrigin = null;
}
};
this.view.addMouseMotionListener(adapter);
this.view.addMouseListener(adapter);
this.view.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
handleKeyPressed(e);
}
});
}
private void handleMouseDragged(MouseEvent evt) {
if (resizeMode && resizing && activeHandle != null) {
int dx = evt.getX() - resizeOrigin.x;
int dy = evt.getY() - resizeOrigin.y;
for (Shape shape : selection) {
shape.resize(activeHandle, dx, dy);
}
resizeOrigin = evt.getPoint();
} else if (resizeMode && !selection.isEmpty()) {
lastMousePos = evt.getPoint();
ResizeHandle handle = getHandleAt(evt.getPoint());
if (handle != null) {
resizing = true;
activeHandle = handle;
resizeOrigin = evt.getPoint();
}
} else {
resizing = false;
activeHandle = null;
int dx = evt.getX() - lastMousePos.x;
int dy = evt.getY() - lastMousePos.y;
for (Shape shape : selection) {
shape.translate(dx, dy);
}
lastMousePos = evt.getPoint();
}
view.repaint();
}
private void handleMousePressed(MouseEvent evt) {
if (addingText) {
placeTextAt(evt.getPoint());
return;
}
resizing = false;
activeHandle = null;
resizeOrigin = null;
if (resizeMode && !selection.isEmpty()) {
ResizeHandle handle = getHandleAt(evt.getPoint());
if (handle != null) {
resizing = true;
activeHandle = handle;
resizeOrigin = evt.getPoint();
view.repaint();
return;
}
}
getTarget(evt, this.model)
.ifPresentOrElse(
s -> {
if (!evt.isShiftDown()) {
resetSelection();
}
lastMousePos = evt.getPoint();
selection.add(s);
logger.debug("Selecting {}", s);
},
this::resetSelection
);
view.repaint();
}
<<<<<<< HEAD
public void enterTextMode() {
addingText = true;
}
private void placeTextAt(Point point) {
String input = JOptionPane.showInputDialog(view, "Enter text:", "Add text", JOptionPane.PLAIN_MESSAGE);
addingText = false;
if (input == null) {
return;
}
model.add(SText.create(point.x, point.y, input));
resetSelection();
view.repaint();
}
public void enterTextMode() {
addingText = true;
}
private ResizeHandle getHandleAt(Point point) {
final int handleSize = 5;
for (Shape shape : selection) {
Rectangle bounds = shape.getBounds();
int cx = bounds.x + bounds.width / 2;
int cy = bounds.y + bounds.height / 2;
if (point.x < cx && point.y < cy) return ResizeHandle.NW;
if (point.x > cx && point.y < cy) return ResizeHandle.NE;
if (point.x < cx && point.y > cy) return ResizeHandle.SW;
if (point.x > cx && point.y > cy) return ResizeHandle.SE;
if (point.y < cy) return ResizeHandle.N;
if (point.y > cy) return ResizeHandle.S;
if (point.x < cx) return ResizeHandle.W;
if (point.x > cx) return ResizeHandle.E;
}
return null;
}
private void handleKeyPressed(KeyEvent evt) {
switch (evt.getKeyCode()) {
case KeyEvent.VK_R -> toggleResizeMode();
case KeyEvent.VK_DELETE -> deleteSelected();
case KeyEvent.VK_C -> copySelection();
case KeyEvent.VK_A -> changeSelectionColor();
case KeyEvent.VK_H -> exportHtml();
default -> logger.warn("Pressed unhandled key: {}", evt.getKeyChar());
}
}
private void toggleResizeMode() {
if (!selection.isEmpty()) {
resizeMode = !resizeMode;
view.setResizeMode(resizeMode);
view.repaint();
}
}
private void exportHtml() {
logger.info("Exporting view to html");
try {
new HTMLExporter(this.model).export();
} catch (FileNotFoundException e) {
logger.error("Unable to export html: {}", e.getMessage());
}
}
private void changeSelectionColor(){
if (selection == null) {
logger.debug("No selection to change color of");
return;
}
for (Shape s : selection) {
if (s instanceof SCollection collection) {
collection.forEach(shape -> shape.addAttributes(new ColorAttributes(false, true, Color.BLACK, new Color((int) (Math.random() * 0x1000000)))));
} else {
s.addAttributes(new ColorAttributes(false, true, Color.BLACK, new Color((int) (Math.random() * 0x1000000))));
}
}
view.repaint();
}
private void copySelection() {
if (selection == null) {
logger.debug("No selection to copy");
return;
}
for (Shape shape : selection) {
this.model.add(shape.clone());
}
view.repaint();
}
public void deleteSelected() {
if (selection == null) return;
logger.debug("Deleting selected shape(s)");
for (Shape s : selection) {
this.model.remove(s);
}
resetSelection();
view.repaint();
}
private void resetSelection() {
logger.debug("Resetting selection");
selection.clear();
}
public void addSelectionChangeListener(SelectionListener listener) {
selection.addListener(listener);
}
private Optional<Shape> getTarget(MouseEvent evt, SCollection sc) {
return sc.stream()
.filter(s -> s.getBounds().contains(evt.getPoint()))
.findFirst();
}
}