com.architexa.org.eclipse.gef.tools.MarqueeSelectionTool.java Source code

Java tutorial

Introduction

Here is the source code for com.architexa.org.eclipse.gef.tools.MarqueeSelectionTool.java

Source

/*******************************************************************************
 * 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;
        }

    }

}