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