/*
* funCKit - functional Circuit Kit
* Copyright (C) 2013 Lukas Elsner <open@mindrunner.de>
* Copyright (C) 2013 Peter Dahlberg <catdog2@tuxzone.org>
* Copyright (C) 2013 Julian Stier <mail@julian-stier.de>
* Copyright (C) 2013 Sebastian Vetter <mail@b4sti.eu>
* Copyright (C) 2013 Thomas Poxrucker <poxrucker_t@web.de>
* Copyright (C) 2013 Alexander Treml <alex.treml@directbox.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
package de.sep2011.funckit.controller;
import com.google.common.collect.Sets;
import de.sep2011.funckit.model.graphmodel.*;
import de.sep2011.funckit.model.graphmodel.implementations.BrickWireDistinguishDispatcher;
import de.sep2011.funckit.model.graphmodel.implementations.CircuitImpl;
import de.sep2011.funckit.model.graphmodel.implementations.commands.MoveBunchOfElementsCommand;
import de.sep2011.funckit.model.sessionmodel.EditPanelModel;
import de.sep2011.funckit.model.sessionmodel.EditPanelModel.ToolMode;
import de.sep2011.funckit.model.sessionmodel.Settings;
import de.sep2011.funckit.util.Pair;
import de.sep2011.funckit.util.command.Command;
import de.sep2011.funckit.validator.Check;
import de.sep2011.funckit.validator.MultipleCollisionCheck;
import de.sep2011.funckit.validator.Result;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;
import static javax.swing.SwingUtilities.isLeftMouseButton;
/**
* Tool to be used in Select mode (select and move bricks, ...).
*/
public class SelectTool extends AbstractTool {
private Point moveStartPointInModel = null;
private int currentMoveDifferenceX = 0;
private int currentMoveDifferenceY = 0;
private MouseEvent pressedMouseEvent; // stores event when mouse was pressed
/**
* Create a new SelectTool.
*
* @param c
* the associated {@link Controller}, should not be null
*/
public SelectTool(Controller c) {
this.controller = c;
}
@Override
public SelectTool getNewInstance(Controller c) {
return new SelectTool(c);
}
@Override
public void mouseReleased(MouseEvent e, EditPanelModel editPanelModel) {
super.mouseReleased(e, editPanelModel);
switch (editPanelModel.getToolMode()) {
case SELECT_RECT_MODE:
Point start = editPanelModel.getSelectionStart();
Point end = editPanelModel.getSelectionEnd();
// calculate selection rectangle
int x = start.x < end.x ? start.x : end.x;
int y = start.y < end.y ? start.y : end.y;
int width = Math.abs(start.x - end.x);
int height = Math.abs(start.y - end.y);
Rectangle selectionArea = new Rectangle(x, y, width, height);
Set<Element> selectedElements = editPanelModel.getCircuit().getIntersectingElements(
selectionArea);
// add new selected elements to the previous selected ones if
// control was pressed
if (e.isControlDown()) {
selectedElements.addAll(editPanelModel.getSelectedElements());
}
editPanelModel.setSelectedElements(selectedElements);
// reset selection rectangle & tool mode
editPanelModel.setSelectionStart(null);
editPanelModel.setSelectionEnd(null);
editPanelModel.setToolMode(ToolMode.DEFAULT_MODE);
editPanelModel.setCursor(getToolDefaultCursor());
break;
case MOVE_ELEMENT_MODE:
if (canDropElementsHere(editPanelModel)) {
Command command = new MoveBunchOfElementsCommand(editPanelModel.getCircuit(),
editPanelModel.getSelectedElements(), currentMoveDifferenceX,
currentMoveDifferenceY);
controller.getSessionModel().getCurrentGraphCommandDispatcher().dispatch(command);
}
// reset everything
moveStartPointInModel = null;
clearGhosts(editPanelModel);
editPanelModel.setToolMode(ToolMode.DEFAULT_MODE);
editPanelModel.setCursor(getToolDefaultCursor());
break;
default:
break;
}
}
private static boolean canDropElementsHere(EditPanelModel epm) {
Set<Element> ghosts = epm.getGhosts();
Set<Element> circuitElements = epm.getCircuit().getElements();
Set<Element> selectedElements = epm.getSelectedElements();
Set<Element> unselectedElements = Sets.difference(circuitElements, selectedElements);
Check collisionCheck = new MultipleCollisionCheck(ghosts);
Circuit tmpCircuit = new CircuitImpl();
tmpCircuit.getElements().addAll(unselectedElements);
Result result = collisionCheck.perform(tmpCircuit);
return result.isPassed();
}
@Override
public void mousePressed(MouseEvent e, EditPanelModel editPanelModel) {
super.mousePressed(e, editPanelModel);
/* click detection */
pressedMouseEvent = e;
}
@Override
public void mouseDragged(MouseEvent e, EditPanelModel editPanelModel) {
super.mouseDragged(e, editPanelModel);
if (pressedMouseEvent == null) {
pressedMouseEvent = e;
}
Point pressedPointOnPanel = pressedMouseEvent.getPoint(); // point when
// mouse was pressed
Point movedToPointInModel = calculateInversePoint(e.getPoint(),
editPanelModel.getTransformation());
// point we clicked on in the model when mouse was pressed
Point pressedPointInModel = calculateInversePoint(pressedPointOnPanel,
editPanelModel.getTransformation());
Circuit c = editPanelModel.getCircuit();
Brick brick = c.getBrickAtPosition(pressedPointInModel); // brick we
// clicked on
Set<Element> selected = editPanelModel.getSelectedElements();
switch (editPanelModel.getToolMode()) {
case DEFAULT_MODE:
if (brick == null) { // no brick when mouse pressed, accept
// selection
editPanelModel.setSelectionStart(pressedPointInModel);
editPanelModel.setSelectionEnd(pressedPointInModel);
editPanelModel.setToolMode(ToolMode.SELECT_RECT_MODE);
editPanelModel.setCursor(new Cursor(Cursor.CROSSHAIR_CURSOR));
} else if (selected.contains(brick)) {
/*
* brick is already selected, start dragging selected Elements
*/
if (!brick.isFixedHint()) { // only drag non fixed
for (Iterator<Element> it = selected.iterator(); it.hasNext();) {
Element elem = it.next();
if(elem instanceof Brick && ((Brick) elem).isFixedHint()) {
it.remove();
}
}
moveStartPointInModel = pressedPointInModel;
editPanelModel.setToolMode(ToolMode.MOVE_ELEMENT_MODE);
editPanelModel.setSelectedBrick(brick);
editPanelModel.setSelectedElements(selected);
editPanelModel.setCursor(new Cursor(Cursor.MOVE_CURSOR));
}
} else {
/*
* brick is not selected, select only that one and start
* dragging it
*/
selected.clear();
selected.add(brick);
editPanelModel.setSelectedElements(selected);
if (!brick.isFixedHint()) { // only drag non fixed
moveStartPointInModel = pressedPointInModel;
editPanelModel.setToolMode(ToolMode.MOVE_ELEMENT_MODE);
editPanelModel.setSelectedBrick(brick);
editPanelModel.setCursor(new Cursor(Cursor.MOVE_CURSOR));
}
}
break;
case SELECT_RECT_MODE:
editPanelModel.setSelectionEnd(movedToPointInModel);
break;
case MOVE_ELEMENT_MODE:
createMoveGhosts(editPanelModel, movedToPointInModel);
break;
default:
break;
}
}
/**
* Create ghosts at the given position in the model on the given
* {@link EditPanelModel}. This is using the moveStartPositionInModel and
* does rastering if enabled. It also updates the current move difference.
*/
private void createMoveGhosts(final EditPanelModel editPanelModel, Point positionInModel) {
currentMoveDifferenceX = positionInModel.x - moveStartPointInModel.x;
currentMoveDifferenceY = positionInModel.y - moveStartPointInModel.y;
if (gridlock()) {
Brick brick = editPanelModel.getSelectedBrick();
int ghostPositionX = brick.getPosition().x + currentMoveDifferenceX;
int ghostPositionY = brick.getPosition().y + currentMoveDifferenceY;
int centeredX = positionInModel.x - brick.getDimension().width / 2;
int centeredY = positionInModel.y - brick.getDimension().height / 2;
Point lockedCenter = lockPointOnGrid(new Point(centeredX, centeredY));
currentMoveDifferenceX += lockedCenter.x - ghostPositionX;
currentMoveDifferenceY += lockedCenter.y - ghostPositionY;
}
final Set<Element> selectionCopy = new LinkedHashSet<Element>();
new BrickWireDistinguishDispatcher() {
{
for (Element elem : editPanelModel.getSelectedElements()) {
elem.dispatch(this);
}
}
@Override
public void visit(Element element) {
selectionCopy.add(element);
}
@Override
protected void visitWire(Wire w) {
}
@Override
protected void visitBrick(Brick b) {
for (Input i : b.getInputs()) {
selectionCopy.addAll(i.getWires());
}
for (Output o : b.getOutputs()) {
selectionCopy.addAll(o.getWires());
}
selectionCopy.add(b);
}
};
Pair<Set<Wire>, Circuit> copy = editPanelModel.getCircuit().getPartCopyAndDiscardedWires(
selectionCopy);
Set<Element> ghosts = copy.getRight().getElements();
ghosts.addAll(copy.getLeft());
for (Element element : ghosts) {
element.setPosition(new Point(element.getBoundingRect().x + currentMoveDifferenceX,
element.getBoundingRect().y + currentMoveDifferenceY));
}
/* draw error */
if (canDropElementsHere(editPanelModel)) {
controller.getSessionModel().getCurrentProject().setErrorGhosts(null);
} else {
controller.getSessionModel().getCurrentProject().setErrorGhosts(ghosts);
}
editPanelModel.setGhosts(ghosts);
}
@Override
public void mouseClicked(MouseEvent e, EditPanelModel editPanelModel) {
super.mouseClicked(e, editPanelModel);
Settings settings = controller.getSessionModel().getSettings();
// position clicked on in the model
Point clickPointInModel = calculateInversePoint(e.getPoint(),
editPanelModel.getTransformation());
Circuit c = editPanelModel.getCircuit();
Set<Element> selected = editPanelModel.getSelectedElements();
// brick we clicked on
Brick brick = c.getBrickAtPosition(clickPointInModel);
// if we did not click on a brick look for a near wire, else take the
// brick
Element clickedElement = (brick == null ? c.getWireAtPosition(clickPointInModel,
settings.getInt(Settings.WIRE_SCATTER_FACTOR)) : brick);
switch (editPanelModel.getToolMode()) {
case DEFAULT_MODE:
// clear selection if clicked on empty space
if (clickedElement == null) {
selected.clear();
editPanelModel.setSelectedElements(selected);
}
// single click on an element with left mouse button?
else if (e.getClickCount() == 1 && isLeftMouseButton(e)) {
// not control button pressed? => only select the clicked brick
if (!e.isControlDown()) {
selected.clear();
selected.add(clickedElement);
} else { // control button pressed
// removed when was selected, add when was not
if (selected.contains(clickedElement)) {
selected.remove(clickedElement);
} else {
selected.add(clickedElement);
}
}
// update selected elements
editPanelModel.setSelectedElements(selected);
}
break;
default:
break;
}
}
private boolean gridlock() {
return controller.getSessionModel().getSettings().getBoolean(Settings.GRID_LOCK);
}
@Override
protected void cancelCurrentAction(EditPanelModel editPanelModel) {
Set<Element> e = editPanelModel.getSelectedElements();
e.clear();
editPanelModel.setSelectedElements(e);
}
}