/* Copyright (c) 2010, Carl Burch. License information is located in the
* com.cburch.logisim.Main source code and at www.cburch.com/logisim/. */
package com.cburch.draw.tools;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import javax.swing.Icon;
import com.cburch.draw.actions.ModelMoveHandleAction;
import com.cburch.draw.actions.ModelRemoveAction;
import com.cburch.draw.actions.ModelTranslateAction;
import com.cburch.draw.canvas.Canvas;
import com.cburch.draw.canvas.Selection;
import com.cburch.draw.model.CanvasModel;
import com.cburch.draw.model.CanvasObject;
import com.cburch.draw.model.Handle;
import com.cburch.draw.model.HandleGesture;
import com.cburch.logisim.data.Attribute;
import com.cburch.logisim.data.Bounds;
import com.cburch.logisim.data.Location;
import com.cburch.logisim.util.GraphicsUtil;
import com.cburch.logisim.util.Icons;
public class SelectTool extends AbstractTool {
private static final int IDLE = 0;
private static final int MOVE_ALL = 1;
private static final int RECT_SELECT = 2;
private static final int RECT_TOGGLE = 3;
private static final int MOVE_HANDLE = 4;
private static final int DRAG_TOLERANCE = 2;
private static final int HANDLE_SIZE = 8;
private static final Color RECT_SELECT_BACKGROUND = new Color(0, 0, 0, 32);
private int curAction;
private List<CanvasObject> beforePressSelection;
private Handle beforePressHandle;
private Location dragStart;
private Location dragEnd;
private boolean dragEffective;
private int lastMouseX;
private int lastMouseY;
private HandleGesture curGesture;
public SelectTool() {
curAction = IDLE;
dragStart = Location.create(0, 0);
dragEnd = dragStart;
dragEffective = false;
}
@Override
public Icon getIcon() {
return Icons.getIcon("select.svg");
}
@Override
public Cursor getCursor(Canvas canvas) {
return Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR);
}
@Override
public List<Attribute<?>> getAttributes() {
return Collections.emptyList();
}
@Override
public void toolSelected(Canvas canvas) {
curAction = IDLE;
canvas.getSelection().clearSelected();
repaintArea(canvas);
}
@Override
public void toolDeselected(Canvas canvas) {
curAction = IDLE;
canvas.getSelection().clearSelected();
repaintArea(canvas);
}
private int getHandleSize(Canvas canvas) {
double zoom = canvas.getZoomFactor();
return (int) Math.ceil(HANDLE_SIZE / Math.sqrt(zoom));
}
@Override
public void mousePressed(Canvas canvas, MouseEvent e) {
beforePressSelection = new ArrayList<CanvasObject>(canvas.getSelection().getSelected());
beforePressHandle = canvas.getSelection().getSelectedHandle();
int mx = e.getX();
int my = e.getY();
boolean shift = (e.getModifiersEx() & InputEvent.SHIFT_DOWN_MASK) != 0;
dragStart = Location.create(mx, my);
dragEffective = false;
dragEnd = dragStart;
lastMouseX = mx;
lastMouseY = my;
Selection selection = canvas.getSelection();
selection.setHandleSelected(null);
// see whether user is pressing within an existing handle
int halfSize = getHandleSize(canvas) / 2;
CanvasObject clicked = null;
for (CanvasObject shape : selection.getSelected()) {
List<Handle> handles = shape.getHandles(null);
for (Handle han : handles) {
int dx = han.getX() - mx;
int dy = han.getY() - my;
if (dx >= -halfSize && dx <= halfSize
&& dy >= -halfSize && dy <= halfSize) {
if (shape.canMoveHandle(han)) {
curAction = MOVE_HANDLE;
curGesture = new HandleGesture(han, 0, 0,
e.getModifiersEx());
repaintArea(canvas);
return;
} else if (clicked == null) {
clicked = shape;
}
}
}
}
// see whether the user is clicking within a shape
if (clicked == null) {
clicked = getObjectAt(canvas.getModel(), e.getX(), e.getY(), false);
}
if (clicked != null) {
if (shift && selection.isSelected(clicked)) {
selection.setSelected(clicked, false);
curAction = IDLE;
} else {
if (!shift && !selection.isSelected(clicked)) {
selection.clearSelected();
}
selection.setSelected(clicked, true);
selection.setMovingShapes(selection.getSelected(), 0, 0);
curAction = MOVE_ALL;
}
repaintArea(canvas);
return;
}
clicked = getObjectAt(canvas.getModel(), e.getX(), e.getY(), true);
if (clicked != null && selection.isSelected(clicked)) {
if (shift) {
selection.setSelected(clicked, false);
curAction = IDLE;
} else {
selection.setMovingShapes(selection.getSelected(), 0, 0);
curAction = MOVE_ALL;
}
repaintArea(canvas);
return;
}
if (shift) {
curAction = RECT_TOGGLE;
} else {
selection.clearSelected();
curAction = RECT_SELECT;
}
repaintArea(canvas);
}
@Override
public void cancelMousePress(Canvas canvas) {
List<CanvasObject> before = beforePressSelection;
Handle handle = beforePressHandle;
beforePressSelection = null;
beforePressHandle = null;
if (before != null) {
curAction = IDLE;
Selection sel = canvas.getSelection();
sel.clearDrawsSuppressed();
sel.setMovingShapes(Collections.<CanvasObject>emptySet(), 0, 0);
sel.clearSelected();
sel.setSelected(before, true);
sel.setHandleSelected(handle);
repaintArea(canvas);
}
}
@Override
public void mouseDragged(Canvas canvas, MouseEvent e) {
setMouse(canvas, e.getX(), e.getY(), e.getModifiersEx());
}
@Override
public void mouseReleased(Canvas canvas, MouseEvent e) {
beforePressSelection = null;
beforePressHandle = null;
setMouse(canvas, e.getX(), e.getY(), e.getModifiersEx());
CanvasModel model = canvas.getModel();
Selection selection = canvas.getSelection();
Set<CanvasObject> selected = selection.getSelected();
int action = curAction;
curAction = IDLE;
if (!dragEffective) {
Location loc = dragEnd;
CanvasObject o = getObjectAt(model, loc.getX(), loc.getY(), false);
if (o != null) {
Handle han = o.canDeleteHandle(loc);
if (han != null) {
selection.setHandleSelected(han);
} else {
han = o.canInsertHandle(loc);
if (han != null) {
selection.setHandleSelected(han);
}
}
}
}
Location start = dragStart;
int x1 = e.getX();
int y1 = e.getY();
switch (action) {
case MOVE_ALL:
Location moveDelta = selection.getMovingDelta();
if (dragEffective && !moveDelta.equals(Location.create(0, 0))) {
canvas.doAction(new ModelTranslateAction(model, selected,
moveDelta.getX(), moveDelta.getY()));
}
break;
case MOVE_HANDLE:
HandleGesture gesture = curGesture;
curGesture = null;
if (dragEffective && gesture != null) {
ModelMoveHandleAction act;
act = new ModelMoveHandleAction(model, gesture);
canvas.doAction(act);
Handle result = act.getNewHandle();
if (result != null) {
Handle h = result.getObject().canDeleteHandle(result.getLocation());
selection.setHandleSelected(h);
}
}
break;
case RECT_SELECT:
if (dragEffective) {
Bounds bds = Bounds.create(start).add(x1, y1);
selection.setSelected(canvas.getModel().getObjectsIn(bds), true);
} else {
CanvasObject clicked;
clicked = getObjectAt(model, start.getX(), start.getY(), true);
if (clicked != null) {
selection.clearSelected();
selection.setSelected(clicked, true);
}
}
break;
case RECT_TOGGLE:
if (dragEffective) {
Bounds bds = Bounds.create(start).add(x1, y1);
selection.toggleSelected(canvas.getModel().getObjectsIn(bds));
} else {
CanvasObject clicked;
clicked = getObjectAt(model, start.getX(), start.getY(), true);
selection.setSelected(clicked, !selected.contains(clicked));
}
break;
default:
break;
}
selection.clearDrawsSuppressed();
repaintArea(canvas);
}
@Override
public void keyPressed(Canvas canvas, KeyEvent e) {
int code = e.getKeyCode();
if ((code == KeyEvent.VK_SHIFT || code == KeyEvent.VK_CONTROL
|| code == KeyEvent.VK_ALT) && curAction != IDLE) {
setMouse(canvas, lastMouseX, lastMouseY, e.getModifiersEx());
}
}
@Override
public void keyReleased(Canvas canvas, KeyEvent e) {
keyPressed(canvas, e);
}
@Override
public void keyTyped(Canvas canvas, KeyEvent e) {
char ch = e.getKeyChar();
Selection selected = canvas.getSelection();
if ((ch == '\u0008' || ch == '\u007F') && !selected.isEmpty()) {
ArrayList<CanvasObject> toRemove = new ArrayList<CanvasObject>();
for (CanvasObject shape : selected.getSelected()) {
if (shape.canRemove()) {
toRemove.add(shape);
}
}
if (!toRemove.isEmpty()) {
e.consume();
CanvasModel model = canvas.getModel();
canvas.doAction(new ModelRemoveAction(model, toRemove));
selected.clearSelected();
repaintArea(canvas);
}
} else if (ch == '\u001b' && !selected.isEmpty()) {
selected.clearSelected();
repaintArea(canvas);
}
}
private void setMouse(Canvas canvas, int mx, int my, int mods) {
lastMouseX = mx;
lastMouseY = my;
boolean shift = (mods & InputEvent.SHIFT_DOWN_MASK) != 0;
boolean ctrl = (mods & InputEvent.CTRL_DOWN_MASK) != 0;
Location newEnd = Location.create(mx, my);
dragEnd = newEnd;
Location start = dragStart;
int dx = newEnd.getX() - start.getX();
int dy = newEnd.getY() - start.getY();
if (!dragEffective) {
if (Math.abs(dx) + Math.abs(dy) > DRAG_TOLERANCE) {
dragEffective = true;
} else {
return;
}
}
switch (curAction) {
case MOVE_HANDLE:
HandleGesture gesture = curGesture;
if (ctrl) {
Handle h = gesture.getHandle();
dx = canvas.snapX(h.getX() + dx) - h.getX();
dy = canvas.snapY(h.getY() + dy) - h.getY();
}
curGesture = new HandleGesture(gesture.getHandle(), dx, dy, mods);
canvas.getSelection().setHandleGesture(curGesture);
break;
case MOVE_ALL:
if (ctrl) {
int minX = Integer.MAX_VALUE;
int minY = Integer.MAX_VALUE;
for (CanvasObject o : canvas.getSelection().getSelected()) {
for (Handle handle : o.getHandles(null)) {
int x = handle.getX();
int y = handle.getY();
if (x < minX) minX = x;
if (y < minY) minY = y;
}
}
dx = canvas.snapX(minX + dx) - minX;
dy = canvas.snapY(minY + dy) - minY;
}
if (shift) {
if (Math.abs(dx) > Math.abs(dy)) {
dy = 0;
} else {
dx = 0;
}
}
canvas.getSelection().setMovingDelta(dx, dy);
break;
}
repaintArea(canvas);
}
private void repaintArea(Canvas canvas) {
canvas.repaint();
}
@Override
public void draw(Canvas canvas, Graphics g) {
Selection selection = canvas.getSelection();
int action = curAction;
Location start = dragStart;
Location end = dragEnd;
HandleGesture gesture = null;
boolean drawHandles;
switch (action) {
case MOVE_ALL:
drawHandles = !dragEffective;
break;
case MOVE_HANDLE:
drawHandles = !dragEffective;
if (dragEffective) gesture = curGesture;
break;
default:
drawHandles = true;
}
CanvasObject moveHandleObj = null;
if (gesture != null) moveHandleObj = gesture.getHandle().getObject();
if (drawHandles) {
// unscale the coordinate system so that the stroke width isn't scaled
double zoom = 1.0;
Graphics gCopy = g.create();
if (gCopy instanceof Graphics2D) {
zoom = canvas.getZoomFactor();
if (zoom != 1.0) {
((Graphics2D) gCopy).scale(1.0 / zoom, 1.0 / zoom);
}
}
GraphicsUtil.switchToWidth(gCopy, 1);
int size = (int) Math.ceil(HANDLE_SIZE * Math.sqrt(zoom));
int offs = size / 2;
for (CanvasObject obj : selection.getSelected()) {
List<Handle> handles;
if (action == MOVE_HANDLE && obj == moveHandleObj) {
handles = obj.getHandles(gesture);
} else {
handles = obj.getHandles(null);
}
for (Handle han : handles) {
int x = han.getX();
int y = han.getY();
if (action == MOVE_ALL && dragEffective) {
Location delta = selection.getMovingDelta();
x += delta.getX();
y += delta.getY();
}
x = (int) Math.round(zoom * x);
y = (int) Math.round(zoom * y);
gCopy.clearRect(x - offs, y - offs, size, size);
gCopy.drawRect(x - offs, y - offs, size, size);
}
}
Handle selHandle = selection.getSelectedHandle();
if (selHandle != null) {
int x = selHandle.getX();
int y = selHandle.getY();
if (action == MOVE_ALL && dragEffective) {
Location delta = selection.getMovingDelta();
x += delta.getX();
y += delta.getY();
}
x = (int) Math.round(zoom * x);
y = (int) Math.round(zoom * y);
int[] xs = { x - offs, x, x + offs, x };
int[] ys = { y, y - offs, y, y + offs };
gCopy.setColor(Color.WHITE);
gCopy.fillPolygon(xs, ys, 4);
gCopy.setColor(Color.BLACK);
gCopy.drawPolygon(xs, ys, 4);
}
}
switch (action) {
case RECT_SELECT:
case RECT_TOGGLE:
if (dragEffective) {
// find rectangle currently to show
int x0 = start.getX();
int y0 = start.getY();
int x1 = end.getX();
int y1 = end.getY();
if (x1 < x0) {
int t = x0;
x0 = x1;
x1 = t;
}
if (y1 < y0) {
int t = y0;
y0 = y1;
y1 = t;
}
// make the region that's not being selected darker
int w = canvas.getWidth();
int h = canvas.getHeight();
g.setColor(RECT_SELECT_BACKGROUND);
g.fillRect(0, 0, w, y0);
g.fillRect(0, y0, x0, y1 - y0);
g.fillRect(x1, y0, w - x1, y1 - y0);
g.fillRect(0, y1, w, h - y1);
// now draw the rectangle
g.setColor(Color.GRAY);
g.drawRect(x0, y0, x1 - x0, y1 - y0);
}
break;
default:
break;
}
}
private static CanvasObject getObjectAt(CanvasModel model, int x, int y,
boolean assumeFilled) {
Location loc = Location.create(x, y);
for (CanvasObject o : model.getObjectsFromTop()) {
if (o.contains(loc, assumeFilled)) {
return o;
}
}
return null;
}
}