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