Java tutorial
/******************************************************************************* * Copyright (c) 2000, 2005 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package com.architexa.org.eclipse.gef.tools; import com.architexa.org.eclipse.draw2d.ColorConstants; import com.architexa.org.eclipse.draw2d.Figure; import com.architexa.org.eclipse.draw2d.Graphics; import com.architexa.org.eclipse.draw2d.IFigure; import com.architexa.org.eclipse.draw2d.PolylineConnection; import com.architexa.org.eclipse.draw2d.geometry.Rectangle; import com.architexa.org.eclipse.gef.ConnectionEditPart; import com.architexa.org.eclipse.gef.EditPart; import com.architexa.org.eclipse.gef.EditPartViewer; import com.architexa.org.eclipse.gef.GraphicalEditPart; import com.architexa.org.eclipse.gef.GraphicalViewer; import com.architexa.org.eclipse.gef.KeyHandler; import com.architexa.org.eclipse.gef.Request; import com.architexa.org.eclipse.gef.RequestConstants; import com.architexa.org.eclipse.gef.SharedCursors; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import org.eclipse.swt.SWT; import org.eclipse.swt.events.KeyEvent; import org.eclipse.swt.widgets.Display; import org.eclipse.jface.viewers.StructuredSelection; /** * A Tool which selects multiple objects inside a rectangular area of a Graphical Viewer. * If the SHIFT key is pressed at the beginning of the drag, the enclosed items will be * appended to the current selection. If the MOD1 key is pressed at the beginning of * the drag, the enclosed items will have their selection state inverted. * <P> * By default, only editparts whose figure's are on the primary layer will be considered * within the enclosed rectangle. */ public class MarqueeSelectionTool extends AbstractTool { /** * The property to be used in {@link AbstractTool#setProperties(java.util.Map)} for * {@link #setMarqueeBehavior(int)}. */ public static final Object PROPERTY_MARQUEE_BEHAVIOR = "marqueeBehavior"; //$NON-NLS-1$ /** * This behaviour selects nodes completely encompassed by the marquee rectangle. This * is the default behaviour for this tool. * @since 3.1 */ public static final int BEHAVIOR_NODES_CONTAINED = new Integer(1).intValue(); /** * This behaviour selects connections that intersect the marquee rectangle. * @since 3.1 */ public static final int BEHAVIOR_CONNECTIONS_TOUCHED = new Integer(2).intValue(); /** * This behaviour selects nodes completely encompassed by the marquee rectangle, and * all connections between those nodes. * @since 3.1 */ public static final int BEHAVIOR_NODES_AND_CONNECTIONS = new Integer(3).intValue(); static final int DEFAULT_MODE = 0; static final int TOGGLE_MODE = 1; static final int APPEND_MODE = 2; private Figure marqueeRectangleFigure; private Set allChildren = new HashSet(); private Collection selectedEditParts; private Request targetRequest; private int marqueeBehavior = BEHAVIOR_NODES_CONTAINED; private int mode; private static final Request MARQUEE_REQUEST = new Request(RequestConstants.REQ_SELECTION); /** * Creates a new MarqueeSelectionTool of default type {@link #BEHAVIOR_NODES_CONTAINED}. */ public MarqueeSelectionTool() { setDefaultCursor(SharedCursors.CROSS); setUnloadWhenFinished(false); } /** * @see com.architexa.org.eclipse.gef.tools.AbstractTool#applyProperty(java.lang.Object, java.lang.Object) */ protected void applyProperty(Object key, Object value) { if (PROPERTY_MARQUEE_BEHAVIOR.equals(key)) { if (value instanceof Integer) setMarqueeBehavior(((Integer) value).intValue()); return; } super.applyProperty(key, value); } private void calculateConnections(Collection newSelections, Collection deselections) { // determine the currently selected nodes minus the ones that are to be deselected Collection currentNodes = new HashSet(); if (getSelectionMode() != DEFAULT_MODE) { // everything is deselected in default mode Iterator iter = getCurrentViewer().getSelectedEditParts().iterator(); while (iter.hasNext()) { EditPart selected = (EditPart) iter.next(); if (!(selected instanceof ConnectionEditPart) && !deselections.contains(selected)) currentNodes.add(selected); } } // add new connections to be selected to newSelections Collection connections = new ArrayList(); for (Iterator nodes = newSelections.iterator(); nodes.hasNext();) { GraphicalEditPart node = (GraphicalEditPart) nodes.next(); for (Iterator itr = node.getSourceConnections().iterator(); itr.hasNext();) { ConnectionEditPart sourceConn = (ConnectionEditPart) itr.next(); if (sourceConn.getSelected() == EditPart.SELECTED_NONE && (newSelections.contains(sourceConn.getTarget()) || currentNodes.contains(sourceConn.getTarget()))) connections.add(sourceConn); } for (Iterator itr = node.getTargetConnections().iterator(); itr.hasNext();) { ConnectionEditPart targetConn = (ConnectionEditPart) itr.next(); if (targetConn.getSelected() == EditPart.SELECTED_NONE && (newSelections.contains(targetConn.getSource()) || currentNodes.contains(targetConn.getSource()))) connections.add(targetConn); } } newSelections.addAll(connections); // add currently selected connections that are to be deselected to deselections connections = new HashSet(); for (Iterator nodes = deselections.iterator(); nodes.hasNext();) { GraphicalEditPart node = (GraphicalEditPart) nodes.next(); for (Iterator itr = node.getSourceConnections().iterator(); itr.hasNext();) { ConnectionEditPart sourceConn = (ConnectionEditPart) itr.next(); if (sourceConn.getSelected() != EditPart.SELECTED_NONE) connections.add(sourceConn); } for (Iterator itr = node.getTargetConnections().iterator(); itr.hasNext();) { ConnectionEditPart targetConn = (ConnectionEditPart) itr.next(); if (targetConn.getSelected() != EditPart.SELECTED_NONE) connections.add(targetConn); } } deselections.addAll(connections); } private void calculateNewSelection(Collection newSelections, Collection deselections) { Rectangle marqueeRect = getMarqueeSelectionRectangle(); for (Iterator itr = getAllChildren().iterator(); itr.hasNext();) { GraphicalEditPart child = (GraphicalEditPart) itr.next(); IFigure figure = child.getFigure(); if (!child.isSelectable() || child.getTargetEditPart(MARQUEE_REQUEST) != child || !isFigureVisible(figure) || !figure.isShowing()) continue; Rectangle r = figure.getBounds().getCopy(); figure.translateToAbsolute(r); boolean included = false; if (child instanceof ConnectionEditPart && marqueeRect.intersects(r)) { Rectangle relMarqueeRect = Rectangle.SINGLETON; figure.translateToRelative(relMarqueeRect.setBounds(marqueeRect)); included = ((PolylineConnection) figure).getPoints().intersects(relMarqueeRect); } else included = marqueeRect.contains(r); if (included) { if (child.getSelected() == EditPart.SELECTED_NONE || getSelectionMode() != TOGGLE_MODE) newSelections.add(child); else deselections.add(child); } } if (marqueeBehavior == BEHAVIOR_NODES_AND_CONNECTIONS) calculateConnections(newSelections, deselections); } private Request createTargetRequest() { return MARQUEE_REQUEST; } /** * Erases feedback if necessary and puts the tool into the terminal state. */ public void deactivate() { if (isInState(STATE_DRAG_IN_PROGRESS)) { eraseMarqueeFeedback(); eraseTargetFeedback(); } super.deactivate(); allChildren.clear(); setState(STATE_TERMINAL); } private void eraseMarqueeFeedback() { if (marqueeRectangleFigure != null) { removeFeedback(marqueeRectangleFigure); marqueeRectangleFigure = null; } } private void eraseTargetFeedback() { if (selectedEditParts == null) return; Iterator oldEditParts = selectedEditParts.iterator(); while (oldEditParts.hasNext()) { EditPart editPart = (EditPart) oldEditParts.next(); editPart.eraseTargetFeedback(getTargetRequest()); } } private Set getAllChildren() { if (allChildren.isEmpty()) getAllChildren(getCurrentViewer().getRootEditPart(), allChildren); return allChildren; } private void getAllChildren(EditPart editPart, Set allChildren) { List children = editPart.getChildren(); for (int i = 0; i < children.size(); i++) { GraphicalEditPart child = (GraphicalEditPart) children.get(i); if (marqueeBehavior == BEHAVIOR_NODES_CONTAINED || marqueeBehavior == BEHAVIOR_NODES_AND_CONNECTIONS) allChildren.add(child); if (marqueeBehavior == BEHAVIOR_CONNECTIONS_TOUCHED) { allChildren.addAll(child.getSourceConnections()); allChildren.addAll(child.getTargetConnections()); } getAllChildren(child, allChildren); } } /** * @see com.architexa.org.eclipse.gef.tools.AbstractTool#getCommandName() */ protected String getCommandName() { return REQ_SELECTION; } /** * @see com.architexa.org.eclipse.gef.tools.AbstractTool#getDebugName() */ protected String getDebugName() { return "Marquee Tool: " + marqueeBehavior;//$NON-NLS-1$ } private IFigure getMarqueeFeedbackFigure() { if (marqueeRectangleFigure == null) { marqueeRectangleFigure = new MarqueeRectangleFigure(); addFeedback(marqueeRectangleFigure); } return marqueeRectangleFigure; } private Rectangle getMarqueeSelectionRectangle() { return new Rectangle(getStartLocation(), getLocation()); } private int getSelectionMode() { return mode; } private Request getTargetRequest() { if (targetRequest == null) targetRequest = createTargetRequest(); return targetRequest; } /** * @see com.architexa.org.eclipse.gef.tools.AbstractTool#handleButtonDown(int) */ protected boolean handleButtonDown(int button) { if (!isGraphicalViewer()) return true; if (button != 1) { setState(STATE_INVALID); handleInvalidInput(); } if (stateTransition(STATE_INITIAL, STATE_DRAG_IN_PROGRESS)) { if (getCurrentInput().isModKeyDown(SWT.MOD1)) setSelectionMode(TOGGLE_MODE); else if (getCurrentInput().isShiftKeyDown()) setSelectionMode(APPEND_MODE); else setSelectionMode(DEFAULT_MODE); } return true; } /** * @see com.architexa.org.eclipse.gef.tools.AbstractTool#handleButtonUp(int) */ protected boolean handleButtonUp(int button) { if (stateTransition(STATE_DRAG_IN_PROGRESS, STATE_TERMINAL)) { eraseTargetFeedback(); eraseMarqueeFeedback(); performMarqueeSelect(); } handleFinished(); return true; } /** * @see com.architexa.org.eclipse.gef.tools.AbstractTool#handleDragInProgress() */ protected boolean handleDragInProgress() { if (isInState(STATE_DRAG | STATE_DRAG_IN_PROGRESS)) { showMarqueeFeedback(); eraseTargetFeedback(); calculateNewSelection(selectedEditParts = new ArrayList(), new ArrayList()); showTargetFeedback(); } return true; } /** * @see com.architexa.org.eclipse.gef.tools.AbstractTool#handleFocusLost() */ protected boolean handleFocusLost() { if (isInState(STATE_DRAG | STATE_DRAG_IN_PROGRESS)) { handleFinished(); return true; } return false; } /** * This method is called when mouse or keyboard input is invalid and erases the feedback. * @return <code>true</code> */ protected boolean handleInvalidInput() { eraseTargetFeedback(); eraseMarqueeFeedback(); return true; } /** * Handles high-level processing of a key down event. * KeyEvents are forwarded to the current viewer's {@link KeyHandler}, * via {@link KeyHandler#keyPressed(KeyEvent)}. * @see AbstractTool#handleKeyDown(KeyEvent) */ protected boolean handleKeyDown(KeyEvent e) { if (super.handleKeyDown(e)) return true; if (getCurrentViewer().getKeyHandler() != null) return getCurrentViewer().getKeyHandler().keyPressed(e); return false; } private boolean isFigureVisible(IFigure fig) { Rectangle figBounds = fig.getBounds().getCopy(); IFigure walker = fig.getParent(); while (!figBounds.isEmpty() && walker != null) { walker.translateToParent(figBounds); figBounds.intersect(walker.getBounds()); walker = walker.getParent(); } return !figBounds.isEmpty(); } private boolean isGraphicalViewer() { return getCurrentViewer() instanceof GraphicalViewer; } /** * MarqueeSelectionTool is only interested in GraphicalViewers, not TreeViewers. * @see com.architexa.org.eclipse.gef.tools.AbstractTool#isViewerImportant(com.architexa.org.eclipse.gef.EditPartViewer) */ protected boolean isViewerImportant(EditPartViewer viewer) { return viewer instanceof GraphicalViewer; } private void performMarqueeSelect() { EditPartViewer viewer = getCurrentViewer(); Collection newSelections = new LinkedHashSet(), deselections = new HashSet(); calculateNewSelection(newSelections, deselections); if (getSelectionMode() != DEFAULT_MODE) { newSelections.addAll(viewer.getSelectedEditParts()); newSelections.removeAll(deselections); } viewer.setSelection(new StructuredSelection(newSelections.toArray())); } /** * Sets the type of parts that this tool will select. This method should only be * invoked once: when the tool is being initialized. * @param type {@link #BEHAVIOR_CONNECTIONS_TOUCHED} or {@link #BEHAVIOR_NODES_CONTAINED} * or {@link #BEHAVIOR_NODES_AND_CONNECTIONS} * @since 3.1 */ public void setMarqueeBehavior(int type) { if (type != BEHAVIOR_CONNECTIONS_TOUCHED && type != BEHAVIOR_NODES_CONTAINED && type != BEHAVIOR_NODES_AND_CONNECTIONS) throw new IllegalArgumentException("Invalid marquee behaviour specified."); //$NON-NLS-1$ marqueeBehavior = type; } private void setSelectionMode(int mode) { this.mode = mode; } /** * @see com.architexa.org.eclipse.gef.Tool#setViewer(com.architexa.org.eclipse.gef.EditPartViewer) */ public void setViewer(EditPartViewer viewer) { if (viewer == getCurrentViewer()) return; super.setViewer(viewer); if (viewer instanceof GraphicalViewer) setDefaultCursor(SharedCursors.CROSS); else setDefaultCursor(SharedCursors.NO); } private void showMarqueeFeedback() { Rectangle rect = getMarqueeSelectionRectangle().getCopy(); getMarqueeFeedbackFigure().translateToRelative(rect); getMarqueeFeedbackFigure().setBounds(rect); } private void showTargetFeedback() { for (Iterator itr = selectedEditParts.iterator(); itr.hasNext();) { EditPart editPart = (EditPart) itr.next(); editPart.showTargetFeedback(getTargetRequest()); } } class MarqueeRectangleFigure extends Figure { private static final int DELAY = 110; //animation delay in millisecond private int offset = 0; private boolean schedulePaint = true; /** * @see com.architexa.org.eclipse.draw2d.Figure#paintFigure(com.architexa.org.eclipse.draw2d.Graphics) */ protected void paintFigure(Graphics graphics) { Rectangle bounds = getBounds().getCopy(); graphics.translate(getLocation()); graphics.setXORMode(true); graphics.setForegroundColor(ColorConstants.white); graphics.setBackgroundColor(ColorConstants.black); graphics.setLineStyle(Graphics.LINE_DOT); int[] points = new int[6]; points[0] = 0 + offset; points[1] = 0; points[2] = bounds.width - 1; points[3] = 0; points[4] = bounds.width - 1; points[5] = bounds.height - 1; graphics.drawPolyline(points); points[0] = 0; points[1] = 0 + offset; points[2] = 0; points[3] = bounds.height - 1; points[4] = bounds.width - 1; points[5] = bounds.height - 1; graphics.drawPolyline(points); graphics.translate(getLocation().getNegated()); if (schedulePaint) { Display.getCurrent().timerExec(DELAY, new Runnable() { public void run() { offset++; if (offset > 5) offset = 0; schedulePaint = true; repaint(); } }); } schedulePaint = false; } } }