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