001: /*
002: * argun 1.0
003: * Web 2.0 delivery framework
004: * Copyright (C) 2007 Hammurapi Group
005: *
006: * This program is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation; either
009: * version 2 of the License, or (at your option) any later version.
010: *
011: * This program is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: *
016: * You should have received a copy of the GNU Lesser General Public
017: * License along with this library; if not, write to the Free Software
018: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019: *
020: * URL: http://www.hammurapi.biz
021: * e-Mail: support@hammurapi.biz
022: */
023: package biz.hammurapi.diagram;
024:
025: import java.awt.event.ActionEvent;
026: import java.awt.event.MouseEvent;
027: import java.awt.geom.Point2D;
028: import java.awt.geom.Rectangle2D;
029: import java.lang.reflect.Constructor;
030: import java.math.BigInteger;
031: import java.net.URL;
032: import java.util.ArrayList;
033: import java.util.Arrays;
034: import java.util.Hashtable;
035: import java.util.Iterator;
036: import java.util.List;
037: import java.util.Map;
038:
039: import javax.swing.AbstractAction;
040: import javax.swing.ImageIcon;
041: import javax.swing.JPopupMenu;
042: import javax.swing.JToolBar;
043:
044: import org.jgraph.JGraph;
045: import org.jgraph.graph.CellHandle;
046: import org.jgraph.graph.CellViewFactory;
047: import org.jgraph.graph.DefaultGraphModel;
048: import org.jgraph.graph.DefaultPort;
049: import org.jgraph.graph.EdgeView;
050: import org.jgraph.graph.GraphConstants;
051: import org.jgraph.graph.GraphContext;
052: import org.jgraph.graph.GraphLayoutCache;
053: import org.jgraph.graph.Port;
054:
055: import biz.hammurapi.diagram.data.Diagram;
056: import biz.hammurapi.diagram.data.DiagramDocument;
057: import biz.hammurapi.diagram.data.Point;
058:
059: public class DiagramModel extends DefaultGraphModel implements
060: Descriptable {
061:
062: private int elementIdCounter;
063: private int revision;
064: private String name;
065: private String description;
066: private JGraph graph;
067: private boolean isModified;
068: private boolean nestConnections;
069:
070: public synchronized int nextElementId() {
071: return elementIdCounter++;
072: }
073:
074: public DiagramModel(boolean nestConnections) {
075: this .nestConnections = nestConnections;
076: }
077:
078: /**
079: * Loads default diagram elements (if any).
080: */
081: public void loadDefault() {
082: // Override in subclasses.
083: }
084:
085: /**
086: * Loads diagram from XML
087: * @param data
088: */
089: public void load(biz.hammurapi.diagram.data.Diagram data) {
090: name = data.getName();
091: description = data.getDescription();
092: elementIdCounter = data.getElementIdCounter().intValue();
093: revision = data.getRevision().intValue();
094:
095: // Elements.
096: GraphLayoutCache glc = graph.getGraphLayoutCache();
097: biz.hammurapi.diagram.data.Cell[] ca = data.getCellArray();
098: for (int i = 0; i < ca.length; ++i) {
099: DiagramCell cell = (DiagramCell) createElement(ca[i]);
100: if (cell != null) {
101: Point2D point = new Point2D.Double(ca[i].getX(), ca[i]
102: .getY());
103:
104: Map cellAttributes = new Hashtable();
105: // Snap the Point to the Grid
106: point = graph.snap((Point2D) point.clone());
107:
108: ((DiagramElement) cell).setAttributes(cellAttributes);
109:
110: // Add a Bounds Attribute to the Map
111: GraphConstants.setBounds(cellAttributes,
112: new Rectangle2D.Double(point.getX(), point
113: .getY(), 40, 20));
114:
115: //GraphConstants.setBeginSize(cellAttributes, 5);
116:
117: cell.getAttributes().applyMap(cellAttributes);
118: // Insert the Vertex (including child port and attributes)
119: glc.insert(cell);
120: }
121: }
122:
123: biz.hammurapi.diagram.data.Edge[] ea = data.getEdgeArray();
124: for (int i = 0; i < ea.length; ++i) {
125: DiagramEdge edge = (DiagramEdge) createElement(ea[i]);
126: if (edge != null) {
127: Map edgeAttributes = new Hashtable();
128: ((DiagramElement) edge).setAttributes(edgeAttributes);
129:
130: edge.getAttributes().applyMap(edgeAttributes);
131:
132: Port source = ((DiagramCell) getDiagramElement(ea[i]
133: .getSource().intValue())).getPort(ea[i]
134: .getSourcePort());
135: Port target = ((DiagramCell) getDiagramElement(ea[i]
136: .getTarget().intValue())).getPort(ea[i]
137: .getSourcePort());
138:
139: if (source != null && target != null) {
140: // Insert the Edge and its Attributes
141: glc.insertEdge(edge, source, target);
142:
143: Point[] pa = ea[i].getPointArray();
144: if (pa != null && pa.length > 0) {
145: List allPoints = new ArrayList();
146: allPoints.add(source);
147: for (int j = 0; j < pa.length; ++j) {
148: allPoints.add(new Point2D.Double(pa[j]
149: .getX(), pa[j].getY()));
150: }
151: allPoints.add(target);
152: Map newAttributes = new Hashtable();
153: GraphConstants.setPoints(newAttributes,
154: allPoints);
155: glc.editCell(edge, newAttributes);
156: }
157: }
158: }
159: }
160:
161: Iterator it = new ArrayList(getRoots()).iterator();
162: while (it.hasNext()) {
163: Object next = it.next();
164: if (next instanceof DiagramCell) {
165: ((DiagramCell) next).connect();
166: }
167: }
168:
169: graph.repaint();
170: }
171:
172: public void setGraph(JGraph graph) {
173: this .graph = graph;
174: }
175:
176: public JGraph getGraph() {
177: return graph;
178: }
179:
180: void setModified() {
181: isModified = true;
182: }
183:
184: public Object createElement(
185: biz.hammurapi.diagram.data.DiagramElement data) {
186: if (data.getType() == null) {
187: if (data instanceof biz.hammurapi.diagram.data.Cell) {
188: return new DiagramCell(this ,
189: (biz.hammurapi.diagram.data.Cell) data);
190: }
191:
192: if (data instanceof biz.hammurapi.diagram.data.Edge) {
193: return new DiagramEdge(this ,
194: (biz.hammurapi.diagram.data.Edge) data);
195: }
196:
197: return null;
198: }
199:
200: try {
201: Class clazz = Class.forName(data.getType());
202: Constructor[] ca = clazz.getConstructors();
203: Constructor candidate = null;
204: for (int i = 0; i < ca.length; ++i) {
205: Class[] pt = ca[i].getParameterTypes();
206: if (pt.length == 2 && pt[0].isInstance(this )
207: && pt[1].isInstance(data)) {
208: if (candidate == null) {
209: candidate = ca[i];
210: } else if (candidate.getParameterTypes()[0]
211: .isAssignableFrom(pt[0])
212: && candidate.getParameterTypes()[1]
213: .isAssignableFrom(pt[1])) {
214: candidate = ca[i];
215: }
216: }
217: }
218:
219: if (candidate == null) {
220: System.err.println("[WARNING] Cannot instantiate "
221: + data.getType()
222: + " - no constructor which takes "
223: + this .getClass());
224: return null;
225: }
226:
227: return candidate.newInstance(new Object[] { this , data });
228: } catch (Exception e) {
229: System.err
230: .println("[WARNING] Cannot create diagram element:"
231: + e);
232: e.printStackTrace();
233: return null;
234: }
235: }
236:
237: public String getName() {
238: return name;
239: }
240:
241: public String getDescription() {
242: return description;
243: }
244:
245: public void setName(String name) {
246: this .name = name;
247:
248: }
249:
250: public void setDescription(String description) {
251: this .description = description;
252: }
253:
254: protected Object cloneCell(Object cellObj) {
255: // TODO - Give cloned objects new name, new model ID and clear external ID's
256: Object ret = super .cloneCell(cellObj);
257: if (ret instanceof DiagramCell) {
258: // ((DiagramCell) ret).setId(nextElementId());
259: }
260: return ret;
261: }
262:
263: public biz.hammurapi.diagram.data.DiagramDocument getData() {
264: DiagramDocument ret = DiagramDocument.Factory.newInstance();
265: Diagram diagram = ret.addNewDiagram();
266: diagram.setName(getName());
267: diagram.setDescription(getDescription());
268: diagram.setElementIdCounter(new BigInteger(String
269: .valueOf(elementIdCounter)));
270: diagram.setRevision(new BigInteger(String
271: .valueOf(isModified ? revision + 1 : revision)));
272:
273: Iterator it = getRoots().iterator();
274: while (it.hasNext()) {
275: Object next = it.next();
276: if (next instanceof DiagramCell) {
277: storeCell(diagram, next);
278: } else if (next instanceof DiagramEdge) {
279: storeEdge(diagram, next);
280: }
281: }
282: return ret;
283: }
284:
285: protected void storeCell(Diagram diagram, Object next) {
286: biz.hammurapi.diagram.data.Cell cellData = diagram.addNewCell();
287: ((DiagramCell) next).store(graph, cellData, nestConnections);
288: }
289:
290: protected void storeEdge(Diagram diagram, Object next) {
291: if (!nestConnections) {
292: biz.hammurapi.diagram.data.Edge edgeData = diagram
293: .addNewEdge();
294: ((DiagramEdge) next)
295: .store(graph, edgeData, nestConnections);
296: }
297: }
298:
299: public DiagramElement getDiagramElement(int id) {
300: Iterator it = getRoots().iterator();
301: while (it.hasNext()) {
302: Object next = it.next();
303: if (next instanceof DiagramElement
304: && ((DiagramElement) next).getId() == id) {
305: return (DiagramElement) next;
306: }
307: }
308: return null;
309: }
310:
311: public DiagramEdge createEdge(DefaultPort source, DefaultPort target) {
312: if (source == null || target == null || source == target) {
313: return null;
314: }
315:
316: return new DiagramEdge(this , null);
317: }
318:
319: public void populatePopupMenu(final DiagramApplet applet,
320: final java.awt.Point pt, JPopupMenu menu) {
321: menu.add(new AbstractAction("Insert") {
322: public void actionPerformed(ActionEvent ev) {
323: applet.insert(pt, new DiagramCell(DiagramModel.this ,
324: null));
325: }
326: });
327: }
328:
329: public void populateToolbar(final DiagramApplet applet,
330: JToolBar toolbar) {
331: // Insert
332: URL insertUrl = getClass().getClassLoader().getResource(
333: "biz/hammurapi/web/interaction/resources/insert.gif");
334: ImageIcon insertIcon = new ImageIcon(insertUrl);
335: toolbar.add(new AbstractAction("Insert element", insertIcon) {
336: public void actionPerformed(ActionEvent e) {
337: applet.insert(new java.awt.Point(20, 20),
338: new DiagramCell(DiagramModel.this , null)); // TODO - iterate over cells to avoid inserting step over other steps.
339: }
340: });
341:
342: }
343:
344: /**
345: * When cell is removed all edges connecting to the cell are also removed.
346: */
347: public void remove(Object[] roots) {
348: List toRemove = new ArrayList();
349: for (int i = 0; i < roots.length; ++i) {
350: if (roots[i] instanceof DefaultPort) {
351: toRemove.addAll(((DefaultPort) roots[i]).getEdges());
352: }
353: }
354:
355: toRemove.addAll(Arrays.asList(roots));
356: super .remove(toRemove.toArray());
357: }
358:
359: /**
360: * Validates model and returns message describing problems or null if the model is OK.
361: * @return Problems description or null if there are no problems.
362: */
363: public String validate() {
364: return DiagramCell.isBlank(getName()) ? "Diagram name shall not be blank"
365: : null;
366: }
367:
368: // Defines a EdgeHandle that uses the Shift-Button (Instead of the Right
369: // Mouse Button, which is Default) to add/remove point to/from an edge.
370: public static class DiagramEdgeHandle extends EdgeView.EdgeHandle {
371:
372: /**
373: * @param edge
374: * @param ctx
375: */
376: public DiagramEdgeHandle(EdgeView edge, GraphContext ctx) {
377: super (edge, ctx);
378: }
379:
380: // Override Superclass Method
381: public boolean isAddPointEvent(MouseEvent event) {
382: // Points are Added using Shift-Click
383: return event.isShiftDown();
384: }
385:
386: // Override Superclass Method
387: public boolean isRemovePointEvent(MouseEvent event) {
388: // Points are Removed using Shift-Click
389: return event.isShiftDown();
390: }
391:
392: }
393:
394: public CellViewFactory getCellViewFactory() {
395: return new biz.hammurapi.diagram.DiagramCellViewFactory() {
396:
397: // Override Superclass Method to Return Custom EdgeView
398: protected EdgeView createEdgeView(Object cell) {
399:
400: // Return Custom EdgeView
401: return new EdgeView(cell) {
402:
403: /**
404: * Returns a cell handle for the view.
405: */
406: public CellHandle getHandle(GraphContext context) {
407: return new DiagramEdgeHandle(this , context);
408: }
409:
410: };
411: }
412: };
413: }
414:
415: /**
416: * Checks if selected cells can be deleted from the model.
417: * @param selectionCells
418: * @return
419: */
420: public boolean canDelete(Object[] selectionCells) {
421: return true;
422: }
423:
424: }
|