001: /*--
002:
003: Copyright (C) 2002 Anthony Eden.
004: All rights reserved.
005:
006: Redistribution and use in source and binary forms, with or without
007: modification, are permitted provided that the following conditions
008: are met:
009:
010: 1. Redistributions of source code must retain the above copyright
011: notice, this list of conditions, and the following disclaimer.
012:
013: 2. Redistributions in binary form must reproduce the above copyright
014: notice, this list of conditions, and the disclaimer that follows
015: these conditions in the documentation and/or other materials
016: provided with the distribution.
017:
018: 3. The names "OBE" and "Open Business Engine" must not be used to
019: endorse or promote products derived from this software without prior
020: written permission. For written permission, please contact
021: me@anthonyeden.com.
022:
023: 4. Products derived from this software may not be called "OBE" or
024: "Open Business Engine", nor may "OBE" or "Open Business Engine"
025: appear in their name, without prior written permission from
026: Anthony Eden (me@anthonyeden.com).
027:
028: In addition, I request (but do not require) that you include in the
029: end-user documentation provided with the redistribution and/or in the
030: software itself an acknowledgement equivalent to the following:
031: "This product includes software developed by
032: Anthony Eden (http://www.anthonyeden.com/)."
033:
034: THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
035: WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
036: OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
037: DISCLAIMED. IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT,
038: INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
039: (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
040: SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
041: HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
042: STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
043: IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
044: POSSIBILITY OF SUCH DAMAGE.
045:
046: For more information on OBE, please see <http://obe.sourceforge.net/>.
047:
048: */
049:
050: package org.obe.designer;
051:
052: import com.anthonyeden.lib.gui.StandardAction;
053: import com.jgraph.JGraph;
054: import com.jgraph.graph.*;
055: import java.awt.*;
056: import java.awt.event.KeyAdapter;
057: import java.awt.event.KeyEvent;
058: import java.awt.event.MouseAdapter;
059: import java.awt.event.MouseEvent;
060: import java.util.*;
061: import java.util.List;
062: import java.beans.PropertyVetoException;
063: import javax.swing.*;
064: import javax.swing.border.BevelBorder;
065: import org.apache.commons.logging.Log;
066: import org.apache.commons.logging.LogFactory;
067: import org.obe.designer.dialog.EditActivityDialog;
068: import org.obe.designer.dialog.EditTransitionDialog;
069: import org.obe.designer.dialog.NewActivityDialog;
070: import org.obe.designer.editor.ImplementationEditor;
071: import org.obe.designer.editor.SubFlowEditor;
072: import org.obe.designer.editor.ToolSetEditor;
073: import org.obe.designer.event.ConnectMouseListener;
074: import org.obe.designer.util.EditMode;
075: import org.obe.designer.util.UUID;
076: import org.obe.designer.view.ActivityView;
077: import org.obe.designer.view.TransitionView;
078: import org.obe.xpdl.model.activity.Activity;
079: import org.obe.xpdl.model.activity.Implementation;
080: import org.obe.xpdl.model.activity.SubFlow;
081: import org.obe.xpdl.model.activity.ToolSet;
082: import org.obe.xpdl.model.transition.*;
083: import org.obe.xpdl.model.workflow.WorkflowProcess;
084: import org.obe.util.ErrorHandler;
085:
086: /**
087: * Extension of the JGraph component which implements features for displaying an
088: * OBE workflow graph.
089: *
090: * @author Anthony Eden
091: */
092: public class OBEGraph extends JGraph {
093:
094: private static final Log log = LogFactory.getLog(OBEGraph.class);
095:
096: private static final Point START_POINT = new Point(100, 100);
097: private static final Dimension DEFAULT_MINIMUM_SIZE = new Dimension(
098: 300, 200);
099: private static final int X_BASE = 50;
100: private static final int X_WIDTH = 140;
101: private static final int Y_WIDTH = 50;
102:
103: private Point lastLocation = START_POINT;
104: private Designer parent;
105: private OBEGraphModel graphModel;
106: private WorkflowProcess workflowProcess;
107: // private List activityColumns = new ArrayList();
108: private Map activityViews = new HashMap();
109: private Map actions;
110: private Map editors;
111:
112: private ConnectMouseListener connectMouseListener;
113: private static final Object[] EMPTY_OBJECTS = new Object[0];
114:
115: /**
116: * Construct a new OBEGraph with the given OBEGraphModel.
117: *
118: * @param graphModel The OBEGraphModel
119: */
120: public OBEGraph(Designer parent, OBEGraphModel graphModel) {
121: super (graphModel);
122:
123: this .parent = parent;
124: this .graphModel = graphModel;
125:
126: connectMouseListener = new ConnectMouseListener(this );
127:
128: initActions();
129: initEditors();
130:
131: setMinimumSize(DEFAULT_MINIMUM_SIZE);
132: setCloneable(false);
133: setDisconnectable(false);
134: setBendable(false);
135:
136: getView().addObserver(new ViewObserver());
137:
138: addKeyListener(new KeyAdapter() {
139: public void keyPressed(KeyEvent evt) {
140: try {
141: switch (evt.getKeyCode()) {
142: case KeyEvent.VK_DELETE:
143: delete();
144: break;
145: case KeyEvent.VK_ENTER:
146: edit();
147: evt.consume();
148: break;
149: case KeyEvent.VK_ESCAPE:
150: connectMouseListener.reset();
151: break;
152: }
153: } catch (PropertyVetoException e) {
154: ErrorHandler.handleError(e);
155: }
156: }
157: });
158:
159: addMouseListener(new MouseAdapter() {
160: public void mousePressed(MouseEvent evt) {
161: if (evt.isPopupTrigger()) {
162: showPopup(evt.getPoint());
163: }
164: }
165:
166: public void mouseReleased(MouseEvent evt) {
167: if (evt.isPopupTrigger()) {
168: showPopup(evt.getPoint());
169: }
170: }
171: });
172:
173: // add a listener for connect mode
174: addMouseListener(connectMouseListener);
175: }
176:
177: public EditMode getEditMode() {
178: return parent.getEditMode();
179: }
180:
181: /**
182: * Get the action map for the graph.
183: *
184: * @return The action map
185: */
186: public Map getActions() {
187: return actions;
188: }
189:
190: /**
191: * Show the graph popup menu at the specified location.
192: *
193: * @param p The popup location
194: */
195: public void showPopup(Point p) {
196: log.debug("Showing popup at " + p.x + ',' + p.y);
197:
198: JPopupMenu popup = null;
199: switch (getEditMode().value()) {
200: case EditMode.EDIT_INT:
201: lastLocation = p;
202: popup = new OBEGraphActivityModePopup(parent, this ,
203: getFirstCellForLocation(p.x, p.y));
204: break;
205: case EditMode.CONNECT_INT:
206: break;
207: case EditMode.VIEW_INT:
208: break;
209: }
210:
211: if (popup != null) {
212: popup.show(this , p.x, p.y);
213: }
214: }
215:
216: public void addActivity(Activity activity,
217: ImplementationEditor editor) {
218: log.info("Adding activity " + activity.getName() + " to graph");
219:
220: ActivityView activityView = new ActivityView(activity, editor);
221: OBEGraphCell cell = new OBEGraphCell(activityView);
222:
223: activityViews.put(activity.getId(), activityView);
224:
225: // set the view's corresponding cell
226: activityView.setCell(cell);
227:
228: // check for bounds values in the extended attributes
229: Rectangle bounds = activity.getBounds();
230:
231: // add the cell to the graph
232: addCell(cell, bounds != null ? bounds : new Rectangle(
233: lastLocation.x, lastLocation.y, X_WIDTH, Y_WIDTH));
234: }
235:
236: /**
237: * Add a tool set activity to the graph.
238: */
239: public void addToolSet() throws PropertyVetoException {
240: ImplementationEditor editor = new ToolSetEditor();
241: editor.setWorkflowPackage(parent.getWorkflowPackage());
242:
243: NewActivityDialog d = new NewActivityDialog(editor);
244: d.setTitle("New Activity : Tool Set");
245: if (d.showDialog() == NewActivityDialog.APPROVE_OPTION) {
246: Activity activity = d.getActivity(workflowProcess);
247: addActivity(activity, editor);
248: log.info("Adding activity to workflow process");
249: workflowProcess.add(activity);
250: }
251: }
252:
253: public void addSubFlow() {
254: int workflowProcessCount = parent.getWorkflowPackage()
255: .getWorkflowProcess().length;
256: log.debug("Workflow processes in package: "
257: + workflowProcessCount);
258:
259: if (workflowProcessCount <= 1) {
260: JOptionPane
261: .showMessageDialog(
262: null,
263: "You must have more than one process in the current workflow\n"
264: + "package in order to create a SubFlow activity.",
265: "Cannot Add SubFlow",
266: JOptionPane.ERROR_MESSAGE);
267: return;
268: }
269:
270: try {
271: ImplementationEditor editor = new SubFlowEditor();
272: editor.setWorkflowPackage(parent.getWorkflowPackage());
273: addActivity(editor, "New Activity : Sub Flow");
274: } catch (Throwable t) {
275: t.printStackTrace();
276: }
277: }
278:
279: public void addNoImplementation() {
280:
281: }
282:
283: public void addRoute() {
284:
285: }
286:
287: protected void addActivity(ImplementationEditor editor, String title)
288: throws PropertyVetoException {
289: NewActivityDialog d = new NewActivityDialog(editor);
290: d.setTitle(title);
291: if (d.showDialog() == NewActivityDialog.APPROVE_OPTION) {
292: Activity activity = d.getActivity(workflowProcess);
293: addActivity(activity, editor);
294: workflowProcess.add(activity);
295: }
296: }
297:
298: public void addTransition(Transition transition) {
299: String fromId = transition.getFrom();
300: String toId = transition.getTo();
301:
302: ActivityView fromView = findActivityViewById(fromId);
303: ActivityView toView = findActivityViewById(toId);
304:
305: connect(fromView, toView, transition);
306: }
307:
308: /**
309: * This form of the addTransition method actually adds the new transition to
310: * the workflow processes transition list.
311: *
312: * @param from The source activity view
313: * @param to The target activity view
314: */
315: public void addTransition(ActivityView from, ActivityView to)
316: throws PropertyVetoException {
317: String fromId = from.getActivity().getId();
318: String toId = to.getActivity().getId();
319:
320: Transition transition = new Transition(UUID.generate(), "",
321: fromId, toId);
322: workflowProcess.add(transition);
323:
324: connect(from, to, transition);
325: }
326:
327: public void connect(ActivityView from, ActivityView to,
328: Transition transition) {
329: log.info("Adding transition from " + from + " to " + to);
330:
331: OBEGraphCell fromCell = (OBEGraphCell) from.getCell();
332: log.debug("From cell: " + fromCell);
333:
334: OBEGraphCell toCell = (OBEGraphCell) to.getCell();
335: log.debug("To Cell: " + toCell);
336:
337: connect(fromCell.getRightPort(), toCell.getLeftPort(),
338: transition);
339: }
340:
341: /**
342: * Edit the selected cell.
343: */
344: public void edit() {
345: try {
346: Object cell = getSelectionCell();
347: if (cell == null) {
348: return;
349: }
350:
351: if (cell instanceof OBEGraphCell) {
352: edit((OBEGraphCell) cell);
353: } else if (cell instanceof OBEEdge) {
354: edit((OBEEdge) cell);
355: }
356: } catch (Throwable t) {
357: t.printStackTrace();
358: }
359: }
360:
361: public void edit(OBEGraphCell graphCell) {
362: Object userObject = graphCell.getUserObject();
363: if (userObject == null) {
364: log.debug("User object is null");
365: return;
366: }
367:
368: log.debug("User object type: " + userObject.getClass());
369: if (userObject instanceof ActivityView) {
370: ActivityView activityView = (ActivityView) userObject;
371: Activity activity = activityView.getActivity();
372: ImplementationEditor editor = activityView.getEditor();
373: Class editorClass = editor == null ? null : editor
374: .getClass();
375: log.info("Editing " + activity.getName() + " with editor "
376: + editorClass);
377:
378: editor.setWorkflowPackage(parent.getWorkflowPackage());
379: EditActivityDialog d = new EditActivityDialog(this ,
380: activity, editor);
381: d.showDialog();
382: repaint();
383: }
384: }
385:
386: public void edit(OBEEdge edge) {
387: Object userObject = edge.getUserObject();
388: log.debug("User object type: " + userObject.getClass());
389: if (userObject instanceof TransitionView) {
390: TransitionView transitionView = (TransitionView) userObject;
391: Transition transition = transitionView.getTransition();
392: EditTransitionDialog d = new EditTransitionDialog(
393: transition);
394: d.showDialog();
395: repaint();
396: }
397: }
398:
399: /**
400: * Delete the selected cell.
401: */
402: public void delete() throws PropertyVetoException {
403: Object cell = getSelectionCell();
404: if (cell == null) {
405: return;
406: }
407:
408: if (cell instanceof OBEGraphCell) {
409: delete((OBEGraphCell) cell);
410: } else if (cell instanceof OBEEdge) {
411: delete((OBEEdge) cell);
412: }
413: }
414:
415: public void delete(OBEGraphCell graphCell)
416: throws PropertyVetoException {
417: List cellsToRemove = new ArrayList();
418:
419: ActivityView activityView = (ActivityView) graphCell
420: .getUserObject();
421: Activity activity = activityView.getActivity();
422:
423: if (JOptionPane.showConfirmDialog(this ,
424: "Are you sure you want to delete the activity "
425: + activity.getName() + '?', "Are You Sure?",
426: JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) {
427:
428: log.info("Deleting " + activityView);
429:
430: cellsToRemove.add(graphCell);
431:
432: Object[] remove = cellsToRemove
433: .toArray(new Object[cellsToRemove.size()]);
434: graphModel.remove(remove);
435:
436: workflowProcess.remove(activity);
437: }
438: }
439:
440: public void delete(OBEEdge edge) throws PropertyVetoException {
441: log.debug("Deleting edge");
442:
443: ArrayList cellsToRemove = new ArrayList();
444:
445: TransitionView transitionView = (TransitionView) edge
446: .getUserObject();
447: Transition transition = transitionView.getTransition();
448:
449: if (JOptionPane.showConfirmDialog(this ,
450: "Are you sure you want to delete the transition "
451: + transition.getName() + '?', "Are You Sure?",
452: JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) {
453:
454: log.info("Deleting " + transitionView);
455:
456: cellsToRemove.add(edge);
457:
458: Object[] remove = cellsToRemove
459: .toArray(new Object[cellsToRemove.size()]);
460: graphModel.remove(remove);
461:
462: workflowProcess.remove(transition);
463: }
464: }
465:
466: /**
467: * Get the workflow process.
468: *
469: * @return The WorkflowProcess
470: */
471: public WorkflowProcess getWorkflowProcess() {
472: return workflowProcess;
473: }
474:
475: /**
476: * Set the WorkflowProcess.
477: *
478: * @param workflowProcess The new WorkflowProcess
479: */
480: public void setWorkflowProcess(WorkflowProcess workflowProcess) {
481: this .workflowProcess = workflowProcess;
482: graphModel.clear();
483:
484: Activity[] activities = workflowProcess.getActivity();
485: for (int i = 0; i < activities.length; i++) {
486: Activity activity = activities[i];
487: Implementation implementation = activity
488: .getImplementation();
489: Class implClass = implementation == null ? null
490: : implementation.getClass();
491: log.debug("Adding activity " + activity.getName()
492: + "; editor " + implClass);
493: addActivity(activity, (ImplementationEditor) editors
494: .get(implClass));
495: }
496:
497: Transition[] transitions = workflowProcess.getTransition();
498: for (int i = 0; i < transitions.length; i++) {
499: Transition transition = transitions[i];
500: log.debug("Adding transition " + transition.getName());
501: addTransition(transition);
502: }
503: }
504:
505: public void cleanUp() {
506: }
507:
508: public void selectAll() {
509: setSelectionCells(getRoots());
510: }
511:
512: public void selectNone() {
513: setSelectionCells(EMPTY_OBJECTS);
514: }
515:
516: public void updateTransitions(String oldId, String newId) {
517: Transition[] iter = workflowProcess.getTransition();
518: for (int i = 0; i < iter.length; i++) {
519: Transition t = iter[i];
520: if (t.getFrom().equals(oldId)) {
521: t.setFrom(newId);
522: }
523: if (t.getTo().equals(oldId)) {
524: t.setTo(newId);
525: }
526: }
527: }
528:
529: /**
530: * Set the join restriction for the currently selected activity.
531: *
532: * @param joinType The join type
533: */
534: public void setJoinRestriction(Activity activity, JoinType joinType)
535: throws PropertyVetoException {
536: Join join = new Join();
537: join.setType(joinType);
538: getTransitionRestriction(activity).setJoin(join);
539: }
540:
541: public void setSplitRestriction(Activity activity,
542: SplitType splitType) throws PropertyVetoException {
543: Split split = new Split();
544: split.setType(splitType);
545: getTransitionRestriction(activity).setSplit(split);
546: }
547:
548: protected TransitionRestriction getTransitionRestriction(
549: Activity activity) throws PropertyVetoException {
550:
551: TransitionRestriction[] transitionRestrictions = activity
552: .getTransitionRestriction();
553: TransitionRestriction[] restrictions = transitionRestrictions;
554: TransitionRestriction restriction = null;
555: if (restrictions != null && restrictions.length > 0)
556: restriction = restrictions[0];
557:
558: if (restriction == null) {
559: restriction = new TransitionRestriction();
560: activity.add(restriction);
561: }
562:
563: return restriction;
564: }
565:
566: /**
567: * Add the given cell to the graph with the given bounds.
568: *
569: * @param cell The graph cell
570: * @param bounds The bounding Rectangle
571: */
572: protected void addCell(OBEGraphCell cell, Rectangle bounds) {
573:
574: // Implementation note: The cell's ports are specified in the
575: // OBEGraphCell constructor. Each OBEGraphCell has a "left" port
576: // and a "right" port for incoming and outgoing transitioins,
577: // respectively.
578:
579: Map attributeMap = new HashMap();
580:
581: Map map = GraphConstants.createMap();
582:
583: GraphConstants.setBounds(map, bounds);
584: GraphConstants.setBorder(map, BorderFactory
585: .createBevelBorder(BevelBorder.RAISED));
586: GraphConstants.setOpaque(map, true);
587: GraphConstants.setBackground(map, Color.lightGray);
588:
589: attributeMap.put(cell, map);
590:
591: Object[] insert = { cell };
592: graphModel.insert(insert, null, null, attributeMap);
593: }
594:
595: protected void connect(Port source, Port target,
596: Transition transition) {
597: log.debug("Source: " + source);
598: log.debug("Target: " + target);
599:
600: if (isConnected(source, target)) {
601: log.debug("Source is already connected to target");
602: return;
603: }
604:
605: TransitionView view = new TransitionView(transition);
606:
607: ConnectionSet cs = new ConnectionSet();
608:
609: Map attributeMap = new HashMap();
610:
611: Map map = GraphConstants.createMap();
612: GraphConstants.setLineEnd(map, GraphConstants.SIMPLE);
613: GraphConstants.setEndFill(map, true);
614:
615: OBEEdge edge = new OBEEdge(view);
616: attributeMap.put(edge, map);
617: cs.connect(edge, source, target);
618:
619: Object[] insert = { edge };
620:
621: graphModel.insert(insert, cs, null, attributeMap);
622:
623: view.setCell(edge);
624: }
625:
626: protected void disconnectPort(Port port) {
627: log.debug("Disconnecting all edges on port " + port);
628: if (port != null) {
629: List edgesToRemove = new ArrayList();
630:
631: Iterator edges = graphModel.edges(port);
632: while (edges.hasNext()) {
633: Object edge = edges.next();
634: edgesToRemove.add(edge);
635: log.debug("Removing edge " + edge);
636: port.remove(edge);
637: }
638:
639: log.debug("Removing edges from model");
640: Object[] remove = edgesToRemove
641: .toArray(new Object[edgesToRemove.size()]);
642: graphModel.remove(remove);
643: }
644: }
645:
646: protected void shiftOverlappingRight(Object cell) {
647: Rectangle graphBounds = getBounds();
648: Rectangle bounds = getCellBounds(cell);
649: Object[] intersect;
650: int x = bounds.x + bounds.width;
651: int xShift = 0;
652:
653: List alreadyShifted = new ArrayList();
654:
655: // first shift all cells that intersect with the source cell
656: intersect = getRoots(bounds);
657: for (int i = 0; i < intersect.length; i++) {
658: if (intersect[i] == cell) {
659: continue;
660: }
661:
662: if (intersect[i] instanceof DefaultGraphCell) {
663: Rectangle intersectBounds = getCellBounds(intersect[i]);
664:
665: // the initial x
666: int ox = intersectBounds.x;
667:
668: // update the x
669: intersectBounds.x = x + X_BASE;
670:
671: // the diff betweeb the old and new x
672: int dx = intersectBounds.x - ox;
673:
674: // the shift is the max difference
675: if (dx > xShift) {
676: xShift = dx;
677: }
678:
679: alreadyShifted.add(intersect[i]);
680: }
681: }
682:
683: // now shift all cells which are to the right of the source cell
684: Rectangle clip = new Rectangle(x, 0, graphBounds.width - x,
685: graphBounds.height);
686:
687: intersect = getRoots(clip);
688: for (int i = 0; i < intersect.length; i++) {
689: if (alreadyShifted.contains(intersect[i])) {
690: continue;
691: }
692:
693: if (intersect[i] instanceof DefaultGraphCell) {
694: Rectangle intersectBounds = getCellBounds(intersect[i]);
695: intersectBounds.x += xShift;
696: }
697: }
698: }
699:
700: private void initActions() {
701: actions = new HashMap();
702: actions.put("graph.addToolSet", new StandardAction(this ,
703: "addToolSet"));
704: actions.put("graph.addSubFlow", new StandardAction(this ,
705: "addSubFlow"));
706: actions.put("graph.addNoImplementation", new StandardAction(
707: this , "addNoImplementation"));
708: actions.put("graph.addRoute", new StandardAction(this ,
709: "addRoute"));
710:
711: actions.put("graph.addTransition", new StandardAction(this ,
712: "addTransition"));
713:
714: actions.put("graph.edit", new StandardAction(this , "edit"));
715: actions.put("graph.delete", new StandardAction(this , "delete"));
716: }
717:
718: private void initEditors() {
719: editors = new HashMap();
720: editors.put(ToolSet.class, new ToolSetEditor());
721: editors.put(SubFlow.class, new SubFlowEditor());
722: }
723:
724: protected boolean isConnected(Port p1, Port p2) {
725: Iterator edges = p1.edges();
726: while (edges.hasNext()) {
727: Edge edge = (Edge) edges.next();
728: if (edge.getTarget() == p2) {
729: return true;
730: }
731: }
732: return false;
733: }
734:
735: protected ActivityView findActivityViewById(String id) {
736: return (ActivityView) activityViews.get(id);
737: }
738:
739: class ViewObserver implements Observer {
740: public void update(Observable o, Object arg) {
741: log.debug("View changed: " + o);
742: graphModel.setModified(true);
743: }
744: }
745: }
|