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