/*
* Copyright (C) 2010 Brockmann Consult GmbH (info@brockmann-consult.de)
*
* 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 com.bc.ceres.swing.figure.interactions;
import com.bc.ceres.swing.figure.Figure;
import com.bc.ceres.swing.figure.FigureEditor;
import com.bc.ceres.swing.figure.FigureEditorInteractor;
import com.bc.ceres.swing.figure.FigureSelection;
import com.bc.ceres.swing.figure.Handle;
import com.bc.ceres.swing.figure.support.VertexHandle;
import java.awt.Cursor;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.event.InputEvent;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
// todo - this Interactor should not be restricted to figure contexts, is the inner Tool interface the solution?
public class SelectionInteractor extends FigureEditorInteractor {
private final Tool selectPointTool = createSelectPointTool();
private final Tool selectRectangleTool = createSelectRectangleTool();
private final Tool moveSelectionTool = createMoveSelectionTool();
private final Tool moveHandleTool = createMoveHandleTool();
protected boolean canceled;
protected Point referencePoint;
private Object figureMemento;
private Tool tool;
public SelectionInteractor() {
tool = new NullTool();
}
@Override
public void cancelInteraction(InputEvent event) {
if (!canceled) {
canceled = true;
FigureEditor figureEditor = getFigureEditor(event);
if (figureMemento != null) {
figureEditor.getFigureSelection().setMemento(figureMemento);
figureMemento = null;
}
figureEditor.getFigureSelection().removeAllFigures();
super.cancelInteraction(event);
}
}
@Override
public void mousePressed(MouseEvent event) {
if (startInteraction(event)) {
referencePoint = event.getPoint();
canceled = false;
tool = selectPointTool;
figureMemento = null;
}
}
@Override
public void mouseDragged(MouseEvent event) {
if (!canceled) {
if (tool == selectPointTool) {
if (selectHandle(event)) {
tool = moveHandleTool;
} else {
if (isMouseOverSelection(event)) {
tool = moveSelectionTool;
} else {
tool = selectRectangleTool;
}
}
tool.start(event);
}
tool.drag(event);
}
}
@Override
public void mouseReleased(MouseEvent event) {
if (!canceled) {
stopInteraction(event);
tool.end(event);
setCursor(event);
}
}
@Override
public void mouseMoved(MouseEvent event) {
setCursor(event);
}
protected SelectPointTool createSelectPointTool() {
return new SelectPointTool();
}
protected SelectRectangleTool createSelectRectangleTool() {
return new SelectRectangleTool();
}
protected MoveSelectionTool createMoveSelectionTool() {
return new MoveSelectionTool();
}
protected MoveHandleTool createMoveHandleTool() {
return new MoveHandleTool();
}
private void setCursor(MouseEvent event) {
Cursor cursor = null;
Handle handle = findHandle(event);
if (handle != null) {
cursor = handle.getCursor();
}
if (cursor == null && isMouseOverSelection(event)) {
cursor = Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR);
}
FigureEditor figureEditor = getFigureEditor(event);
if (cursor == null && figureEditor.getSelectionRectangle() != null) {
cursor = Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR);
}
if (cursor == null) {
cursor = Cursor.getDefaultCursor();
}
figureEditor.getEditorComponent().setCursor(cursor);
}
private Point2D.Double toModelDelta(MouseEvent event) {
Point2D.Double p = new Point2D.Double(event.getX() - referencePoint.x,
event.getY() - referencePoint.y);
AffineTransform transform = getViewToModelTransform(event);
transform.deltaTransform(p, p);
return p;
}
private void dragFigure(Figure figure, MouseEvent event) {
Point2D.Double p = toModelDelta(event);
figure.move(p.getX(), p.getY());
referencePoint = event.getPoint();
}
protected boolean isMouseOverSelection(MouseEvent event) {
return getFigureEditor(event).getFigureSelection().isCloseTo(toModelPoint(event),
getModelToViewTransform(event));
}
private Figure findFigure(MouseEvent event) {
return getFigureEditor(event).getFigureCollection().getFigure(toModelPoint(event),
getModelToViewTransform(event));
}
private Handle findHandle(MouseEvent event) {
FigureEditor figureEditor = getFigureEditor(event);
FigureSelection figureSelection = figureEditor.getFigureSelection();
Point2D modelPoint = toModelPoint(event);
AffineTransform m2v = getModelToViewTransform(event);
for (Handle handle : figureSelection.getHandles()) {
if (handle.isSelectable()) {
if (handle.isCloseTo(modelPoint, m2v)) {
return handle;
}
}
}
return null;
}
private boolean selectHandle(MouseEvent event) {
Handle handle = findHandle(event);
if (handle != null) {
getFigureEditor(event).getFigureSelection().setSelectedHandle(handle);
return true;
}
return false;
}
// todo - Tool is a helper, it may later be replaced by an Interactor delegate
protected interface Tool {
void start(MouseEvent event);
void drag(MouseEvent event);
void end(MouseEvent event);
}
private static class NullTool implements Tool {
@Override
public void start(MouseEvent event) {
}
@Override
public void drag(MouseEvent event) {
}
@Override
public void end(MouseEvent event) {
}
}
protected class MoveSelectionTool implements Tool {
@Override
public void start(MouseEvent event) {
figureMemento = getFigureEditor(event).getFigureSelection().createMemento();
}
@Override
public void drag(MouseEvent event) {
dragFigure(getFigureEditor(event).getFigureSelection(), event);
}
@Override
public void end(MouseEvent event) {
FigureEditor figureEditor = getFigureEditor(event);
figureEditor.changeFigure(figureEditor.getFigureSelection(), figureMemento, "Move Figure");
}
}
protected class MoveHandleTool implements Tool {
@Override
public void start(MouseEvent event) {
figureMemento = getFigureEditor(event).getFigureSelection().createMemento();
if (event.isControlDown()) {
maybeAddSegment(event);
}
}
@Override
public void drag(MouseEvent event) {
final Handle selectedHandle = getFigureEditor(event).getFigureSelection().getSelectedHandle();
dragFigure(selectedHandle, event);
}
@Override
public void end(MouseEvent event) {
if (event.isControlDown()) {
maybeRemoveSegment(event);
}
// Handle selection no longer required
FigureEditor figureEditor = getFigureEditor(event);
figureEditor.getFigureSelection().setSelectedHandle(null);
figureEditor.changeFigure(figureEditor.getFigureSelection(), figureMemento, "Change figure shape");
}
private void maybeAddSegment(MouseEvent event) {
FigureSelection figureSelection = getFigureEditor(event).getFigureSelection();
Handle selectedHandle = figureSelection.getSelectedHandle();
if (selectedHandle instanceof VertexHandle) {
VertexHandle selectedVertexHandle = (VertexHandle) selectedHandle;
int segmentIndex = selectedVertexHandle.getSegmentIndex();
Figure figure = figureSelection.getFigure(0);
double[] segment = figure.getSegment(segmentIndex);
// Need to add some offsets, otherwise (AWT) shapes won't accept new segment
segment[0] += 0.1;
segment[1] += 0.1;
figure.addSegment(segmentIndex, segment);
VertexHandle newVertexHandle = new VertexHandle(figure, segmentIndex,
selectedVertexHandle.getNormalStyle(),
selectedVertexHandle.getSelectedStyle());
figureSelection.setSelectedHandle(newVertexHandle);
for (Handle handle : figureSelection.getHandles()) {
if (handle instanceof VertexHandle) {
VertexHandle vertexHandle = (VertexHandle) handle;
if (vertexHandle != newVertexHandle
&& vertexHandle.getSegmentIndex() >= segmentIndex) {
vertexHandle.setSegmentIndex(vertexHandle.getSegmentIndex() + 1);
}
}
}
}
}
private void maybeRemoveSegment(MouseEvent event) {
FigureSelection figureSelection = getFigureEditor(event).getFigureSelection();
Handle selectedHandle = figureSelection.getSelectedHandle();
if (selectedHandle instanceof VertexHandle) {
VertexHandle selectedVertexHandle = (VertexHandle) selectedHandle;
AffineTransform m2v = getModelToViewTransform(event);
Point2D p1 = m2v.transform(selectedVertexHandle.getLocation(), null);
int segmentIndex = selectedVertexHandle.getSegmentIndex();
Figure figure = figureSelection.getFigure(0);
for (Handle handle : figureSelection.getHandles()) {
if (handle instanceof VertexHandle) {
VertexHandle vertexHandle = (VertexHandle) handle;
Point2D p2 = m2v.transform(vertexHandle.getLocation(), null);
if ((vertexHandle.getSegmentIndex() == segmentIndex - 1
|| vertexHandle.getSegmentIndex() == segmentIndex + 1)
&& vertexHandle.getShape().contains(new Point2D.Double(p1.getX()-p2.getX(), p1.getY()-p2.getY()))) {
figure.removeSegment(segmentIndex);
}
}
}
}
}
}
protected class SelectPointTool implements Tool {
@Override
public void start(MouseEvent event) {
figureMemento = null;
}
@Override
public void drag(MouseEvent event) {
}
@Override
public void end(MouseEvent event) {
// Check first if user has selected a selectable handle
if (selectHandle(event)) {
return;
}
// Then check if user has selected a figure
Figure clickedFigure = findFigure(event);
FigureEditor figureEditor = getFigureEditor(event);
if (clickedFigure == null) {
// Nothing clicked, thus clear selection.
figureEditor.getFigureSelection().removeAllFigures();
figureEditor.getFigureSelection().setSelectionStage(0);
} else if (figureEditor.getFigureSelection().getFigureCount() == 0) {
// If figure clicked and current selection is empty then select this figure at first selection level.
figureEditor.getFigureSelection().addFigure(clickedFigure);
// Single selection starts at selection level 1 (highlighted boundary)
figureEditor.getFigureSelection().setSelectionStage(1);
} else if (figureEditor.getFigureSelection().getFigureCount() == 1) {
// If figure clicked and we already have a single figure selected.
if (figureEditor.getFigureSelection().contains(clickedFigure)) {
// If the clicked figure is the currently selected figure, then increment selection level.
int selectionLevel = figureEditor.getFigureSelection().getSelectionStage() + 1;
if (selectionLevel > clickedFigure.getMaxSelectionStage()) {
selectionLevel = 0;
}
figureEditor.getFigureSelection().setSelectionStage(selectionLevel);
} else {
// If the clicked figure is NOT the currently selected figure, then
// if CTRL down add the clicked figure to the selection,
// otherwise clicked figure is new selection.
if (event.isControlDown()) {
figureEditor.getFigureSelection().addFigure(clickedFigure);
// Multiple selection is always at selection level 2 (scale handles + rotation handle).
figureEditor.getFigureSelection().setSelectionStage(2);
} else {
figureEditor.getFigureSelection().removeAllFigures();
figureEditor.getFigureSelection().addFigure(clickedFigure);
// Single selection starts at selection level 1 (highlighted boundary)
figureEditor.getFigureSelection().setSelectionStage(1);
}
}
} else if (figureEditor.getFigureSelection().getFigureCount() >= 2) {
// If figure clicked and we already have more than one figure selected.
if (figureEditor.getFigureSelection().contains(clickedFigure)) {
// If the clicked figure is a currently selected figure, then
// if CTRL down, we deleselct clicked figure, otherwise do nothing.
if (event.isControlDown()) {
figureEditor.getFigureSelection().removeFigure(clickedFigure);
}
} else {
// If the clicked figure is NOT a currently selected figure, then
// if CTRL down we add clicked figure to selection, otherwise
// the clicked figure is the only selected one.
if (event.isControlDown()) {
figureEditor.getFigureSelection().addFigure(clickedFigure);
} else {
figureEditor.getFigureSelection().removeAllFigures();
figureEditor.getFigureSelection().addFigure(clickedFigure);
}
}
// Multiple selection is always at selection level 2 (scale handles + rotation handle).
figureEditor.getFigureSelection().setSelectionStage(2);
}
}
}
protected class SelectRectangleTool implements Tool {
@Override
public void start(MouseEvent event) {
figureMemento = null;
}
@Override
public void drag(MouseEvent event) {
int width = event.getX() - referencePoint.x;
int height = event.getY() - referencePoint.y;
int x = referencePoint.x;
int y = referencePoint.y;
if (width < 0) {
width *= -1;
x -= width;
}
if (height < 0) {
height *= -1;
y -= height;
}
getFigureEditor(event).setSelectionRectangle(new Rectangle(x, y, width, height));
}
@Override
public void end(MouseEvent event) {
FigureEditor figureEditor = getFigureEditor(event);
if (figureEditor.getSelectionRectangle() != null) {
AffineTransform transform = getViewToModelTransform(event);
Shape shape = transform.createTransformedShape(figureEditor.getSelectionRectangle());
if (!event.isControlDown()) {
figureEditor.getFigureSelection().removeAllFigures();
}
final Figure[] figures = figureEditor.getFigureCollection().getFigures(shape);
figureEditor.getFigureSelection().addFigures(figures);
if (figureEditor.getFigureSelection().getFigureCount() == 0) {
figureEditor.getFigureSelection().setSelectionStage(0);
} else if (figureEditor.getFigureSelection().getFigureCount() == 1) {
figureEditor.getFigureSelection().setSelectionStage(1);
} else {
figureEditor.getFigureSelection().setSelectionStage(2);
}
figureEditor.setSelectionRectangle(null);
}
}
}
}