/*
Copyright (c) 1998-2005 The Regents of the University of California
All rights reserved.
Permission is hereby granted, without written agreement and without
license or royalty fees, to use, copy, modify, and distribute this
software and its documentation for any purpose, provided that the above
copyright notice and the following two paragraphs appear in all copies
of this software.
IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
PROVIDED HEREUNDER IS ON AN BASIS, AND THE UNIVERSITY OF
CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
ENHANCEMENTS, OR MODIFICATIONS.
PT_COPYRIGHT_VERSION_2
COPYRIGHTENDKEY
*
*/
package diva.canvas.interactor;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import diva.canvas.Figure;
import diva.canvas.FigureDecorator;
import diva.canvas.FigureLayer;
import diva.canvas.GeometricSet;
import diva.canvas.GraphicsPane;
import diva.canvas.OverlayLayer;
import diva.canvas.event.EventLayer;
import diva.canvas.event.LayerEvent;
import diva.canvas.event.MouseFilter;
import diva.util.CompoundIterator;
/** A class that implements rubber-banding on a canvas. It contains
* references to one or more instances of SelectionInteractor, which it
* notifies whenever dragging on the canvas covers or uncovers items.
* The SelectionDragger requires three layers: an Event Layer,
* which it listens to perform drag-selection, an OutlineLayer, on
* which it draws the drag-selection box, and a FigureLayer, which it
* selects figures on. It can also accept a GraphicsPane in its
* constructor, in which case it will use the background event layer,
* outline layer, and foreground event layer from that pane.
*
* @version $Id: SelectionDragger.java,v 1.17 2005/07/08 19:54:55 cxh Exp $
* @author John Reekie
*/
public class SelectionDragger extends DragInteractor {
/* The overlay layer
*/
private OverlayLayer _overlayLayer;
/* The event layer
*/
private EventLayer _eventLayer;
/* The figure layer
*/
private FigureLayer _figureLayer;
/* The rubber-band
*/
private Rectangle2D _rubberBand = null;
/* The set of figures covered by the rubber-band
*/
private GeometricSet _intersectedFigures;
/** A hash-set containing those figures
*/
private HashSet _currentFigures;
/** A hash-set containing figures that overlap the rubber-band
* but are not "hit"
*/
private HashSet _holdovers;
/* The origin points
*/
private double _originX;
private double _originY;
/** The list of selection models selected by this dragger.
*/
private List _selectionModels = new ArrayList();
/** The mouse filter for selecting items
*/
private MouseFilter _selectionFilter = MouseFilter.selectionFilter;
/** The mouse filter for toggling items
*/
private MouseFilter _toggleFilter = MouseFilter.alternateSelectionFilter;
/** The selection mode flags
*/
private boolean _isSelecting;
private boolean _isToggling;
///////////////////////////////////////////////////////////////////
//// constructors ////
/**
* Create a new SelectionDragger
*/
public SelectionDragger() {
super();
}
/**
* Create a new SelectionDragger attached to the given graphics
* pane.
*/
public SelectionDragger(GraphicsPane gpane) {
super();
setOverlayLayer(gpane.getOverlayLayer());
setEventLayer(gpane.getBackgroundEventLayer());
setFigureLayer(gpane.getForegroundLayer());
}
///////////////////////////////////////////////////////////////////
//// public methods
/**
* Add the given selection model to the set of selection models
* selected by this dragger. When drag-selecting, only figures
* that have a selection interactor with a selection model in this
* list are added to the selection model.
*/
public void addSelectionModel(SelectionModel model) {
if (!(_selectionModels.contains(model))) {
_selectionModels.add(model);
}
}
/**
* Add the selection model of the given selection interactor to the
* set of selection models selected by this dragger. When
* drag-selecting, only figures that have a selection interactor
* with a selection model in this list are added to the selection
* model.
*/
public void addSelectionInteractor(SelectionInteractor interactor) {
if (!(_selectionModels.contains(interactor.getSelectionModel()))) {
_selectionModels.add(interactor.getSelectionModel());
}
}
/**
* Clear the selection in all the relevant selection interactors.
*/
public void clearSelection() {
Iterator models = _selectionModels.iterator();
while (models.hasNext()) {
SelectionModel model = (SelectionModel) models.next();
model.clearSelection();
}
}
/**
* Contract the selection by removing an item from it and
* removing highlight rendering. If the figure is not in
* the selection, do nothing.
*/
public void contractSelection(SelectionInteractor i, Figure figure) {
if (i.getSelectionModel().containsSelection(figure)) {
i.getSelectionModel().removeSelection(figure);
}
}
/**
* Expand the selection by adding an item to it and adding
* highlight rendering to it. If the
* figure is already in the selection, do nothing.
*/
public void expandSelection(SelectionInteractor i, Figure figure) {
if (!(i.getSelectionModel().containsSelection(figure))) {
i.getSelectionModel().addSelection(figure);
}
}
/**
* Get the layer that drag rectangles are drawn on
*/
public OverlayLayer getOverlayLayer() {
return _overlayLayer;
}
/**
* Get the layer that drag events are listened on
*/
public EventLayer getEventLayer() {
return _eventLayer;
}
/**
* Get the layer that figures are selected on
*/
public FigureLayer getFigureLayer() {
return _figureLayer;
}
/**
* Get the mouse filter that controls when this selection
* filter is activated.
*/
public MouseFilter getSelectionFilter() {
return _selectionFilter;
}
/**
* Get the mouse filter that controls the toggling of
* selections
*/
public MouseFilter getToggleFilter() {
return _toggleFilter;
}
/** Reshape the rubber-band, swapping coordinates if necessary.
* Any figures that are newly included or excluded from
* the drag region are added to or removed from the appropriate
* selection.
*/
public void mouseDragged(LayerEvent event) {
if (!isEnabled()) {
return;
}
if (!_isToggling && !_isSelecting) {
return;
}
if (_rubberBand == null) {
// This should never happen, but it does.
return;
}
double x = event.getLayerX();
double y = event.getLayerY();
double w;
double h;
// Figure out the coordinates of the rubber band
_overlayLayer.repaint(_rubberBand);
if (x < _originX) {
w = _originX - x;
} else {
w = x - _originX;
x = _originX;
}
if (y < _originY) {
h = _originY - y;
} else {
h = y - _originY;
y = _originY;
}
_rubberBand.setFrame(x, y, w, h);
_overlayLayer.repaint(_rubberBand);
// Update the intersected figure set
_intersectedFigures.setGeometry(_rubberBand);
HashSet freshFigures = new HashSet();
for (Iterator i = _intersectedFigures.figures(); i.hasNext();) {
Figure f = (Figure) i.next();
if (f instanceof FigureDecorator) {
f = ((FigureDecorator) f).getDecoratedFigure();
}
if (f.hit(_rubberBand)) {
freshFigures.add(f);
} else {
_holdovers.add(f);
}
}
for (Iterator i = ((HashSet) _holdovers.clone()).iterator(); i
.hasNext();) {
Figure f = (Figure) i.next();
if (f.hit(_rubberBand)) {
freshFigures.add(f);
_holdovers.remove(f);
}
}
// stale = current-fresh;
HashSet staleFigures = (HashSet) _currentFigures.clone();
staleFigures.removeAll(freshFigures);
// current = fresh-current
HashSet temp = (HashSet) freshFigures.clone();
freshFigures.removeAll(_currentFigures);
_currentFigures = temp;
// If in selection mode, add and remove figures
if (_isSelecting) {
// Add figures to the selection
Iterator i = freshFigures.iterator();
while (i.hasNext()) {
Figure f = (Figure) i.next();
Interactor r = f.getInteractor();
if ((r != null) && r instanceof SelectionInteractor) {
SelectionInteractor interactor = (SelectionInteractor) r;
if (_selectionModels.contains(interactor
.getSelectionModel())) {
expandSelection((SelectionInteractor) r, f);
}
}
}
// Remove figures from the selection
i = staleFigures.iterator();
while (i.hasNext()) {
Figure f = (Figure) i.next();
Interactor r = f.getInteractor();
if ((r != null) && r instanceof SelectionInteractor) {
SelectionInteractor interactor = (SelectionInteractor) r;
if (_selectionModels.contains(interactor
.getSelectionModel())) {
contractSelection((SelectionInteractor) r, f);
}
}
}
} else {
// Toggle figures into and out of the selection
Iterator i = new CompoundIterator(freshFigures.iterator(),
staleFigures.iterator());
while (i.hasNext()) {
Figure f = (Figure) i.next();
Interactor r = f.getInteractor();
if ((r != null) && r instanceof SelectionInteractor) {
SelectionInteractor interactor = (SelectionInteractor) r;
if (_selectionModels.contains(interactor
.getSelectionModel())) {
if (interactor.getSelectionModel().containsSelection(f)) {
contractSelection(interactor, f);
} else {
expandSelection(interactor, f);
}
}
}
}
}
// Consume the event
if (isConsuming()) {
event.consume();
}
}
/** Clear the selection, and create the rubber-band
*/
public void mousePressed(LayerEvent event) {
if (!isEnabled()) {
return;
}
// Check mouse event, set flags, etc
_isSelecting = _selectionFilter.accept(event);
_isToggling = _toggleFilter.accept(event);
if (!_isToggling && !_isSelecting) {
return;
}
// Do it
_originX = event.getLayerX();
_originY = event.getLayerY();
_rubberBand = new Rectangle2D.Double(_originX, _originY, 0.0, 0.0);
_overlayLayer.add(_rubberBand);
_overlayLayer.repaint(_rubberBand);
_intersectedFigures = _figureLayer.getFigures().getIntersectedFigures(
_rubberBand);
_currentFigures = new HashSet();
_holdovers = new HashSet();
// Clear all selections
if (_isSelecting) {
clearSelection();
}
// Consume the event
if (isConsuming()) {
event.consume();
}
}
/** Delete the rubber-band
*/
public void mouseReleased(LayerEvent event) {
if (!isEnabled()) {
return;
}
if (_rubberBand == null) {
// This should never happen, but it does.
return;
}
terminateDragSelection();
// Consume the event
if (isConsuming()) {
event.consume();
}
}
/**
* Remove a selection model from the list of models selected by
* this dragger.
*/
public void removeSelectionModel(SelectionModel model) {
if (_selectionModels.contains(model)) {
_selectionModels.remove(model);
}
}
/**
* Get the selection interactors
*/
public Iterator selectionModels() {
return _selectionModels.iterator();
}
/**
* Set the layer that drag rectangles are drawn on
*/
public void setOverlayLayer(OverlayLayer l) {
_overlayLayer = l;
}
/**
* Set the layer that drag events are listened on
*/
public void setEventLayer(EventLayer l) {
if (_eventLayer != null) {
_eventLayer.removeLayerListener(this);
}
_eventLayer = l;
_eventLayer.addLayerListener(this);
}
/**
* Set the layer that figures are selected on
*/
public void setFigureLayer(FigureLayer l) {
_figureLayer = l;
}
/**
* Set the mouse filter that controls when this selection
* filter is activated.
*/
public void setSelectionFilter(MouseFilter f) {
_selectionFilter = f;
}
/**
* Set the mouse filter that controls the toggling of
* selections.
*/
public void setToggleFilter(MouseFilter f) {
_toggleFilter = f;
}
/** Terminate drag-selection operation. This must only be called
* from events that are triggered during a drag operation.
*/
public void terminateDragSelection() {
if (!_isToggling && !_isSelecting) {
return;
}
_overlayLayer.repaint(_rubberBand);
_overlayLayer.remove(_rubberBand);
_rubberBand = null;
_currentFigures = null;
_holdovers = null;
}
}