1   /****************************************************************************
2    **
3    ** This file is part of yFiles-2.6. 
4    ** 
5    ** yWorks proprietary/confidential. Use is subject to license terms.
6    **
7    ** Redistribution of this file or of an unauthorized byte-code version
8    ** of this file is strictly forbidden.
9    **
10   ** Copyright (c) 2000-2008 by yWorks GmbH, Vor dem Kreuzberg 28, 
11   ** 72070 Tuebingen, Germany. All rights reserved.
12   **
13   ***************************************************************************/
14  package demo.view.layout.genealogy;
15  
16  import demo.view.DemoBase;
17  import demo.view.layout.genealogy.iohandler.GedcomHandler;
18  import y.base.DataProvider;
19  import y.base.Node;
20  import y.base.NodeList;
21  import y.base.NodeMap;
22  import y.io.GMLIOHandler;
23  import y.layout.genealogy.FamilyTreeLayouter;
24  import y.module.FamilyTreeLayoutModule;
25  import y.util.D;
26  import y.util.DataProviderAdapter;
27  import y.util.GraphHider;
28  import y.view.BendList;
29  import y.view.BevelNodePainter;
30  import y.view.BridgeCalculator;
31  import y.view.DefaultGraph2DRenderer;
32  import y.view.EdgeRealizer;
33  import y.view.EditMode;
34  import y.view.GenericEdgePainter;
35  import y.view.GenericEdgeRealizer;
36  import y.view.GenericNodeRealizer;
37  import y.view.Graph2D;
38  import y.view.LineType;
39  import y.view.NodeRealizer;
40  import y.view.ShinyPlateNodePainter;
41  import y.view.NavigationMode;
42  import y.algo.Bfs;
43  
44  import javax.swing.AbstractAction;
45  import javax.swing.Action;
46  import javax.swing.JComboBox;
47  import javax.swing.JFileChooser;
48  import javax.swing.JToolBar;
49  import javax.swing.ScrollPaneConstants;
50  import javax.swing.filechooser.FileFilter;
51  import java.awt.BasicStroke;
52  import java.awt.Color;
53  import java.awt.Graphics2D;
54  import java.awt.Stroke;
55  import java.awt.Cursor;
56  import java.awt.event.ActionEvent;
57  import java.awt.event.ActionListener;
58  import java.awt.geom.GeneralPath;
59  import java.awt.geom.PathIterator;
60  import java.io.File;
61  import java.io.FilenameFilter;
62  import java.io.IOException;
63  import java.util.Map;
64  import java.net.URL;
65  
66  
67  /**
68   * This Demo shows how to use the FamilyTreeLayouter.
69   * <br>
70   * <b>Usage:</b>
71   * <br>
72   * Load a Gedcom file with "Load..." from the "File" menu. The gedcom file is converted on the fly into GML
73   * and loaded into the graph by the {@link demo.view.layout.genealogy.iohandler.GedcomHandler}. After loading,
74   * the graph will be layed out by the {@link y.layout.genealogy.FamilyTreeLayouter}.
75   * NOTE: you will find some sample files in your &lt;src&gt;/demo/view/layout/genealogy/samples folder
76   * <br>
77   * To re-layout the graph press the "layout" button. An options dialog will open where you can modify
78   * some basic and advanced options. Clicking "OK" will induce a new layout with the new settings.
79   * <br>
80   * To load some sample graphs which are provided with this demo select one from the "Examples" ComboBox.
81   * There are four different family trees with different complexitiy provided.
82   * NOTE: for this feature, Gedcom files (ending: .ged) must be exported as resources.
83   * <br/>
84   * Clicking on a node will collapse the graph to two generations around the clicked node. The "Show all" button
85   * will expand the graph again
86   * <br/>
87   * <br/>
88   * API usage:
89   * <br>
90   * The FamilyTreeLayouter needs to distinguish between family nodes, i.e. nodes representing a FAM entry
91   * in the Gedcom file, and nodes representing individuals (i.e. persons, INDI entries). To do so,
92   * a data provider with the key {@link y.layout.genealogy.FamilyTreeLayouter#DP_KEY_FAMILY_TYPE} has to be registered
93   * to the graph. This data provider will return a String which denotes nodes representing individuals.
94   * In this demo, this is achieved by comparing the node's background color with the color, familiy nodes are
95   * painted with (Color.black).
96   * <br>
97   * For writing, the GedcomHandler needs to distinguish between family nodes and individuals as well
98   * as between male and female individuals. To do so, a data provider with the key
99   * {@link y.layout.genealogy.FamilyTreeLayouter#DP_KEY_FAMILY_TYPE} has to be registered to the graph.
100  * These data provider will return a String which can be used to distinguish between families, male and female
101  * individual.
102  * In this demo, this is achieved by comparing the node's background color with predefined values.
103  * As this demo is a viewer without editing capabilities, the export function is not implemented in the GUI
104  * (Although it is implemented in the code: see class ExportAction)
105  * <br>
106  * <br>
107  * Users of the GraphML extension have the possibility to use GraphML instead of GML for the format conversion.
108  * This format offers the possibility to extract additional information from the original file.
109  * In this demo, String attributes whether the node represents a family or a male or female individual are mapped
110  * to the graph and can be used as data providers for the layouter and the GedcomHandler.
111  * <br>
112  * To use this feature, modify the code as indicated in the source code. The methods to modify are
113  * {@link FamilyTreeDemo#loadGedcom} and {@link GedcomHandler#read}
114  */
115 
116 public class FamilyTreeDemo extends DemoBase {
117 
118   /**
119   * Launches this demo.
120   */
121  public static void main(String args[])
122  {
123    initLnF();
124 
125    FamilyTreeDemo ftd = new FamilyTreeDemo();
126    ftd.start("Family Tree Demo");
127  }
128 
129   /**
130    * Create the edit mode and disable node and edge creation. Add a NavigationMode.
131    *
132    * @return The edit mode
133    */
134   protected EditMode createEditMode() {
135     EditMode editMode = new EditMode() {
136       protected void nodeClicked(Node v) {
137         moveToCenter(v);
138       }
139     };
140     editMode.allowEdgeCreation(false);
141     editMode.allowNodeCreation(false);
142     editMode.allowMoveLabels(false);
143     editMode.allowBendCreation(false);
144     editMode.allowMoveSelection(false);
145     editMode.allowMoving(true);
146     return editMode;
147   }
148 
149   private GraphHider graphHider;
150 
151 
152   /**
153    * Centers the graph on the given node and hides all nodes which are more than 2 generations away.
154    * @param newCenter The node to be the new center of the graph
155    */
156   private void moveToCenter(final Node newCenter) {
157 
158     graphHider.unhideAll();     // undo the previous selection
159 
160     // the list for all nodes that are to hide: initially filled with all nodes in the graph
161     NodeList toHide = new NodeList( view.getGraph2D().nodes() );
162     NodeMap nodeMap = view.getGraph2D().createNodeMap();
163     // search the graph for the newCenter's adjacent 5 generations
164     // NOTE: familes also count as one generation, so 5 "graph" generations correspond to 2 "genealogic" generations
165     // These will be returned arranged in layers in the Array of nodeLists
166     NodeList[] layers = Bfs.getLayers( view.getGraph2D(), new NodeList( newCenter ), false, nodeMap, 5 );
167     view.getGraph2D().disposeNodeMap( nodeMap );
168     // remove these nodes from the toHide list (which initially contains all nodes of the graph)
169     for ( int i = 0; i < layers.length; i++ ) {
170       NodeList layer = layers[ i ];
171       toHide.removeAll( layer );
172     }
173 
174     // hide all nodes in the toHide list
175     graphHider.hide( toHide );
176 
177     // run a layout for the remaining nodes
178     getLayoutModule().mainrun();
179     // fit the view
180     view.fitContent();
181   }
182 
183    /**
184    * Action to run a layout.
185    */
186   protected class ShowAllAction extends AbstractAction {
187 
188 
189     public ShowAllAction() {
190       super("Show all");
191     }
192 
193     /**
194      * Invoked when an action occurs. Displays an options dialog and runs a layout with the selected parameters
195      * if the user clicks ok. Uses a module for this task.
196      */
197     public void actionPerformed(ActionEvent e) {
198       if (graphHider != null) {
199         graphHider.unhideAll();
200       }
201       getLayoutModule().mainrun();
202       view.fitContent();
203     }
204 
205 /* Use this if you want to configure the layouter programmatically */
206 //    public void actionPerformed(ActionEvent e) {
207 //      FamilyTreeLayouter ftl = new FamilyTreeLayouter();
208 //       try {
209 //         ftl.doLayout( view.getGraph2D() );
210 //       } catch (Exception e1) {
211 //           D.show( e1 );
212 //       }
213 //    }
214 
215 
216   }
217 
218 
219 
220   /**
221    * Creates a toolbar for this demo.
222    */
223   protected JToolBar createToolBar() {
224     JToolBar jToolBar = super.createToolBar();
225     jToolBar.addSeparator();
226     jToolBar.add(new LayoutAction());
227     //jToolBar.add(new ExportAction());
228     JComboBox jcb = createExampleComboBox();
229     if (jcb != null) {
230       jToolBar.add(jcb);
231     }
232     jToolBar.add(new ShowAllAction());
233     return jToolBar;
234   }
235 
236   /**
237    * Creates a ComboBox to select the provided samples.
238    * @return The sample ComboBox or null, if no samples were found or provided.
239    */
240   private JComboBox createExampleComboBox() {
241     String fqResourceName = FamilyTreeDemo.class.getPackage().getName().replace( '.', '/' ) + "/samples/KENNEDY.GED";
242 
243 
244     URL resource = getClass().getResource("samples/KENNEDY.GED");
245     if (resource == null) {
246       D.showError("Cannot load example files: missing resource " + fqResourceName + "\n" +
247           "Please ensure that your IDE recognizes \"*.ged\" files as resource files. \n" +
248           "Meanwhile you can load the sample files via the \"File/Load\" menu from the source folder of your distribution.");
249       return null;
250     }
251     String name = resource.getFile();
252     final String dirName = name.substring(0, name.lastIndexOf('/'));
253 
254     final String[] dir = new File(dirName).list(new FilenameFilter(){
255             public boolean accept( File d, String s ) {
256                 return s.toLowerCase().endsWith( ".ged" );
257               } } );
258     if (dir == null) {
259       D.showError("Cannot load example files: "+ dirName +" not found");
260       return null;
261     }
262     String[] combo = new String[dir.length + 1];
263     System.arraycopy(dir, 0, combo, 1, dir.length);
264     combo[0] = "Examples";
265     final JComboBox jcb = new JComboBox(combo);
266     jcb.addActionListener(new ActionListener() {
267       public void actionPerformed( ActionEvent e) {
268         String fileName = (String)jcb.getSelectedItem();
269         if (!"Examples".equals(fileName)) {
270           loadGedcom(dirName + System.getProperty("file.separator") + fileName);
271         }
272       }
273     });
274     return jcb;
275   }
276 
277   /**
278    * Loads a gedcom file with the provided filename into the editor
279    * @param name
280    */
281   private void loadGedcom(String name) {
282 
283     Cursor oldCursor = view.getViewCursor();
284     view.setViewCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
285 
286     final Graph2D graph = view.getGraph2D();
287     if (graphHider != null) {
288       graphHider.unhideAll();
289     }
290     graph.clear();
291 // BEGIN: uncomment if you use the GraphML package
292 //         GedcomHandler gh = new GedcomHandler();
293 //         GraphMLIOHandler delegate = new GraphMLIOHandler();
294 //         NodeMap nodeTypeMap = graph.createNodeMap();
295 //         delegate.addAttribute(nodeTypeMap, "NodeType", AttributeConstants.TYPE_STRING);
296 //         graph.addDataProvider(FamilyTreeLayouter.DP_KEY_FAMILY_TYPE, nodeTypeMap);
297 // END: uncomment
298 // BEGIN: comment out if you use the GraphML package
299     GedcomHandler gh = new GedcomHandler();
300     GMLIOHandler delegate = new GMLIOHandler();
301 // END: comment out
302     gh.setReaderDelegate(delegate);
303     try {
304       gh.read(graph, name);
305     } catch (IOException e1) {
306       D.show(e1);
307     }
308 
309 // BEGIN: comment out if you use the GraphML package
310     DataProvider dpType = null;
311     if(graph.getDataProvider(FamilyTreeLayouter.DP_KEY_FAMILY_TYPE) == null) {
312       /* Create the family info if not already existing */
313       dpType = new DataProviderAdapter() {
314         public Object get(Object o) {
315           NodeRealizer nr = graph.getRealizer((Node)o);
316           Color nodeColor = nr.getFillColor();
317           if (nodeColor != null && nodeColor.getRGB() == 0xFFCCCCFF) {
318             return FamilyTreeLayouter.TYPE_MALE;
319           }
320           if (nodeColor != null && nodeColor.getRGB() == 0xFFFF99CC) {
321             return FamilyTreeLayouter.TYPE_FEMALE;
322           }
323           if (nodeColor != null && nodeColor.getRGB() == 0xFF000000) {
324             return FamilyTreeLayouter.TYPE_FAMILY;
325           }
326           return null;
327         }
328       };
329       graph.addDataProvider(FamilyTreeLayouter.DP_KEY_FAMILY_TYPE, dpType);
330     }
331 
332 // END: comment out
333 
334 //    FamilyTreeLayouter ftl = new FamilyTreeLayouter();
335 //    try {
336 //      ftl.doLayout(graph);
337 //    } catch (Exception e1) {
338 //      D.show(e1);
339 //    }
340 
341     try {
342       getLayoutModule().mainrun();
343     } catch (Exception e1) {
344       D.show(e1);
345     }
346 
347     //force redisplay of view contents
348 
349     view.fitContent();
350     graph.updateViews();
351     view.setViewCursor(oldCursor);
352   }
353 
354 
355   /**
356    * Overrides the default method which creates the loadGedcom entry in the file menu to import a gedcom file
357    * rather than to loadGedcom a graph.
358    * @return A new instance of ImportAction
359    */
360   protected Action createLoadAction() {
361     return new ImportAction();
362   }
363 
364 
365 
366 
367 
368 
369   /**
370     * Action that loads a Gedcom file.
371     */
372    protected class ImportAction extends AbstractAction {
373      JFileChooser chooser;
374 
375      public ImportAction() {
376        super( "Load..." );
377        chooser = null;
378      }
379 
380      public void actionPerformed( ActionEvent e ) {
381        if ( chooser == null ) {
382          chooser = new JFileChooser();
383          chooser.setFileFilter(new FileFilter() {
384             public boolean accept(File f) {
385                 return f.isDirectory() || f.getName().toLowerCase().endsWith(".ged");
386             }
387             public String getDescription() {
388                 return "Gedcom files";
389             }
390         });
391        }
392        
393        if ( chooser.showOpenDialog( contentPane ) == JFileChooser.APPROVE_OPTION ) {
394          loadGedcom(chooser.getSelectedFile().toString());
395        }
396      }
397 
398 
399   }
400 
401   private FamilyTreeLayoutModule getLayoutModule() {
402     if (ftlm == null) {
403       ftlm = new FamilyTreeLayoutModule();
404       ftlm.setGraph2D(view.getGraph2D());
405     }
406     return ftlm;
407   }
408 
409   private FamilyTreeLayoutModule ftlm;
410 
411 
412   /**
413    * Action to run a layout.
414    */
415   protected class LayoutAction extends AbstractAction {
416 
417 
418     public LayoutAction() {
419       super("Layout");
420     }
421 
422     /**
423      * Invoked when an action occurs. Displays an options dialog and runs a layout with the selected parameters
424      * if the user clicks ok. Uses a module for this task.
425      */
426     public void actionPerformed(ActionEvent e) {
427       if (getLayoutModule().getOptionHandler().showEditor()) {
428         Cursor oldCursor = view.getViewCursor();
429         view.setViewCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
430 
431         getLayoutModule().mainrun();
432         view.setViewCursor(oldCursor);
433       }
434        
435     }
436 
437 /* Use this if you want to configure the layouter programmatically */
438 //    public void actionPerformed(ActionEvent e) {
439 //      FamilyTreeLayouter ftl = new FamilyTreeLayouter();
440 //       try {
441 //         ftl.doLayout( view.getGraph2D() );
442 //       } catch (Exception e1) {
443 //           D.show( e1 );
444 //       }
445 //    }
446 
447 
448   }
449 
450   /**
451    * Action to export the graph into a gedcom file
452    * NOTE: This action is not added to the toolbar
453    */
454   protected class ExportAction extends AbstractAction {
455 
456     private JFileChooser chooser;
457 
458     public ExportAction() {
459       super("Export");
460     }
461 
462     /**
463      * Invoked when an action occurs.
464      */
465     public void actionPerformed(ActionEvent e) {
466 
467       if (chooser == null) {
468         chooser = new JFileChooser();
469         chooser.setFileFilter(new FileFilter() {
470           public boolean accept(File f) {
471             return f.isDirectory()
472                 || f.getName().toLowerCase().endsWith(".ged");
473           }
474 
475           public String getDescription() {
476             return "Gedcom files";
477           }
478         });
479       }
480       if (chooser.showOpenDialog(contentPane) == JFileChooser.APPROVE_OPTION) {
481         String name = chooser.getSelectedFile().toString();
482         if (!name.toLowerCase().endsWith(".ged")) {
483           name = name + ".ged";
484         }
485 
486         GedcomHandler gh = new GedcomHandler();
487         final Graph2D graph = view.getGraph2D();
488 
489         // Write the graph using the GedcomHandler
490         try {
491           gh.write(graph, name);
492         } catch (IOException e1) {
493           D.show(e1);
494         }
495       }
496     }
497 
498   }
499 
500   //////////////////////////////////////// Optical improvements :-) /////////////////////////////////////////////////
501 
502    /**
503     * Initialize the node and edge style
504     */
505   protected void initialize() {
506 
507     // Use a BevelNodePainter for the Individuals
508     GenericNodeRealizer.Factory factory = GenericNodeRealizer.getFactory();
509     Map implementationsMap = factory.createDefaultConfigurationMap();
510     ShinyPlateNodePainter spnp = new ShinyPlateNodePainter();
511     spnp.setDrawShadow(true);
512     implementationsMap.put(GenericNodeRealizer.Painter.class, spnp);
513     factory.addConfiguration("Individual", implementationsMap);
514 
515     // Use a BevelNodePainter with rounded corners for the families
516     implementationsMap = factory.createDefaultConfigurationMap();
517     BevelNodePainter painter = new BevelNodePainter();
518     painter.setRadius(10);
519     painter.setDrawShadow(true);
520     implementationsMap.put(GenericNodeRealizer.Painter.class, painter);
521     factory.addConfiguration("Family", implementationsMap);
522 
523     // Use a custom edge realizer
524     GenericEdgeRealizer.Factory edgeFactory = GenericEdgeRealizer.getFactory();
525     implementationsMap = edgeFactory.createDefaultConfigurationMap();
526     implementationsMap.put(GenericEdgeRealizer.Painter.class, new CustomEdgePainter());
527     edgeFactory.addConfiguration("Edge", implementationsMap);
528     GenericEdgeRealizer ger = new GenericEdgeRealizer();
529     ger.setConfiguration("Edge");
530     ger.setLineColor(new Color(0x808080));
531     ger.setLineType(LineType.LINE_2);
532     view.getGraph2D().setDefaultEdgeRealizer(ger);
533 
534     // Crossing/Bridges: Vertical edges over horiziontal edges display gaps in horizontal edges
535     BridgeCalculator bc = new BridgeCalculator();
536     bc.setCrossingStyle(BridgeCalculator.CROSSING_STYLE_GAP);
537     bc.setCrossingMode(BridgeCalculator.CROSSING_MODE_ORDER_INDUCED);
538     ((DefaultGraph2DRenderer) view.getGraph2DRenderer()).setBridgeCalculator(bc);
539 
540      graphHider = new GraphHider(view.getGraph2D());
541 
542 
543   }
544 
545 
546    /**
547    * A custom EdgePainter implementation that draws the edge path 3D-ish and adds
548    * a drop shadow also. (see demo.view.realizer.GenericEdgePainterDemo)
549    */
550   static final class CustomEdgePainter extends GenericEdgePainter {
551 
552      protected GeneralPath adjustPath(EdgeRealizer context, BendList bends, GeneralPath path,
553                                       BridgeCalculator bridgeCalculator,
554                                       boolean selected) {
555        if (bridgeCalculator != null) {
556          GeneralPath p = new GeneralPath();
557          try {
558            PathIterator pathIterator = bridgeCalculator.insertBridges(path.getPathIterator(null, 1.0d));
559            p.append(pathIterator, true);
560            return super.adjustPath(context, bends, p, bridgeCalculator, selected);
561          } finally {
562          }
563        } else {
564          return super.adjustPath(context, bends, path, bridgeCalculator, selected);
565        }
566 
567 
568      }
569 
570 
571 
572     protected void paintPath(EdgeRealizer context, BendList bends, GeneralPath path, Graphics2D gfx, boolean selected) {
573       Stroke s = gfx.getStroke();
574       Color oldColor = gfx.getColor();
575       if (s instanceof BasicStroke){
576         Color c;
577         if (!context.isSelected()){
578           initializeLine(context, gfx, selected);
579           c = gfx.getColor();
580           gfx.setColor(new Color(128,128,128,40));
581           gfx.translate(4, 4);
582           gfx.draw(path);
583           gfx.translate(-4, -4);
584         } else {
585           initializeSelectionLine(context, gfx, selected);
586           c = gfx.getColor();
587         }
588         Color newC = context.isSelected() ? Color.red :c;
589         gfx.setColor(new Color(128 + newC.getRed()/ 2, 128 + newC.getGreen()/ 2,128 + newC.getBlue()/ 2));
590         gfx.translate(-1, -1);
591         gfx.draw(path);
592         gfx.setColor(new Color(newC.getRed()/ 2, newC.getGreen()/ 2,newC.getBlue()/ 2));
593         gfx.translate(2, 2);
594         gfx.draw(path);
595         gfx.translate(-1, -1);
596         gfx.setColor(c);
597         gfx.draw(path);
598         gfx.setColor(oldColor);
599       } else {
600         gfx.draw(path);
601       }
602     }
603   }
604 
605 
606 }
607