/* $Id: SelectionNodeClarifiers2.java 17865 2010-01-12 20:45:26Z linus $
*****************************************************************************
* Copyright (c) 2009 Contributors - see below
* 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:
* mvw
*****************************************************************************
*
* Some portions of this file was previously release using the BSD License:
*/
// Copyright (c) 1996-2007 The Regents of the University of California. All
// Rights Reserved. Permission to use, copy, modify, and distribute this
// software and its documentation without fee, and without a written
// agreement is hereby granted, provided that the above copyright notice
// and this paragraph appear in all copies. This software program and
// documentation are copyrighted by The Regents of the University of
// California. The software program and documentation are supplied "AS
// IS", without any accompanying services from The Regents. The Regents
// does not warrant that the operation of the program will be
// uninterrupted or error-free. The end-user understands that the program
// was developed for research purposes and is advised not to rely
// exclusively on the program for any reason. IN NO EVENT SHALL THE
// UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
// SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS,
// 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 "AS IS" BASIS, AND THE UNIVERSITY OF
// CALIFORNIA HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT,
// UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
package org.argouml.uml.diagram.ui;
import java.awt.Graphics;
import java.awt.Rectangle;
import javax.swing.Icon;
import org.apache.log4j.Logger;
import org.tigris.gef.base.Editor;
import org.tigris.gef.base.Globals;
import org.tigris.gef.base.Mode;
import org.tigris.gef.base.ModeCreateEdgeAndNode;
import org.tigris.gef.base.ModeManager;
import org.tigris.gef.base.ModeModify;
import org.tigris.gef.base.ModePlace;
import org.tigris.gef.base.SelectionButtons;
import org.tigris.gef.base.SelectionManager;
import org.tigris.gef.graph.MutableGraphModel;
import org.tigris.gef.presentation.Fig;
import org.tigris.gef.presentation.FigNode;
import org.tigris.gef.presentation.Handle;
/**
* Enhanced version of SelectionNodeClarifiers with the new methods
* necessary for the enhanced support marked as abstract so that implementors
* are forced to implement them. SelectionNodeClarifiers is simple
* extension of this which implements null versions of the required
* methods for backward compatibility with the previous implementation.
* <p>
* To upgrade subtypes of SelectionNodeClarifiers, change them to
* extend this class instead and implement the required abstract methods.
* The methods paintButtons, dragHandle, hitHandle, and createEdge* can
* all usually be removed.
*
* @author jrobbins
* @author Tom Morris
*/
public abstract class SelectionNodeClarifiers2 extends SelectionButtons {
private static final Logger LOG =
Logger.getLogger(SelectionNodeClarifiers2.class);
/** Base index of array */
protected static final int BASE = 10;
/** Top Handle */
protected static final int TOP = 10;
/** Bottom Handle */
protected static final int BOTTOM = 11;
/** Left Handle */
protected static final int LEFT = 12;
/** Right Handle */
protected static final int RIGHT = 13;
/** Lower left corner Handle */
protected static final int LOWER_LEFT = 14;
private static final int OFFSET = 2;
private Object newEdge = null;
private int button;
/**
* Construct a new SelectionNodeClarifiers for the given Fig
*
* @param f
* the given Fig
*/
public SelectionNodeClarifiers2(Fig f) {
super(f);
}
/*
* @see org.tigris.gef.base.SelectionButtons#paint(java.awt.Graphics)
*/
@Override
public void paint(Graphics g) {
final Mode topMode = Globals.curEditor().getModeManager().top();
if (!(topMode instanceof ModePlace)) {
// If the user has selected ModePlace either by a diagram
// tool or AddToDiagram then we don't want to show the
// clarifiers.
((Clarifiable) getContent()).paintClarifiers(g);
}
super.paint(g);
}
/*
* @see org.tigris.gef.base.SelectionButtons#paintButtons(Graphics)
*/
public final void paintButtons(Graphics g) {
final Mode topMode = Globals.curEditor().getModeManager().top();
if (!(topMode instanceof ModePlace)) {
// If the user has selected ModePlace either by a diagram
// tool or AddToDiagram then we don't want to show the
// toolbelt items.
Icon[] icons = getIcons();
if (icons == null) {
return;
}
int cx = getContent().getX();
int cy = getContent().getY();
int cw = getContent().getWidth();
int ch = getContent().getHeight();
if (icons[0] != null) {
paintButtonAbove(icons[0], g, cx + cw / 2, cy - OFFSET, TOP);
}
if (icons[1] != null) {
paintButtonBelow(icons[1], g, cx + cw / 2, cy + ch + OFFSET,
BOTTOM);
}
if (icons[2] != null) {
paintButtonLeft(icons[2], g, cx - OFFSET, cy + ch / 2, LEFT);
}
if (icons[3] != null) {
paintButtonRight(icons[3], g, cx + cw + OFFSET, cy + ch / 2,
RIGHT);
}
if (icons[4] != null) {
paintButtonLeft(icons[4], g, cx - OFFSET, cy + ch, LOWER_LEFT);
}
}
}
/*
* @see org.tigris.gef.base.SelectionButtons#getNewNode(int)
*/
protected Object getNewNode(int arg0) {
return null;
}
/**
* Compute handle selection, if any, from cursor location.
*
* @param cursor
* cursor point represented by a 0-size rectangle
* @param h
* handle in which to return selected Handle information (output
* parameter). A handle index of -1 indicates that the cursor is
* not over any handle.
*
* If GEF had any API documentation you could see the following:
* @see org.tigris.gef.base.SelectionResize#hitHandle(java.awt.Rectangle,
* org.tigris.gef.presentation.Handle)
*/
public void hitHandle(Rectangle cursor, Handle h) {
super.hitHandle(cursor, h);
if (h.index != -1) {
// super implementation found a hit
return;
}
if (!isPaintButtons()) {
return;
}
Icon[] icons = getIcons();
if (icons == null) {
return;
}
Editor ce = Globals.curEditor();
SelectionManager sm = ce.getSelectionManager();
if (sm.size() != 1) {
return;
}
ModeManager mm = ce.getModeManager();
if (mm.includes(ModeModify.class) && getPressedButton() == -1) {
return;
}
int cx = getContent().getX();
int cy = getContent().getY();
int cw = getContent().getWidth();
int ch = getContent().getHeight();
/*
* Crazy numbering scheme at work here. Here's how the handle numbers
* are laid out. Values 0-7 are defined by GEF and go left to
* right, top to bottom (ie not clockwise or counterclockwise).
* Values 10-14 zigzag North, South, West, East, Southwest.
* If you can correctly guess where 15 will go, you should buy
* a lottery ticket immediately.
* <pre>
* 10
* 0-------1-------2
* | |
* 12 3 4 13
* | |
* 14 5-------6-------7
* 11
* </pre>
*/
if (icons[0] != null && hitAbove(cx + cw / 2, cy,
icons[0].getIconWidth(), icons[0].getIconHeight(),
cursor)) {
h.index = TOP;
} else if (icons[1] != null && hitBelow(cx + cw / 2, cy + ch,
icons[1].getIconWidth(), icons[1].getIconHeight(),
cursor)) {
h.index = BOTTOM;
} else if (icons[2] != null && hitLeft(cx, cy + ch / 2,
icons[2].getIconWidth(), icons[2].getIconHeight(),
cursor)) {
h.index = LEFT;
} else if (icons[3] != null && hitRight(cx + cw, cy + ch / 2,
icons[3].getIconWidth(), icons[3].getIconHeight(),
cursor)) {
h.index = RIGHT;
} else if (icons[4] != null && hitLeft(cx, cy + ch,
icons[4].getIconWidth(), icons[4].getIconHeight(),
cursor)) {
h.index = LOWER_LEFT;
} else {
h.index = -1;
}
if (h.index == -1) {
h.instructions = getInstructions(15);
} else {
h.instructions = getInstructions(h.index);
}
}
/*
* @see org.tigris.gef.base.Selection#dragHandle(int, int, int, int,
* org.tigris.gef.presentation.Handle)
*/
public void dragHandle(int mX, int mY, int anX, int anY, Handle hand) {
// Don't allow drag outside of bounds of diagram
mX = Math.max(mX, 0);
mY = Math.max(mY, 0);
if (hand.index < 10) {
setPaintButtons(false);
super.dragHandle(mX, mY, anX, anY, hand);
return;
}
if (!isDraggableHandle(hand.index)) {
return;
}
int cx = getContent().getX(), cy = getContent().getY();
int cw = getContent().getWidth(), ch = getContent().getHeight();
int bx = mX, by = mY;
// Remember what handle was clicked for the case where the drag
// is released over empty space
button = hand.index;
switch (hand.index) {
case TOP:
by = cy;
bx = cx + cw / 2;
break;
case BOTTOM:
by = cy + ch;
bx = cx + cw / 2;
break;
case LEFT:
by = cy + ch / 2;
bx = cx;
break;
case RIGHT:
by = cy + ch / 2;
bx = cx + cw;
break;
case LOWER_LEFT:
by = cy + ch;
bx = cx;
break;
default:
LOG.warn("invalid handle number");
break;
}
Object nodeType = getNewNodeType(hand.index);
Object edgeType = getNewEdgeType(hand.index);
boolean reverse = isReverseEdge(hand.index);
if (edgeType != null && nodeType != null) {
Editor ce = Globals.curEditor();
ModeCreateEdgeAndNode m =
getNewModeCreateEdgeAndNode(ce,
edgeType, isEdgePostProcessRequested(), this);
m.setup((FigNode) getContent(), getContent().getOwner(),
bx, by, reverse);
ce.pushMode(m);
}
}
/**
* Override this to implement post-processing.
*
* @param ce the current Editor
* @param edgeType the new edge type
* @param postProcess true if post-processing is wanted
* @param nodeCreator this class will create the node
* @return the ModeCreate
*/
protected ModeCreateEdgeAndNode getNewModeCreateEdgeAndNode(
Editor ce, Object edgeType, boolean postProcess,
SelectionNodeClarifiers2 nodeCreator) {
return new ModeCreateEdgeAndNode(ce,
edgeType, postProcess, nodeCreator);
}
@Override
public void buttonClicked(int buttonCode) {
super.buttonClicked(buttonCode);
if (isEdgePostProcessRequested()) {
postProcessEdge2(newEdge);
}
}
protected Object createEdgeAbove(MutableGraphModel gm, Object newNode) {
return createEdge(gm, newNode, TOP);
}
protected Object createEdgeUnder(MutableGraphModel gm, Object newNode) {
return createEdge(gm, newNode, BOTTOM);
}
protected Object createEdgeLeft(MutableGraphModel gm, Object newNode) {
return createEdge(gm, newNode, LEFT);
}
protected Object createEdgeRight(MutableGraphModel gm, Object newNode) {
return createEdge(gm, newNode, RIGHT);
}
private Object createEdge(MutableGraphModel gm, Object newNode, int index) {
if (isReverseEdge(index)) {
newEdge = gm.connect(
newNode, getContent().getOwner(), getNewEdgeType(index));
} else {
newEdge = gm.connect(
getContent().getOwner(), newNode, getNewEdgeType(index));
}
return newEdge;
}
protected Object createEdgeToSelf(MutableGraphModel gm) {
Object edge = gm.connect(
getContent().getOwner(), getContent().getOwner(),
getNewEdgeType(LOWER_LEFT));
return edge;
}
/**
* Get array of icons to use when drawing handles.
* @return icon or null
*/
protected abstract Icon[] getIcons();
/**
* Get the "instructions" string to pass to GEF for the given handle number.
*
* @param index
* handle number that is being dragged from
* @return string or null
*/
protected abstract String getInstructions(int index);
/**
* Get the node type to create when dragging from the given handle number.
*
* @param index
* handle number that is being dragged from
* @return metatype for model element. Null to disallow drag.
*/
protected abstract Object getNewNodeType(int index);
/**
* Get the edge type to create when dragging from the given handle number.
*
* @param index
* handle number that is being dragged from
* @return metatype for model element. Null to disallow drag.
*/
protected abstract Object getNewEdgeType(int index);
/**
* Get the node type to create when dragging from the given handle number.
*
* @param index
* handle number that is being dragged from
* @return true to reverse direction of assocation from direction of drag.
* eg. specialization instead of generalization. Default
* implementation always returns false.
*/
protected boolean isReverseEdge(int index) {
return false;
}
/**
* Get the draggability of a particular handle. Default implementation
* always returns true. Override to return false for handles which shouldn't
* be draggable (i.e. they only support clicks, not drags).
*
* @param index
* handle index to check draggability for
* @return true if this handle is draggable, false otherwise
*/
protected boolean isDraggableHandle(int index) {
return true;
}
/**
* Request post processing of edge by GEF after it is created using
* {@link ModeCreateEdgeAndNode#ModeCreateEdgeAndNode(Editor, Object, Object, boolean)}
*
* @return true if postprocessing requested
*/
protected boolean isEdgePostProcessRequested() {
return false;
}
/**
* @param newEdge the new edge to post-process
*/
protected void postProcessEdge2(Object newEdge) {
// do nothing by default
}
/**
* @return index of last button/handle that was clicked
*/
protected int getButton() {
return button;
}
}