/*
* @(#)SelectionTool.java
*
* Copyright (c) 1996-2010 The authors and contributors of JHotDraw.
* You may not use, copy or modify this file, except in compliance with the
* accompanying license terms.
*/
package org.jhotdraw.draw.tool;
import org.jhotdraw.draw.*;
import org.jhotdraw.draw.handle.Handle;
import org.jhotdraw.draw.event.ToolEvent;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.Point2D;
import java.util.HashSet;
import org.jhotdraw.draw.event.ToolAdapter;
/**
* Tool to select and manipulate figures.
* <p>
* A selection tool is in one of three states: 1) area
* selection, 2) figure dragging, 3) handle manipulation. The different
* states are handled by different tracker objects: the
* <code>DefaultSelectAreaTracker</code>, the <code>DefaultDragTracker</code> and the
* <code>DefaultHandleTracker</code>.
* <p>
* A Figure can be selected by clicking at it. Holding the alt key or the
* ctrl key down, selects the Figure behind it.
* <hr>
* <b>Design Patterns</b>
*
* <p><em>Strategy</em><br>
* The different behavior states of the selection tool are implemented by
* trackers.<br>
* Context: {@link SelectionTool}; State: {@link DragTracker},
* {@link HandleTracker}, {@link SelectAreaTracker}.
*
* <p><em>Chain of responsibility</em><br>
* Mouse and keyboard events of the user occur on the drawing view, and are
* preprocessed by the {@code DragTracker} of a {@code SelectionTool}. In
* turn {@code DragTracker} invokes "track" methods on a {@code Handle} which in
* turn changes an aspect of a figure.<br>
* Client: {@link SelectionTool}; Handler: {@link DragTracker}, {@link Handle}.
* <hr>
*
* @author Werner Randelshofer
* @version $Id$
*/
public class SelectionTool extends AbstractTool {
private static final long serialVersionUID = 1L;
/**
* The tracker encapsulates the current state of the SelectionTool.
*/
private Tool tracker;
/**
* The tracker encapsulates the current state of the SelectionTool.
*/
private HandleTracker handleTracker;
/**
* The tracker encapsulates the current state of the SelectionTool.
*/
private SelectAreaTracker selectAreaTracker;
/**
* The tracker encapsulates the current state of the SelectionTool.
*/
private DragTracker dragTracker;
private class TrackerHandler extends ToolAdapter {
@Override
public void toolDone(ToolEvent event) {
// Empty
Tool newTracker = getSelectAreaTracker();
if (newTracker != null) {
if (tracker != null) {
tracker.deactivate(getEditor());
tracker.removeToolListener(this);
}
tracker = newTracker;
tracker.activate(getEditor());
tracker.addToolListener(this);
}
fireToolDone();
}
/**
* Sent when an area of the drawing view needs to be repainted.
*/
@Override
public void areaInvalidated(ToolEvent e) {
fireAreaInvalidated(e.getInvalidatedArea());
}
/**
* Sent when the bounds need to be revalidated.
*/
@Override
public void boundsInvalidated(ToolEvent e) {
fireBoundsInvalidated(e.getInvalidatedArea());
}
}
private TrackerHandler trackerHandler;
/**
* Constant for the name of the selectBehindEnabled property.
*/
public static final String SELECT_BEHIND_ENABLED_PROPERTY = "selectBehindEnabled";
/**
* Represents the state of the selectBehindEnabled property.
* By default, this property is set to true.
*/
private boolean isSelectBehindEnabled = true;
/** Creates a new instance. */
public SelectionTool() {
tracker = getSelectAreaTracker();
trackerHandler = new TrackerHandler();
tracker.addToolListener(trackerHandler);
}
/**
* Sets the selectBehindEnabled property.
* This is a bound property.
*
* @param newValue The new value.
*/
public void setSelectBehindEnabled(boolean newValue) {
boolean oldValue = isSelectBehindEnabled;
isSelectBehindEnabled = newValue;
firePropertyChange(SELECT_BEHIND_ENABLED_PROPERTY, oldValue, newValue);
}
/**
* Returns the value of the selectBehindEnabled property.
* This is a bound property.
*
* @return The property value.
*/
public boolean isSelectBehindEnabled() {
return isSelectBehindEnabled;
}
@Override
public void activate(DrawingEditor editor) {
super.activate(editor);
tracker.activate(editor);
}
@Override
public void deactivate(DrawingEditor editor) {
super.deactivate(editor);
tracker.deactivate(editor);
}
@Override
public void keyPressed(KeyEvent e) {
if (getView() != null && getView().isEnabled()) {
tracker.keyPressed(e);
}
}
@Override
public void keyReleased(KeyEvent evt) {
if (getView() != null && getView().isEnabled()) {
tracker.keyReleased(evt);
}
}
@Override
public void keyTyped(KeyEvent evt) {
if (getView() != null && getView().isEnabled()) {
tracker.keyTyped(evt);
}
}
@Override
public void mouseClicked(MouseEvent evt) {
if (getView() != null && getView().isEnabled()) {
tracker.mouseClicked(evt);
}
}
@Override
public void mouseDragged(MouseEvent evt) {
if (getView() != null && getView().isEnabled()) {
tracker.mouseDragged(evt);
}
}
@Override
public void mouseEntered(MouseEvent evt) {
super.mouseEntered(evt);
tracker.mouseEntered(evt);
}
@Override
public void mouseExited(MouseEvent evt) {
super.mouseExited(evt);
tracker.mouseExited(evt);
}
@Override
public void mouseMoved(MouseEvent evt) {
tracker.mouseMoved(evt);
}
@Override
public void mouseReleased(MouseEvent evt) {
if (getView() != null && getView().isEnabled()) {
tracker.mouseReleased(evt);
}
}
@Override
public void draw(Graphics2D g) {
tracker.draw(g);
}
@Override
public void mousePressed(MouseEvent evt) {
if (getView() != null && getView().isEnabled()) {
super.mousePressed(evt);
DrawingView view = getView();
Handle handle = view.findHandle(anchor);
Tool newTracker = null;
if (handle != null) {
newTracker = getHandleTracker(handle);
} else {
Figure figure;
Drawing drawing = view.getDrawing();
Point2D.Double p = view.viewToDrawing(anchor);
if (isSelectBehindEnabled() &&
(evt.getModifiersEx() &
(InputEvent.ALT_DOWN_MASK | InputEvent.CTRL_DOWN_MASK)) != 0) {
// Select a figure behind the current selection
figure = view.findFigure(anchor);
while (figure != null && !figure.isSelectable()) {
figure = drawing.findFigureBehind(p, figure);
}
HashSet<Figure> ignoredFigures = new HashSet<>(view.getSelectedFigures());
ignoredFigures.add(figure);
Figure figureBehind = view.getDrawing().findFigureBehind(
view.viewToDrawing(anchor), ignoredFigures);
if (figureBehind != null) {
figure = figureBehind;
}
} else {
// Note: The search sequence used here, must be
// consistent with the search sequence used by the
// DefaultHandleTracker, the DefaultSelectAreaTracker and DelegationSelectionTool.
// If possible, continue to work with the current selection
figure = null;
if (isSelectBehindEnabled()) {
for (Figure f : view.getSelectedFigures()) {
if (f.contains(p)) {
figure = f;
break;
}
}
}
// If the point is not contained in the current selection,
// search for a figure in the drawing.
if (figure == null) {
figure = view.findFigure(anchor);
while (figure != null && !figure.isSelectable()) {
figure = drawing.findFigureBehind(p, figure);
}
}
}
if (figure != null && figure.isSelectable()) {
newTracker = getDragTracker(figure);
} else {
if (!evt.isShiftDown()) {
view.clearSelection();
view.setHandleDetailLevel(0);
}
newTracker = getSelectAreaTracker();
}
}
if (newTracker != null) {
setTracker(newTracker);
}
tracker.mousePressed(evt);
}
}
protected void setTracker(Tool newTracker) {
if (tracker != null) {
tracker.deactivate(getEditor());
tracker.removeToolListener(trackerHandler);
}
tracker = newTracker;
if (tracker != null) {
tracker.activate(getEditor());
tracker.addToolListener(trackerHandler);
}
}
/**
* Method to get a {@code HandleTracker} which handles user interaction
* for the specified handle.
*/
protected HandleTracker getHandleTracker(Handle handle) {
if (handleTracker == null) {
handleTracker = new DefaultHandleTracker();
}
handleTracker.setHandles(handle, getView().getCompatibleHandles(handle));
return handleTracker;
}
/**
* Method to get a {@code DragTracker} which handles user interaction
* for dragging the specified figure.
*/
protected DragTracker getDragTracker(Figure f) {
if (dragTracker == null) {
dragTracker = new DefaultDragTracker();
}
dragTracker.setDraggedFigure(f);
return dragTracker;
}
/**
* Method to get a {@code SelectAreaTracker} which handles user interaction
* for selecting an area on the drawing.
*/
protected SelectAreaTracker getSelectAreaTracker() {
if (selectAreaTracker == null) {
selectAreaTracker = new DefaultSelectAreaTracker();
}
return selectAreaTracker;
}
/**
* Method to set a {@code HandleTracker}. If you specify null, the
* {@code SelectionTool} uses the {@code DefaultHandleTracker}.
*/
public void setHandleTracker(HandleTracker newValue) {
handleTracker = newValue;
}
/**
* Method to set a {@code SelectAreaTracker}. If you specify null, the
* {@code SelectionTool} uses the {@code DefaultSelectAreaTracker}.
*/
public void setSelectAreaTracker(SelectAreaTracker newValue) {
selectAreaTracker = newValue;
}
/**
* Method to set a {@code DragTracker}. If you specify null, the
* {@code SelectionTool} uses the {@code DefaultDragTracker}.
*/
public void setDragTracker(DragTracker newValue) {
dragTracker = newValue;
}
/**
* Returns true, if this tool lets the user interact with handles.
* <p>
* Handles may draw differently, if interaction is not possible.
*
* @return True, if this tool supports interaction with the handles.
*/
@Override
public boolean supportsHandleInteraction() {
return true;
}
}