Left: Implement composite structures

main
Minh Bui 4 years ago
commit ce1a245ca6

@ -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<IUndoable> UNDO_STACK = new Stack<>();
private static final Stack<IUndoable> REDO_STACK = new Stack<>();
private static List<AbstractShape> 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<AbstractShape> copiedShapes = shapesListObj.getSelectedShapes();
if (copiedShapes.size() == 0) return;
Iterator<AbstractShape> 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<AbstractShape> copyAbstractShapes(List<AbstractShape> shapes) {
List<AbstractShape> componentsClone = new ArrayList<>();
Iterator<AbstractShape> 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();
}
}

@ -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<AbstractShape> 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();
}
}

@ -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();
}
}

@ -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<AbstractShape> 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();
}
}

@ -0,0 +1,5 @@
package controller;
public interface IJPaintController {
void setup();
}

@ -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));
}
}

@ -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));
}
}
}

@ -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<AbstractShape> 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();
}
}

@ -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<AbstractShape> pasteShapes;
public PasteCommand(ShapeList shapeList, List<AbstractShape> 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();
}
}

@ -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<AbstractShape> 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();
}
}

@ -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();
}
}

@ -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<AbstractShape> getComponents();
public abstract boolean isComposite();
}

@ -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<AbstractShape> 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<AbstractShape> 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<AbstractShape> 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<AbstractShape> getShapes() { return this.shapes; }
@Override
public List<AbstractShape> getComponents() {
List<AbstractShape> components = new ArrayList<>();
components.addAll(this.shapes);
if (!this.isInGroup())
components.add(groupBoundary);
return components;
}
}

@ -0,0 +1,7 @@
package model;
public enum MouseMode {
DRAW,
SELECT,
MOVE
}

@ -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<AbstractShape> getComponents() {
List<AbstractShape> components = new ArrayList<>();
if (!this.isInGroup())
components.add(this.boundary);
return components;
}
}

@ -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<AbstractShape> getComponents() {
return null;
}
}

@ -0,0 +1,17 @@
package model;
public enum ShapeColor {
BLACK,
BLUE,
CYAN,
DARK_GRAY,
GRAY,
GREEN,
LIGHT_GRAY,
MAGENTA,
ORANGE,
PINK,
RED,
WHITE,
YELLOW
}

@ -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<AbstractShape> SHAPE_LIST;
private final List<IShapeListObserver> 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<IShapeListObserver> 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<AbstractShape> shapes) {
this.SHAPE_LIST.addAll(shapes);
this.notifyObservers();
}
public List<AbstractShape> cloneAndAddAllShapes(List<AbstractShape> shapes) {
List<AbstractShape> clones = new ArrayList<>();
Iterator<AbstractShape> 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<AbstractShape> cloneAndAddComponents(List<AbstractShape> shapes) {
List<AbstractShape> componentsClone = new ArrayList<>();
Iterator<AbstractShape> 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<AbstractShape> shapeIter = this.SHAPE_LIST.iterator();
while (shapeIter.hasNext()) {
AbstractShape curShape = shapeIter.next();
if (curShape.equals(shape))
shapeIter.remove();
}
this.notifyObservers();
}
public List<AbstractShape> removeSelectedShape() {
Iterator<AbstractShape> shapeIter = this.SHAPE_LIST.iterator();
List<AbstractShape> 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<AbstractShape> 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<AbstractShape> curComponents = curShape.getComponents();
if (curComponents != null) {
this.printComponents(curComponents);
}
}
System.out.println();
}
private void printComponents(List<AbstractShape> components) {
if (components != null) {
System.out.print('\t');
Iterator<AbstractShape> 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<AbstractShape> 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<AbstractShape> moveShapes(int dX, int dY, List<AbstractShape> shapesToMove) {
List<AbstractShape> movedShapes = new ArrayList<>();
Iterator<AbstractShape> 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<AbstractShape> shapeIter = this.SHAPE_LIST.iterator();
while(shapeIter.hasNext()) {
AbstractShape curShape = shapeIter.next();
curShape.setIsSelected(false);
}
this.notifyObservers();
}
public void removeShapesList(List<AbstractShape> shapes) {
this.SHAPE_LIST.removeAll(shapes);
this.notifyObservers();
}
public List<AbstractShape> getSelectedShapes() {
List<AbstractShape> selectedShapes = new ArrayList<>();
Iterator<AbstractShape> shapeIter = this.SHAPE_LIST.iterator();
while (shapeIter.hasNext()) {
AbstractShape curShape = shapeIter.next();
if (curShape.isSelected())
selectedShapes.add(curShape);
}
return selectedShapes;
}
public List<AbstractShape> groupShapes2(List<AbstractShape> shapes) {
Iterator<AbstractShape> 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<AbstractShape> grouped = new ArrayList<>();
grouped.add(newGroup);
//this.printShapeList();
return grouped;
}
public List<AbstractShape> ungroupShapes2(List<AbstractShape> shapes) {
Iterator<AbstractShape> shapeIter = shapes.iterator();
// List of Shapes to be added back to the ShapeList and returned by this method.
List<AbstractShape> 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<AbstractShape> ushapes = group.getShapes();
Iterator<AbstractShape> 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<AbstractShape> groupSelectedShapes() {
List<AbstractShape> selectedShape = this.getSelectedShapes();
if (selectedShape.isEmpty()) return selectedShape;
Iterator<AbstractShape> 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<AbstractShape> groupShapes(List<AbstractShape> shapes) {
Iterator<AbstractShape> 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<AbstractShape> ungroupSelectedShapes() {
Iterator<AbstractShape> shapeIter = this.SHAPE_LIST.iterator();
List<AbstractShape> 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<AbstractShape> ungroupShapes(List<AbstractShape> shapes) {
Iterator<AbstractShape> 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<AbstractShape> shapeIter = this.SHAPE_LIST.iterator();
while (shapeIter.hasNext()) {
if (shapeIter.next().isSelected())
count++;
}
return count;
}
public void notifyObservers() {
Iterator<IShapeListObserver> 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)); }
}

@ -0,0 +1,7 @@
package model;
public enum ShapeShadingType {
FILLED_IN,
OUTLINE,
OUTLINE_AND_FILLED_IN
}

@ -0,0 +1,7 @@
package model;
public enum ShapeType {
ELLIPSE,
RECTANGLE,
TRIANGLE
}

@ -0,0 +1,34 @@
package model.dialogs;
import model.ShapeColor;
import model.interfaces.IApplicationState;
import view.interfaces.IDialogChoice;
public class ChoosePrimaryColorDialog implements IDialogChoice<ShapeColor> {
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();
}
}

@ -0,0 +1,34 @@
package model.dialogs;
import model.ShapeColor;
import model.interfaces.IApplicationState;
import view.interfaces.IDialogChoice;
public class ChooseSecondaryColorDialog implements IDialogChoice<ShapeColor> {
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();
}
}

@ -0,0 +1,33 @@
package model.dialogs;
import model.ShapeShadingType;
import model.interfaces.IApplicationState;
import view.interfaces.IDialogChoice;
public class ChooseShadingTypeDialog implements IDialogChoice<ShapeShadingType> {
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();
}
}

@ -0,0 +1,33 @@
package model.dialogs;
import model.ShapeType;
import model.interfaces.IApplicationState;
import view.interfaces.IDialogChoice;
public class ChooseShapeDialog implements IDialogChoice<ShapeType> {
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();
}
}

@ -0,0 +1,33 @@
package model.dialogs;
import model.MouseMode;
import model.interfaces.IApplicationState;
import view.interfaces.IDialogChoice;
public class ChooseStartAndEndPointModeDialog implements IDialogChoice<MouseMode> {
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();
}
}

@ -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<ShapeType> CHOOSE_SHAPE_DIALOG;
private final IDialogChoice<ShapeColor> CHOOSE_PRIMARY_COLOR_DIALOG;
private final IDialogChoice<ShapeColor> CHOOSE_SECONDARY_DIALOG;
private final IDialogChoice<ShapeShadingType> CHOOSE_SHADING_TYPE_DIALOG;
private final IDialogChoice<MouseMode> 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<ShapeType> getChooseShapeDialog() {
return CHOOSE_SHAPE_DIALOG;
}
@Override
public IDialogChoice<ShapeColor> getChoosePrimaryColorDialog() {
return CHOOSE_PRIMARY_COLOR_DIALOG;
}
@Override
public IDialogChoice<ShapeColor> getChooseSecondaryColorDialog() {
return CHOOSE_SECONDARY_DIALOG;
}
@Override
public IDialogChoice<ShapeShadingType> getChooseShadingTypeDialog() {
return CHOOSE_SHADING_TYPE_DIALOG;
}
@Override
public IDialogChoice<MouseMode> getChooseStartAndEndPointModeDialog() {
return CHOOSE_START_END_PT_DIALOG;
}
}

@ -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();
}

@ -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<ShapeType> getChooseShapeDialog();
IDialogChoice<ShapeColor> getChoosePrimaryColorDialog();
IDialogChoice<ShapeColor> getChooseSecondaryColorDialog();
IDialogChoice<ShapeShadingType> getChooseShadingTypeDialog();
IDialogChoice<MouseMode> getChooseStartAndEndPointModeDialog();
}

@ -0,0 +1,10 @@
package model.interfaces;
import java.util.List;
import model.Shape;
import model.AbstractShape;
public interface IShapeListObserver {
void update(List<AbstractShape> shapeList);
}

@ -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();
}

@ -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;
}
}

@ -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
}

@ -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<ShapeColor, Color> 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);
}
}
}

@ -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<ShapeColor, Color> 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);
}
}
}

@ -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<ShapeColor, Color> 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);
}
}
}

@ -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<ShapeColor, Color> colorMap;
private final EnumMap<ShapeType, IDrawShape> 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<AbstractShape> shapeList) {
// Clear the canvas for redraw
Graphics2D graphics2d = canvas.getGraphics2D();
graphics2d.setColor(canvas.getBackground());
graphics2d.fillRect(0, 0, canvas.getWidth(), canvas.getHeight());
Iterator<AbstractShape> 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<AbstractShape> aShapeComponents = aShape.getComponents();
if (aShapeComponents == null) return;
Iterator<AbstractShape> 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);
}
}
}
}
}

@ -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> 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;
}
}

@ -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<EventName, JButton> 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;
}
}

@ -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();
}
}

@ -0,0 +1,11 @@
package view.interfaces;
public interface IDialogChoice<T> {
String getDialogTitle();
String getDialogText();
T[] getDialogOptions();
T getCurrentSelection();
}

@ -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<ShapeColor, Color> colorMap, Stroke selectStroke,
boolean offset);
}

@ -0,0 +1,5 @@
package view.interfaces;
public interface IEventCallback {
void run();
}

@ -0,0 +1,9 @@
package view.interfaces;
import view.EventName;
import javax.swing.*;
public interface IGuiWindow {
JButton getButton(EventName eventName);
}

@ -0,0 +1,8 @@
package view.interfaces;
import view.EventName;
public interface IUiModule {
void addEvent(EventName eventName, IEventCallback command);
<T> T getDialogResponse(IDialogChoice dialogChoice);
}

@ -0,0 +1,8 @@
package view.interfaces;
import javax.swing.*;
import java.awt.*;
public abstract class PaintCanvasBase extends JComponent {
public abstract Graphics2D getGraphics2D();
}
Loading…
Cancel
Save