package org.enhydra.jawe.components.graph; import java.awt.Graphics; import java.awt.Point; import java.awt.Rectangle; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.io.Serializable; import java.util.Map; import javax.swing.Action; import javax.swing.KeyStroke; import javax.swing.SwingUtilities; import org.enhydra.jawe.JaWEManager; import org.enhydra.jawe.base.controller.JaWEActions; import org.enhydra.shark.xpdl.XMLElement; import org.enhydra.shark.xpdl.XPDLConstants; import org.enhydra.shark.xpdl.elements.Activity; import org.jgraph.JGraph; import org.jgraph.event.GraphLayoutCacheEvent; import org.jgraph.event.GraphLayoutCacheListener; import org.jgraph.event.GraphModelEvent; import org.jgraph.event.GraphModelListener; import org.jgraph.graph.CellHandle; import org.jgraph.graph.CellView; import org.jgraph.graph.DefaultGraphModel; import org.jgraph.graph.GraphConstants; import org.jgraph.graph.GraphContext; import org.jgraph.graph.GraphLayoutCache; import org.jgraph.plaf.basic.BasicGraphUI; /** * This class and it's inner classes controls mouse actions and clipboard. * It is addapted to get wanted editing cell behaviour, selection behaviour * , to implement cell overlaping, to implement right participant adjustment * after cell (or group of cells) is moved, and to implement proper copying * and pasting/cloning of cells, as well as pasting at wanted location (along * with right participant adjustment). */ public class JaWEGraphUI extends BasicGraphUI { public final static int SELECTION = 0; public final static int MULTIPLE_SELECTION = 1; public final static int INSERT_ELEMENT = 2; public final static int INSERT_PARTICIPANT = 3; public final static int INSERT_SPEC_ELEMENT = 4; public final static int INSERT_TRANSITION_START = 5; public final static int INSERT_TRANSITION_POINTS = 6; protected int status; protected boolean aborted = false; protected boolean selectOnRelease = false; protected GraphActivityInterface gai; /** * Returns graph. */ public Graph getGraph() { return (Graph) graph; } public GraphController getGraphController() { return ((GraphMarqueeHandler) marquee).getGraphController(); } /** * Paint the background of this graph. Calls paintGrid. */ protected void paintBackground(Graphics g) { Rectangle pageBounds = new Rectangle(0, 0, graph.getWidth(), graph.getHeight()); if (graph.isGridVisible()) { paintGrid(graph.getGridSize(), g, pageBounds); } } /** * This method is called by EditAction class, as well as by * pressing F2 or clicking a mouse on a cell. */ protected boolean startEditing(Object cell, MouseEvent event) { if (cell instanceof WorkflowElement) { XMLElement el = ((WorkflowElement) cell).getPropertyObject(); if (el instanceof Activity) { Activity act = (Activity) el; if (act.getActivityType() == XPDLConstants.ACTIVITY_TYPE_SUBFLOW) { //CUSTOM JaWEManager.getInstance().getJaWEController().getJaWEActions().getAction(JaWEActions.EDIT_PROPERTIES_ACTION).actionPerformed(null); //END CUSTOM return true; } else if (act.getActivityType() == XPDLConstants.ACTIVITY_TYPE_BLOCK) { //CUSTOM JaWEManager.getInstance().getJaWEController().getJaWEActions().getAction(JaWEActions.EDIT_PROPERTIES_ACTION).actionPerformed(null); //END CUSTOM return true; } } else if (el instanceof FreeTextExpressionParticipant || el instanceof CommonExpressionParticipant) { return true; } JaWEManager.getInstance().getJaWEController().getJaWEActions().getAction(JaWEActions.EDIT_PROPERTIES_ACTION).actionPerformed(null); return true; } return false; } // FIXED by xxp - there was a problem when zooming-out activity public void startEditingAtCell(JGraph pGraph, Object cell) { if (cell != null) { startEditing(cell, null); } } /** * Creates the listener responsible for updating the selection based on * mouse events. */ protected MouseListener createMouseListener() { return new PEMouseHandler(); } /** * Handles selection in a way that we expect. */ public class PEMouseHandler extends MouseHandler { public void mousePressed(MouseEvent e) { if (!graph.hasFocus()) { graph.requestFocus(); } aborted = false; selectOnRelease = false; if (status == SELECTION && graph.isSelectionEnabled()) { // find where was clicked... int s = graph.getTolerance(); Rectangle2D r = graph.fromScreen(new Rectangle(e.getX() - s, e.getY() - s, 2 * s, 2 * s)); focus = (focus != null && focus.intersects(graph, r)) ? focus : null; Point2D point = graph.fromScreen(new Point(e.getPoint())); // changed from original because of overlapping if (focus == null) { cell = graph.getNextViewAt(focus, point.getX(), point.getY()); focus = cell; } else { cell = focus; } // if it's right mouse button show popup menu, otherwise user whish to select something if (SwingUtilities.isRightMouseButton(e)) { // POPUP if (cell != null) { if (!graph.isCellSelected(cell.getCell())) { selectCellForEvent(cell.getCell(), e); } } else { graph.clearSelection(); } ((GraphMarqueeHandler) marquee).popupMenu(e.getPoint()); } else { // SIMPLE SELECTION if (cell != null) { if (e.getClickCount() == 2) { startEditing(cell.getCell(), e); } else { if (graph.isCellSelected(cell.getCell()) && !e.isControlDown() && graph.getSelectionCells().length > 1) { selectOnRelease = true; } else { if (!graph.isCellSelected(cell.getCell()) || e.isControlDown()) { selectCellForEvent(cell.getCell(), e); } } if (handle != null) { handle.mousePressed(e); } } } else { // MULTIPLE SELECTION marquee.mousePressed(e); status = MULTIPLE_SELECTION; } } } if (SwingUtilities.isRightMouseButton(e)) { if (status == INSERT_TRANSITION_POINTS) { status = INSERT_TRANSITION_START; ((GraphMarqueeHandler) marquee).reset(); } else { ((GraphMarqueeHandler) marquee).setSelectionMode(); status = SELECTION; } } else { if (graph.isEditable()) { if (status == INSERT_PARTICIPANT) { // maybe latter we can add inserting participants where user choose, not at end... ((GraphMarqueeHandler) marquee).insertParticipant(); } else if (status == INSERT_SPEC_ELEMENT) { // this is reserved for special buttons... like block or process. Maybe we should move them somewhere. ((GraphMarqueeHandler) marquee).insertSpecialElement(); } else if (status == INSERT_ELEMENT) { ((GraphMarqueeHandler) marquee).insertElement((Point) getGraph().fromScreen(e.getPoint())); } else if (status == INSERT_TRANSITION_START) { GraphPortViewInterface gpvi = (GraphPortViewInterface) graph.getPortViewAt(e.getX(), e.getY()); if (gpvi != null) { if (((GraphMarqueeHandler) marquee).insertTransitionFirstPort(gpvi)) { status = INSERT_TRANSITION_POINTS; gai = gpvi.getGraphActivity(); } } } else if (status == INSERT_TRANSITION_POINTS) { GraphPortViewInterface gpvi = (GraphPortViewInterface) graph.getPortViewAt(e.getX(), e.getY()); if (gpvi == null) { ((GraphMarqueeHandler) marquee).addPoint(e.getPoint()); ((GraphMarqueeHandler) marquee).drawTransition(e); } else { if (((GraphMarqueeHandler) marquee).insertTransitionSecondPort(gpvi)) { status = INSERT_TRANSITION_START; ((GraphMarqueeHandler) marquee).reset(); } } } } else { // maybe display info... external package so can't be edited... } } e.consume(); } public void mouseMoved(MouseEvent e) { if (status == INSERT_TRANSITION_START || status == INSERT_TRANSITION_POINTS) { ((GraphMarqueeHandler) marquee).drawTransition(e); } e.consume(); } public void mouseDragged(MouseEvent e) { if (status == SELECTION && !aborted) { // added - if one of selected cell is Participant there must be no dragging Object[] sc = graph.getSelectionCells(); if (sc != null) { for (int i = 0; i < sc.length; i++) { if (sc[i] instanceof GraphParticipantInterface) { e.consume(); return; } } } selectOnRelease = false; autoscroll(graph, e.getPoint()); if (handle != null) { handle.mouseDragged(e); } } else if (status == MULTIPLE_SELECTION && !aborted) { // drag rectangle for multiply selection marquee.mouseDragged(e); } // CUSTOM if (status == INSERT_TRANSITION_START) { insertTransitionStart(e.getX(), e.getY()); } else if (status == INSERT_TRANSITION_POINTS) { ((GraphMarqueeHandler) marquee).drawTransition(e); } // END CUSTOM e.consume(); } public void mouseReleased(MouseEvent e) { // CUSTOM if (status == INSERT_TRANSITION_POINTS) { GraphPortViewInterface gpvi = (GraphPortViewInterface) graph.getPortViewAt(e.getX(), e.getY()); if (gpvi != null) { if (!gai.equals(gpvi.getGraphActivity())) { if (((GraphMarqueeHandler) marquee).insertTransitionSecondPort(gpvi)) { status = INSERT_TRANSITION_START; ((GraphMarqueeHandler) marquee).reset(); } } } } // END CUSTOM if (status == SELECTION) { if (handle != null && !aborted) { handle.mouseReleased(e); } if (selectOnRelease) { selectCellForEvent(cell.getCell(), e); } } else if (status == MULTIPLE_SELECTION && graph.isSelectionEnabled() && !aborted) { marquee.mouseReleased(e); status = SELECTION; } e.consume(); } } // CUSTOM: extracted out this method for reuse by GraphControllerPanel public void insertTransitionStart(int x, int y) { GraphPortViewInterface gpvi = (GraphPortViewInterface) graph.getPortViewAt(x, y); if (gpvi != null) { if (((GraphMarqueeHandler) marquee).insertTransitionFirstPort(gpvi)) { status = INSERT_TRANSITION_POINTS; } } } // END CUSTOM /** * Constructs the "root handle" for <code>context</code>. * * @param context * reference to the context of the current selection. */ public CellHandle createHandle(GraphContext context) { if (context != null && !context.isEmpty() && graph.isEnabled()) { return new PERootHandle(context); } return null; } /** * Manages selection movement. It is adapted to suport proper * undo in coordination with WorkflowManager class. */ public class PERootHandle extends RootHandle { /** * Creates a root handle which contains handles for the given * cells. The root handle and all its childs point to the * specified JGraph instance. The root handle is responsible * for dragging the selection. */ public PERootHandle(GraphContext ctx) { super(ctx); } protected Point2D getInitialLocation(Object[] cells) { try { return super.getInitialLocation(cells); } catch (Throwable thr) { return null; } } public void mouseReleased(MouseEvent event) { if (event != null && !event.isConsumed()) { if (activeHandle != null) { activeHandle.mouseReleased(event); activeHandle = null; } else if (isMoving && !event.getPoint().equals(start)) { if (cachedBounds != null) { int dx = event.getX() - (int) start.getX();//HM, JGraph3.4.1 int dy = event.getY() - (int) start.getY();//HM, JGraph3.4.1 Point2D tmp = graph.fromScreen(new Point(dx, dy));//HM, JGraph3.4.1 GraphLayoutCache.translateViews(views, tmp.getX(), tmp.getY());//HM, JGraph3.4.1 } // Harald Meister: snap activities to grid if grid is enabled if (GraphUtilities.getGraphController().getGraphSettings().shouldShowGrid() && views[0] instanceof GraphActivityViewInterface) { GraphActivityViewInterface view = (GraphActivityViewInterface) views[0]; Rectangle2D rect = view.getBounds();//HM, JGraph3.4.1 int dx = 0; int dy = 0; int gridsize = GraphUtilities.getGraphController().getGraphSettings().getGridSize(); int deltax = (int) rect.getX() % gridsize; int deltay = (int) rect.getY() % gridsize; int halfgrid = gridsize / 2; if (deltax > halfgrid) { dx += (gridsize - deltax); } else { dx -= deltax; } if (deltay > halfgrid) { dy += (gridsize - deltay); } else { dy -= deltay; } Point2D tmp = graph.fromScreen(new Point(dx, dy));//HM, JGraph3.4.1 GraphLayoutCache.translateViews(views, tmp.getX(), tmp.getY());//HM, JGraph3.4.1 } // Harald Meister CellView[] all = graphLayoutCache.getAllDescendants(views); if (graph.isMoveable()) { // Move Cells Map propertyMap = GraphConstants.createAttributes(all, null); GraphManager gm = getGraph().getGraphManager(); gm.moveCellsAndArrangeParticipants(propertyMap); } event.consume(); } } start = null; } } /** * Returns a listener that can update the graph when the view changes. */ protected GraphLayoutCacheListener createGraphLayoutCacheListener() { return new PEGraphLayoutCacheHandler(); } /** * This class observes view changes and is adapted to disallow * deselection of cells after dragging. */ public class PEGraphLayoutCacheHandler extends GraphLayoutCacheHandler { /* * (non-Javadoc) * * @see org.jgraph.event.GraphLayoutCacheListener#graphLayoutCacheChanged(org.jgraph.event.GraphLayoutCacheEvent) */ public void graphLayoutCacheChanged(GraphLayoutCacheEvent e) { Object[] changed = e.getChange().getChanged(); if (changed != null && changed.length > 0) { for (int i = 0; i < changed.length; i++) { graph.updateAutoSize(graphLayoutCache.getMapping(changed[i], false)); } } Object[] inserted = e.getChange().getInserted(); if (inserted != null && inserted.length > 0 && graphLayoutCache.isSelectsLocalInsertedCells() && !(graphLayoutCache.isSelectsAllInsertedCells() && !graphLayoutCache.isPartial()) && graph.isEnabled()) { Object[] roots = DefaultGraphModel.getRoots(graphModel, inserted); if (roots != null && roots.length > 0) { focus = graphLayoutCache.getMapping(roots[0], false); graph.setSelectionCells(roots); } } updateSize(); } } /** * Listens for changes in the graph model and updates the view accordingly. */ public class PEGraphModelHandler implements GraphModelListener, Serializable { public void graphChanged(GraphModelEvent e) { Object[] removed = e.getChange().getRemoved(); // Remove from selection & focus if (removed != null && removed.length > 0) { // Update Focus If Necessary for (int i = 0; i < removed.length && focus != null; i++) { if (removed[i] == focus.getCell()) { focus = null; break; } } // Remove from selection graph.getSelectionModel().removeSelectionCells(removed); } if (graphLayoutCache != null) { graphLayoutCache.graphChanged(e.getChange()); } // Get arrays Object[] inserted = e.getChange().getInserted(); Object[] changed = e.getChange().getChanged(); // Insert if (inserted != null && inserted.length > 0) { // Update focus to first inserted cell focus = graphLayoutCache.getMapping(inserted[0], false); for (int i = 0; i < inserted.length; i++) { graph.updateAutoSize(graphLayoutCache.getMapping(inserted[i], false)); } } // Change (update size) if (changed != null && changed.length > 0) { for (int i = 0; i < changed.length; i++) { graph.updateAutoSize(graphLayoutCache.getMapping(changed[i], false)); } } // Select if not partial if (!graphLayoutCache.isPartial() && graphLayoutCache.isSelectsAllInsertedCells() && graph.isEnabled()) { graph.setSelectionCells(inserted); } updateSize(); } } // End of BasicGraphUI.GraphModelHandler public void reset() { status = ((GraphMarqueeHandler) marquee).getStatus(); } /** * Creates the listener reponsible for getting key events from the graph. */ protected KeyListener createKeyListener() { return new PEKeyHandler(); } /** * This is used to get multiple key down events to appropriately generate * events. */ public class PEKeyHandler extends KeyAdapter implements Serializable { /** Key code that is being generated for. */ protected Action repeatKeyAction; /** Set to true while keyPressed is active. */ protected boolean isKeyDown; public void keyPressed(KeyEvent e) { KeyStroke keyStroke = KeyStroke.getKeyStroke(e.getKeyCode(), e.getModifiers()); if (keyStroke.getKeyCode() == KeyEvent.VK_ESCAPE) { if (marquee != null) { marquee.mouseReleased(null); } ((GraphMarqueeHandler) marquee).setSelectionMode(); aborted = true; } } public void keyReleased(KeyEvent e) { } } /** * Paints the renderer of <code>view</code> to <code>g</code> at * <code>bounds</code>. Recursive implementation that paints the children * first. * <p> * The reciever should NOT modify <code>clipBounds</code>, or * <code>insets</code>. The <code>preview</code> flag is passed to the * renderer, and is not used here. */ public void paintCell(Graphics g, CellView view, Rectangle2D bounds, boolean preview) { super.paintCell(g, view, bounds, preview); } }