package ui.view.canvas;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Point;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TimerTask;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.Timer;
import classes.AbstractCanvasObject;
import classes.Edge;
import classes.GroupNode;
import classes.HolonElement;
import classes.HolonObject;
import classes.Node;
import ui.controller.Control;
import ui.model.Model;
import utility.Vector2Int;
/**
* Collection of methods and values needed in both MyCanvas
and
* UpperNodeCanvas
*
* Although Java works on references we chose to add explicit return values for
* clearer code understanding in most cases
*
* @author: I. Dix
*/
public abstract class AbstractCanvas extends JPanel {
/**
* Version
*/
private static final long serialVersionUID = 1L;
final JMenuItem itemCut = new JMenuItem("Cut");
final JMenuItem itemCopy = new JMenuItem("Copy");
public final JMenuItem itemPaste = new JMenuItem("Paste");
final JMenuItem itemDelete = new JMenuItem("Delete");
final JMenuItem itemGroup = new JMenuItem("Group");
final JMenuItem itemUngroup = new JMenuItem("Ungroup");
final JMenuItem itemAlign = new JMenuItem("Align selected");
final JMenuItem itemCreateTemplate = new JMenuItem("Create Template");
final int ANIMTIME = 500; // animation Time
private final int animFPS = 60;
final int animDelay = 1000 / animFPS; // animation Delay
protected Model model;
protected Control controller;
protected int x = 0;
protected int y = 0;
// Selection
public AbstractCanvasObject tempCps = null;
// Replacement
/**
* the CpsObject that might be replaced by drag&drop
*/
public AbstractCanvasObject mayBeReplaced = null;
// PopUpMenu
JPopupMenu popmenu = new JPopupMenu();
// Tooltip
boolean toolTip; // Tooltip on or off
Vector2Int toolTipPos = new Vector2Int(); // Tooltip Position
String toolTipText = "";
List dataSelected = new ArrayList<>();
protected Set tempSelected = new HashSet<>();
boolean showConnectionInformation;
boolean dragging = false; // for dragging
boolean dragged = false; // if an object/objects was/were dragged
boolean drawEdge = false; // for drawing edges
boolean doMark = false; // for double click
public Edge edgeHighlight = null;
Point mousePosition = new Point(); // Mouse Position when
ArrayList savePos;
// edge Object Start Point
int cx, cy;
int sx, sy; // Mark Coords
Vector2Int unPos;
// Animation
Timer animT; // animation Timer
int animDuration = ANIMTIME; // animation Duration
int animSteps = animDuration / animDelay; // animation Steps;
ArrayList animCps = null;
// Graphics
Image img = null; // Contains the image to draw on the Canvas
Graphics2D g2; // For Painting
float scalediv20;
// Mouse
private boolean click = false;
// ------------------------------------------ METHODS
// ------------------------------------------
class ACpsHandle {
public AbstractCanvasObject object;
ACpsHandle(AbstractCanvasObject object) {
this.object = object;
}
public String toString() {
return object.toString();
}
}
void drawMarker() {
if (sx > x && sy > y) {
g2.drawRect(x, y, sx - x, sy - y);
} else if (sx < x && sy < y) {
g2.drawRect(sx, sy, x - sx, y - sy);
} else if (sx >= x) {
g2.drawRect(x, sy, sx - x, y - sy);
} else if (sy >= y) {
g2.drawRect(sx, y, x - sx, sy - y);
}
}
/**
* @deprecated
* @param g
*/
void showTooltip(Graphics g) {
if (toolTip) {
g2.setColor(new Color(255, 225, 150));
g2.setStroke(new BasicStroke(1));
int textWidth = g.getFontMetrics().stringWidth(toolTipText) + 2; // Text
// width
// fixed x and y Position to the screen
int fixXPos = toolTipPos.getX() - (textWidth >> 1) + model.getScaleDiv2();
int fixYPos = toolTipPos.getY();
if (fixXPos < 0) {
fixXPos = 0;
} else if (fixXPos + textWidth + 1 > this.getWidth()) {
fixXPos -= (fixXPos + textWidth + 1) - this.getWidth();
}
if (fixYPos + 16 > this.getHeight()) {
fixYPos -= (fixYPos + 16) - this.getHeight();
}
g2.fillRect(fixXPos, fixYPos, textWidth, 15);
g2.setColor(Color.BLACK);
g2.drawRect(fixXPos, fixYPos, textWidth, 15);
g2.drawString(toolTipText, fixXPos + 2, fixYPos + 12);
}
}
void setRightClickMenu(MouseEvent e) {
if (e.getButton() == MouseEvent.BUTTON3) {
itemPaste.setEnabled(true);
if (tempCps != null) {
itemPaste.setEnabled(true);
itemDelete.setEnabled(true);
itemCut.setEnabled(true);
itemCopy.setEnabled(true);
itemAlign.setEnabled(true);
// tracking
if (tempCps != null) {
itemGroup.setEnabled(true);
}
// ungrouping
if (tempCps instanceof GroupNode)
itemUngroup.setEnabled(true);
else
itemUngroup.setEnabled(false);
if (model.getSelectedObjects().size() == 0) {
controller.addSelectedObject(tempCps);
}
if (tempCps instanceof HolonObject) {
itemCreateTemplate.setEnabled(true);
} else {
itemCreateTemplate.setEnabled(false);
}
} else {
itemAlign.setEnabled(false);
itemCut.setEnabled(false);
itemCopy.setEnabled(false);
itemGroup.setEnabled(false);
itemUngroup.setEnabled(false);
itemCreateTemplate.setEnabled(false);
if (edgeHighlight != null) {
itemDelete.setEnabled(true);
itemPaste.setEnabled(false);
} else {
itemDelete.setEnabled(false);
itemPaste.setEnabled(true);
}
}
mousePosition = this.getMousePosition();
popmenu.show(e.getComponent(), e.getX(), e.getY());
}
}
void markObjects() {
if (doMark) {
doMark = false;
if (!tempSelected.isEmpty()) {
controller.toggleSelectedObjects(tempSelected);
controller.getObjectsInDepth();
tempSelected.clear();
}
}
}
int[] determineMousePositionOnEdge(Edge p) {
int lx, ly, hx, hy;
if (p.getA().getPosition().getX() > p.getB().getPosition().getX()) {
hx = p.getA().getPosition().getX() + model.getScaleDiv2() + 7;
lx = p.getB().getPosition().getX() + model.getScaleDiv2() - 7;
} else {
lx = p.getA().getPosition().getX() + model.getScaleDiv2() - 7;
hx = p.getB().getPosition().getX() + model.getScaleDiv2() + 7;
}
if (p.getA().getPosition().getY() > p.getB().getPosition().getY()) {
hy = p.getA().getPosition().getY() + model.getScaleDiv2() + 7;
ly = p.getB().getPosition().getY() + model.getScaleDiv2() - 7;
} else {
ly = p.getA().getPosition().getY() + model.getScaleDiv2() - 7;
hy = p.getB().getPosition().getY() + model.getScaleDiv2() + 7;
}
return new int[] { lx, ly, hx, hy };
}
/**
* Checks if a double click was made.
*
* @return true if doublecklick, false if not
*/
boolean doubleClick() {
if (click) {
click = false;
return true;
} else {
click = true;
java.util.Timer t = new java.util.Timer("doubleclickTimer", false);
t.schedule(new TimerTask() {
@Override
public void run() {
click = false;
}
}, 500);
}
return false;
}
boolean setToolTipInfoAndPosition(boolean on, AbstractCanvasObject cps) {
if (x - controller.getScale() <= cx && y - controller.getScale() <= cy && x >= cx && y >= cy) {
on = true;
toolTipPos.setX(cps.getPosition().getX() - controller.getScaleDiv2());
toolTipPos.setY(cps.getPosition().getY() + controller.getScaleDiv2());
toolTipText = cps.getName() + ", " + cps.getId();
}
return on;
}
abstract void drawDeleteEdge();
/**
* Checks if {@code draggedCps} or a new cpsObject at Position (x,y) could
* replace exactly one object in {@code objects}. Saves the object that would be
* replaced in {@link AbstractCanvas}.{@code MayBeReplaced}
*
* @param objects list of objects that could be replaced
* @param draggedCps Object that might replace
* @param x Position of the objects that might replace
* @param y Position of the objects that might replace
* @return true if exactly one Object could be replaced
*/
protected boolean checkForReplacement(ArrayList objects, AbstractCanvasObject draggedCps,
int x, int y) {
/** distance treshold for replacement */
int treshhold = controller.getScale() / 2;
/** number of Objects that might be replaced (should be 1) */
int replaceCounter = 0;
/** last object that could be replaced */
AbstractCanvasObject toBeReplaced = null;
/** Position of object that might be replaced */
Vector2Int p;
/** for each cps on Canvas */
if (draggedCps == null || !(draggedCps instanceof Node) && !(draggedCps instanceof Node)) {
for (AbstractCanvasObject cps : objects) {
/** same object -> ignore */
if (cps == draggedCps)
continue;
/** set Position of object that might be replaced */
p = cps.getPosition();
/** if near enough */
if (Math.abs(x - p.getX()) < treshhold && Math.abs(y - p.getY()) < treshhold) {
replaceCounter++;
toBeReplaced = cps;
/**
* if too many Objects could be replaced: stop searching, because it would not
* be clear which one should be replaced
*/
if (replaceCounter > 1)
break;
}
}
}
/**
* return true if exactly one obect would be replaced
*/
if (replaceCounter == 1 && toBeReplaced != null) {
mayBeReplaced = toBeReplaced;
return true;
} else {
mayBeReplaced = null;
return false;
}
}
/**
* Checks if an inserted new Object could replace exactly one object on the
* canvas. Saves the object that would be replaced in
* {@link AbstractCanvas}.{@code MayBeReplaced}
*
* @param x Position of the objects that might replace
* @param y Position of the objects that might replace
* @return true if exactly one Object could be replaced
*/
public abstract boolean checkForReplacement(int x, int y);
/**
* highlights the object that mayBeReplaced
*
* @param g2
*/
protected void highlightMayBeReplaced(Graphics2D g2) {
if (mayBeReplaced != null) {
g2.setColor(Color.RED);
g2.fillRect((int) (mayBeReplaced.getPosition().getX() - controller.getScaleDiv2() - (scalediv20 + 3)),
(int) (mayBeReplaced.getPosition().getY() - controller.getScaleDiv2() - (scalediv20 + 3)),
(int) (controller.getScale() + ((scalediv20 + 3) * 2)),
(int) (controller.getScale() + ((scalediv20 + 3) * 2)));
}
}
/**
* Align alle Objects on the Canvas to a Grid with objects every 10 pixels
*/
public abstract void tryToAlignObjects();
/**
* Aligns the Object the a grid
*
* @param cps Object that should be aligned
* @param distance distance between the AlignmentGrid Lines. (objects every
* 'distance' pixels
*/
protected void align(AbstractCanvasObject cps, int distance) {
/** Position of the AbstractCpsObject which should be aligned */
Vector2Int p = cps.getPosition();
// calculate how many pixels the cps should be decreased to align
/** x offset relative to a grid with lines every distance pixels */
int x_off = cps.getPosition().getX() % distance;
/** y offset relative to a grid with lines every distance pixels */
int y_off = cps.getPosition().getY() % distance;
// align to the other Line, if it is nearer
if (x_off > distance / 2)
x_off -= distance;
if (y_off > distance / 2)
y_off -= distance;
/** set new Position */
cps.setPosition(p.getX() - x_off, p.getY() - y_off);
}
/**
* Closes a tab of the UpperNode with ID upperNodeID
*
* @param upperNodeId
*/
public abstract void closeUpperNodeTab(int upperNodeId);
}