Xcos palette: add xcosPalGenerateAllIcon utility
[scilab.git] / scilab / modules / xcos / src / java / org / scilab / modules / xcos / palette / Palette.java
1 /*
2  * Scilab ( http://www.scilab.org/ ) - This file is part of Scilab
3  * Copyright (C) 2010 - DIGITEO - Clement DAVID
4  *
5  * This file must be used under the terms of the CeCILL.
6  * This source file is licensed as described in the file COPYING, which
7  * you should have received as part of this distribution.  The terms
8  * are also available at
9  * http://www.cecill.info/licences/Licence_CeCILL_V2-en.txt
10  *
11  */
12
13 package org.scilab.modules.xcos.palette;
14
15 import java.awt.GraphicsEnvironment;
16 import java.awt.event.ActionEvent;
17 import java.awt.event.ActionListener;
18 import java.awt.image.BufferedImage;
19 import java.io.File;
20 import java.io.IOException;
21 import java.lang.reflect.InvocationTargetException;
22 import java.util.Collections;
23 import java.util.List;
24 import java.util.logging.Level;
25 import java.util.logging.Logger;
26
27 import javax.imageio.ImageIO;
28 import javax.swing.SwingUtilities;
29
30 import org.scilab.modules.graph.utils.ScilabExported;
31 import org.scilab.modules.javasci.JavasciException;
32 import org.scilab.modules.javasci.Scilab;
33 import org.scilab.modules.localization.Messages;
34 import org.scilab.modules.types.ScilabTList;
35 import org.scilab.modules.xcos.Xcos;
36 import org.scilab.modules.xcos.block.BasicBlock;
37 import org.scilab.modules.xcos.graph.XcosDiagram;
38 import org.scilab.modules.xcos.io.scicos.ScicosFormatException;
39 import org.scilab.modules.xcos.io.scicos.ScilabDirectHandler;
40 import org.scilab.modules.xcos.palette.model.Category;
41 import org.scilab.modules.xcos.palette.model.PaletteBlock;
42 import org.scilab.modules.xcos.palette.model.PaletteNode;
43 import org.scilab.modules.xcos.palette.model.PreLoaded;
44 import org.scilab.modules.xcos.utils.BlockPositioning;
45 import org.scilab.modules.xcos.utils.XcosConstants;
46
47 import com.mxgraph.swing.mxGraphComponent;
48 import com.mxgraph.util.mxCellRenderer;
49 import com.mxgraph.util.mxRectangle;
50 import com.mxgraph.view.mxGraphView;
51 import com.mxgraph.view.mxStylesheet;
52
53 /**
54  * Utility class which is the entry point from Scilab for palette related
55  * functions.
56  */
57 public final class Palette {
58     /** the "name" argument */
59     public static final String NAME = "name";
60
61     /** Error message used on invalid path */
62     public static final String WRONG_INPUT_ARGUMENT_S_INVALID_TREE_PATH = Messages.gettext("Wrong input argument \"%s\": invalid tree path.\n");
63     /** Error message used on invalid node */
64     public static final String WRONG_INPUT_ARGUMENT_S_INVALID_NODE = Messages
65             .gettext("Wrong input argument \"%s\": invalid node, use 'xcosPalDisable' instead.\n");
66     /** "Unable to import" string */
67     public static final String UNABLE_TO_IMPORT = Messages.gettext("Unable to import %s .\n");
68
69     private static final Logger LOG = Logger.getLogger(Palette.class.getName());
70     private static final String XCOS = "xcos";
71     private static final String PALETTE_GIWS_XML = "Palette.giws.xml";
72
73     /**
74      * Default hidden constructor
75      */
76     private Palette() {
77     }
78
79     /**
80      * Get the {@link PaletteNode} of the path.
81      *
82      * @param path
83      *            the path
84      * @param create
85      *            if <code>true</code> the Category path will be created,
86      *            otherwise it will not.
87      * @return the selected node
88      */
89     private static PaletteNode getPathNode(final String[] path, final boolean create) {
90
91         if (!SwingUtilities.isEventDispatchThread()) {
92             throw new RuntimeException("Unable to manipulate palette outside the EDT thread.");
93         }
94
95         Category node = PaletteManager.getInstance().getRoot();
96
97         if (path == null || path.length == 0 || (path.length == 1 && path[0].isEmpty())) {
98             return node;
99         }
100
101         for (int categoryCounter = 0; categoryCounter < path.length; categoryCounter++) {
102
103             for (final PaletteNode next : node.getNode()) {
104                 if (next.getName().equals(path[categoryCounter]) && next instanceof Category) {
105                     node = (Category) next;
106                     break;
107                 } else if (next.getName().equals(path[categoryCounter]) && (categoryCounter == path.length - 1)) {
108                     return next; // found the terminal Palette instance
109                 }
110             }
111
112             if (!node.toString().equals(path[categoryCounter])) {
113                 if (create) {
114                     final Category cat = new Category();
115                     cat.setName(path[categoryCounter]);
116                     cat.setEnable(create);
117
118                     cat.setParent(node);
119                     node.getNode().add(cat);
120
121                     node = cat;
122                 } else {
123                     return null;
124                 }
125             }
126         }
127         return node;
128     }
129
130     /**
131      * Load an xcos palette into the palette manager
132      *
133      * @param name
134      *            the scilab exported palette variable name
135      * @param category
136      *            TreePath of the palette
137      * @throws JavasciException
138      *             on invocation error
139      */
140     @ScilabExported(module = XCOS, filename = PALETTE_GIWS_XML)
141     public static void loadPal(final String name, final String[] category) throws JavasciException {
142         /*
143          * If the env. is headless only perform fake loading to assert data
144          * integrity.
145          */
146         if (GraphicsEnvironment.isHeadless()) {
147             LOG.warning("Headless environment detected, only perform sanity check");
148             loadPalHeadless(name);
149             return;
150         }
151
152         /*
153          * Import the palette
154          */
155         final ScilabTList data = (ScilabTList) Scilab.getInCurrentScilabSession(name);
156
157         /*
158          * handle shared data on the EDT thread
159          */
160         try {
161             SwingUtilities.invokeAndWait(new Runnable() {
162                 @Override
163                 public void run() {
164                     try {
165                         /*
166                          * Decode the style part of the palette
167                          */
168                         final mxStylesheet styleSheet = Xcos.getInstance().getStyleSheet();
169                         try {
170                             new StyleElement().decode(data, styleSheet);
171                         } catch (final ScicosFormatException e) {
172                             throw new RuntimeException(e);
173                         }
174
175                         // reload all the opened diagram (clear states)
176                         for (final XcosDiagram d : Xcos.getInstance().openedDiagrams()) {
177                             if (d != null) {
178                                 final mxGraphView view = d.getView();
179                                 if (view != null) {
180                                     view.reload();
181                                 }
182
183                                 final mxGraphComponent comp = d.getAsComponent();
184                                 if (comp != null) {
185                                     comp.refresh();
186                                 }
187                             }
188                         }
189
190                         final PaletteNode node = getPathNode(category, true);
191                         if (!(node instanceof Category)) {
192                             throw new RuntimeException(String.format(WRONG_INPUT_ARGUMENT_S_INVALID_TREE_PATH, "category"));
193                         }
194                         final Category cat = (Category) node;
195
196                         /*
197                          * Adding the palette tree part of the palette
198                          */
199                         PreLoaded pal;
200                         try {
201                             pal = new PreLoadedElement().decode(data, new PreLoaded.Dynamic());
202                         } catch (final ScicosFormatException e) {
203                             throw new RuntimeException(e);
204                         }
205                         cat.getNode().add(pal);
206                         pal.setParent(cat);
207
208                         PaletteNode.refreshView(pal);
209                     } catch (Exception e) {
210                         e.printStackTrace();
211                     }
212                 }
213             });
214         } catch (final InterruptedException e) {
215             LOG.severe(e.toString());
216         } catch (final InvocationTargetException e) {
217             Throwable throwable = e;
218             String firstMessage = null;
219             while (throwable != null) {
220                 firstMessage = throwable.getLocalizedMessage();
221                 throwable = throwable.getCause();
222             }
223
224             throw new RuntimeException(firstMessage, e);
225         }
226     }
227
228     private static final void loadPalHeadless(final String name) throws JavasciException {
229         try {
230             final ScilabTList data = (ScilabTList) Scilab.getInCurrentScilabSession(name);
231
232             // style check
233             new StyleElement().decode(data, new mxStylesheet());
234
235             // palette data check
236             new PreLoadedElement().decode(data, new PreLoaded.Dynamic());
237
238         } catch (ScicosFormatException e) {
239             throw new RuntimeException(e);
240         }
241     }
242
243     /**
244      * Load an xcos palette into the palette manager at the root category.
245      *
246      * @param name
247      *            the scilab exported palette variable name
248      * @throws JavasciException
249      *             on invocation error
250      */
251     @ScilabExported(module = XCOS, filename = PALETTE_GIWS_XML)
252     public static void loadPal(final String name) throws JavasciException {
253         loadPal(name, null);
254     }
255
256     /**
257      * Add a category into the palette manager
258      *
259      * @param name
260      *            TreePath of the palette
261      * @param visible
262      *            default visibility of the palette
263      */
264     @ScilabExported(module = XCOS, filename = PALETTE_GIWS_XML)
265     public static void addCategory(final String[] name, final boolean visible) {
266         try {
267             SwingUtilities.invokeAndWait(new Runnable() {
268                 @Override
269                 public void run() {
270                     final PaletteNode node = getPathNode(name, true);
271                     if (node instanceof Category) {
272                         node.setEnable(visible);
273                     } else {
274                         throw new RuntimeException(String.format(WRONG_INPUT_ARGUMENT_S_INVALID_TREE_PATH, NAME));
275                     }
276
277                     PaletteNode.refreshView(node.getParent());
278                 }
279             });
280         } catch (final InterruptedException e) {
281             Logger.getLogger(Palette.class.getName()).severe(e.toString());
282         } catch (final InvocationTargetException e) {
283             Throwable throwable = e;
284             String firstMessage = null;
285             while (throwable != null) {
286                 firstMessage = throwable.getLocalizedMessage();
287                 throwable = throwable.getCause();
288             }
289
290             throw new RuntimeException(firstMessage, e);
291         }
292     }
293
294     /**
295      * Remove a palette or a category of the palette manager
296      *
297      * @param name
298      *            TreePath of the palette
299      */
300     @ScilabExported(module = XCOS, filename = PALETTE_GIWS_XML)
301     public static void remove(final String[] name) {
302         try {
303             SwingUtilities.invokeAndWait(new Runnable() {
304                 @Override
305                 public void run() {
306                     final PaletteNode node = getPathNode(name, false);
307                     PaletteNode.remove(node);
308                 }
309             });
310         } catch (final InterruptedException e) {
311             LOG.severe(e.toString());
312         } catch (final InvocationTargetException e) {
313             Throwable throwable = e;
314             String firstMessage = null;
315             while (throwable != null) {
316                 firstMessage = throwable.getLocalizedMessage();
317                 throwable = throwable.getCause();
318             }
319
320             throw new RuntimeException(firstMessage, e);
321         }
322     }
323
324     /**
325      * Remove a palette or a category of the palette manager
326      *
327      * @param name
328      *            TreePath of the palette or category
329      * @param status
330      *            True to set the palette visible, false otherwise
331      */
332     @ScilabExported(module = XCOS, filename = PALETTE_GIWS_XML)
333     public static void enable(final String[] name, final boolean status) {
334         try {
335             SwingUtilities.invokeAndWait(new Runnable() {
336                 @Override
337                 public void run() {
338                     final PaletteNode node = getPathNode(name, false);
339                     if (node == null) {
340                         throw new RuntimeException(String.format(WRONG_INPUT_ARGUMENT_S_INVALID_TREE_PATH, NAME));
341                     }
342
343                     node.setEnable(status);
344
345                     PaletteNode.refreshView(node.getParent());
346                 }
347             });
348         } catch (final InterruptedException e) {
349             LOG.severe(e.toString());
350         } catch (final InvocationTargetException e) {
351             Throwable throwable = e;
352             String firstMessage = null;
353             while (throwable != null) {
354                 firstMessage = throwable.getLocalizedMessage();
355                 throwable = throwable.getCause();
356             }
357
358             throw new RuntimeException(firstMessage, e);
359         }
360     }
361
362     /**
363      * Move a palette or a category of the palette manager
364      *
365      * @param source
366      *            TreePath of the palette or category
367      * @param target
368      *            TreePath of the destination
369      */
370     @ScilabExported(module = XCOS, filename = PALETTE_GIWS_XML)
371     public static void move(final String[] source, final String[] target) {
372         try {
373             SwingUtilities.invokeAndWait(new Runnable() {
374                 @Override
375                 public void run() {
376
377                     final PaletteNode src = getPathNode(source, false);
378                     if (src == null) {
379                         throw new RuntimeException(String.format(WRONG_INPUT_ARGUMENT_S_INVALID_TREE_PATH, "source"));
380                     }
381
382                     final PaletteNode trg = getPathNode(target, true);
383                     if (trg == null || !(trg instanceof Category)) {
384                         throw new RuntimeException(String.format(WRONG_INPUT_ARGUMENT_S_INVALID_TREE_PATH, "target"));
385                     }
386                     final Category destination = (Category) trg;
387
388                     final Category[] toBeReloaded = new Category[] { src.getParent(), destination };
389
390                     if (toBeReloaded[0] != null) {
391                         toBeReloaded[0].getNode().remove(src);
392                     }
393                     destination.getNode().add(src);
394                     src.setParent(destination);
395
396                     PaletteNode.refreshView(toBeReloaded[0]);
397                     PaletteNode.refreshView(toBeReloaded[1]);
398                 }
399             });
400         } catch (final InterruptedException e) {
401             LOG.severe(e.toString());
402         } catch (final InvocationTargetException e) {
403             Throwable throwable = e;
404             String firstMessage = null;
405             while (throwable != null) {
406                 firstMessage = throwable.getLocalizedMessage();
407                 throwable = throwable.getCause();
408             }
409
410             throw new RuntimeException(firstMessage, e);
411         }
412     }
413
414     /**
415      * Generate a palette block image from a block instance stored into scilab
416      * (need a valid style).
417      *
418      * @param iconPath
419      *            the output file path use to save the palette block.
420      * @throws Exception
421      *             on error
422      */
423     @ScilabExported(module = XCOS, filename = PALETTE_GIWS_XML)
424     public static void generatePaletteIcon(final String iconPath) throws Exception {
425         /*
426          * If the env. is headless does nothing
427          */
428         if (GraphicsEnvironment.isHeadless()) {
429             LOG.warning("Headless environment detected, do not generate icons");
430             return;
431         }
432
433         final ScilabDirectHandler handler = ScilabDirectHandler.acquire();
434         try {
435             final BasicBlock block = handler.readBlock();
436
437             generateIcon(block, iconPath);
438         } finally {
439             handler.release();
440         }
441
442         if (LOG.isLoggable(Level.FINEST)) {
443             LOG.finest(iconPath + " updated.");
444         }
445     }
446
447     private static void generateIcon(BasicBlock block, final String iconPath) throws IOException {
448         if (block == null || block.getGeometry() == null) {
449             return;
450         }
451         block.getGeometry().setX(XcosConstants.PALETTE_BLOCK_WIDTH);
452         block.getGeometry().setY(XcosConstants.PALETTE_BLOCK_HEIGHT);
453
454         final XcosDiagram graph = new XcosDiagram();
455         graph.installListeners();
456
457         graph.addCell(block);
458         graph.selectAll();
459
460         BlockPositioning.updateBlockView(block);
461
462         /*
463          * Render
464          */
465         final mxGraphComponent graphComponent = graph.getAsComponent();
466         graphComponent.refresh();
467
468         final mxRectangle bounds = graph.getPaintBounds(new Object[] { block });
469         final double width = bounds.getWidth();
470         final double height = bounds.getHeight();
471
472         final double scale;
473         if (width > XcosConstants.PALETTE_BLOCK_WIDTH || height > XcosConstants.PALETTE_BLOCK_HEIGHT) {
474             scale = Math.min(XcosConstants.PALETTE_BLOCK_WIDTH / width, XcosConstants.PALETTE_BLOCK_HEIGHT / height) / XcosConstants.PALETTE_BLOCK_ICON_RATIO;
475         } else {
476             scale = 1.0;
477         }
478
479         final BufferedImage image = mxCellRenderer.createBufferedImage(graph, null, scale, graphComponent.getBackground(), graphComponent.isAntiAlias(), null,
480                                     graphComponent.getCanvas());
481
482         final String extension = iconPath.substring(iconPath.lastIndexOf('.') + 1);
483         ImageIO.write(image, extension, new File(iconPath));
484     }
485
486     /**
487      * Helper function used to regenerate palette block images from the block
488      * instance.
489      *
490      * @param path
491      *            the path to emit the images for.
492      */
493     public static void generateAllImages(final String[] path) {
494         PaletteManager.getInstance();
495
496         SwingUtilities.invokeLater(new Runnable() {
497
498             @Override
499             public void run() {
500                 final PaletteNode src = getPathNode(path, false);
501                 if (src == null) {
502                     System.err.print(String.format(WRONG_INPUT_ARGUMENT_S_INVALID_TREE_PATH, "path"));
503                     return;
504                 }
505
506                 iterate(Collections.singletonList(src));
507             }
508         });
509
510         LOG.finest("All images has been generated.");
511     }
512
513     private static void iterate(final List<PaletteNode> children) {
514         for (final PaletteNode next : children) {
515             if (next instanceof Category) {
516                 iterate(((Category) next).getNode());
517             } else if (next instanceof PreLoaded) {
518                 generatePreLoadedIcon((PreLoaded) next);
519             }
520         }
521     }
522
523     /**
524      * Generate PreLoaded icon file.
525      *
526      * @param current
527      *            the current node
528      */
529     private static void generatePreLoadedIcon(final PreLoaded current) {
530         final List<PaletteBlock> blocks = current.getBlock();
531         for (final PaletteBlock paletteBlock : blocks) {
532
533             new PaletteBlockCtrl(paletteBlock).loadBlock(new ActionListener() {
534
535                 @Override
536                 public void actionPerformed(ActionEvent event) {
537                     try {
538                         generateIcon((BasicBlock) event.getSource(), paletteBlock.getIcon().getEvaluatedPath());
539                         if (LOG.isLoggable(Level.FINEST)) {
540                             LOG.finest(String.format("%s has been generated", paletteBlock.getIcon()));
541                         }
542                     } catch (IOException e) {
543                         e.printStackTrace();
544                     }
545                 }
546             });
547         }
548     }
549 }