From ce1a245ca65f3962aa7f8ea739fe2acc0f0e7553 Mon Sep 17 00:00:00 2001 From: Minh Bui Date: Tue, 8 Dec 2020 18:38:32 +0700 Subject: [PATCH] Left: Implement composite structures --- src/controller/CommandHistory.java | 135 ++++++ src/controller/DeleteCommand.java | 35 ++ src/controller/DrawShapeCommand.java | 40 ++ src/controller/GroupCommand.java | 33 ++ src/controller/IJPaintController.java | 5 + src/controller/JPaintController.java | 39 ++ src/controller/MouseAdapter.java | 93 ++++ src/controller/MoveCommand.java | 39 ++ src/controller/PasteCommand.java | 36 ++ src/controller/UngroupCommand.java | 32 ++ src/main/Main.java | 31 ++ src/model/AbstractShape.java | 24 ++ src/model/GroupShapes.java | 126 ++++++ src/model/MouseMode.java | 7 + src/model/Shape.java | 177 ++++++++ src/model/ShapeBoundary.java | 95 ++++ src/model/ShapeColor.java | 17 + src/model/ShapeList.java | 408 ++++++++++++++++++ src/model/ShapeShadingType.java | 7 + src/model/ShapeType.java | 7 + .../dialogs/ChoosePrimaryColorDialog.java | 34 ++ .../dialogs/ChooseSecondaryColorDialog.java | 34 ++ .../dialogs/ChooseShadingTypeDialog.java | 33 ++ src/model/dialogs/ChooseShapeDialog.java | 33 ++ .../ChooseStartAndEndPointModeDialog.java | 33 ++ src/model/dialogs/DialogProvider.java | 52 +++ src/model/interfaces/IApplicationState.java | 28 ++ src/model/interfaces/IDialogProvider.java | 20 + src/model/interfaces/IShapeListObserver.java | 10 + src/model/interfaces/IUndoable.java | 14 + src/model/persistence/ApplicationState.java | 86 ++++ src/view/EventName.java | 41 ++ src/view/gui/DrawEllipse.java | 62 +++ src/view/gui/DrawRectangle.java | 61 +++ src/view/gui/DrawTriangle.java | 72 ++++ src/view/gui/Drawer.java | 129 ++++++ src/view/gui/Gui.java | 38 ++ src/view/gui/GuiWindow.java | 96 +++++ src/view/gui/PaintCanvas.java | 13 + src/view/interfaces/IDialogChoice.java | 11 + src/view/interfaces/IDrawShape.java | 27 ++ src/view/interfaces/IEventCallback.java | 5 + src/view/interfaces/IGuiWindow.java | 9 + src/view/interfaces/IUiModule.java | 8 + src/view/interfaces/PaintCanvasBase.java | 8 + 45 files changed, 2343 insertions(+) create mode 100755 src/controller/CommandHistory.java create mode 100755 src/controller/DeleteCommand.java create mode 100755 src/controller/DrawShapeCommand.java create mode 100644 src/controller/GroupCommand.java create mode 100755 src/controller/IJPaintController.java create mode 100755 src/controller/JPaintController.java create mode 100755 src/controller/MouseAdapter.java create mode 100755 src/controller/MoveCommand.java create mode 100644 src/controller/PasteCommand.java create mode 100644 src/controller/UngroupCommand.java create mode 100755 src/main/Main.java create mode 100644 src/model/AbstractShape.java create mode 100644 src/model/GroupShapes.java create mode 100755 src/model/MouseMode.java create mode 100755 src/model/Shape.java create mode 100644 src/model/ShapeBoundary.java create mode 100755 src/model/ShapeColor.java create mode 100755 src/model/ShapeList.java create mode 100755 src/model/ShapeShadingType.java create mode 100755 src/model/ShapeType.java create mode 100755 src/model/dialogs/ChoosePrimaryColorDialog.java create mode 100755 src/model/dialogs/ChooseSecondaryColorDialog.java create mode 100755 src/model/dialogs/ChooseShadingTypeDialog.java create mode 100755 src/model/dialogs/ChooseShapeDialog.java create mode 100755 src/model/dialogs/ChooseStartAndEndPointModeDialog.java create mode 100755 src/model/dialogs/DialogProvider.java create mode 100755 src/model/interfaces/IApplicationState.java create mode 100755 src/model/interfaces/IDialogProvider.java create mode 100755 src/model/interfaces/IShapeListObserver.java create mode 100755 src/model/interfaces/IUndoable.java create mode 100755 src/model/persistence/ApplicationState.java create mode 100755 src/view/EventName.java create mode 100644 src/view/gui/DrawEllipse.java create mode 100644 src/view/gui/DrawRectangle.java create mode 100644 src/view/gui/DrawTriangle.java create mode 100755 src/view/gui/Drawer.java create mode 100755 src/view/gui/Gui.java create mode 100755 src/view/gui/GuiWindow.java create mode 100755 src/view/gui/PaintCanvas.java create mode 100755 src/view/interfaces/IDialogChoice.java create mode 100644 src/view/interfaces/IDrawShape.java create mode 100755 src/view/interfaces/IEventCallback.java create mode 100755 src/view/interfaces/IGuiWindow.java create mode 100755 src/view/interfaces/IUiModule.java create mode 100755 src/view/interfaces/PaintCanvasBase.java diff --git a/src/controller/CommandHistory.java b/src/controller/CommandHistory.java new file mode 100755 index 0000000..5a05549 --- /dev/null +++ b/src/controller/CommandHistory.java @@ -0,0 +1,135 @@ +package controller; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Stack; + +import model.GroupShapes; +import model.Shape; +import model.AbstractShape; +import model.ShapeList; +import model.interfaces.IUndoable; + +import javax.swing.*; + + +/** + * Static class that manages commands, that implement IUndoable, performed on JPaint. + * + * Functionalities: + * Undo/Redo commands + * Copy/Paste commands + * Delete command + */ + +class CommandHistory { + private static final Stack UNDO_STACK = new Stack<>(); + private static final Stack REDO_STACK = new Stack<>(); + private static List copyShapesBuffer = null; + + public static void add(IUndoable cmd) { + UNDO_STACK.push(cmd); + REDO_STACK.clear(); + } + + public static boolean undo() { + System.out.println("Undo was pressed!"); + boolean result = !UNDO_STACK.empty(); + if (result) { + IUndoable c = UNDO_STACK.pop(); + REDO_STACK.push(c); + c.undo(); + //c.printShapeList(); + } + + return result; + } + + public static boolean redo() { + System.out.println("Redo was pressed!"); + boolean result = !REDO_STACK.empty(); + if (result) { + IUndoable c = REDO_STACK.pop(); + UNDO_STACK.push(c); + c.redo(); + //c.printShapeList(); + } + return result; + } + + public static void copy(ShapeList shapesListObj) { + List copiedShapes = shapesListObj.getSelectedShapes(); + if (copiedShapes.size() == 0) return; + Iterator iter = copiedShapes.iterator(); + copyShapesBuffer = new ArrayList<>(); + while (iter.hasNext()) { + AbstractShape cur = iter.next(); + if (cur instanceof Shape) { + Shape copyCur = new Shape(cur.getPrimaryColor(), cur.getSecondaryColor(), + cur.getShadingType(), cur.getShapeType(), 0, 0, + Math.abs(cur.getEndX() - cur.getStartX()), + Math.abs(cur.getEndY() - cur.getStartY())); + copyShapesBuffer.add(copyCur); + } else if (cur instanceof GroupShapes) { + GroupShapes copyCur = new GroupShapes(cur.getPrimaryColor(), cur.getSecondaryColor(), + cur.getShadingType(), cur.getShapeType(), 0, 0, + Math.abs(cur.getEndX() - cur.getStartX()), + Math.abs(cur.getEndY() - cur.getStartY()), + copyAbstractShapes(((GroupShapes) cur).getShapes())); + } + } + } + + private static List copyAbstractShapes(List shapes) { + List componentsClone = new ArrayList<>(); + + Iterator i = shapes.iterator(); + while (i.hasNext()) { + AbstractShape cur = i.next(); + if (i instanceof GroupShapes) { + GroupShapes copyCur = new GroupShapes(cur.getPrimaryColor(), cur.getSecondaryColor(), + cur.getShadingType(), cur.getShapeType(), 0, 0, + cur.getEndX(), cur.getEndY(), copyAbstractShapes(((GroupShapes) cur).getShapes())); + componentsClone.add(copyCur); + } else { + Shape copyCur = new Shape(cur.getPrimaryColor(), cur.getSecondaryColor(), + cur.getShadingType(), cur.getShapeType(), 0, 0, + cur.getEndX(), cur.getEndY()); + componentsClone.add(copyCur); + } + } + + return componentsClone; + } + + public static void paste(ShapeList shapesListObj) { + if (copyShapesBuffer != null) { + shapesListObj.deselectAllShapes(); + IUndoable pasteCmd = new PasteCommand(shapesListObj, copyShapesBuffer); + UNDO_STACK.push(pasteCmd); + REDO_STACK.clear(); + } + } + + public static void delete(ShapeList shapesListObj) { + if (shapesListObj.getNumberOfSelectedShapes() == 0) return; + IUndoable deleteCmd = new DeleteCommand(shapesListObj); + UNDO_STACK.push(deleteCmd); + REDO_STACK.clear(); + } + + public static void group(ShapeList shapesListObj) { + if (shapesListObj.getNumberOfSelectedShapes() == 0) return; + IUndoable groupCmd = new GroupCommand(shapesListObj); + UNDO_STACK.push(groupCmd); + REDO_STACK.clear(); + } + + public static void ungroup(ShapeList shapesListObj) { + if (shapesListObj.getNumberOfSelectedShapes() == 0) return; + IUndoable ungroupCmd = new UngroupCommand(shapesListObj); + UNDO_STACK.push(ungroupCmd); + REDO_STACK.clear(); + } +} diff --git a/src/controller/DeleteCommand.java b/src/controller/DeleteCommand.java new file mode 100755 index 0000000..aaeddec --- /dev/null +++ b/src/controller/DeleteCommand.java @@ -0,0 +1,35 @@ +package controller; + +import java.util.List; + +import model.Shape; +import model.AbstractShape; +import model.ShapeList; +import model.interfaces.IUndoable; + +public class DeleteCommand implements IUndoable { + + private final ShapeList SHAPE_LIST; + private List deletedShapes; + + public DeleteCommand(ShapeList shapeList) { + this.SHAPE_LIST = shapeList; + deletedShapes = this.SHAPE_LIST.removeSelectedShape(); + } + + @Override + public void undo() { + this.SHAPE_LIST.addAllShapes(this.deletedShapes); + } + + @Override + public void redo() { + deletedShapes = this.SHAPE_LIST.removeSelectedShape(); + } + + @Override + public void printShapeList() { + System.out.println("Delete CMD"); + this.SHAPE_LIST.printShapeList(); + } +} diff --git a/src/controller/DrawShapeCommand.java b/src/controller/DrawShapeCommand.java new file mode 100755 index 0000000..35ad0c4 --- /dev/null +++ b/src/controller/DrawShapeCommand.java @@ -0,0 +1,40 @@ +package controller; + +import model.Shape; +import model.ShapeList; +import model.AbstractShape; + +import model.interfaces.IUndoable; + +public class DrawShapeCommand implements IUndoable { + + private final AbstractShape SHAPE; + private final ShapeList SHAPE_LIST; + + public DrawShapeCommand(AbstractShape shape, ShapeList shapeList) { + this.SHAPE = shape; + this.SHAPE_LIST = shapeList; + this.SHAPE_LIST.addShape(this.SHAPE); + } + + @Override + public void undo() { + //System.out.println("Undo was pressed!"); + SHAPE_LIST.removeShape(this.SHAPE); + //SHAPE_LIST.printShapeList(); + } + + @Override + public void redo() { + //System.out.println("Redo was pressed!"); + SHAPE_LIST.addShape(this.SHAPE); + //SHAPE_LIST.printShapeList(); + } + + @Override + public void printShapeList() { + System.out.println("DrawShape CMD"); + this.SHAPE_LIST.printShapeList(); + } + +} diff --git a/src/controller/GroupCommand.java b/src/controller/GroupCommand.java new file mode 100644 index 0000000..07acf88 --- /dev/null +++ b/src/controller/GroupCommand.java @@ -0,0 +1,33 @@ +package controller; + +import model.interfaces.IUndoable; +import model.ShapeList; +import model.AbstractShape; +import java.util.List; + +public class GroupCommand implements IUndoable { + + private final ShapeList SHAPE_LIST; + private List groupedShapes; + + public GroupCommand(ShapeList shapeList) { + this.SHAPE_LIST = shapeList; + this.groupedShapes = this.SHAPE_LIST.groupShapes(this.SHAPE_LIST.getSelectedShapes()); + } + + @Override + public void undo() { + this.groupedShapes = this.SHAPE_LIST.ungroupShapes(this.groupedShapes); + } + + @Override + public void redo() { + this.groupedShapes = this.SHAPE_LIST.groupShapes(this.groupedShapes); + } + + @Override + public void printShapeList() { + System.out.println("Group CMD"); + this.SHAPE_LIST.printShapeList(); + } +} diff --git a/src/controller/IJPaintController.java b/src/controller/IJPaintController.java new file mode 100755 index 0000000..847c52d --- /dev/null +++ b/src/controller/IJPaintController.java @@ -0,0 +1,5 @@ +package controller; + +public interface IJPaintController { + void setup(); +} diff --git a/src/controller/JPaintController.java b/src/controller/JPaintController.java new file mode 100755 index 0000000..6f02016 --- /dev/null +++ b/src/controller/JPaintController.java @@ -0,0 +1,39 @@ +package controller; + +import model.ShapeList; +import model.interfaces.IApplicationState; +import view.EventName; +import view.interfaces.IUiModule; + +public class JPaintController implements IJPaintController { + private final IUiModule UI_MODULE; + private final IApplicationState APP_STATE; + private final ShapeList SHAPE_LIST; + + + public JPaintController(IUiModule uiModule, IApplicationState applicationState, ShapeList shapeList) { + this.UI_MODULE = uiModule; + this.APP_STATE = applicationState; + this.SHAPE_LIST = shapeList; + } + + @Override + public void setup() { + setupEvents(); + } + + private void setupEvents() { + UI_MODULE.addEvent(EventName.CHOOSE_SHAPE, () -> APP_STATE.setActiveShape()); + UI_MODULE.addEvent(EventName.CHOOSE_PRIMARY_COLOR, () -> APP_STATE.setActivePrimaryColor()); + UI_MODULE.addEvent(EventName.CHOOSE_SECONDARY_COLOR, () -> APP_STATE.setActiveSecondaryColor()); + UI_MODULE.addEvent(EventName.CHOOSE_SHADING_TYPE, () -> APP_STATE.setActiveShadingType()); + UI_MODULE.addEvent(EventName.CHOOSE_MOUSE_MODE, () -> APP_STATE.setActiveStartAndEndPointMode()); + UI_MODULE.addEvent(EventName.UNDO, () -> CommandHistory.undo()); + UI_MODULE.addEvent(EventName.REDO, () -> CommandHistory.redo()); + UI_MODULE.addEvent(EventName.COPY, () -> CommandHistory.copy(this.SHAPE_LIST)); + UI_MODULE.addEvent(EventName.PASTE, () -> CommandHistory.paste(this.SHAPE_LIST)); + UI_MODULE.addEvent(EventName.DELETE, () -> CommandHistory.delete(this.SHAPE_LIST)); + UI_MODULE.addEvent(EventName.GROUP, () -> CommandHistory.group(this.SHAPE_LIST)); + UI_MODULE.addEvent(EventName.UNGROUP, () -> CommandHistory.ungroup(this.SHAPE_LIST)); + } +} diff --git a/src/controller/MouseAdapter.java b/src/controller/MouseAdapter.java new file mode 100755 index 0000000..c817643 --- /dev/null +++ b/src/controller/MouseAdapter.java @@ -0,0 +1,93 @@ +package controller; + +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; + +import model.MouseMode; +import model.Shape; +import model.ShapeList; +import model.interfaces.IApplicationState; +import model.persistence.ApplicationState; + +/** + * MouseAdapter accepts input from the mouse pointer and creates appropriates + * commands for JPaint. + */ + +public class MouseAdapter implements MouseListener { + + private static MouseAdapter uniqueInstance; + + private int startX; + private int startY; + private int endX; + private int endY; + + private IApplicationState applicationState; + private ShapeList shapes; + + private MouseAdapter(ApplicationState appState, ShapeList shapes) { + this.applicationState = appState; + this.shapes = shapes; + } + + public static MouseAdapter getInstance(ApplicationState appState, ShapeList shapes) { + if (uniqueInstance == null) { + uniqueInstance = new MouseAdapter(appState, shapes); + } else { + uniqueInstance.applicationState = appState; + uniqueInstance.shapes = shapes; + } + return uniqueInstance; + } + + @Override + public void mouseClicked(MouseEvent arg0) { + this.startX = arg0.getX(); + this.startY = arg0.getY(); + + if (applicationState.getActiveMouseMode() == MouseMode.SELECT) { + this.shapes.markSelect(this.startX + 1, this.startY + 1, 1, 1); + } + } + + @Override + public void mouseEntered(MouseEvent arg0) { } + + @Override + public void mouseExited(MouseEvent arg0) { } + + @Override + public void mousePressed(MouseEvent arg0) { + this.startX = arg0.getX(); + this.startY = arg0.getY(); + } + + @Override + public void mouseReleased(MouseEvent arg0) { + this.endX = arg0.getX(); + this.endY = arg0.getY(); + + int width = Math.abs(this.endX - this.startX); + int height = Math.abs(this.endY - this.startY); + + int x = Math.min(this.startX, this.endX); + int y = Math.min(this.startY, this.endY); + + if (applicationState.getActiveMouseMode() == MouseMode.DRAW) { + Shape newShape = new Shape(applicationState.getActivePrimaryColor(), + applicationState.getActiveSecondaryColor(), applicationState.getActiveShapeShadingType(), + applicationState.getActiveShapeType(), x, y, x + width, y + height); + + CommandHistory.add(new DrawShapeCommand(newShape, this.shapes)); + } else if (applicationState.getActiveMouseMode() == MouseMode.SELECT) { + + this.shapes.markSelect(x, y, width, height); + } else if (applicationState.getActiveMouseMode() == MouseMode.MOVE) { + int dX = this.endX - this.startX; + int dY = this.endY - this.startY; + + CommandHistory.add(new MoveCommand(dX, dY, this.shapes)); + } + } +} diff --git a/src/controller/MoveCommand.java b/src/controller/MoveCommand.java new file mode 100755 index 0000000..245708a --- /dev/null +++ b/src/controller/MoveCommand.java @@ -0,0 +1,39 @@ +package controller; + +import java.util.List; + +import model.Shape; +import model.AbstractShape; +import model.ShapeList; +import model.interfaces.IUndoable; + +public class MoveCommand implements IUndoable { + + private final int DX; + private final int DY; + private List selectedShapes; + private final ShapeList SHAPE_LIST; + + public MoveCommand(int dX, int dY, ShapeList shapeListObj) { + this.DX = dX; + this.DY = dY; + this.SHAPE_LIST = shapeListObj; + this.selectedShapes = this.SHAPE_LIST.moveShapes(DX, DY, null); + } + + @Override + public void undo() { + this.selectedShapes = SHAPE_LIST.moveShapes(-DX, -DY, selectedShapes); + } + + @Override + public void redo() { + this.selectedShapes = SHAPE_LIST.moveShapes(DX, DY, selectedShapes); + } + + @Override + public void printShapeList() { + System.out.println("Move CMD"); + this.SHAPE_LIST.printShapeList(); + } +} diff --git a/src/controller/PasteCommand.java b/src/controller/PasteCommand.java new file mode 100644 index 0000000..2c6aea3 --- /dev/null +++ b/src/controller/PasteCommand.java @@ -0,0 +1,36 @@ +package controller; + +import java.util.List; + +import model.Shape; +import model.AbstractShape; +import model.ShapeList; +import model.interfaces.IUndoable; + +public class PasteCommand implements IUndoable { + + private final ShapeList SHAPE_LIST; + private List pasteShapes; + + public PasteCommand(ShapeList shapeList, List pasteShapes) { + this.SHAPE_LIST = shapeList; + this.pasteShapes = this.SHAPE_LIST.cloneAndAddAllShapes(pasteShapes); + this.printShapeList(); + } + + @Override + public void undo() { + this.SHAPE_LIST.removeShapesList(pasteShapes); + } + + @Override + public void redo() { + this.pasteShapes = this.SHAPE_LIST.cloneAndAddAllShapes(pasteShapes); + } + + @Override + public void printShapeList() { + System.out.println("Paste CMD"); + this.SHAPE_LIST.printShapeList(); + } +} diff --git a/src/controller/UngroupCommand.java b/src/controller/UngroupCommand.java new file mode 100644 index 0000000..10cb1ec --- /dev/null +++ b/src/controller/UngroupCommand.java @@ -0,0 +1,32 @@ +package controller; + +import model.ShapeList; +import model.AbstractShape; +import model.interfaces.IUndoable; +import java.util.List; + +public class UngroupCommand implements IUndoable { + private final ShapeList SHAPE_LIST; + private List ungrouped; + + public UngroupCommand(ShapeList shapeList) { + this.SHAPE_LIST = shapeList; + this.ungrouped = this.SHAPE_LIST.ungroupShapes(this.SHAPE_LIST.getSelectedShapes()); + } + + @Override + public void undo() { + this.ungrouped = this.SHAPE_LIST.groupShapes(this.ungrouped); + } + + @Override + public void redo() { + this.ungrouped = this.SHAPE_LIST.ungroupShapes(this.ungrouped); + } + + @Override + public void printShapeList() { + System.out.println("Ungrouped CMD"); + this.SHAPE_LIST.printShapeList(); + } +} diff --git a/src/main/Main.java b/src/main/Main.java new file mode 100755 index 0000000..bb49bd9 --- /dev/null +++ b/src/main/Main.java @@ -0,0 +1,31 @@ +package main; + +import controller.IJPaintController; +import controller.JPaintController; +import model.persistence.ApplicationState; +import view.gui.Drawer; +import view.gui.Gui; +import view.gui.GuiWindow; +import view.gui.PaintCanvas; +import view.interfaces.IGuiWindow; +import view.interfaces.PaintCanvasBase; +import view.interfaces.IUiModule; +import controller.MouseAdapter; +import model.ShapeList; + + +public class Main { + public static void main(String[] args){ + ShapeList shapes = ShapeList.getInstance(); + PaintCanvasBase paintCanvas = new PaintCanvas(); + IGuiWindow guiWindow = new GuiWindow(paintCanvas); + IUiModule uiModule = new Gui(guiWindow); + ApplicationState appState = new ApplicationState(uiModule); + MouseAdapter mouseController = MouseAdapter.getInstance(appState, shapes); + paintCanvas.addMouseListener(mouseController); + Drawer drawer = Drawer.getInstance(paintCanvas); + shapes.registerObserver(drawer); + IJPaintController controller = new JPaintController(uiModule, appState, shapes); + controller.setup(); + } +} diff --git a/src/model/AbstractShape.java b/src/model/AbstractShape.java new file mode 100644 index 0000000..cae39a8 --- /dev/null +++ b/src/model/AbstractShape.java @@ -0,0 +1,24 @@ +package model; + +import view.interfaces.IDrawShape; +import java.util.List; + +public abstract class AbstractShape { + public abstract int getStartX(); + public abstract int getStartY(); + public abstract int getEndX(); + public abstract int getEndY(); + public abstract boolean isSelected(); + public abstract void setIsSelected(boolean isSelected); + public abstract boolean isBoundary(); + public abstract boolean isInGroup(); + public abstract void setIsInGroup(boolean groupFlag); + public abstract void move(int dx, int dy); + public abstract ShapeType getShapeType(); + public abstract ShapeColor getPrimaryColor(); + public abstract ShapeColor getSecondaryColor(); + public abstract ShapeShadingType getShadingType(); + public abstract boolean isOverlapping(AbstractShape shape); + public abstract List getComponents(); + public abstract boolean isComposite(); +} diff --git a/src/model/GroupShapes.java b/src/model/GroupShapes.java new file mode 100644 index 0000000..35863f9 --- /dev/null +++ b/src/model/GroupShapes.java @@ -0,0 +1,126 @@ +package model; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + + +public class GroupShapes extends AbstractShape { + + private List shapes; + private ShapeBoundary groupBoundary; + + private ShapeColor primaryColor; + private ShapeColor secondaryColor; + private ShapeShadingType shadingType; + private ShapeType shapeType; + private int startX; + private int startY; + private int endX; + private int endY; + private boolean isSelected; + private boolean isInGroup; + + private final boolean IS_COMPOSITE = true; + private final boolean IS_BOUNDARY = false; + + public GroupShapes(ShapeColor primaryColor, ShapeColor secondaryColor, ShapeShadingType shadingType, + ShapeType shapeType, int startX, int startY, int endX, int endY, List shapes) { + this.primaryColor = primaryColor; + this.secondaryColor = secondaryColor; + this.shadingType = shadingType; + this.shapeType = shapeType; + + this.startX = startX; + this.startY = startY; + this.endX = endX; + this.endY = endY; + + this.shapes = shapes; + this.isInGroup = false; + this.isSelected = true; + + this.resetBoundary(); + } + + public void resetBoundary() { + this.groupBoundary = new ShapeBoundary(ShapeColor.BLACK, ShapeColor.BLACK, ShapeShadingType.OUTLINE, + this.shapeType, startX, startY, endX, endY); + } + + @Override + public boolean isComposite() { return this.IS_COMPOSITE; } + + @Override + public boolean isBoundary() { return this.IS_BOUNDARY; } + + @Override + public boolean isInGroup() { return this.isInGroup; } + + @Override + public void setIsInGroup(boolean groupFlag) { this.isInGroup = groupFlag; } + + @Override + public boolean isOverlapping(AbstractShape shape) { + if ((startX > shape.getEndX()) || (endX < shape.getStartX()) || + startY > shape.getEndY() || endY < shape.getStartY()) + return false; + return true; + } + + @Override + public int getStartX() { return startX; } + + @Override + public int getStartY() { return startY; } + + @Override + public int getEndX() { return endX; } + + @Override + public int getEndY() { return endY; } + + @Override + public boolean isSelected() { return this.isSelected; } + + @Override + public void setIsSelected(boolean isSelected) { this.isSelected = isSelected; } + + @Override + public void move(int dx, int dy) { + this.startX = this.startX + dx; + this.startY = this.startY + dy; + this.endX = this.endX + dx; + this.endY = this.endY + dy; + + Iterator shapeIter = this.shapes.iterator(); + while (shapeIter.hasNext()) { + shapeIter.next().move(dx, dy); + } + + this.groupBoundary.move(dx,dy); + } + + @Override + public ShapeType getShapeType() { return this.shapeType; } + + @Override + public ShapeColor getPrimaryColor() { return this.primaryColor; } + + @Override + public ShapeColor getSecondaryColor() { return this.secondaryColor; } + + @Override + public ShapeShadingType getShadingType() { return this.shadingType; } + + public List getShapes() { return this.shapes; } + + @Override + public List getComponents() { + List components = new ArrayList<>(); + components.addAll(this.shapes); + if (!this.isInGroup()) + components.add(groupBoundary); + return components; + } +} diff --git a/src/model/MouseMode.java b/src/model/MouseMode.java new file mode 100755 index 0000000..b9ce699 --- /dev/null +++ b/src/model/MouseMode.java @@ -0,0 +1,7 @@ +package model; + +public enum MouseMode { + DRAW, + SELECT, + MOVE +} diff --git a/src/model/Shape.java b/src/model/Shape.java new file mode 100755 index 0000000..45941e0 --- /dev/null +++ b/src/model/Shape.java @@ -0,0 +1,177 @@ +package model; + +import view.interfaces.IDrawShape; + +import java.util.List; +import java.util.ArrayList; + +/** + * Shape Class + * + * Responsibility: Maintains information/metadata about a generic shape. + * + * A shape primary's color, secondary color, type, shaping type, selection, and starting and ending coordinates. + * + * + * @author Minh Bui + */ + +public class Shape extends AbstractShape { + private ShapeColor primaryColor; + private ShapeColor secondaryColor; + private ShapeShadingType shadingType; + private ShapeType shapeType; + private int startX; + private int startY; + private int endX; + private int endY; + private boolean isSelected; + private boolean isInGroup; + private final boolean IS_BOUNDARY = false; + private final boolean IS_COMPOSITE = false; + private ShapeBoundary groupBoundary; + private ShapeBoundary boundary; + + public Shape(ShapeColor primaryColor, ShapeColor secondaryColor, ShapeShadingType shadingType, ShapeType shapeType, + int startX, int startY, int endX, int endY) { + this.primaryColor = primaryColor; + this.secondaryColor = secondaryColor; + this.shadingType = shadingType; + this.shapeType = shapeType; + this.startX = startX; + this.startY = startY; + this.endX = endX; + this.endY = endY; + this.isSelected = false; + this.groupBoundary = null; + this.isInGroup = false; + resetBoundary(); + } + + public boolean isBoundary() { return this.IS_BOUNDARY; } + + /* // Uncomment this if use solution with Composite design pattern. + + @Override + public boolean isInGroup() { return this.isInGroup; } + + */ + + @Override + public void setIsInGroup(boolean groupFlag) { this.isInGroup = groupFlag; } + + public void resetBoundary() { + this.boundary = new ShapeBoundary(ShapeColor.BLACK, ShapeColor.BLACK, ShapeShadingType.OUTLINE, this.shapeType, + startX, startY, endX, endY); + } + + public void setGroupBoundary(int x, int y, int width, int height) { + this.groupBoundary = new ShapeBoundary(ShapeColor.BLACK, ShapeColor.BLACK, ShapeShadingType.OUTLINE, + ShapeType.RECTANGLE, x, y, x + width, y + height); + } + + public void unsetGroupBoundary() { this.groupBoundary = null; } + + public void updateGroupBoundary(int dx, int dy) { + int[] groupBoundaryParams = this.getGroupBoundaryParams(); + + this.groupBoundary = new ShapeBoundary(ShapeColor.BLACK, ShapeColor.BLACK, ShapeShadingType.OUTLINE, + ShapeType.RECTANGLE, groupBoundaryParams[0] + dx, groupBoundaryParams[1] + dy, + groupBoundaryParams[0] + groupBoundaryParams[2] + dx, + groupBoundaryParams[1] + groupBoundaryParams[3] + dy); + } + + public int[] getGroupBoundaryParams() { + int[] groupBoundaryParams = new int[4]; + groupBoundaryParams[0] = this.groupBoundary.getStartX(); + groupBoundaryParams[1] = this.groupBoundary.getStartY(); + groupBoundaryParams[2] = this.groupBoundary.getEndX() - this.groupBoundary.getStartX(); + groupBoundaryParams[3] = this.groupBoundary.getEndY() - this.groupBoundary.getStartY(); + return groupBoundaryParams; + } + + public AbstractShape getBoundary() { return this.boundary; } + public AbstractShape getGroupBoundary() { return this.groupBoundary; } + + + @Override + public boolean isInGroup() { return this.groupBoundary != null; } + + + public boolean isSelected() { return this.isSelected; } + + public void setIsSelected(boolean isSelected) { this.isSelected = isSelected; } + + @Override + public int getStartX() { return startX; } + + @Override + public int getStartY() { return startY; } + + @Override + public int getEndX() { return endX; } + + @Override + public int getEndY() { return endY; } + + public ShapeColor getPrimaryColor() { return primaryColor; } + + public ShapeColor getSecondaryColor() { return secondaryColor; } + + public void move(int dx, int dy) { + + // Comment out this block if using solution with Composite design pattern. + if (this.isInGroup()) + this.updateGroupBoundary(dx, dy); + + this.startX = this.startX + dx; + this.startY = this.startY + dy; + this.endX = this.endX + dx; + this.endY = this.endY + dy; + + + // Comment this if using solution with Composite design pattern. + resetBoundary(); + + // Uncomment this if using solution with Composite design pattern. + //this.boundary.move(dx, dy); + } + + public ShapeShadingType getShadingType() { return shadingType; } + + public ShapeType getShapeType() { return shapeType; } + + @Override + public boolean isOverlapping(AbstractShape shape) { + if (this.groupBoundary != null) { + return this.groupBoundary.isOverlapping(shape); + } else { + return this.boundary.isOverlapping(shape); + } + } + + /* + @Override + public String toString() { + String shapeToStr = ""; + shapeToStr += "startX: " + this.startX + "\n"; + shapeToStr += "startY: " + this.startY + "\n"; + shapeToStr += "endX: " + this.endX + "\n"; + shapeToStr += "endY: " + this.endY + "\n"; + shapeToStr += "shadingType: " + this.shadingType + "\n"; + shapeToStr += "shadeType: " + this.shapeType + "\n"; + return shapeToStr; + } + */ + + @Override + public boolean isComposite() { return this.IS_COMPOSITE; } + + @Override + public List getComponents() { + List components = new ArrayList<>(); + if (!this.isInGroup()) + components.add(this.boundary); + return components; + } +} diff --git a/src/model/ShapeBoundary.java b/src/model/ShapeBoundary.java new file mode 100644 index 0000000..2945539 --- /dev/null +++ b/src/model/ShapeBoundary.java @@ -0,0 +1,95 @@ +package model; + +import view.interfaces.IDrawShape; +import java.util.List; + +public class ShapeBoundary extends AbstractShape { + + private ShapeColor primaryColor; + private ShapeColor secondaryColor; + private ShapeShadingType shadingType; + private ShapeType shapeType; + private int startX; + private int startY; + private int endX; + private int endY; + private boolean isInGroup; + private final boolean IS_BOUNDARY = true; + private final boolean IS_COMPOSITE = true; + + public ShapeBoundary(ShapeColor primaryColor, ShapeColor secondaryColor, ShapeShadingType shadingType, + ShapeType shapeType, int startX, int startY, int endX, int endY) { + this.primaryColor = primaryColor; + this.secondaryColor = secondaryColor; + this.shadingType = shadingType; + this.shapeType = shapeType; + this.startX = startX; + this.startY = startY; + this.endX = endX; + this.endY = endY; + this.isInGroup = false; + } + + @Override + public boolean isInGroup() { return this.isInGroup; } + + @Override + public void setIsInGroup(boolean groupFlag) { this.isInGroup = groupFlag; } + + @Override + public boolean isBoundary() { return IS_BOUNDARY; } + + @Override + public boolean isComposite() { return this.IS_COMPOSITE; } + + @Override + public boolean isOverlapping(AbstractShape shape) { + if ((startX > shape.getEndX()) || (endX < shape.getStartX()) || + startY > shape.getEndY() || endY < shape.getStartY()) + return false; + return true; + } + + @Override + public int getStartX() { return startX; } + + @Override + public int getStartY() { return startY; } + + @Override + public int getEndX() { return endX; } + + @Override + public int getEndY() { return endY; } + + @Override + public boolean isSelected() { return false; } + + @Override + public void setIsSelected(boolean isSelected) { } + + @Override + public void move(int dx, int dy) { + this.startX = this.startX + dx; + this.startY = this.startY + dy; + this.endX = this.endX + dx; + this.endY = this.endY + dy; + } + + @Override + public ShapeType getShapeType() { return this.shapeType; } + + @Override + public ShapeColor getPrimaryColor() { return this.primaryColor; } + + @Override + public ShapeColor getSecondaryColor() { return this.secondaryColor; } + + @Override + public ShapeShadingType getShadingType() { return this.shadingType; } + + @Override + public List getComponents() { + return null; + } +} diff --git a/src/model/ShapeColor.java b/src/model/ShapeColor.java new file mode 100755 index 0000000..0007296 --- /dev/null +++ b/src/model/ShapeColor.java @@ -0,0 +1,17 @@ +package model; + +public enum ShapeColor { + BLACK, + BLUE, + CYAN, + DARK_GRAY, + GRAY, + GREEN, + LIGHT_GRAY, + MAGENTA, + ORANGE, + PINK, + RED, + WHITE, + YELLOW +} diff --git a/src/model/ShapeList.java b/src/model/ShapeList.java new file mode 100755 index 0000000..e607ef8 --- /dev/null +++ b/src/model/ShapeList.java @@ -0,0 +1,408 @@ +package model; + +import java.util.ArrayList; +import java.util.List; +import java.util.Iterator; + +import model.interfaces.IShapeListObserver; + +/** + * ShapeList + * + * Responsibility: Maintains a list of shapes. It's basically a wrapper class for the List data structure. + * ShapeList is a Singleton since we only need 1 instance. + * + * Note to myself: Might change the class name to ShapeManager instead. + * + * @author Minh Bui + */ + +public class ShapeList { + + private static ShapeList uniqueInstance; + + private final List SHAPE_LIST; + private final List OBSERVER_LIST; + + private ShapeList() { + this.SHAPE_LIST = new ArrayList<>(); + this.OBSERVER_LIST = new ArrayList<>(); + } + + public static ShapeList getInstance() { + if (uniqueInstance == null) + uniqueInstance = new ShapeList(); + return uniqueInstance; + } + + public void registerObserver(IShapeListObserver observer) { + this.OBSERVER_LIST.add(observer); + } + + public void removeObserver(IShapeListObserver observer) { + Iterator obsIter = OBSERVER_LIST.iterator(); + while (obsIter.hasNext()) { + IShapeListObserver curObs = obsIter.next(); + if (curObs.equals(observer)) { + obsIter.remove(); + } + } + } + + public void addShape(AbstractShape newShape) { + this.SHAPE_LIST.add(newShape); + this.notifyObservers(); + } + + public void addAllShapes(List shapes) { + this.SHAPE_LIST.addAll(shapes); + this.notifyObservers(); + } + + public List cloneAndAddAllShapes(List shapes) { + List clones = new ArrayList<>(); + Iterator iter = shapes.iterator(); + while (iter.hasNext()) { + AbstractShape cur = iter.next(); + if (cur instanceof Shape) { + Shape copyCur = new Shape(cur.getPrimaryColor(), cur.getSecondaryColor(), + cur.getShadingType(), cur.getShapeType(), cur.getStartX(), cur.getStartY(), + cur.getEndX(), cur.getEndY()); + clones.add(copyCur); + } else if (cur instanceof GroupShapes) { + System.out.println("hello"); + GroupShapes copyCur = new GroupShapes(cur.getPrimaryColor(), cur.getSecondaryColor(), + cur.getShadingType(), cur.getShapeType(), cur.getStartX(), cur.getStartY(), + cur.getEndX(), cur.getEndY(), cloneAndAddComponents(((GroupShapes) cur).getShapes())); + clones.add(copyCur); + } + } + this.SHAPE_LIST.addAll(clones); + this.notifyObservers(); + return clones; + } + + private List cloneAndAddComponents(List shapes) { + List componentsClone = new ArrayList<>(); + + Iterator i = shapes.iterator(); + while (i.hasNext()) { + AbstractShape cur = i.next(); + if (i instanceof GroupShapes) { + GroupShapes copyCur = new GroupShapes(cur.getPrimaryColor(), cur.getSecondaryColor(), + cur.getShadingType(), cur.getShapeType(), cur.getStartX(), cur.getStartY(), + cur.getEndX(), cur.getEndY(), cloneAndAddComponents(((GroupShapes) cur).getShapes())); + componentsClone.add(copyCur); + } else { + Shape copyCur = new Shape(cur.getPrimaryColor(), cur.getSecondaryColor(), + cur.getShadingType(), cur.getShapeType(), cur.getStartX(), cur.getStartY(), + cur.getEndX(), cur.getEndY()); + componentsClone.add(copyCur); + } + } + + return componentsClone; + } + + public void removeShape(AbstractShape shape) { + Iterator shapeIter = this.SHAPE_LIST.iterator(); + + while (shapeIter.hasNext()) { + AbstractShape curShape = shapeIter.next(); + if (curShape.equals(shape)) + shapeIter.remove(); + } + this.notifyObservers(); + } + + public List removeSelectedShape() { + Iterator shapeIter = this.SHAPE_LIST.iterator(); + List removedShapes = new ArrayList<>(); + while(shapeIter.hasNext()) { + AbstractShape curShape = shapeIter.next(); + if (curShape.isSelected()) { + shapeIter.remove(); + removedShapes.add(curShape); + } + } + this.notifyObservers(); + return removedShapes; + } + + public void printShapeList() { + Iterator shapeIter = this.SHAPE_LIST.iterator(); + + System.out.println("ShapeList currently has: "); + while(shapeIter.hasNext()) { + AbstractShape curShape = shapeIter.next(); + System.out.println(curShape + " " + curShape.isBoundary()); + } + + System.out.println("--------------- END OF SHAPE LIST -----------------"); + System.out.println("----------- BEGIN PRINTING ALL COMPONENTS -------------"); + + shapeIter = this.SHAPE_LIST.iterator(); + while (shapeIter.hasNext()) { + AbstractShape curShape = shapeIter.next(); + System.out.println(curShape + " " + curShape.isBoundary()); + + List curComponents = curShape.getComponents(); + if (curComponents != null) { + this.printComponents(curComponents); + } + } + System.out.println(); + } + + private void printComponents(List components) { + if (components != null) { + System.out.print('\t'); + + Iterator i = components.iterator(); + while(i.hasNext()) { + AbstractShape aShape = i.next(); + System.out.println(aShape + " " + aShape.isBoundary()); + printComponents(aShape.getComponents()); + } + } + } + + public void markSelect(int x, int y, int width, int height) { + AbstractShape boundary = new ShapeBoundary(ShapeColor.BLACK, ShapeColor.BLACK, ShapeShadingType.OUTLINE, + ShapeType.RECTANGLE, x, y, x + width, y + height); + Iterator shapeIter = this.SHAPE_LIST.iterator(); + while (shapeIter.hasNext()) { + AbstractShape curShape = shapeIter.next(); + if (curShape.isOverlapping(boundary)) { + curShape.setIsSelected(true); + } else { + curShape.setIsSelected(false); + } + } + this.notifyObservers(); + } + + public List moveShapes(int dX, int dY, List shapesToMove) { + List movedShapes = new ArrayList<>(); + Iterator shapeIter = this.SHAPE_LIST.iterator(); + + if (shapesToMove == null) { + while (shapeIter.hasNext()) { + AbstractShape curShape = shapeIter.next(); + if (curShape.isSelected()) { + curShape.move(dX, dY); + movedShapes.add(curShape); + } + } + } else { + while (shapeIter.hasNext()) { + AbstractShape curShape = shapeIter.next(); + if (shapesToMove.contains(curShape)) { + curShape.move(dX, dY); + movedShapes.add(curShape); + } + } + } + + this.notifyObservers(); + return movedShapes; + } + + public void deselectAllShapes() { + Iterator shapeIter = this.SHAPE_LIST.iterator(); + while(shapeIter.hasNext()) { + AbstractShape curShape = shapeIter.next(); + curShape.setIsSelected(false); + } + this.notifyObservers(); + } + + public void removeShapesList(List shapes) { + this.SHAPE_LIST.removeAll(shapes); + this.notifyObservers(); + } + + public List getSelectedShapes() { + List selectedShapes = new ArrayList<>(); + Iterator shapeIter = this.SHAPE_LIST.iterator(); + while (shapeIter.hasNext()) { + AbstractShape curShape = shapeIter.next(); + if (curShape.isSelected()) + selectedShapes.add(curShape); + } + return selectedShapes; + } + + + public List groupShapes2(List shapes) { + Iterator shapeIter = shapes.iterator(); + + int xMax = Integer.MIN_VALUE; + int yMax = Integer.MIN_VALUE; + int xMin = Integer.MAX_VALUE; + int yMin = Integer.MAX_VALUE; + + while (shapeIter.hasNext()) { + AbstractShape curShape = shapeIter.next(); + xMax = max(curShape.getStartX(), curShape.getEndX(), xMax); + yMax = max(curShape.getStartY(), curShape.getEndY(), yMax); + xMin = min(curShape.getStartX(), curShape.getEndX(), xMin); + yMin = min(curShape.getStartY(), curShape.getEndY(), yMin); + curShape.setIsInGroup(true); + } + + GroupShapes newGroup = new GroupShapes(ShapeColor.BLACK, ShapeColor.BLACK, ShapeShadingType.OUTLINE, + ShapeType.RECTANGLE, xMin, yMin, xMax, yMax, shapes); + + this.removeShapesList(shapes); + this.addShape(newGroup); + this.notifyObservers(); + + List grouped = new ArrayList<>(); + grouped.add(newGroup); + //this.printShapeList(); + return grouped; + } + + public List ungroupShapes2(List shapes) { + Iterator shapeIter = shapes.iterator(); + + // List of Shapes to be added back to the ShapeList and returned by this method. + List ungroup = new ArrayList<>(); + + while (shapeIter.hasNext()) { + AbstractShape curShape = shapeIter.next(); + curShape.setIsInGroup(false); + if (curShape instanceof GroupShapes) { + // Removes the current GroupShapes object and re-add the shapes that are held + // by the removed GroupShapes object. + + this.removeShape(curShape); + GroupShapes group = (GroupShapes) curShape; + + List ushapes = group.getShapes(); + Iterator shapeIter2 = ushapes.iterator(); + while(shapeIter2.hasNext()) { + AbstractShape aShape = shapeIter2.next(); + aShape.setIsInGroup(false); + } + + this.addAllShapes(ushapes); + ungroup.addAll(ushapes); + } else { + + } + } + //this.printShapeList(); + this.notifyObservers(); + return ungroup; + } + + public List groupSelectedShapes() { + List selectedShape = this.getSelectedShapes(); + + if (selectedShape.isEmpty()) return selectedShape; + + Iterator shapeIter = selectedShape.iterator(); + + int xMax = Integer.MIN_VALUE; + int yMax = Integer.MIN_VALUE; + int xMin = Integer.MAX_VALUE; + int yMin = Integer.MAX_VALUE; + + while (shapeIter.hasNext()) { + AbstractShape curShape = shapeIter.next(); + if (curShape.isSelected()) { + xMax = max(curShape.getStartX(), curShape.getEndX(), xMax); + yMax = max(curShape.getStartY(), curShape.getEndY(), yMax); + xMin = min(curShape.getStartX(), curShape.getEndX(), xMin); + yMin = min(curShape.getStartY(), curShape.getEndY(), yMin); + } + } + + shapeIter = selectedShape.iterator(); + while (shapeIter.hasNext()) { + Shape curShape = (Shape) shapeIter.next(); + curShape.setGroupBoundary(xMin, yMin, xMax - xMin, yMax - yMin); + } + this.notifyObservers(); + return selectedShape; + } + + public List groupShapes(List shapes) { + Iterator shapeIter = shapes.iterator(); + + int xMax = Integer.MIN_VALUE; + int yMax = Integer.MIN_VALUE; + int xMin = Integer.MAX_VALUE; + int yMin = Integer.MAX_VALUE; + + while (shapeIter.hasNext()) { + AbstractShape curShape = shapeIter.next(); + xMax = max(curShape.getStartX(), curShape.getEndX(), xMax); + yMax = max(curShape.getStartY(), curShape.getEndY(), yMax); + xMin = min(curShape.getStartX(), curShape.getEndX(), xMin); + yMin = min(curShape.getStartY(), curShape.getEndY(), yMin); + } + + shapeIter = shapes.iterator(); + while (shapeIter.hasNext()) { + Shape curShape = (Shape) shapeIter.next(); + curShape.setGroupBoundary(xMin, yMin, xMax - xMin, yMax - yMin); + } + this.notifyObservers(); + return shapes; + } + + public List ungroupSelectedShapes() { + Iterator shapeIter = this.SHAPE_LIST.iterator(); + List ungrouped = new ArrayList<>(); + while (shapeIter.hasNext()) { + Shape curShape = (Shape) shapeIter.next(); + if (curShape.isSelected() && curShape.isInGroup()) { + curShape.unsetGroupBoundary(); + ungrouped.add(curShape); + } + } + this.notifyObservers(); + return ungrouped; + } + + public List ungroupShapes(List shapes) { + Iterator shapeIter = shapes.iterator(); + + while (shapeIter.hasNext()) { + + //AbstractShape curShape = shapeIter.next(); + Shape curShape = (Shape) shapeIter.next(); + if (curShape.isInGroup()) { + curShape.unsetGroupBoundary(); + } + } + this.notifyObservers(); + return shapes; + } + + public int getNumberOfSelectedShapes() { + int count = 0; + Iterator shapeIter = this.SHAPE_LIST.iterator(); + while (shapeIter.hasNext()) { + if (shapeIter.next().isSelected()) + count++; + } + + return count; + } + + public void notifyObservers() { + Iterator obsIter = OBSERVER_LIST.iterator(); + while (obsIter.hasNext()) { + IShapeListObserver curObs = obsIter.next(); + curObs.update(SHAPE_LIST); + } + } + + private int max(int a, int b, int c) { return Math.max(a, Math.max(b, c)); } + + private int min(int a, int b, int c) { return Math.min(a, Math.min(b, c)); } +} diff --git a/src/model/ShapeShadingType.java b/src/model/ShapeShadingType.java new file mode 100755 index 0000000..301498c --- /dev/null +++ b/src/model/ShapeShadingType.java @@ -0,0 +1,7 @@ +package model; + +public enum ShapeShadingType { + FILLED_IN, + OUTLINE, + OUTLINE_AND_FILLED_IN +} diff --git a/src/model/ShapeType.java b/src/model/ShapeType.java new file mode 100755 index 0000000..8a61f84 --- /dev/null +++ b/src/model/ShapeType.java @@ -0,0 +1,7 @@ +package model; + +public enum ShapeType { + ELLIPSE, + RECTANGLE, + TRIANGLE +} diff --git a/src/model/dialogs/ChoosePrimaryColorDialog.java b/src/model/dialogs/ChoosePrimaryColorDialog.java new file mode 100755 index 0000000..17f46f4 --- /dev/null +++ b/src/model/dialogs/ChoosePrimaryColorDialog.java @@ -0,0 +1,34 @@ +package model.dialogs; + +import model.ShapeColor; +import model.interfaces.IApplicationState; +import view.interfaces.IDialogChoice; + +public class ChoosePrimaryColorDialog implements IDialogChoice { + + private final IApplicationState APP_STATE; + + public ChoosePrimaryColorDialog(IApplicationState applicationState) { + this.APP_STATE = applicationState; + } + + @Override + public String getDialogTitle() { + return "Primary Color"; + } + + @Override + public String getDialogText() { + return "Select a primary color from the menu below:"; + } + + @Override + public ShapeColor[] getDialogOptions() { + return ShapeColor.values(); + } + + @Override + public ShapeColor getCurrentSelection() { + return APP_STATE.getActivePrimaryColor(); + } +} diff --git a/src/model/dialogs/ChooseSecondaryColorDialog.java b/src/model/dialogs/ChooseSecondaryColorDialog.java new file mode 100755 index 0000000..ac462e1 --- /dev/null +++ b/src/model/dialogs/ChooseSecondaryColorDialog.java @@ -0,0 +1,34 @@ +package model.dialogs; + +import model.ShapeColor; +import model.interfaces.IApplicationState; +import view.interfaces.IDialogChoice; + +public class ChooseSecondaryColorDialog implements IDialogChoice { + + private final IApplicationState APP_STATE; + + public ChooseSecondaryColorDialog(IApplicationState applicationState) { + this.APP_STATE = applicationState; + } + + @Override + public String getDialogTitle() { + return "Secondary Color"; + } + + @Override + public String getDialogText() { + return "Select a secondary color from the menu below:"; + } + + @Override + public ShapeColor[] getDialogOptions() { + return ShapeColor.values(); + } + + @Override + public ShapeColor getCurrentSelection() { + return APP_STATE.getActiveSecondaryColor(); + } +} diff --git a/src/model/dialogs/ChooseShadingTypeDialog.java b/src/model/dialogs/ChooseShadingTypeDialog.java new file mode 100755 index 0000000..f40bf06 --- /dev/null +++ b/src/model/dialogs/ChooseShadingTypeDialog.java @@ -0,0 +1,33 @@ +package model.dialogs; + +import model.ShapeShadingType; +import model.interfaces.IApplicationState; +import view.interfaces.IDialogChoice; + +public class ChooseShadingTypeDialog implements IDialogChoice { + private final IApplicationState APP_STATE; + + public ChooseShadingTypeDialog(IApplicationState applicationState) { + this.APP_STATE = applicationState; + } + + @Override + public String getDialogTitle() { + return "Shading Type"; + } + + @Override + public String getDialogText() { + return "Select a shading type from the menu below:"; + } + + @Override + public ShapeShadingType[] getDialogOptions() { + return ShapeShadingType.values(); + } + + @Override + public ShapeShadingType getCurrentSelection() { + return APP_STATE.getActiveShapeShadingType(); + } +} diff --git a/src/model/dialogs/ChooseShapeDialog.java b/src/model/dialogs/ChooseShapeDialog.java new file mode 100755 index 0000000..81ba225 --- /dev/null +++ b/src/model/dialogs/ChooseShapeDialog.java @@ -0,0 +1,33 @@ +package model.dialogs; + +import model.ShapeType; +import model.interfaces.IApplicationState; +import view.interfaces.IDialogChoice; + +public class ChooseShapeDialog implements IDialogChoice { + private final IApplicationState APP_STATE; + + public ChooseShapeDialog(IApplicationState applicationState) { + this.APP_STATE = applicationState; + } + + @Override + public String getDialogTitle() { + return "Shape"; + } + + @Override + public String getDialogText() { + return "Select a shape from the menu below:"; + } + + @Override + public ShapeType[] getDialogOptions() { + return ShapeType.values(); + } + + @Override + public ShapeType getCurrentSelection() { + return APP_STATE.getActiveShapeType(); + } +} diff --git a/src/model/dialogs/ChooseStartAndEndPointModeDialog.java b/src/model/dialogs/ChooseStartAndEndPointModeDialog.java new file mode 100755 index 0000000..a32d6c9 --- /dev/null +++ b/src/model/dialogs/ChooseStartAndEndPointModeDialog.java @@ -0,0 +1,33 @@ +package model.dialogs; + +import model.MouseMode; +import model.interfaces.IApplicationState; +import view.interfaces.IDialogChoice; + +public class ChooseStartAndEndPointModeDialog implements IDialogChoice { + private final IApplicationState APP_STATE; + + public ChooseStartAndEndPointModeDialog(IApplicationState applicationState) { + this.APP_STATE = applicationState; + } + + @Override + public String getDialogTitle() { + return "Start and End Point Mode"; + } + + @Override + public String getDialogText() { + return "Select a shading type from the menu below:"; + } + + @Override + public MouseMode[] getDialogOptions() { + return MouseMode.values(); + } + + @Override + public MouseMode getCurrentSelection() { + return APP_STATE.getActiveMouseMode(); + } +} diff --git a/src/model/dialogs/DialogProvider.java b/src/model/dialogs/DialogProvider.java new file mode 100755 index 0000000..f044556 --- /dev/null +++ b/src/model/dialogs/DialogProvider.java @@ -0,0 +1,52 @@ +package model.dialogs; + +import model.ShapeColor; +import model.ShapeShadingType; +import model.ShapeType; +import model.MouseMode; +import model.interfaces.IApplicationState; +import model.interfaces.IDialogProvider; +import view.interfaces.IDialogChoice; + +public class DialogProvider implements IDialogProvider { + private final IDialogChoice CHOOSE_SHAPE_DIALOG; + private final IDialogChoice CHOOSE_PRIMARY_COLOR_DIALOG; + private final IDialogChoice CHOOSE_SECONDARY_DIALOG; + private final IDialogChoice CHOOSE_SHADING_TYPE_DIALOG; + private final IDialogChoice CHOOSE_START_END_PT_DIALOG; + private final IApplicationState APP_STATE; + + public DialogProvider(IApplicationState applicationState) { + this.APP_STATE = applicationState; + CHOOSE_SHAPE_DIALOG = new ChooseShapeDialog(this.APP_STATE); + CHOOSE_PRIMARY_COLOR_DIALOG = new ChoosePrimaryColorDialog(this.APP_STATE); + CHOOSE_SECONDARY_DIALOG = new ChooseSecondaryColorDialog(this.APP_STATE); + CHOOSE_SHADING_TYPE_DIALOG = new ChooseShadingTypeDialog(this.APP_STATE); + CHOOSE_START_END_PT_DIALOG = new ChooseStartAndEndPointModeDialog(this.APP_STATE); + } + + @Override + public IDialogChoice getChooseShapeDialog() { + return CHOOSE_SHAPE_DIALOG; + } + + @Override + public IDialogChoice getChoosePrimaryColorDialog() { + return CHOOSE_PRIMARY_COLOR_DIALOG; + } + + @Override + public IDialogChoice getChooseSecondaryColorDialog() { + return CHOOSE_SECONDARY_DIALOG; + } + + @Override + public IDialogChoice getChooseShadingTypeDialog() { + return CHOOSE_SHADING_TYPE_DIALOG; + } + + @Override + public IDialogChoice getChooseStartAndEndPointModeDialog() { + return CHOOSE_START_END_PT_DIALOG; + } +} diff --git a/src/model/interfaces/IApplicationState.java b/src/model/interfaces/IApplicationState.java new file mode 100755 index 0000000..cb52d6e --- /dev/null +++ b/src/model/interfaces/IApplicationState.java @@ -0,0 +1,28 @@ +package model.interfaces; + +import model.ShapeColor; +import model.ShapeShadingType; +import model.ShapeType; +import model.MouseMode; + +public interface IApplicationState { + void setActiveShape(); + + void setActivePrimaryColor(); + + void setActiveSecondaryColor(); + + void setActiveShadingType(); + + void setActiveStartAndEndPointMode(); + + ShapeType getActiveShapeType(); + + ShapeColor getActivePrimaryColor(); + + ShapeColor getActiveSecondaryColor(); + + ShapeShadingType getActiveShapeShadingType(); + + MouseMode getActiveMouseMode(); +} diff --git a/src/model/interfaces/IDialogProvider.java b/src/model/interfaces/IDialogProvider.java new file mode 100755 index 0000000..b98b7ee --- /dev/null +++ b/src/model/interfaces/IDialogProvider.java @@ -0,0 +1,20 @@ +package model.interfaces; + +import model.ShapeColor; +import model.ShapeShadingType; +import model.ShapeType; +import model.MouseMode; +import view.interfaces.IDialogChoice; + +public interface IDialogProvider { + + IDialogChoice getChooseShapeDialog(); + + IDialogChoice getChoosePrimaryColorDialog(); + + IDialogChoice getChooseSecondaryColorDialog(); + + IDialogChoice getChooseShadingTypeDialog(); + + IDialogChoice getChooseStartAndEndPointModeDialog(); +} diff --git a/src/model/interfaces/IShapeListObserver.java b/src/model/interfaces/IShapeListObserver.java new file mode 100755 index 0000000..a7f1d98 --- /dev/null +++ b/src/model/interfaces/IShapeListObserver.java @@ -0,0 +1,10 @@ +package model.interfaces; + +import java.util.List; + +import model.Shape; +import model.AbstractShape; + +public interface IShapeListObserver { + void update(List shapeList); +} diff --git a/src/model/interfaces/IUndoable.java b/src/model/interfaces/IUndoable.java new file mode 100755 index 0000000..41964d3 --- /dev/null +++ b/src/model/interfaces/IUndoable.java @@ -0,0 +1,14 @@ +package model.interfaces; + +/** + * IUndoable interface + * + * Responsibility: An interface that's part of the command pattern. + * + */ + +public interface IUndoable { + void undo(); + void redo(); + void printShapeList(); +} diff --git a/src/model/persistence/ApplicationState.java b/src/model/persistence/ApplicationState.java new file mode 100755 index 0000000..13354ff --- /dev/null +++ b/src/model/persistence/ApplicationState.java @@ -0,0 +1,86 @@ +package model.persistence; + +import model.ShapeColor; +import model.ShapeShadingType; +import model.ShapeType; +import model.MouseMode; +import model.dialogs.DialogProvider; +import model.interfaces.IApplicationState; +import model.interfaces.IDialogProvider; +import view.interfaces.IUiModule; + +public class ApplicationState implements IApplicationState { + private final IUiModule uiModule; + private final IDialogProvider dialogProvider; + + private ShapeType activeShapeType; + private ShapeColor activePrimaryColor; + private ShapeColor activeSecondaryColor; + private ShapeShadingType activeShapeShadingType; + private MouseMode activeMouseMode; + + public ApplicationState(IUiModule uiModule) { + this.uiModule = uiModule; + this.dialogProvider = new DialogProvider(this); + setDefaults(); + } + + @Override + public void setActiveShape() { + activeShapeType = uiModule.getDialogResponse(dialogProvider.getChooseShapeDialog()); + } + + @Override + public void setActivePrimaryColor() { + activePrimaryColor = uiModule.getDialogResponse(dialogProvider.getChoosePrimaryColorDialog()); + } + + @Override + public void setActiveSecondaryColor() { + activeSecondaryColor = uiModule.getDialogResponse(dialogProvider.getChooseSecondaryColorDialog()); + } + + @Override + public void setActiveShadingType() { + activeShapeShadingType = uiModule.getDialogResponse(dialogProvider.getChooseShadingTypeDialog()); + } + + @Override + public void setActiveStartAndEndPointMode() { + activeMouseMode = uiModule.getDialogResponse(dialogProvider.getChooseStartAndEndPointModeDialog()); + } + + @Override + public ShapeType getActiveShapeType() { + return activeShapeType; + } + + @Override + public ShapeColor getActivePrimaryColor() { + return activePrimaryColor; + } + + @Override + public ShapeColor getActiveSecondaryColor() { + return activeSecondaryColor; + } + + @Override + public ShapeShadingType getActiveShapeShadingType() { + return activeShapeShadingType; + } + + @Override + public MouseMode getActiveMouseMode() { + return activeMouseMode; + } + + + private void setDefaults() { + activeShapeType = ShapeType.RECTANGLE; + activePrimaryColor = ShapeColor.BLUE; + activeSecondaryColor = ShapeColor.GREEN; + activeShapeShadingType = ShapeShadingType.FILLED_IN; + activeMouseMode = MouseMode.DRAW; + } +} diff --git a/src/view/EventName.java b/src/view/EventName.java new file mode 100755 index 0000000..e72d017 --- /dev/null +++ b/src/view/EventName.java @@ -0,0 +1,41 @@ +package view; + +public enum EventName { + CHOOSE_SHAPE{ + @Override + public String toString() { + return "CHOOSE SHAPE"; + } + }, + CHOOSE_PRIMARY_COLOR { + @Override + public String toString() { + return "CHOOSE PRIMARY COLOR"; + } + }, + CHOOSE_SECONDARY_COLOR { + @Override + public String toString() { + return "CHOOSE SECONDARY COLOR"; + } + }, + CHOOSE_SHADING_TYPE { + @Override + public String toString() { + return "CHOOSE SHADING TYPE"; + } + }, + CHOOSE_MOUSE_MODE { + @Override + public String toString() { + return "CHOOSE MOUSE MODE"; + } + }, + UNDO, + REDO, + COPY, + PASTE, + DELETE, + GROUP, + UNGROUP +} diff --git a/src/view/gui/DrawEllipse.java b/src/view/gui/DrawEllipse.java new file mode 100644 index 0000000..9773f83 --- /dev/null +++ b/src/view/gui/DrawEllipse.java @@ -0,0 +1,62 @@ +package view.gui; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.Stroke; +import java.util.EnumMap; + +import model.AbstractShape; +import model.ShapeColor; +import model.ShapeShadingType; +import view.interfaces.IDrawShape; +import view.interfaces.PaintCanvasBase; + +/** + * DrawEllipse class + * + * Responsibility: Contains strategy for drawing an ellipse. + * + * @author Minh Bui + */ + +public class DrawEllipse implements IDrawShape { + + @Override + public void draw(AbstractShape shape, PaintCanvasBase canvas, EnumMap colorMap, + Stroke selectStroke, boolean offset) { + Graphics2D graphics2d = canvas.getGraphics2D(); + + graphics2d.setColor(colorMap.get(shape.getPrimaryColor())); + + Stroke customStroke = new BasicStroke(5f); + + int width = Math.abs(shape.getEndX() - shape.getStartX()); + int height = Math.abs(shape.getEndY() - shape.getStartY()); + + int x = Math.min(shape.getStartX(), shape.getEndX()); + int y = Math.min(shape.getStartY(), shape.getEndY()); + + if (offset) { + x = x - SELECT_STROKE_OFFSET; + y = y - SELECT_STROKE_OFFSET; + width = width + SELECT_WIDTH_HEIGHT_OFFSET; + height = height + SELECT_WIDTH_HEIGHT_OFFSET; + graphics2d.setStroke(selectStroke); + } else { + graphics2d.setStroke(customStroke); + } + + if (shape.getShadingType() == ShapeShadingType.FILLED_IN) { + graphics2d.fillOval(x, y, width, height); + } else if (shape.getShadingType() == ShapeShadingType.OUTLINE) { + graphics2d.drawOval(x, y, width, height); + } else { + graphics2d.fillOval(x, y, width, height); + graphics2d.setColor(colorMap.get(shape.getSecondaryColor())); + graphics2d.drawOval(x, y, width, height); + } + + } + +} diff --git a/src/view/gui/DrawRectangle.java b/src/view/gui/DrawRectangle.java new file mode 100644 index 0000000..d19f08b --- /dev/null +++ b/src/view/gui/DrawRectangle.java @@ -0,0 +1,61 @@ +package view.gui; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.Stroke; +import java.util.EnumMap; + +import model.AbstractShape; +import model.ShapeColor; +import model.ShapeShadingType; +import view.interfaces.IDrawShape; +import view.interfaces.PaintCanvasBase; + +/** + * DrawRectangle class + * + * Responsibility: Contains strategy for drawing rectangle. + * + * @author Minh Bui + */ + +public class DrawRectangle implements IDrawShape { + + @Override + public void draw(AbstractShape shape, PaintCanvasBase canvas, EnumMap colorMap, + Stroke selectStroke, boolean offset) { + Graphics2D graphics2d = canvas.getGraphics2D(); + + int width = Math.abs(shape.getEndX() - shape.getStartX()); + int height = Math.abs(shape.getEndY() - shape.getStartY()); + + int x = Math.min(shape.getStartX(), shape.getEndX()); + int y = Math.min(shape.getStartY(), shape.getEndY()); + + Stroke customStroke = new BasicStroke(5f); + + if (offset) { + x = x - SELECT_STROKE_OFFSET; + y = y - SELECT_STROKE_OFFSET; + width = width + SELECT_WIDTH_HEIGHT_OFFSET; + height = height + SELECT_WIDTH_HEIGHT_OFFSET; + graphics2d.setStroke(selectStroke); + } else { + graphics2d.setStroke(customStroke); + } + + graphics2d.setColor(colorMap.get(shape.getPrimaryColor())); + + if (shape.getShadingType() == ShapeShadingType.FILLED_IN) { + graphics2d.fillRect(x, y, width, height); + } else if (shape.getShadingType() == ShapeShadingType.OUTLINE) { + graphics2d.drawRect(x, y, width, height); + } else { + graphics2d.fillRect(x, y, width, height); + graphics2d.setColor(colorMap.get(shape.getSecondaryColor())); + graphics2d.drawRect(x, y, width, height); + } + } + +} diff --git a/src/view/gui/DrawTriangle.java b/src/view/gui/DrawTriangle.java new file mode 100644 index 0000000..e3eff2e --- /dev/null +++ b/src/view/gui/DrawTriangle.java @@ -0,0 +1,72 @@ +package view.gui; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.Polygon; +import java.awt.Stroke; +import java.util.EnumMap; + +import model.AbstractShape; +import model.Shape; +import model.ShapeColor; +import model.ShapeShadingType; +import view.interfaces.IDrawShape; +import view.interfaces.PaintCanvasBase; + +/** + * DrawTriangle class + * + * Responsibility: Contains strategy for drawing a triangle. + ** + * @author Minh Bui + */ + +public class DrawTriangle implements IDrawShape { + + @Override + public void draw(AbstractShape shape, PaintCanvasBase canvas, EnumMap colorMap, + Stroke selectStroke, boolean offset) { + Graphics2D graphics2d = canvas.getGraphics2D(); + graphics2d.setColor(colorMap.get(shape.getPrimaryColor())); + Stroke customStroke = new BasicStroke(5f); + + //int width = Math.abs(shape.getEndX() - shape.getStartX()); + int height = Math.abs(shape.getEndY() - shape.getStartY()); + + int x = Math.min(shape.getStartX(), shape.getEndX()); + int y = Math.min(shape.getStartY(), shape.getEndY()); + + int xMax = Math.max(shape.getStartX(), shape.getEndX()); + int yMax = Math.max(shape.getStartY(), shape.getEndY()); + + int[] xPoints = new int[3]; + int[] yPoints = new int[3]; + + if (!offset) { + xPoints[0] = x; xPoints[1] = xMax; xPoints[2] = x; + yPoints[0] = y; yPoints[1] = yMax; yPoints[2] = y + height; + graphics2d.setStroke(customStroke); + } else { + xPoints[0] = x - SELECT_STROKE_OFFSET; + xPoints[1] = xMax + SELECT_STROKE_OFFSET * 2; + xPoints[2] = x - SELECT_STROKE_OFFSET; + + yPoints[0] = y - SELECT_STROKE_OFFSET * 2; + yPoints[1] = yMax + SELECT_STROKE_OFFSET; + yPoints[2] = y - SELECT_STROKE_OFFSET + height + SELECT_WIDTH_HEIGHT_OFFSET; + graphics2d.setStroke(selectStroke); + } + + Polygon triangle = new Polygon(xPoints, yPoints, 3); + if (shape.getShadingType() == ShapeShadingType.FILLED_IN) { + graphics2d.fillPolygon(triangle); + } else if (shape.getShadingType() == ShapeShadingType.OUTLINE) { + graphics2d.drawPolygon(triangle); + } else { + graphics2d.fillPolygon(triangle); + graphics2d.setColor(colorMap.get(shape.getSecondaryColor())); + graphics2d.drawPolygon(triangle); + } + } +} diff --git a/src/view/gui/Drawer.java b/src/view/gui/Drawer.java new file mode 100755 index 0000000..5f6bddb --- /dev/null +++ b/src/view/gui/Drawer.java @@ -0,0 +1,129 @@ +package view.gui; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.Stroke; +import java.util.EnumMap; +import java.util.Iterator; +import java.util.List; + +import model.*; +import model.interfaces.IShapeListObserver; +import view.interfaces.IDrawShape; +import view.interfaces.PaintCanvasBase; + +/** + * Drawer class + * + * Responsibility: Responsible for selecting specific algorithm to draw shapes and their + * corresponding boundaries from a ShapeList object. + * + * @author Minh Bui + */ + +public class Drawer implements IShapeListObserver { + + private static Drawer uniqueInstance; + + private PaintCanvasBase canvas; + private final EnumMap colorMap; + private final EnumMap strategies; + private IDrawShape strategy; + + private final Stroke selectStroke; + + private Drawer(PaintCanvasBase canvas) { + this.canvas = canvas; + + colorMap = new EnumMap<>(ShapeColor.class); + colorMap.put(ShapeColor.BLACK, Color.BLACK); + colorMap.put(ShapeColor.BLUE, Color.BLUE); + colorMap.put(ShapeColor.CYAN, Color.CYAN); + colorMap.put(ShapeColor.DARK_GRAY, Color.DARK_GRAY); + colorMap.put(ShapeColor.GRAY, Color.GRAY); + colorMap.put(ShapeColor.GREEN, Color.GREEN); + colorMap.put(ShapeColor.LIGHT_GRAY, Color.LIGHT_GRAY); + colorMap.put(ShapeColor.MAGENTA, Color.MAGENTA); + colorMap.put(ShapeColor.ORANGE, Color.ORANGE); + colorMap.put(ShapeColor.PINK, Color.PINK); + colorMap.put(ShapeColor.RED, Color.RED); + colorMap.put(ShapeColor.WHITE, Color.WHITE); + colorMap.put(ShapeColor.YELLOW, Color.YELLOW); + + strategies = new EnumMap<>(ShapeType.class); + + strategies.put(ShapeType.RECTANGLE, new DrawRectangle()); + strategies.put(ShapeType.ELLIPSE, new DrawEllipse()); + strategies.put(ShapeType.TRIANGLE, new DrawTriangle()); + + strategy = null; + + selectStroke = new BasicStroke(3, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, + 1, new float[]{9}, 0); + } + + public static Drawer getInstance(PaintCanvasBase canvas) { + if (uniqueInstance == null) + uniqueInstance = new Drawer(canvas); + else + uniqueInstance.canvas = canvas; + return uniqueInstance; + } + + @Override + public void update(List shapeList) { + // Clear the canvas for redraw + Graphics2D graphics2d = canvas.getGraphics2D(); + graphics2d.setColor(canvas.getBackground()); + graphics2d.fillRect(0, 0, canvas.getWidth(), canvas.getHeight()); + + Iterator shapeListIter = shapeList.iterator(); + while (shapeListIter.hasNext()) { + AbstractShape curShape = shapeListIter.next(); + //this.draw2(curShape, curShape.isSelected()); + this.draw(curShape); + } + } + + public void draw2(AbstractShape aShape, boolean isSelected) { + if (aShape != null) { + // Draw the abstract shape first. + strategy = strategies.get(aShape.getShapeType()); + if (strategy != null) { + if (!aShape.isBoundary() && !aShape.isComposite()) + strategy.draw(aShape, canvas, colorMap, selectStroke, false); + else + if (isSelected && !aShape.isInGroup()) + strategy.draw(aShape, canvas, colorMap, selectStroke, true); + } + + // Draw its children. + List aShapeComponents = aShape.getComponents(); + if (aShapeComponents == null) return; + Iterator componentsIter = aShapeComponents.iterator(); + + while(componentsIter.hasNext()) { + AbstractShape component = componentsIter.next(); + draw2(component, isSelected); + } + } + } + + public void draw(AbstractShape aShape) { + strategy = strategies.get(aShape.getShapeType()); + if (strategy != null) { + strategy.draw(aShape, canvas, colorMap, selectStroke, false); + + if (aShape instanceof Shape) { + Shape shape = (Shape) aShape; + if (shape.isInGroup() && shape.isSelected()) { + strategy = strategies.get(ShapeType.RECTANGLE); + strategy.draw(shape.getGroupBoundary(), canvas, colorMap, selectStroke, true); + } else if (shape.isSelected()) { + strategy.draw(shape.getBoundary(), canvas, colorMap, selectStroke, true); + } + } + } + } +} diff --git a/src/view/gui/Gui.java b/src/view/gui/Gui.java new file mode 100755 index 0000000..b2dd2c1 --- /dev/null +++ b/src/view/gui/Gui.java @@ -0,0 +1,38 @@ +package view.gui; + +import javax.swing.*; + +import view.EventName; +import view.interfaces.IDialogChoice; +import view.interfaces.IEventCallback; +import view.interfaces.IGuiWindow; +import view.interfaces.IUiModule; + +public class Gui implements IUiModule { + + private final IGuiWindow GUI; + + public Gui(IGuiWindow gui) { + this.GUI = gui; + } + + @Override + public void addEvent(EventName eventName, IEventCallback callback) { + JButton button = GUI.getButton(eventName); + button.addActionListener((ActionEvent) -> callback.run()); + } + + @Override + public T getDialogResponse(IDialogChoice dialogSettings) { + Object selectedValue = JOptionPane.showInputDialog(null, + dialogSettings.getDialogText(), + dialogSettings.getDialogTitle(), + JOptionPane.PLAIN_MESSAGE, + null, + dialogSettings.getDialogOptions(), + dialogSettings.getCurrentSelection()); + return selectedValue == null + ? (T)dialogSettings.getCurrentSelection() + : (T)selectedValue; + } +} diff --git a/src/view/gui/GuiWindow.java b/src/view/gui/GuiWindow.java new file mode 100755 index 0000000..3e9df24 --- /dev/null +++ b/src/view/gui/GuiWindow.java @@ -0,0 +1,96 @@ +package view.gui; + +import java.util.HashMap; +import java.util.Map; +import java.util.NoSuchElementException; + +import javax.swing.*; +import javax.swing.border.*; + +import view.interfaces.IGuiWindow; +import view.EventName; + +import java.awt.*; + +public class GuiWindow extends JFrame implements IGuiWindow { + private final int DEFAULT_WIDTH = 1250; + private final int DEFAULT_HEIGHT = 800; + private final String DEFAULT_TITLE = "JPaint"; + private final Insets defaultButtonDimensions + = new Insets(5, 8, 5, 8); + private final Map eventButtons = new HashMap<>(); + + public GuiWindow(JComponent canvas){ + setVisible(true); + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + setTitle(DEFAULT_TITLE); + setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); + setBackground(new Color(155, 155, 155)); + setExtendedState(JFrame.MAXIMIZED_BOTH); + JPanel window = createWindow(); + window.add(canvas, BorderLayout.CENTER); + validate(); + } + + @Override + public JButton getButton(EventName eventName) { + if(!eventButtons.containsKey(eventName)) + throw new NoSuchElementException("No button exists for action " + eventName.toString()); + + return eventButtons.get(eventName); + } + + private JPanel createWindow() { + JPanel contentPane = createBackgroundPanel(); + JPanel buttonPanel = createMenu(); + contentPane.add(buttonPanel, BorderLayout.NORTH); + return contentPane; + } + + private JPanel createMenu() { + JPanel buttonPanel = createButtonPanel(); + + for(EventName eventName : EventName.values()){ + addButtonToPanel(eventName, buttonPanel); + } + + return buttonPanel; + } + + private void addButtonToPanel(EventName eventName, JPanel panel) { + JButton newButton = createButton(eventName); + eventButtons.put(eventName, newButton); + panel.add(newButton); + } + + private JButton createButton(EventName eventName) { + JButton newButton = new JButton(eventName.toString()); + newButton.setForeground(Color.BLACK); + newButton.setBackground(Color.WHITE); + newButton.setBorder(createButtonBorder()); + return newButton; + } + + private Border createButtonBorder() { + Border line = new LineBorder(Color.BLACK); + Border margin = new EmptyBorder(defaultButtonDimensions); + return new CompoundBorder(line, margin); + } + + private JPanel createButtonPanel() { + JPanel panel = new JPanel(); + FlowLayout flowLayout = (FlowLayout) panel.getLayout(); + flowLayout.setAlignment(FlowLayout.LEFT); + panel.setBackground(Color.lightGray); + return panel; + } + + private JPanel createBackgroundPanel() { + JPanel contentPane = new JPanel(); + contentPane.setBorder(new EmptyBorder(0, 0, 0, 0)); + contentPane.setLayout(new BorderLayout(0, 0)); + contentPane.setBackground(Color.WHITE); + setContentPane(contentPane); + return contentPane; + } +} diff --git a/src/view/gui/PaintCanvas.java b/src/view/gui/PaintCanvas.java new file mode 100755 index 0000000..f9a5250 --- /dev/null +++ b/src/view/gui/PaintCanvas.java @@ -0,0 +1,13 @@ +package view.gui; + +import view.interfaces.PaintCanvasBase; + +import javax.swing.JComponent; +import java.awt.*; + +public class PaintCanvas extends PaintCanvasBase { + + public Graphics2D getGraphics2D() { + return (Graphics2D)getGraphics(); + } +} diff --git a/src/view/interfaces/IDialogChoice.java b/src/view/interfaces/IDialogChoice.java new file mode 100755 index 0000000..3df6056 --- /dev/null +++ b/src/view/interfaces/IDialogChoice.java @@ -0,0 +1,11 @@ +package view.interfaces; + +public interface IDialogChoice { + String getDialogTitle(); + + String getDialogText(); + + T[] getDialogOptions(); + + T getCurrentSelection(); +} diff --git a/src/view/interfaces/IDrawShape.java b/src/view/interfaces/IDrawShape.java new file mode 100644 index 0000000..8b953c2 --- /dev/null +++ b/src/view/interfaces/IDrawShape.java @@ -0,0 +1,27 @@ +package view.interfaces; + +import java.awt.Color; +import java.awt.Stroke; +import java.util.EnumMap; + +import model.AbstractShape; +import model.ShapeColor; + +/** + * IDrawShape interface + * + * Responsibility: Interface for the strategy pattern for drawing shapes. + * + * Note that if offset is true, an algorithm for drawing boundary is used instead. + * + * @author Minh Bui + * + */ + +public interface IDrawShape { + int SELECT_STROKE_OFFSET = 5; + int SELECT_WIDTH_HEIGHT_OFFSET = 10; + + void draw(AbstractShape shape, PaintCanvasBase canvas, EnumMap colorMap, Stroke selectStroke, + boolean offset); +} diff --git a/src/view/interfaces/IEventCallback.java b/src/view/interfaces/IEventCallback.java new file mode 100755 index 0000000..8f15496 --- /dev/null +++ b/src/view/interfaces/IEventCallback.java @@ -0,0 +1,5 @@ +package view.interfaces; + +public interface IEventCallback { + void run(); +} diff --git a/src/view/interfaces/IGuiWindow.java b/src/view/interfaces/IGuiWindow.java new file mode 100755 index 0000000..a69653d --- /dev/null +++ b/src/view/interfaces/IGuiWindow.java @@ -0,0 +1,9 @@ +package view.interfaces; + +import view.EventName; + +import javax.swing.*; + +public interface IGuiWindow { + JButton getButton(EventName eventName); +} diff --git a/src/view/interfaces/IUiModule.java b/src/view/interfaces/IUiModule.java new file mode 100755 index 0000000..285371e --- /dev/null +++ b/src/view/interfaces/IUiModule.java @@ -0,0 +1,8 @@ +package view.interfaces; + +import view.EventName; + +public interface IUiModule { + void addEvent(EventName eventName, IEventCallback command); + T getDialogResponse(IDialogChoice dialogChoice); +} diff --git a/src/view/interfaces/PaintCanvasBase.java b/src/view/interfaces/PaintCanvasBase.java new file mode 100755 index 0000000..1000f3b --- /dev/null +++ b/src/view/interfaces/PaintCanvasBase.java @@ -0,0 +1,8 @@ +package view.interfaces; + +import javax.swing.*; +import java.awt.*; + +public abstract class PaintCanvasBase extends JComponent { + public abstract Graphics2D getGraphics2D(); +}