1   /****************************************************************************
2    **
3    ** This file is part of the yFiles extension package yExport-1.3.
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) 2007-2012 by yWorks GmbH, Vor dem Kreuzberg 28, 
11   ** 72070 Tuebingen, Germany. All rights reserved.
12   **
13   ***************************************************************************/
14  
15  package demo.yext.export;
16  
17  import y.io.GraphMLIOHandler;
18  import y.option.OptionHandler;
19  import y.option.OptionGroup;
20  import y.option.ConstraintManager;
21  import y.view.AreaZoomMode;
22  import y.view.Arrow;
23  import y.view.EditMode;
24  import y.view.Graph2D;
25  import y.view.Graph2DView;
26  import y.io.IOHandler;
27  import y.io.SuffixFileFilter;
28  import y.util.D;
29  
30  import javax.swing.AbstractAction;
31  import javax.swing.Action;
32  import javax.swing.BorderFactory;
33  import javax.swing.ImageIcon;
34  import javax.swing.JFileChooser;
35  import javax.swing.JLabel;
36  import javax.swing.JMenu;
37  import javax.swing.JMenuBar;
38  import javax.swing.JPanel;
39  import javax.swing.JToolBar;
40  import javax.swing.UIManager;
41  
42  import y.view.ViewMode;
43  import y.view.YRenderingHints;
44  import yext.export.io.OutputHandler;
45  
46  import java.awt.BorderLayout;
47  import java.awt.Color;
48  import java.awt.FlowLayout;
49  import java.awt.Font;
50  import java.awt.event.ActionEvent;
51  import java.awt.Point;
52  import java.awt.Rectangle;
53  import java.beans.PropertyChangeEvent;
54  import java.beans.PropertyChangeListener;
55  import java.io.IOException;
56  import java.net.URL;
57  import java.text.NumberFormat;
58  import java.util.Iterator;
59  
60  /**
61   * Provides a generic export action that is used in all the file format specific
62   * export demos.
63   * For the most part the code given here handles user interaction such as
64   * setting up the view port and specifying a destination file for the graph
65   * export.
66   * The actual write process is handled by the last four lines of code in
67   * {@link demo.yext.export.AbstractExportDemo.ExportAction#export(y.io.IOHandler)}!
68   *
69   */
70  public abstract class AbstractExportDemo extends ViewActionDemo
71  {
72    static final String GRAPH = "Graph";
73    static final String USE_CUSTOM_HEIGHT = "Use Custom Height";
74    static final String USE_CUSTOM_WIDTH = "Use Custom Width";
75    static final String VIEW = "View";
76    static final String SIZE = "Size";
77    static final String CUSTOM_WIDTH = "Custom Width";
78    static final String CUSTOM_HEIGHT = "Custom Height";
79    static final String EMPTY_BORDER_WIDTH = "Empty Border Width";
80    static final String CLIP_REGION = "Clip Region";
81    static final String SELECTION_PAINTING = "Paint Selection Marker";
82  
83    protected AbstractExportDemo()
84    {
85      Color color = UIManager.getColor("Panel.background");
86      color = color != null ? color.darker() : Color.GRAY;
87      view.setBorder(BorderFactory.createMatteBorder(1, 0, 1, 0, color));
88  
89      view.getGraph2D().getDefaultEdgeRealizer().setTargetArrow(Arrow.STANDARD);
90  
91      final JPanel statusBar = createStatusBar();
92      add(statusBar, BorderLayout.SOUTH);
93    }
94  
95    /**
96     * Creates a status bar for this demo which shows the current zoom level.
97     */
98    protected JPanel createStatusBar()
99    {
100     final NumberFormat nf = NumberFormat.getPercentInstance();
101     nf.setMaximumFractionDigits(0);
102 
103     final JLabel label = new JLabel("Zoom Factor");
104     final Font font = label.getFont().deriveFont(Font.PLAIN);
105     label.setFont(font);
106     final JLabel zoom = new JLabel();
107     zoom.setFont(font);
108 
109     view.getCanvasComponent()
110         .addPropertyChangeListener("Zoom", new PropertyChangeListener()
111         {
112           public void propertyChange(final PropertyChangeEvent e)
113           {
114             zoom.setText(nf.format(e.getNewValue()));
115           }
116         });
117     zoom.setText(nf.format(view.getZoom()));
118 
119     final JPanel statusBar = new JPanel(new FlowLayout(FlowLayout.LEADING, 2, 2));
120     statusBar.add(label);
121     statusBar.add(zoom);
122 
123     return statusBar;
124   }
125 
126   /**
127    * Creates a toolbar for this demo.
128    * @return the application toolbar.
129    */
130   protected JToolBar createToolBar()
131   {
132     JToolBar bar = new JToolBar();
133     bar.add(new ClearGraphAction());
134     bar.add(new DeleteSelection());
135     bar.add(new Zoom(1.2));
136     bar.add(new Zoom(0.8));
137     bar.add(new ResetZoom());
138     bar.add(new FitContent());
139     bar.add(new ZoomArea());
140 
141     return bar;
142   }
143 
144   /**
145    * Create a menu bar for this demo.
146    * @return the application menu bar.
147    */
148   protected JMenuBar createMenuBar()
149   {
150     JMenuBar bar = super.createMenuBar();
151 
152     JMenu menu = createSampleGraphMenu();
153     if (menu != null) {
154       bar.add(menu);
155     }
156 
157     return bar;
158   }
159 
160   /**
161    * Creates a menu for choosing sample graphs.
162    */
163   protected JMenu createSampleGraphMenu()
164   {
165     String[] resources = {
166         "resource/ygraph/problemsolving.graphml",
167         "resource/ygraph/grouping.graphml",
168         "resource/ygraph/visual_edges_fight2.graphml",
169         "resource/ygraph/visual_nodes_yedxp.graphml",
170         "resource/ygraph/visual_features.graphml",
171     };
172     return createSampleGraphMenu(resources);
173   }
174 
175   /**
176    * Creates a menu for choosing the specified sample graphs.
177    */
178   JMenu createSampleGraphMenu(final String[] resources)
179   {
180     JMenu menu = new JMenu("Sample Graphs");
181     int resolvedResourceCount = 0;
182     for (int i = 0; i < resources.length; ++i) {
183       final URL resource = getClass().getResource(resources[i]);
184       if (resource != null) {
185         ++resolvedResourceCount;
186         menu.add(new LoadResourceAction(resource));
187       }
188       else {
189         System.err.println("Could not resolve sample resource " + resources[i]);
190       }
191     }
192     if (resolvedResourceCount > 0) {
193       return menu;
194     }
195     else {
196       return null;
197     }
198   }
199 
200   /**
201    * Loads a graph from a specified GraphML file.
202    * @param resource the GraphML file to load.
203    * @param updateView whether or not the view is updated after loading.
204    */
205   protected void loadGraph(final URL resource, final boolean updateView)
206   {
207     GraphMLIOHandler ioh = createGraphMLIOHandler();
208     try {
209       view.getGraph2D().clear();
210       ioh.read(view.getGraph2D(), resource);
211     } catch (IOException ioe) {
212       D.show(ioe);
213     }
214 
215     if (updateView) {
216       view.fitContent();
217       view.getGraph2D().updateViews();
218     }
219   }
220 
221   /**
222    * Creates an icon from the specified <code>resource</code>.
223    */
224   static ImageIcon createIcon(final String resource)
225   {
226     final URL url = AbstractExportDemo.class.getResource(resource);
227     if (url != null) {
228       return new ImageIcon(url);
229     }
230     else {
231       System.err.println("Could not resolve resource " + resource);
232       return null;
233     }
234   }
235 
236 
237   /**
238    * Action that clears the graph to present an empty view.
239    */
240   class ClearGraphAction extends AbstractAction
241   {
242     ClearGraphAction()
243     {
244       super("Clear Graph");
245       this.putValue(Action.SHORT_DESCRIPTION, "Clear Graph");
246       URL imageURL = getClass().getResource("resource/New16.gif");
247       if (imageURL != null) {
248         this.putValue(Action.SMALL_ICON, new ImageIcon(imageURL));
249       }
250     }
251 
252     public void actionPerformed(ActionEvent e)
253     {
254       view.getGraph2D().clear();
255       view.updateView();
256     }
257   }
258 
259   /**
260    * Action that zooms the view to the selected box.
261    */
262   class ZoomArea extends AbstractAction
263   {
264     ZoomArea()
265     {
266       super("Zoom Area");
267       this.putValue(Action.SHORT_DESCRIPTION, "Zoom Area");
268       URL imageURL = getClass().getResource("resource/ZoomArea16.gif");
269       if (imageURL != null) {
270         this.putValue(Action.SMALL_ICON, new ImageIcon(imageURL));
271       }
272     }
273 
274     public void actionPerformed(ActionEvent e)
275     {
276       Iterator viewModes = view.getViewModes();
277       while (viewModes.hasNext()) {
278         ViewMode viewMode = (ViewMode) viewModes.next();
279         if (viewMode instanceof EditMode) {
280           EditMode editMode = (EditMode) viewMode;
281           editMode.setChild(new AreaZoomMode(), null, null);
282         }
283       }
284     }
285   }
286 
287   /**
288    * Action that loads a graph from a specified file resource in GraphML format.
289    */
290   class LoadResourceAction extends AbstractAction
291   {
292     URL resource;
293 
294     LoadResourceAction(URL resource)
295     {
296       this.resource = resource;
297       String file = resource.getFile();
298       putValue(Action.NAME, file.substring(file.lastIndexOf('/') + 1));
299     }
300 
301     public void actionPerformed(ActionEvent ev)
302     {
303       loadGraph(resource, true);
304     }
305   }
306 
307   /**
308    * Action that exports the given graph to a specific output format.
309    */
310   class ExportAction extends AbstractAction
311   {
312     OptionHandler options;
313     OutputHandler outputHandler;
314 
315     ExportAction( String name, OutputHandler outputHandler )
316     {
317       putValue(Action.NAME, name);
318       putValue(Action.SMALL_ICON, createIcon("resource/Export16.gif"));
319       putValue(Action.SHORT_DESCRIPTION, name);
320 
321 
322       this.outputHandler = outputHandler;
323 
324 
325       final String[] sizes = {
326               "Use Original Size", USE_CUSTOM_WIDTH, USE_CUSTOM_HEIGHT
327       };
328       final String[] clipRegions = {
329               GRAPH, VIEW
330       };
331 
332       // set up an OptionHandler to allow for user customizations
333       options = new OptionHandler(name);
334       final OptionGroup group = new OptionGroup();
335       group.setAttribute(OptionGroup.ATTRIBUTE_TITLE, name + " Options");
336       group.addItem(options.addEnum(SIZE, sizes, 0));
337       group.addItem(options.addInt(CUSTOM_WIDTH,  500));
338       group.addItem(options.addInt(CUSTOM_HEIGHT, 500));
339       group.addItem(options.addInt(EMPTY_BORDER_WIDTH, 10));
340       group.addItem(options.addEnum(CLIP_REGION, clipRegions, 0));
341       group.addItem(options.addBool(SELECTION_PAINTING, false));
342 
343       final ConstraintManager cm = new ConstraintManager(options);
344       cm.setEnabledOnValueEquals(SIZE, USE_CUSTOM_WIDTH, CUSTOM_WIDTH);
345       cm.setEnabledOnValueEquals(SIZE, USE_CUSTOM_HEIGHT, CUSTOM_HEIGHT);
346       cm.setEnabledOnValueEquals(CLIP_REGION, GRAPH, EMPTY_BORDER_WIDTH);
347     }
348 
349     public void actionPerformed( final ActionEvent e )
350     {
351       // show our OptionHandler to allow the user to set up the export view
352       // according to his or her wishes
353       if (!options.showEditor(view.getFrame())) {
354         return;
355       }
356 
357       if (options.getBool(SELECTION_PAINTING)) {
358         outputHandler.removeRenderingHint(YRenderingHints.KEY_SELECTION_PAINTING);
359       } else {
360         outputHandler.addRenderingHint(
361                 YRenderingHints.KEY_SELECTION_PAINTING,
362                 YRenderingHints.VALUE_SELECTION_PAINTING_OFF);
363       }
364 
365       Graph2D graph = view.getGraph2D();
366       Graph2DView viewPort = outputHandler.createDefaultGraph2DView(graph);
367 
368       // configure the export view according to the options specified by the
369       // user
370       configureViewPort(viewPort);
371       graph.setCurrentView(viewPort);
372 
373       // ask for the destination file and finally write the graph to the chosen
374       // destination
375       export(outputHandler);
376 
377       graph.removeView(viewPort);
378     }
379 
380     /**
381      * Configures the view port used for writing according to user
382      * customizations.
383      */
384     void configureViewPort( final Graph2DView viewPort )
385     {
386       Graph2D graph = view.getGraph2D();
387 
388       double width  = options.getInt(CUSTOM_WIDTH);
389       double height = options.getInt(CUSTOM_HEIGHT);
390       Point viewPoint = viewPort.getViewPoint();
391       double zoom = 1.0;
392 
393       if (GRAPH.equals(options.get(CLIP_REGION))) {
394         Rectangle box = graph.getBoundingBox();
395         int border = options.getInt(EMPTY_BORDER_WIDTH);
396         box.width  += 2*border;
397         box.height += 2*border;
398         box.x -= border;
399         box.y -= border;
400 
401         if (USE_CUSTOM_HEIGHT.equals(options.get(SIZE))) {
402           width = height*box.getWidth()/box.getHeight();
403         } else if (USE_CUSTOM_WIDTH.equals(options.get(SIZE))) {
404           height = width*box.getHeight()/box.getWidth();
405         } else {
406           width =  box.getWidth();
407           height = box.getHeight();
408         }
409         zoom = width/box.getWidth();
410         viewPoint = new Point(box.x, box.y);
411       } else if (VIEW.equals(options.get(CLIP_REGION))) {
412         if (USE_CUSTOM_HEIGHT.equals(options.get(SIZE))) {
413           width = height*view.getWidth()/view.getHeight();
414         } else if (USE_CUSTOM_WIDTH.equals(options.get(SIZE))) {
415           height = width*view.getHeight()/view.getWidth();
416         } else {
417           width =  view.getWidth();
418           height = view.getHeight();
419         }
420         viewPoint = view.getViewPoint();
421         zoom = view.getZoom()*width/view.getWidth();
422       }
423 
424       viewPort.setZoom(zoom);
425       viewPort.setSize((int)width, (int)height);
426       viewPort.setViewPoint(viewPoint.x, viewPoint.y);
427     }
428 
429     /**
430      * Asks for a destination file and writes the currently visible graph
431      * to that destination.
432      */
433     void export( final IOHandler ioh)
434     {
435       // ask for a destination file
436       JFileChooser chooser = new JFileChooser();
437       chooser.setAcceptAllFileFilterUsed(false);
438       String fne = ioh.getFileNameExtension();
439       chooser.setFileFilter(new SuffixFileFilter(
440         fne, ioh.getFileFormatString()));
441 
442       if (chooser.showSaveDialog(AbstractExportDemo.this) ==
443           JFileChooser.APPROVE_OPTION) {
444         String name = chooser.getSelectedFile().toString();
445         if (!name.endsWith(fne)) {
446           name = name + '.' + fne;
447         }
448 
449         // the actual export
450         // it's this simple with yExport (and yFiles in general)   :-D
451         try {
452           ioh.write(view.getGraph2D(), name);
453         } catch(IOException ex) {
454           D.show(ex);
455         }
456       }
457     }
458   }
459 }