001: /*
002: * Copyright 2001-2006 C:1 Financial Services GmbH
003: *
004: * This software is free software; you can redistribute it and/or
005: * modify it under the terms of the GNU Lesser General Public
006: * License Version 2.1, as published by the Free Software Foundation.
007: *
008: * This software is distributed in the hope that it will be useful,
009: * but WITHOUT ANY WARRANTY; without even the implied warranty of
010: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
011: * Lesser General Public License for more details.
012: *
013: * You should have received a copy of the GNU Lesser General Public
014: * License along with this library; if not, write to the Free Software
015: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA
016: */
017:
018: package de.finix.contelligent.client.gui.action;
019:
020: import java.awt.BorderLayout;
021: import java.awt.Color;
022: import java.awt.Dimension;
023: import java.awt.Graphics;
024: import java.awt.Graphics2D;
025: import java.awt.Rectangle;
026: import java.awt.RenderingHints;
027: import java.awt.dnd.DnDConstants;
028: import java.awt.event.MouseEvent;
029: import java.awt.event.MouseListener;
030: import java.awt.geom.Line2D;
031: import java.awt.image.BufferedImage;
032: import java.util.Enumeration;
033: import java.util.HashMap;
034: import java.util.Iterator;
035: import java.util.List;
036: import java.util.Vector;
037: import java.util.logging.Level;
038: import java.util.logging.Logger;
039:
040: import javax.swing.ImageIcon;
041: import javax.swing.JFrame;
042: import javax.swing.JLabel;
043: import javax.swing.JOptionPane;
044:
045: import de.finix.contelligent.client.base.ComponentFactory;
046: import de.finix.contelligent.client.base.ComponentNotFoundException;
047: import de.finix.contelligent.client.base.ComponentPath;
048: import de.finix.contelligent.client.base.ContelligentComponent;
049: import de.finix.contelligent.client.base.Type;
050: import de.finix.contelligent.client.event.ContelligentEvent;
051: import de.finix.contelligent.client.gui.AbstractComponentEditor;
052: import de.finix.contelligent.client.i18n.Resources;
053: import de.finix.contelligent.client.util.ExceptionDialog;
054: import de.finix.contelligent.client.util.dnd.ComponentDropEvent;
055: import de.finix.contelligent.client.util.dnd.ComponentDropListener;
056: import de.finix.contelligent.client.util.dnd.JPanelDND;
057:
058: public class RoutingEditor extends AbstractComponentEditor implements
059: MouseListener, ComponentDropListener {
060: private static Logger logger = Logger.getLogger(RoutingEditor.class
061: .getName());
062:
063: public final static String ROUTING_FOLDER = "routing";
064:
065: BufferedImage bufferedSitemap;
066:
067: HashMap<String, Node> nodes = null;
068:
069: Node rootNode;
070:
071: String rootName = "root";
072:
073: static int HORIZONTAL_GAP = 120;
074:
075: static int VERTICAL_GAP = 40;
076:
077: static int LINK_GAP = 25;
078:
079: static int ARROW_LENGTH = 3;
080:
081: int width, height;
082:
083: public void init() {
084: setLayout(null);
085: nodes = new HashMap<String, Node>();
086: buildSitemap();
087: // only rootNode is visible:
088: rootNode.setVisible(true);
089: calculateSitemap(isEditable());
090: setPreferredSize(new Dimension(width, height));
091: addMouseListener(this );
092: revalidate();
093: repaint();
094: }
095:
096: public void setRootName(String name) {
097: rootName = name;
098: }
099:
100: public void update() {
101: nodes = new HashMap<String, Node>();
102: removeAll();
103: buildSitemap();
104: // only rootNode is visible:
105: rootNode.setVisible(true);
106: calculateSitemap(isEditable());
107: revalidate();
108: repaint();
109: }
110:
111: public void rollback() {
112: List subcomponents = getComponent().getSubcomponents();
113: for (int i = 0; i < subcomponents.size(); i++) {
114: try {
115: ContelligentComponent subcomponent = ComponentFactory
116: .getInstance()
117: .getComponent(
118: getComponent().getPath() + "/"
119: + (String) subcomponents.get(i));
120: ComponentFactory.getInstance().reload(subcomponent);
121: } catch (ComponentNotFoundException cnfe) {
122: logger.log(Level.SEVERE, "Could not rollback: ", cnfe);
123: }
124: }
125: super .rollback();
126: }
127:
128: public void commit() {
129: ComponentFactory.getInstance().save(getComponent());
130: List subcomponents = getComponent().getSubcomponents();
131: for (int i = 0; i < subcomponents.size(); i++) {
132: try {
133: ContelligentComponent subcomponent = ComponentFactory
134: .getInstance()
135: .getComponent(
136: getComponent().getPath() + "/"
137: + (String) subcomponents.get(i));
138: ComponentFactory.getInstance().save(subcomponent);
139: } catch (ComponentNotFoundException cnfe) {
140: logger.log(Level.SEVERE, "Could not commit: ", cnfe);
141: }
142:
143: }
144: super .commit();
145: }
146:
147: public void calculateSitemap(boolean dropEnabled) {
148: // The target nodes & actions are placed as swing components,
149: // the arrows are painted to the background image...
150: removeAll();
151:
152: calculateSize(rootNode, 50, 50);
153:
154: bufferedSitemap = new BufferedImage(width, height,
155: BufferedImage.TYPE_INT_RGB);
156: Graphics2D g2d = bufferedSitemap.createGraphics();
157: g2d.setColor(Color.white);
158: g2d.fillRect(0, 0, width, height);
159: g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
160: RenderingHints.VALUE_ANTIALIAS_ON);
161: g2d.setPaint(Color.black);
162:
163: // prepare sitemap for rendering
164: for (Iterator i = nodes.values().iterator(); i.hasNext();) {
165: ((Node) i.next()).setPainted(false);
166: }
167: paintSitemap(g2d, rootNode, 10, 10, dropEnabled);
168: }
169:
170: public void buildSitemap() {
171: rootNode = new Node(getComponent(), rootName);
172: nodes.put(getComponent().getPath(), rootNode);
173:
174: for (Iterator iterator = getComponent().getSubcomponents()
175: .iterator(); iterator.hasNext();) {
176: String subcomponentName = (String) iterator.next();
177: try {
178: ContelligentComponent subcomponent = ComponentFactory
179: .getInstance().getComponent(
180: getComponent().getPath() + "/"
181: + subcomponentName);
182:
183: // get node
184: Node node = new Node(subcomponent);
185: nodes.put(subcomponent.getPath(), node);
186: Link link = new Link(subcomponent);
187: rootNode.addLink(link);
188: } catch (ComponentNotFoundException cnfe) {
189: ExceptionDialog.show(cnfe);
190: }
191: }
192: }
193:
194: public void calculateSize(Node node, int x, int y) {
195: node.setPainted(true);
196: node.setPosition(x, y);
197:
198: // resize component if node does not fit
199: width = Math.max(width, x + Node.WIDTH);
200: height = Math.max(height, y + Node.HEIGHT);
201:
202: // calculate linked nodes if not yet calculated
203: int targetX = x + Node.WIDTH + HORIZONTAL_GAP;
204: int targetY = y;
205:
206: Vector links = node.getLinks();
207: for (Enumeration e = links.elements(); e.hasMoreElements();) {
208: Link link = (Link) e.nextElement();
209: Node target = (Node) nodes.get(link.getTargetNode());
210: if (target == null) {
211: // dead link
212: } else if (!target.isPainted() && target.isVisible()) {
213: calculateSize(target, targetX, targetY);
214: targetY += Node.HEIGHT + VERTICAL_GAP;
215: }
216: }
217: }
218:
219: public void paintComponent(Graphics g) {
220: Graphics2D g2d = (Graphics2D) g;
221: g2d.setColor(Color.white);
222: g2d.fillRect(0, 0, getSize().width, getSize().height);
223: g2d.drawImage(bufferedSitemap, null, 0, 0);
224: }
225:
226: private void paintSitemap(Graphics2D g2d, Node node, int xPos,
227: int yPos, boolean dropEnabled) {
228: JPanelDND nodePanel = new JPanelDND(new BorderLayout(),
229: getView().getEnvironment());
230: nodePanel.setComponent(node.getComponent());
231: nodePanel.setSourceActions(DnDConstants.ACTION_LINK);
232: nodePanel.setDragEnabled(true);
233: if (node == rootNode) {
234: nodePanel.setDropEnabled(false);
235: } else {
236: nodePanel.setDropEnabled(dropEnabled);
237: }
238: nodePanel.setBounds(xPos, yPos, Node.WIDTH, Node.HEIGHT);
239: nodePanel.add(new JLabel(node.getPreview()),
240: BorderLayout.CENTER);
241: nodePanel.addComponentDropListener(this );
242: add(nodePanel);
243:
244: node.setPainted(true);
245: node.setPosition(xPos, yPos);
246:
247: g2d.drawString(node.getName(), xPos, yPos + Node.HEIGHT + 15);
248:
249: // resize component if node does not fit
250: if (height < yPos + Node.HEIGHT) {
251: height = yPos + Node.HEIGHT;
252: }
253: if (width < xPos + Node.WIDTH) {
254: width = xPos + Node.WIDTH;
255: }
256:
257: // draw linked nodes if not yet painted
258: int targetXPos = xPos + Node.WIDTH + HORIZONTAL_GAP;
259: int targetYPos = yPos;
260:
261: Vector links = node.getLinks();
262: int countNewLinks = 0;
263: int countOldLinks = 0;
264: int newLinkGap = LINK_GAP;
265: int oldLinkGap = LINK_GAP;
266: for (Enumeration e = links.elements(); e.hasMoreElements();) {
267: Link link = (Link) e.nextElement();
268: Node target = (Node) nodes.get(link.getTargetNode());
269: if (target == null) {
270: // dead link!
271: } else if (!target.isPainted() && target.isVisible()) {
272: countNewLinks++;
273: newLinkGap = Math.min(LINK_GAP,
274: ((Node.HEIGHT / 2) / countNewLinks));
275: } else if (target.isVisible) {
276: countOldLinks++;
277: oldLinkGap = Math.min(LINK_GAP,
278: ((Node.HEIGHT / 2) / countOldLinks));
279: }
280: }
281: int i = 0;
282: int j = 0;
283: for (Enumeration e = links.elements(); e.hasMoreElements();) {
284: Link link = (Link) e.nextElement();
285: Node target = (Node) nodes.get(link.getTargetNode());
286: if (target == null) {
287: // dead link!
288: } else if (!target.isPainted() && target.isVisible()) {
289: paintSitemap(g2d, target, targetXPos, targetYPos,
290: dropEnabled);
291: // draw link to new node
292: g2d.setPaint(Color.gray);
293: g2d.draw(new Line2D.Double(xPos + Node.WIDTH, yPos
294: + Node.HEIGHT / 2 + i * newLinkGap, xPos
295: + (Node.WIDTH + HORIZONTAL_GAP / 2) - i
296: * newLinkGap, yPos + Node.HEIGHT / 2 + i
297: * newLinkGap));
298: g2d.draw(new Line2D.Double(xPos
299: + (Node.WIDTH + HORIZONTAL_GAP / 2) - i
300: * newLinkGap, yPos + Node.HEIGHT / 2 + i
301: * newLinkGap, xPos
302: + (Node.WIDTH + HORIZONTAL_GAP / 2) - i
303: * newLinkGap, targetYPos + Node.HEIGHT / 2));
304: g2d.draw(new Line2D.Double(xPos
305: + (Node.WIDTH + HORIZONTAL_GAP / 2) - i
306: * newLinkGap, targetYPos + Node.HEIGHT / 2,
307: targetXPos, targetYPos + Node.HEIGHT / 2));
308: // arrow
309: g2d.draw(new Line2D.Double(targetXPos
310: - (ARROW_LENGTH + 1), targetYPos + Node.HEIGHT
311: / 2 - ARROW_LENGTH, targetXPos - 1, targetYPos
312: + Node.HEIGHT / 2));
313: g2d.draw(new Line2D.Double(targetXPos
314: - (ARROW_LENGTH + 1), targetYPos + Node.HEIGHT
315: / 2 + ARROW_LENGTH, targetXPos - 1, targetYPos
316: + Node.HEIGHT / 2));
317:
318: g2d.setPaint(Color.black);
319: g2d.drawString(link.getName(), xPos
320: + (Node.WIDTH + HORIZONTAL_GAP / 2) + 2,
321: targetYPos + Node.HEIGHT / 2 - 3);
322:
323: targetYPos += Node.HEIGHT + VERTICAL_GAP;
324: i++;
325: } else if (target.isVisible()) {
326: target.increaseReceivedLinks();
327: // draw link to old node
328: j++;
329: g2d.setPaint(Color.gray);
330: g2d.draw(new Line2D.Double(xPos, yPos + Node.HEIGHT / 2
331: - j * oldLinkGap, target.x + Node.WIDTH + 10
332: + (j * oldLinkGap), yPos + Node.HEIGHT / 2 - j
333: * oldLinkGap));
334: g2d.draw(new Line2D.Double(target.x + Node.WIDTH + 10
335: + (j * oldLinkGap), yPos + Node.HEIGHT / 2 - j
336: * oldLinkGap, target.x + Node.WIDTH + 10
337: + (j * oldLinkGap), target.y + Node.HEIGHT / 2
338: - j * oldLinkGap));
339: g2d.draw(new Line2D.Double(target.x + Node.WIDTH + 10
340: + (j * oldLinkGap), target.y + Node.HEIGHT / 2
341: - j * oldLinkGap, target.x + Node.WIDTH,
342: target.y + Node.HEIGHT / 2 - j * oldLinkGap));
343: // arrow
344: g2d.draw(new Line2D.Double(target.x + Node.WIDTH
345: + ARROW_LENGTH + 1, target.y + Node.HEIGHT / 2
346: - j * oldLinkGap - ARROW_LENGTH, target.x
347: + Node.WIDTH + 1, target.y + Node.HEIGHT / 2
348: - j * oldLinkGap));
349: g2d.draw(new Line2D.Double(target.x + Node.WIDTH
350: + ARROW_LENGTH + 1, target.y + Node.HEIGHT / 2
351: - j * oldLinkGap + ARROW_LENGTH, target.x
352: + Node.WIDTH + 1, target.y + Node.HEIGHT / 2
353: - j * oldLinkGap));
354: g2d.setPaint(Color.black);
355: g2d.drawString(link.getName(), xPos
356: - g2d.getFontMetrics().stringWidth(
357: link.getName()) - 10, yPos - 3
358: + Node.HEIGHT / 2 - j * oldLinkGap);
359: }
360: }
361: }
362:
363: public void setEditable(boolean editable) {
364: super .setEditable(editable);
365: if (nodes != null) {
366: update();
367: }
368: }
369:
370: public void componentDropped(ComponentDropEvent event) {
371: if (nodes.containsKey(event.getTargetComponent().getPath())) {
372: logger.log(Level.FINE, "Drop event occured on a node");
373: if (event.getTargetComponent().instanceOf(Type.LINK)) {
374: event.getTargetComponent().setPropertyValue(
375: ContelligentComponent.TARGET_PATH,
376: event.getDraggedComponent().getPath());
377: calculateSitemap(isEditable());
378: setPreferredSize(new Dimension(width, height));
379: revalidate();
380: repaint();
381: }
382: }
383: }
384:
385: public void mouseClicked(MouseEvent e) {
386: int x = e.getX();
387: int y = e.getY();
388:
389: // find the node that was selected
390: for (Iterator i = nodes.values().iterator(); i.hasNext();) {
391: Node node = (Node) i.next();
392: if (node.getRectangle().contains(e.getX(), e.getY())) {
393: boolean linksVisible = true;
394: boolean expanded = true;
395: if (node.isExpanded()) {
396: // node can be only be collapsed if no linked nodes have
397: // active links...
398: boolean collapsable = true;
399: for (Enumeration en = node.getLinks().elements(); en
400: .hasMoreElements();) {
401: Link link = (Link) en.nextElement();
402: Node target = (Node) nodes.get(link
403: .getTargetNode());
404: for (Enumeration f = target.getLinks()
405: .elements(); f.hasMoreElements();) {
406: Link targetLink = (Link) f.nextElement();
407: Node targetTarget = (Node) nodes
408: .get(targetLink.getTargetNode());
409: if (targetTarget.isVisible()) {
410: collapsable = false;
411: }
412: }
413: }
414: if (collapsable) {
415: linksVisible = false;
416: expanded = false;
417: } else {
418: JOptionPane
419: .showMessageDialog(
420: new JFrame(),
421: Resources
422: .getLocalString("node_not_collapsable"));
423: }
424: }
425: // set all linked nodes invisible
426: for (Enumeration enumeration = node.getLinks()
427: .elements(); enumeration.hasMoreElements();) {
428: Link link = (Link) enumeration.nextElement();
429: Node target = (Node) nodes
430: .get(link.getTargetNode());
431: if (target != null) {
432: target.setVisible(linksVisible);
433: }
434: }
435: // prepare sitemap for rendering
436: for (Iterator j = nodes.values().iterator(); j
437: .hasNext();) {
438: ((Node) j.next()).setPainted(false);
439: }
440: calculateSitemap(isEditable());
441: setPreferredSize(new Dimension(width, height));
442: node.setExpanded(expanded);
443: revalidate();
444: repaint();
445: break;
446: }
447: }
448: }
449:
450: public void mousePressed(MouseEvent e) {
451: }
452:
453: public void mouseEntered(MouseEvent e) {
454: }
455:
456: public void mouseExited(MouseEvent e) {
457: }
458:
459: public void mouseReleased(MouseEvent e) {
460: }
461:
462: protected void updateComponent() {
463: }
464:
465: protected void componentChanged(ContelligentEvent event) {
466: update();
467: }
468:
469: protected void childComponentAdded(ContelligentEvent event) {
470: }
471:
472: protected void childComponentRemoved(ContelligentEvent event) {
473: }
474:
475: protected void childComponentChanged(ContelligentEvent event) {
476: }
477:
478: protected void descendentComponentChanged(ContelligentEvent event) {
479: }
480:
481: static class Node {
482:
483: public final static String PAGE = "page";
484:
485: public final static String ACTION = "action";
486:
487: ContelligentComponent component;
488:
489: String name;
490:
491: ImageIcon preview;
492:
493: Vector<Link> links = new Vector<Link>();
494:
495: boolean isPainted = false;
496:
497: boolean isVisible = true;
498:
499: boolean isExpanded = true;
500:
501: int x, y, receivedLinks;
502:
503: static int HEIGHT = 100;
504:
505: static int WIDTH = 100;
506:
507: public Node(ContelligentComponent component, String name) {
508: this .component = component;
509: this .name = name;
510: }
511:
512: public Node(ContelligentComponent component) {
513: this (component, component.getName());
514: }
515:
516: public void addLink(Link link) {
517: links.add(link);
518: }
519:
520: public Vector<Link> getLinks() {
521: return links;
522: }
523:
524: public boolean isPainted() {
525: return isPainted;
526: }
527:
528: public void setPainted(boolean isPainted) {
529: this .isPainted = isPainted;
530: }
531:
532: public boolean isVisible() {
533: return isVisible;
534: }
535:
536: public void setVisible(boolean isVisible) {
537: this .isVisible = isVisible;
538: }
539:
540: public boolean isExpanded() {
541: return isExpanded;
542: }
543:
544: public void setExpanded(boolean isExpanded) {
545: this .isExpanded = isExpanded;
546: }
547:
548: public void setPosition(int x, int y) {
549: this .x = x;
550: this .y = y;
551: }
552:
553: public Rectangle getRectangle() {
554: if (x + y > 0) {
555: return new Rectangle(x, y, WIDTH, HEIGHT);
556: } else {
557: return new Rectangle(0, 0, 0, 0);
558: }
559: }
560:
561: public void increaseReceivedLinks() {
562: receivedLinks++;
563: }
564:
565: public String getName() {
566: if (component.instanceOf(Type.LINK)) {
567: return ComponentPath
568: .getComponentName(component
569: .getPropertyValue(ContelligentComponent.TARGET_PATH));
570: }
571: return name;
572: }
573:
574: public String getType() {
575: if (component.instanceOf(Type.PAGE)) {
576: return PAGE;
577: } else {
578: return ACTION;
579: }
580: }
581:
582: public ContelligentComponent getComponent() {
583: return component;
584: }
585:
586: public ImageIcon getPreview() {
587: if (component.instanceOf(Type.LINK)) {
588: try {
589: ContelligentComponent linkedComponent = ComponentFactory
590: .getInstance()
591: .getComponent(
592: component
593: .getPropertyValue(ContelligentComponent.TARGET_PATH));
594: if (linkedComponent.instanceOf(Type.PAGE)) {
595: return Resources.thumbnailPage;
596: } else {
597: return Resources.thumbnailAction;
598: }
599: } catch (ComponentNotFoundException cnfe) {
600: ExceptionDialog.show(cnfe);
601: }
602: }
603: if (component.instanceOf(Type.PAGE)) {
604: return Resources.thumbnailPage;
605: } else {
606: return Resources.thumbnailAction;
607: }
608: }
609: }
610:
611: static class Link {
612: private ContelligentComponent component;
613:
614: public Link(ContelligentComponent component) {
615: this .component = component;
616: }
617:
618: public String getName() {
619: return component.getName();
620: }
621:
622: public String getTargetNode() {
623: return component.getPath();
624: }
625: }
626:
627: }
|