/**
* Dot.java
* Copyright (C) 2010 New Zealand Digital Library, http://expeditee.org
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
package org.expeditee.items;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import org.expeditee.core.Colour;
import org.expeditee.core.Dimension;
import org.expeditee.core.Fill;
import org.expeditee.core.Point;
import org.expeditee.core.bounds.AxisAlignedBoxBounds;
import org.expeditee.core.bounds.Bounds;
import org.expeditee.core.bounds.PolygonBounds;
import org.expeditee.gio.EcosystemManager;
import org.expeditee.gio.GraphicsManager;
import org.expeditee.gui.DisplayController;
import org.expeditee.gui.Frame;
/**
* Represents a point on the screen. All Point objects are stored as x,y pairs
* with an Item ID. Note: Lines and all combinates of lines (rectangles, etc)
* are represented only as Points that share line IDs.
*
* @author jdm18
*
*/
public class Dot extends Item {
// Standard Item variables
// private static final int _MINIMUM_DOT_SIZE = 6;
private static final int MINIMUM_DOT_SIZE = 2;
public Dot(int id) {
super();
setID(id);
}
/**
* Constructs a new Point with the given x,y coordinates and Item ID.
*
* @param x
* The x coordinate of this point
* @param y
* The y coordinate of this point
* @param id
* The Item ID of this Point
*/
public Dot(int x, int y, int id) {
super();
setID(id);
setPosition(x, y);
}
@Override
public void setColor(Colour c) {
super.setColor(c);
// update the colour of any lines
for (Line line : getLines())
line.setColor(c);
}
@Override
public void setAnchorLeft(Integer anchor) {
if (!isLineEnd()) {
super.setAnchorLeft(anchor);
return;
}
invalidateFill();
invalidateCommonTrait(ItemAppearence.PreMoved);
this._anchoring.setLeftAnchor(anchor);
int oldX = getX();
if (anchor != null) {
float deltaX = anchor - oldX;
anchorConnected(AnchorEdgeType.Left, deltaX);
}
invalidateCommonTrait(ItemAppearence.PostMoved);
invalidateFill();
}
@Override
public void setAnchorRight(Integer anchor) {
if (!isLineEnd()) {
super.setAnchorRight(anchor);
return;
}
invalidateFill();
invalidateCommonTrait(ItemAppearence.PreMoved);
this._anchoring.setRightAnchor(anchor);
int oldX = getX();
if (anchor != null) {
float deltaX = DisplayController.getFramePaintAreaWidth() - anchor - getBoundsWidth() - oldX;
anchorConnected(AnchorEdgeType.Right, deltaX);
}
invalidateCommonTrait(ItemAppearence.PostMoved);
invalidateFill();
}
@Override
public void setAnchorTop(Integer anchor) {
if (!isLineEnd()) {
super.setAnchorTop(anchor);
return;
}
invalidateFill();
invalidateCommonTrait(ItemAppearence.PreMoved);
this._anchoring.setTopAnchor(anchor);
int oldY = getY();
if (anchor != null) {
float deltaY = anchor - oldY;
anchorConnected(AnchorEdgeType.Top, deltaY);
}
invalidateCommonTrait(ItemAppearence.PostMoved);
invalidateFill();
}
@Override
public void setAnchorBottom(Integer anchor) {
if (!isLineEnd()) {
super.setAnchorBottom(anchor);
return;
}
invalidateFill();
invalidateCommonTrait(ItemAppearence.PreMoved);
this._anchoring.setBottomAnchor(anchor);
int oldY = getY();
if (anchor != null) {
float deltaY = DisplayController.getFramePaintAreaHeight() - anchor - getBoundsHeight() - oldY;
anchorConnected(AnchorEdgeType.Bottom, deltaY);
}
invalidateCommonTrait(ItemAppearence.PostMoved);
invalidateFill();
}
@Override
public void paint() {
GraphicsManager g = EcosystemManager.getGraphicsManager();
if (isHighlighted() /* && !FreeItems.getInstance().contains(this) */) {
Colour highlightColor = getHighlightColor();
Fill fill = new Fill(highlightColor);
// g.setStroke()
// Draw the highlighting rectangle surrounding the dot
// this is drawn even if its part of a rectangle
if (isVectorItem()) invalidateBounds();
AxisAlignedBoxBounds bounds = getBoundingBox();
if (_highlightMode == HighlightMode.Enclosed || this.getConnected().size() <= 1) { // Make sure single dots are highlighted filled
g.drawRectangle(bounds.getTopLeft(), bounds.getSize(), 0.0f, fill, highlightColor, DOT_STROKE, null);
} else if (_highlightMode == HighlightMode.Connected) {
g.drawRectangle(bounds.getTopLeft(), bounds.getSize(), 0.0f, null, highlightColor, HIGHLIGHT_STROKE, null);
} else if (_highlightMode == HighlightMode.Normal) {
g.drawOval(bounds.getCentre(), bounds.getSize(), 0.0f, fill, highlightColor, DOT_STROKE);
}
// System.out.println(_mode.toString());
}
// dots on lines are hidden
if (getLines().size() > 0) return;
int thick = (int) getThickness();
if (thick < MINIMUM_DOT_SIZE) thick = MINIMUM_DOT_SIZE;
Fill fill = _filled ? new Fill(getPaintColor()) : null;
// TODO: On testing, this code doesn't seem to work (can't change to any type except square). cts16
switch (_type) {
case circle:
g.drawOval(
new Point(getX(), getY()),
new Dimension(thick, thick),
0.0,
fill,
getPaintColor(),
null
);
break;
case diamond:
PolygonBounds diamond = PolygonBounds.getDiamond(thick, thick).translate(getX(), getY()).close();
g.drawPolygon(diamond, null, null, 0.0, fill, getPaintColor(), null);
break;
case triangle:
PolygonBounds triangle = PolygonBounds.getTriangle(thick, thick).translate(getX(), getY()).close();
g.drawPolygon(triangle, null, null, 0.0, fill, getPaintColor(), null);
break;
case roundSquare:
int arc = thick / 4;
g.drawRectangle(
new Point(getX(), getY()),
new Dimension(thick, thick),
0.0,
fill,
getPaintColor(),
null,
new Dimension(arc, arc)
);
break;
default:
g.drawRectangle(
new Point(getX(), getY()),
new Dimension(thick, thick),
0.0,
fill,
getPaintColor(),
null,
null
);
}
}
/**
* Updates the bounds surrounding this Dot.
* TODO: Standardise Dot minimum size. cts16
*/
public Bounds updateBounds()
{
int thick = Math.round(getThickness());
// Sets a minimum size for the dot
thick = Math.max(thick, getGravity() * 2);
// Include the gravity in the thickness
thick += 2 * getGravity();
int x = getX() - thick / 2;
int y = getY() - thick / 2;
return new AxisAlignedBoxBounds(x, y, thick, thick);
}
@Override
public Item copy() {
Dot copy = new Dot(getX(), getY(), getID());
Item.DuplicateItem(this, copy);
return copy;
}
@Override
public void setHighlightColorToDefault() {
super.setHighlightColorToDefault();
//return Item.DEFAULT_CURSOR;
}
@Override
public void setAnnotation(boolean val) {
DisplayController.setCursorPosition(this.getPosition());
Item.replaceDot(this, '@');
}
@Override
public Item merge(Item merger, int mouseX, int mouseY) {
// if the item being merged with is another Dot
if (merger instanceof Dot) {
if (merger.hasEnclosures() || hasEnclosures())
return merger;
Item dot = (Item) merger;
merger.setPosition(this.getPosition());
// prevent concurrency issues if removing lines
List lines = new ArrayList();
lines.addAll(dot.getLines());
for (Line line : lines) {
// remove lines that are in common
if (getLines().contains(line)) {
dot.removeLine(line);
removeLine(line);
} else {
// check for duplicated lines as a result of merging
Item check = (Item) line.getOppositeEnd(dot);
boolean dup = false;
for (Line l : getLines()) {
Item opposite = l.getOppositeEnd(this);
if (check == opposite) {
line.getStartItem().removeLine(line);
line.getEndItem().removeLine(line);
dup = true;
break;
}
}
if (!dup)
line.replaceLineEnd(dot, this);
}
}
setThickness(dot.getThickness());
setColor(dot.getColor());
setFillColor(dot.getFillColor());
return null;
}
if (merger instanceof Text) {
merger.setPosition(this.getPosition());
List lines = new LinkedList();
lines.addAll(getLines());
for (Line line : lines)
line.replaceLineEnd(this, merger);
this.delete();
return merger;
}
// if the item being merged with is a Line
if (merger instanceof Line) {
Line line = (Line) merger;
// if this dot is part of the line then keep it, otherwise it
// can be removed
if (line.getOppositeEnd(this) != null && getLines().contains(line))
return merger;
else
return null;
}
return merger;
}
@Override
public String getTypeAndID() {
return "P " + getID();
}
@Override
public void delete() {
super.delete();
for (Line l : this.getLines())
l.delete();
}
@Override
public void anchor() {
Frame current = getParentOrCurrentFrame();
// This is to make lines anchored across frames be on one frame
for (Line l : getLines()) {
Frame parent = l.getOppositeEnd(this).getParent();
if (parent != null && parent != current) {
this.setParent(parent);
if (DisplayController.getCurrentSide() == DisplayController.TwinFramesSide.LEFT)
this.setX(this.getX() - DisplayController.getTwinFramesSeparatorX());
else
this.setX(this.getX() + DisplayController.getTwinFramesSeparatorX());
}
break;
}
super.anchor();
// TODO is the code below needed... what for?
for (Line line : getLines()) {
if (line.getID() < 0 && !current.getItems().contains(line)) {
line.setID(current.getNextItemID());
line.setHighlightColorToDefault();
// Mike: Why was this line here?
// anchor(line);
}
}
}
@Override
public void addLine(Line line) {
super.addLine(line);
line.setColor(getColor());
line.setThickness(getThickness());
line.setLinePattern(getLinePattern());
}
@Override
public void lineColorChanged(Colour c) {
if (getColor() != c) {
setColor(c);
}
}
@Override
public boolean dontSave() {
if (getThickness() <= 1 && (getLines().size() == 0)
&& getConstraints().size() == 0) {
return true;
}
return super.dontSave();
}
@Override
public float getSize()
{
return getThickness();
}
}