package com.kartoflane.superluminal2.mvc.controllers;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Composite;
import com.kartoflane.superluminal2.components.EventHandler;
import com.kartoflane.superluminal2.components.enums.Orientations;
import com.kartoflane.superluminal2.components.interfaces.Boundable;
import com.kartoflane.superluminal2.components.interfaces.Collidable;
import com.kartoflane.superluminal2.components.interfaces.Deletable;
import com.kartoflane.superluminal2.components.interfaces.Disposable;
import com.kartoflane.superluminal2.components.interfaces.Followable;
import com.kartoflane.superluminal2.components.interfaces.Follower;
import com.kartoflane.superluminal2.components.interfaces.MouseInputListener;
import com.kartoflane.superluminal2.components.interfaces.Pinnable;
import com.kartoflane.superluminal2.components.interfaces.Predicate;
import com.kartoflane.superluminal2.components.interfaces.Resizable;
import com.kartoflane.superluminal2.components.interfaces.Selectable;
import com.kartoflane.superluminal2.core.Grid;
import com.kartoflane.superluminal2.core.Grid.Snapmodes;
import com.kartoflane.superluminal2.core.LayeredPainter;
import com.kartoflane.superluminal2.core.LayeredPainter.Layers;
import com.kartoflane.superluminal2.core.Manager;
import com.kartoflane.superluminal2.events.SLDeleteEvent;
import com.kartoflane.superluminal2.events.SLDeselectionEvent;
import com.kartoflane.superluminal2.events.SLDisposeEvent;
import com.kartoflane.superluminal2.events.SLEvent;
import com.kartoflane.superluminal2.events.SLListener;
import com.kartoflane.superluminal2.events.SLModShiftEvent;
import com.kartoflane.superluminal2.events.SLMoveEvent;
import com.kartoflane.superluminal2.events.SLResizeEvent;
import com.kartoflane.superluminal2.events.SLSelectionEvent;
import com.kartoflane.superluminal2.events.SLVisibilityEvent;
import com.kartoflane.superluminal2.mvc.Controller;
import com.kartoflane.superluminal2.mvc.Model;
import com.kartoflane.superluminal2.mvc.View;
import com.kartoflane.superluminal2.mvc.controllers.props.PropController;
import com.kartoflane.superluminal2.mvc.models.BaseModel;
import com.kartoflane.superluminal2.mvc.views.BaseView;
import com.kartoflane.superluminal2.tools.Tool.Tools;
import com.kartoflane.superluminal2.ui.EditorWindow;
import com.kartoflane.superluminal2.ui.sidebar.data.DataComposite;
import com.kartoflane.superluminal2.undo.UndoableMoveEdit;
import com.kartoflane.superluminal2.undo.ValueUndoableEdit;
import com.kartoflane.superluminal2.utils.Utils;
public abstract class AbstractController implements Controller, Selectable, Disposable, Deletable, Resizable, Pinnable,
MouseInputListener, Collidable, Boundable, Follower, Followable, SLListener {
protected Followable parent = null;
protected Point followOffset = null;
protected HashSet<Follower> followers = null;
protected EventHandler eventHandler = null;
protected HashSet<PropController> props = null;
protected BaseModel model = null;
protected BaseView view = null;
protected int presentedFactor = 1;
protected boolean selectable = true;
protected boolean selected = false;
protected boolean moving = false;
protected boolean containsMouse = false;
protected boolean followableActive = true;
protected boolean deleted = false;
protected boolean monoDirectionalDrag = false;
protected Orientations lockDragTo = null;
protected Snapmodes snapmode = Snapmodes.FREE;
protected Point clickOffset = new Point(0, 0);
protected ValueUndoableEdit<?> currentEdit = null;
@Override
public void setModel(Model model) {
if (model == null)
throw new IllegalArgumentException("Argument must not be null.");
this.model = (BaseModel) model;
}
protected BaseModel getModel() {
return model;
}
@Override
public void setView(View view) {
if (view == null)
throw new IllegalArgumentException("Argument must not be null.");
if (this.view != null && this.view != view)
this.view.dispose();
this.view = (BaseView) view;
view.setController(this);
view.setModel(model);
}
protected BaseView getView() {
return view;
}
public void updateView() {
view.updateView();
}
public void addToPainter(Layers layer) {
view.addToPainter(layer);
}
public void addToPainterBottom(Layers layer) {
view.addToPainterBottom(layer);
}
public void removeFromPainter() {
view.removeFromPainter();
}
public void removeFromPainter(Layers layer) {
view.removeFromPainter(layer);
}
public boolean setLocation(int x, int y) {
// if the new location falls outside of the area the object is
// bounded to, change the parameters to be as close the border as possible
if (isBounded() && !isWithinBoundingArea(x, y)) {
Point p = limitToBoundingArea(x, y);
x = p.x;
y = p.y;
}
if (isCollidable()) {
Rectangle b = new Rectangle(x - getW() / 2, y - getH() / 2, getW(), getH());
if (collidesAs(b, this))
return false;
}
model.setLocation(x, y);
if (isFollowActive() && followers != null) { // followers set is lazily instantiated when it's needed
for (Follower fol : followers)
fol.updateFollower();
}
if (eventHandler != null && eventHandler.hooks(SLEvent.MOVE))
eventHandler.sendEvent(new SLMoveEvent(this, getLocation()));
return true;
}
public boolean setLocation(Point p) {
return setLocation(p.x, p.y);
}
@Override
public Point getLocation() {
return model.getLocation();
}
public boolean setLocationCorner(int x, int y) {
Rectangle b = model.getBounds();
x -= b.x;
y -= b.y;
return translate(x, y);
}
public Point getLocationCorner() {
Rectangle b = model.getBounds();
return new Point(b.x, b.y);
}
@Override
public boolean translate(int dx, int dy) {
// if the new location falls outside of the area the object is
// bounded to, change the parameters to be as close the border as possible
if (isBounded() && !isWithinBoundingArea(getX() + dx, getY() + dy)) {
Point p = limitToBoundingArea(getX() + dx, getY() + dy);
dx = p.x - getX();
dy = p.y - getY();
}
if (isCollidable()) {
Rectangle b = new Rectangle(getX() - getW() / 2, getY() - getH() / 2, getW(), getH());
b.x += dx;
b.y += dy;
if (collidesAs(b, this))
return false;
}
model.translate(dx, dy);
if (isFollowActive() && followers != null) { // followers set is lazily instantiated when it's needed
for (Follower fol : followers)
fol.updateFollower();
}
if (eventHandler != null && eventHandler.hooks(SLEvent.MOVE))
eventHandler.sendEvent(new SLMoveEvent(this, getLocation()));
return true;
}
@Override
public int getX() {
return model.getX();
}
@Override
public int getY() {
return model.getY();
}
@Override
public boolean setSize(int w, int h) {
model.setSize(w, h);
if (eventHandler != null && eventHandler.hooks(SLEvent.RESIZE))
eventHandler.sendEvent(new SLResizeEvent(this, getSize()));
return true;
}
public boolean setSize(Point p) {
return setSize(p.x, p.y);
}
@Override
public Point getSize() {
return model.getSize();
}
@Override
public int getW() {
return model.getW();
}
@Override
public int getH() {
return model.getH();
}
public Rectangle getBounds() {
return Utils.rotate(model.getBounds(), getRotation());
}
/**
* @param rad
* angle in degrees, 0 = north.
*/
public void setRotation(float angle) {
view.setRotation(angle);
}
/**
* @param rad
* angle in degrees
*/
public void rotate(float angle) {
view.setRotation(view.getRotation() + angle);
}
/**
* @return rotation in degrees, 0 = north.
*/
public float getRotation() {
return view.getRotation();
}
/**
* @return rectangle that is exactly represents the area of the controller.
*/
public Rectangle getDimensions() {
return new Rectangle(getX() - getW() / 2, getY() - getH() / 2, getW(), getH());
}
/**
* Sets the location of the controller to the specified coordinates, redrawing
* immediately if the controller is visible.
*/
public void reposition(int x, int y) {
if (isVisible()) {
setVisible(false);
setLocation(x, y);
updateView();
setVisible(true);
} else {
setLocation(x, y);
}
}
/**
* Sets the location of the controller to the specified location, redrawing
* immediately if the controller is visible.
*/
public void reposition(Point p) {
reposition(p.x, p.y);
}
/**
* Sets the size of the controller to the specified values, redrawing
* immediately if the controller is visible.
*/
public void resize(int w, int h) {
if (isVisible()) {
setVisible(false);
setSize(w, h);
updateView();
setVisible(true);
} else {
setSize(w, h);
}
}
/**
* Sets the size of the controller to the specified values, redrawing
* immediately if the controller is visible.
*/
public void resize(Point p) {
resize(p.x, p.y);
}
public Point getPresentedLocation() {
return getLocation();
}
public Point getPresentedSize() {
return getSize();
}
public void setPresentedLocation(int x, int y) {
reposition(x * presentedFactor, y * presentedFactor);
}
public void setPresentedLocation(Point p) {
setPresentedLocation(p.x, p.y);
}
public void setPresentedSize(int w, int h) {
resize(w, h);
updateBoundingArea();
}
public void setPresentedSize(Point p) {
setPresentedSize(p.x, p.y);
}
/**
* Presented factor is the number by which the controller's dimensions and location are multiplied
* before they are displayed in the sidebar.
*/
public void setPresentedFactor(int i) {
presentedFactor = i;
}
/**
* Presented factor is the number by which the controller's dimensions and location are multiplied
* before they are displayed in the sidebar.
*
* @return the presented factor of the room
*/
public int getPresentedFactor() {
return presentedFactor;
}
/**
* Redraws the controller's area.
*/
public void redraw() {
EditorWindow.getInstance().canvasRedraw(getBounds());
}
/**
* Convenience method to redraw the rectangle.
*/
public static void redraw(Rectangle r) {
EditorWindow.getInstance().canvasRedraw(r);
}
/**
* Forwards the PaintEvent to the View.
*/
public void redraw(PaintEvent e) {
view.redraw(e);
}
/**
* Allows to (un)pin the controller. Pinned controllers cannot be moved by dragging.
*/
@Override
public void setPinned(boolean pin) {
model.setPinned(pin);
setMoving(false);
updateView();
redraw();
}
/**
* @return true if the controller is pinned and cannot be moved by dragging, false otherwise.
*/
@Override
public boolean isPinned() {
return model.isPinned();
}
/**
* Allows to make the controller (un)selectable.
*/
@Override
public void setSelectable(boolean sel) {
selectable = sel;
}
/**
* @return true if the controller can be selected by clicking on it, false otherwie.
*/
@Override
public boolean isSelectable() {
return selectable;
}
/**
* Sets the controller as selected. <br>
* Override this to execute additional actions when this controller is selected.<br>
* This method <b>should not</b> be called directly. Use {@link Manager#setSelected(ObjectController)} instead.
*/
@Override
public void select() {
selected = selectable;
updateView();
redraw();
if (eventHandler != null && eventHandler.hooks(SLEvent.SELECT))
eventHandler.sendEvent(new SLSelectionEvent(this));
}
/**
* Sets the controller as deselected. <br>
* Override this to execute additional actions when this controller is deselected.<br>
* This method <b>should not</b> be called directly. Use {@link Manager#setSelected(ObjectController)} instead.
*/
@Override
public void deselect() {
selected = false;
setMoving(false);
updateView();
redraw();
if (eventHandler != null && eventHandler.hooks(SLEvent.DESELECT))
eventHandler.sendEvent(new SLDeselectionEvent(this));
}
@Override
public boolean isSelected() {
return selected;
}
/**
* Checks whether the Followable is fit for the role of a parent.<br>
* Throws appropriate exception when it is not.
*/
protected final void checkParentConditions(Followable followable) {
if (followable != null && !(followable instanceof Controller)) // instanceof returns false for null, but we allow null argument
throw new IllegalArgumentException("Followable passed in argument is not a Controller.");
if (followable == this)
throw new IllegalArgumentException("Cannot be parent to itself.");
if (followable instanceof Follower && ((Follower) followable).getParent() == this)
throw new IllegalArgumentException("Cannot follow an object that is following the receiver.");
}
@Override
public void setParent(Followable followable) {
checkParentConditions(followable);
if (parent == followable)
return;
// if it passes, change the parent
if (parent != null)
parent.removeFollower(this);
if (followable != null)
followable.addFollower(this);
parent = followable;
}
@Override
public Followable getParent() {
return parent;
}
@Override
public Point getFollowOffset() {
if (followOffset == null)
followOffset = new Point(0, 0);
return Utils.copy(followOffset);
}
public int getFollowOffsetX() {
if (followOffset == null)
return 0;
return followOffset.x;
}
public int getFollowOffsetY() {
if (followOffset == null)
return 0;
return followOffset.y;
}
@Override
public void setFollowOffset(int x, int y) {
if (followOffset == null)
followOffset = new Point(x, y);
else {
followOffset.x = x;
followOffset.y = y;
}
}
public void setFollowOffset(Point p) {
setFollowOffset(p.x, p.y);
}
@Override
public Set<Follower> getFollowers() {
if (followers == null)
followers = new HashSet<Follower>();
return Collections.unmodifiableSet(followers);
}
@Override
public boolean addFollower(Follower fol) {
if (followers == null)
followers = new HashSet<Follower>();
return followers.add(fol);
}
@Override
public boolean removeFollower(Follower fol) {
if (followers == null)
followers = new HashSet<Follower>();
return followers.remove(fol);
}
@Override
public int getFollowerCount() {
if (followers == null)
followers = new HashSet<Follower>();
return followers.size();
}
@Override
public void setFollowActive(boolean active) {
this.followableActive = active;
}
@Override
public boolean isFollowActive() {
return followableActive;
}
@Override
public void updateFollower() {
updateBoundingArea();
Followable parent = getParent();
if (parent == null)
throw new IllegalArgumentException("Parent is null.");
Point offset = getFollowOffset();
reposition(parent.getX() + offset.x, parent.getY() + offset.y);
updateBoundingArea();
}
@Override
public void setCollidable(boolean collidable) {
model.setCollidable(collidable);
}
@Override
public boolean isCollidable() {
return model.isCollidable();
}
public void setMoving(boolean mov) {
moving = mov && isSelected() && !isPinned();
}
public boolean isMoving() {
return moving;
}
public void setVisible(boolean vis) {
view.setVisible(vis);
redraw();
if (eventHandler != null)
eventHandler.sendEvent(new SLVisibilityEvent(this, vis));
}
public boolean isVisible() {
return view.isVisible();
}
/**
* Sets the snapping method used to align the controller to the grid.
*
* @see {@link Grid#snapToGrid(int, int, int)}
* @see {@link Snapmodes}
*/
public void setSnapMode(Snapmodes snapmode) {
this.snapmode = snapmode;
}
/** @return the snapping method used to align the controller to the grid. */
public Snapmodes getSnapMode() {
return snapmode;
}
/**
* Sets the highlighted state of the controller.
*/
public void setHighlighted(boolean high) {
view.setHighlighted(high);
updateView();
redraw();
}
public boolean isHighlighted() {
return view.isHighlighted();
}
public DataComposite getDataComposite(Composite parent) {
if (selectable)
throw new IllegalStateException("This Controller is selectable, but doesn't define a DataComposite");
else
return null;
}
@Override
public void mouseDown(MouseEvent e) {
if (Manager.getSelectedToolId() == Tools.POINTER) {
boolean contains = contains(e.x, e.y);
if (e.button == 1) {
clickOffset.x = e.x - getX();
clickOffset.y = e.y - getY();
if (contains && !selected)
Manager.setSelected(this);
setMoving(contains);
currentEdit = new UndoableMoveEdit(this);
undoInit();
}
}
}
@Override
public void mouseUp(MouseEvent e) {
if (Manager.getSelectedToolId() == Tools.POINTER) {
if (e.button == 1) {
lockDragTo = null;
if (!contains(e.x, e.y) && !contains(getX() + clickOffset.x, getY() + clickOffset.y) && selected)
Manager.setSelected(null);
setMoving(false);
undoFinalize();
EditorWindow.getInstance().updateSidebarContent();
}
}
}
@Override
public void mouseMove(MouseEvent e) {
if (Manager.getSelectedToolId() == Tools.POINTER) {
if (selected && moving) {
// Decide direction for mono-directional dragging
if (monoDirectionalDrag && lockDragTo == null) {
int d = Utils.distance(getX() + clickOffset.x, getY() + clickOffset.y, e.x, e.y);
// Have the user drag more than 2px away from click point to determine the direction
// Not noticeable to the user, but increases the chance of correct detection
if (d > 2) {
Point click = new Point(getX() + clickOffset.x, getY() + clickOffset.y);
Point cursor = new Point(e.x, e.y);
double angle = (Utils.angle(click, cursor) + 90) % 360;
if ((angle >= 315 || angle < 45) || (angle >= 135 && angle < 225)) {
lockDragTo = Orientations.HORIZONTAL;
} else {
lockDragTo = Orientations.VERTICAL;
}
}
} else {
Point p = Grid.getInstance().snapToGrid(e.x - clickOffset.x, e.y - clickOffset.y, snapmode);
if (lockDragTo == Orientations.HORIZONTAL) {
p.y = getY();
} else if (lockDragTo == Orientations.VERTICAL) {
p.x = getX();
}
if (p.x != getX() || p.y != getY()) {
reposition(p.x, p.y);
updateFollowOffset();
}
}
}
}
}
@Override
public void mouseEnter(MouseEvent e) {
if (!containsMouse) {
setHighlighted(true);
}
containsMouse = true;
}
@Override
public void mouseExit(MouseEvent e) {
if (containsMouse) {
setHighlighted(false);
}
containsMouse = false;
}
@Override
public void mouseDoubleClick(MouseEvent e) {
}
@Override
public void mouseHover(MouseEvent e) {
}
public boolean collides(Rectangle rect) {
return collides(rect.x, rect.y, rect.width, rect.height);
}
@Override
public boolean collides(int x, int y, int w, int h) {
Rectangle rect = new Rectangle(x + getTolerance(), y + getTolerance(),
w - 2 * getTolerance(), h - 2 * getTolerance());
return rect.intersects(new Rectangle(getX() - getW() / 2, getY() - getH() / 2, getW(), getH()));
}
@Override
public boolean isBounded() {
return model.isBounded();
}
@Override
public void setBounded(boolean bound) {
model.setBounded(bound);
}
public boolean isWithinBoundingArea(int x, int y) {
return model.isWithinBoundingArea(x, y);
}
public Point limitToBoundingArea(int x, int y) {
return model.limitToBoundingArea(x, y);
}
public Rectangle getBoundingArea() {
return model.getBoundingArea();
}
public int getBoundingAreaX() {
return model.getBoundingAreaX();
}
public int getBoundingAreaY() {
return model.getBoundingAreaY();
}
public int getBoundingAreaW() {
return model.getBoundingAreaW();
}
public int getBoundingAreaH() {
return model.getBoundingAreaH();
}
/**
* Sets the area to which the controller is bounded.
*
* @param x
* x coordinate of the top-left corner of the bounding area
* @param y
* y coordinate of the top-left corner of the bounding area
* @param w
* width of the bounding area
* @param h
* height of the bounding area
*/
public void setBoundingArea(int x, int y, int w, int h) {
model.setBoundingArea(x, y, w, h);
}
/** Sets the area to which the controller is bounded. */
public void setBoundingArea(Rectangle r) {
setBoundingArea(r.x, r.y, r.width, r.height);
}
/**
* Sets the area to which the controller is bounded.
*
* @param x1
* x coordinate of the top-left corner of the bounding area
* @param y1
* y coordinate of the top-left corner of the bounding area
* @param x2
* x coordinate of the bottom-right corner of the bounding area
* @param y2
* y coordinate of the bottom-right corner of the bounding area
*/
public void setBoundingPoints(int x1, int y1, int x2, int y2) {
setBoundingArea(x1, y1, x2 - x1, y2 - y1);
}
/**
* Sets the area to which the controller is bounded.
*
* @param start
* the top-left corner of the bounding area
* @param end
* the bottom-right corner of the bounding area
*/
public void setBoundingPoints(Point start, Point end) {
setBoundingArea(start.x, start.y, end.x - start.x, end.y - start.y);
}
/** @return an array of two points describing the area to which the controller is bounded. */
public Point[] getBoundingPoints() {
Rectangle b = model.getBoundingArea();
return new Point[] {
new Point(b.x, b.y), new Point(b.x + b.width, b.y + b.height)
};
}
/** Updates the area that the controller is bounded to. */
public void updateBoundingArea() {
}
public void dispose() {
if (isDisposed())
return;
if (eventHandler != null)
eventHandler.sendEvent(new SLDisposeEvent(this));
// unregister this follower
setParent(null);
model.dispose();
view.dispose();
for (PropController prop : getProps()) {
prop.dispose();
}
if (eventHandler != null)
eventHandler.dispose();
}
@Override
public boolean contains(int x, int y) {
return getBounds().contains(x, y); // Some controllers modify their bounds, so use that instead of the model's
}
@Override
public boolean intersects(Rectangle rect) {
return getBounds().intersects(rect); // Some controllers modify their bounds, so use that instead of the model's
}
/**
* Flags the controller's location as modifiable, which means that the controller's position
* can be changed by nudging or via the sidebar coordinate boxes.
*/
public void setLocModifiable(boolean b) {
model.setLocModifiable(b);
}
/**
* @see #setLocModifiable(boolean)
*/
public boolean isLocModifiable() {
return model.isLocModifiable();
}
@Override
public void delete() {
deleted = true;
view.removeFromPainter();
setVisible(false);
if (eventHandler != null && eventHandler.hooks(SLEvent.DELETE))
eventHandler.sendEvent(new SLDeleteEvent(this));
for (PropController prop : getProps()) {
prop.removeFromPainter();
prop.redraw();
prop.delete();
prop.dispose();
}
if (props != null)
props.clear();
}
@Override
public void restore() {
monoDirectionalDrag = false;
deleted = false;
setView(view);
setVisible(true);
createProps();
}
public boolean isDeleted() {
return deleted;
}
public boolean isDisposed() {
return model.isDisposed();
}
@Override
public void setDeletable(boolean deletable) {
model.setDeletable(deletable);
}
@Override
public boolean isDeletable() {
return model.isDeletable();
}
@Override
public void setTolerance(int px) {
model.setTolerance(px);
}
@Override
public int getTolerance() {
return model.getTolerance();
}
public void addListener(int eventType, SLListener listener) {
if (eventHandler == null)
eventHandler = new EventHandler();
eventHandler.hook(eventType, listener);
}
public void removeListener(int eventType, SLListener listener) {
if (eventHandler == null)
return;
eventHandler.unhook(eventType, listener);
}
public void removeListener(SLListener listener) {
if (eventHandler == null)
return;
eventHandler.unhook(listener);
}
public void handleEvent(SLEvent e) {
if (e instanceof SLMoveEvent) {
Point p = (Point) e.data;
notifyLocationChanged(p.x, p.y);
} else if (e instanceof SLResizeEvent) {
Point p = (Point) e.data;
notifySizeChanged(p.x, p.y);
} else if (e instanceof SLDisposeEvent) {
if (eventHandler != null && e.data != null && e.data instanceof SLListener) {
eventHandler.unhook((SLListener) e.data);
}
} else if (e instanceof SLVisibilityEvent) {
if (e.source == getParent())
setVisible((Boolean) e.data);
} else if (e instanceof SLModShiftEvent) {
monoDirectionalDrag = (Boolean) e.data;
if (!monoDirectionalDrag)
lockDragTo = null;
}
}
/**
* By default, this method gets called when the controller receives a {@link SLEvent#MOVE} event
*/
public void notifyLocationChanged(int x, int y) {
}
/**
* By default, this method gets called when the controller receives a {@link SLEvent#RESIZE} event
*/
public void notifySizeChanged(int w, int h) {
resize(w, h);
}
public void addProp(PropController prop) {
if (prop == null)
throw new IllegalArgumentException("Argument must not be null.");
if (props == null)
props = new HashSet<PropController>();
if (getProp(prop.getIdentifier()) != null)
throw new IllegalArgumentException(String.format("This object already owns a prop named '%s'", prop.getIdentifier()));
props.add(prop);
addListener(SLEvent.VISIBLE, prop);
}
public void removeProp(PropController prop) {
if (prop == null)
throw new IllegalArgumentException("Argument must not be null.");
if (props == null)
props = new HashSet<PropController>();
props.remove(prop);
removeListener(SLEvent.VISIBLE, prop);
}
public PropController[] getProps() {
if (props == null)
return new PropController[0];
return props.toArray(new PropController[0]);
}
/**
* @param id
* the identifier of the sought prop
* @return prop with the given identifier, or null if not found
*/
public PropController getProp(String id) {
if (id == null)
throw new IllegalArgumentException("Argument must not be null.");
if (props == null) {
return null;
} else {
PropController result = null;
Iterator<PropController> it = props.iterator();
while (it.hasNext() && result == null) {
PropController p = it.next();
if (p.getIdentifier().equals(id))
result = p;
}
return result;
}
}
public static boolean collidesAs(Rectangle rect, AbstractController col) {
CollisionPredicate predicate = new CollisionPredicate(rect, col);
Layers layer = ((AbstractController) col).view.getLayerId();
return LayeredPainter.getInstance().getControllerMatching(predicate, layer) != null;
}
public static class CollisionPredicate implements Predicate<AbstractController> {
private final Rectangle b;
private final Controller controller;
public CollisionPredicate(Rectangle b, AbstractController controller) {
this.b = b;
this.controller = controller;
}
@Override
public boolean accept(AbstractController control) {
return control.isVisible() && control.isCollidable() &&
control != controller && control.collides(b);
}
}
public void updateFollowOffset() {
if (getParent() != null) {
Point p = getLocation();
setFollowOffset(p.x - getParent().getX(), p.y - getParent().getY());
}
}
protected void undoInit() {
if (currentEdit instanceof UndoableMoveEdit) {
UndoableMoveEdit move = (UndoableMoveEdit) currentEdit;
move.setOld(getLocation());
}
}
protected void undoFinalize() {
if (currentEdit == null)
return;
if (currentEdit instanceof UndoableMoveEdit) {
UndoableMoveEdit move = (UndoableMoveEdit) currentEdit;
move.setCurrent(getLocation());
}
if (!currentEdit.isValuesEqual())
Manager.getCurrentShip().postEdit(currentEdit);
currentEdit = null;
}
protected void createProps() {
}
}