Fix misc. issues with Tab opening
[scilab.git] / scilab / modules / xcos / src / java / org / scilab / modules / xcos / Xcos.java
1 /*
2  * Scilab ( http://www.scilab.org/ ) - This file is part of Scilab
3  * Copyright (C) 2009 - DIGITEO - Bruno JOFRET
4  * Copyright (C) 2010 - DIGITEO - Clement DAVID
5  * Copyright (C) 2011-2017 - Scilab Enterprises - Clement DAVID
6  *
7  * Copyright (C) 2012 - 2016 - Scilab Enterprises
8  *
9  * This file is hereby licensed under the terms of the GNU GPL v2.0,
10  * pursuant to article 5.3.4 of the CeCILL v.2.1.
11  * This file was originally licensed under the terms of the CeCILL v2.1,
12  * and continues to be available under such terms.
13  * For more information, see the COPYING file which you should have received
14  * along with this program.
15  *
16  */
17
18 package org.scilab.modules.xcos;
19
20 import java.awt.Component;
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.Arrays;
26 import java.util.Collection;
27 import java.util.Collections;
28 import java.util.HashMap;
29 import java.util.HashSet;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.logging.LogManager;
33 import java.util.logging.Logger;
34
35 import javax.swing.Action;
36 import javax.swing.ImageIcon;
37 import javax.swing.SwingUtilities;
38
39 import org.scilab.modules.action_binding.InterpreterManagement;
40 import org.scilab.modules.commons.gui.FindIconHelper;
41 import org.scilab.modules.commons.xml.XConfiguration;
42 import org.scilab.modules.core.Scilab;
43 import org.scilab.modules.graph.actions.base.GraphActionManager;
44 import org.scilab.modules.graph.utils.ScilabExported;
45 import org.scilab.modules.gui.bridge.menu.SwingScilabMenu;
46 import org.scilab.modules.gui.bridge.menubar.SwingScilabMenuBar;
47 import org.scilab.modules.gui.bridge.tab.SwingScilabDockablePanel;
48 import org.scilab.modules.gui.messagebox.ScilabModalDialog;
49 import org.scilab.modules.gui.messagebox.ScilabModalDialog.AnswerOption;
50 import org.scilab.modules.gui.messagebox.ScilabModalDialog.ButtonType;
51 import org.scilab.modules.gui.messagebox.ScilabModalDialog.IconType;
52 import org.scilab.modules.gui.tabfactory.AbstractScilabTabFactory;
53 import org.scilab.modules.gui.tabfactory.ScilabTabFactory;
54 import org.scilab.modules.gui.utils.BarUpdater;
55 import org.scilab.modules.gui.utils.ClosingOperationsManager;
56 import org.scilab.modules.gui.utils.WindowsConfigurationManager;
57 import org.scilab.modules.localization.Messages;
58 import org.scilab.modules.xcos.actions.ExternalAction;
59 import org.scilab.modules.xcos.actions.StopAction;
60 import org.scilab.modules.xcos.configuration.ConfigurationManager;
61 import org.scilab.modules.xcos.configuration.model.DocumentType;
62 import org.scilab.modules.xcos.graph.DiagramComparator;
63 import org.scilab.modules.xcos.graph.XcosDiagram;
64 import org.scilab.modules.xcos.io.XcosFileType;
65 import org.scilab.modules.xcos.palette.PaletteManager;
66 import org.scilab.modules.xcos.palette.view.PaletteManagerView;
67 import org.scilab.modules.xcos.preferences.XcosConfiguration;
68 import org.scilab.modules.xcos.utils.FileUtils;
69 import org.scilab.modules.xcos.utils.XcosMessages;
70
71 import com.mxgraph.model.mxCell;
72 import com.mxgraph.util.mxEvent;
73 import com.mxgraph.util.mxEventObject;
74 import com.mxgraph.view.mxStylesheet;
75 import javax.swing.Timer;
76 import org.scilab.modules.commons.ScilabCommons;
77 import org.scilab.modules.xcos.graph.model.ScicosObjectOwner;
78 import org.scilab.modules.xcos.graph.model.XcosGraphModel;
79
80 /**
81  * Xcos entry point class
82  */
83 // CSOFF: ClassFanOutComplexity
84 // CSOFF: ClassDataAbstractionCoupling
85 public final class Xcos {
86     /**
87      * The current Xcos version
88      */
89     public static final String VERSION = "2.0";
90     /**
91      * The current Xcos tradename
92      */
93     public static final String TRADENAME = Xcos.class.getSimpleName();
94     public static final ImageIcon ICON = new ImageIcon(FindIconHelper.findIcon("utilities-system-monitor", "256x256"));
95
96     private static final String LOAD_XCOS_LIBS_LOAD_SCICOS = "prot=funcprot(); funcprot(0); loadXcosLibs(); loadScicos(); funcprot(prot); clear prot";
97
98     private static final String CALLED_OUTSIDE_THE_EDT_THREAD = "Called outside the EDT thread.";
99     private static final Logger LOG = Logger.getLogger(Xcos.class.getName());
100
101     /** common shared instance */
102     private static volatile Xcos sharedInstance;
103
104     static {
105         Scilab.registerInitialHook(() -> {
106             // wait the Scilab startup termination
107             final Timer t = new Timer(500, null);
108             t.addActionListener((e) -> {
109                 if (ScilabCommons.getStartProcessing() == 1)
110                     return;
111                 t.stop();
112
113                 /* load scicos libraries (macros) */
114                 InterpreterManagement.requestScilabExec(LOAD_XCOS_LIBS_LOAD_SCICOS);
115             });
116             t.start();
117         });
118
119         XConfiguration.addXConfigurationListener(new XcosConfiguration());
120     }
121
122     /*
123      * Instance data
124      */
125     private final Map<ScicosObjectOwner, List<XcosDiagram>> diagrams;
126     private XcosView view;
127     private BrowserView browser;
128     private boolean onDiagramIteration = false;
129     private String lastError = null;
130
131     /*
132      * Instance handlers
133      */
134     private final PaletteManager palette;
135     private final ConfigurationManager configuration;
136     private final mxStylesheet styleSheet;
137     private final List<ExternalAction> externalActions;
138
139     private final XcosTabFactory factory;
140
141     /**
142      * Construct an Xcos instance.
143      *
144      * There must be only one Xcos instance per Scilab application
145      */
146     private Xcos(final XcosTabFactory factory) {
147         /*
148          * Read the configuration to support dynamic (before Xcos launch) settings.
149          */
150         try {
151             LogManager.getLogManager().readConfiguration();
152         } catch (final SecurityException | IOException e) {
153             LOG.severe(e.toString());
154         }
155
156         /*
157          * Allocate data
158          */
159         diagrams = new HashMap<>();
160
161         view = new XcosView();
162         // allocate and install the browser view on demand to avoid any cost
163         browser = null;
164
165         /*
166          * get the handlers instance
167          */
168         palette = PaletteManager.getInstance();
169         configuration = ConfigurationManager.getInstance();
170         styleSheet = new mxStylesheet();
171         externalActions = new ArrayList<>();
172
173         try {
174             FileUtils.decodeStyle(styleSheet);
175         } catch (final IOException e) {
176             LOG.severe(e.toString());
177         }
178
179         /*
180          * Register as an AbstractScilabTabFactory
181          */
182         if (factory == null) {
183             this.factory = new XcosTabFactory(false);
184         } else {
185             this.factory = factory;
186         }
187         ScilabTabFactory.getInstance().addTabFactory(this.factory);
188
189     }
190
191     @Override
192     protected void finalize() throws Throwable {
193         if (browser != null) {
194             JavaController.unregister_view(browser);
195         }
196
197         JavaController.unregister_view(view);
198
199         super.finalize();
200     }
201
202     /**
203      * @return the per Scilab application, Xcos instance
204      */
205     public static synchronized Xcos getInstance() {
206         return getInstance(null);
207     }
208
209     /**
210      * @param factory
211      *            the tab factory instance or null on creation
212      * @return the per Scilab application, Xcos instance
213      */
214     private static synchronized Xcos getInstance(final XcosTabFactory factory) {
215         if (sharedInstance == null) {
216             try {
217                 if (!SwingUtilities.isEventDispatchThread()) {
218                     SwingUtilities.invokeAndWait(() -> {
219                         sharedInstance = new Xcos(factory);
220                     });
221                 } else {
222                     sharedInstance = new Xcos(factory);
223                 }
224             } catch (InvocationTargetException e) {
225                 e.printStackTrace();
226             } catch (InterruptedException e) {
227                 e.printStackTrace();
228             }
229
230             LOG.finest("Session started");
231         }
232
233         return sharedInstance;
234     }
235
236     /**
237      * Try to quit xcos
238      */
239     public void quit(boolean force) {
240         if (sharedInstance == null) {
241             return;
242         }
243
244         // TODO : perform something ?
245     }
246
247     /**
248      * Clear the shared instance.
249      */
250     private static synchronized void clearInstance() {
251         sharedInstance = null;
252         LOG.finest("Session ended");
253     }
254
255     /**
256      * All Opened diagrams
257      *
258      * @return the opened diagrams list
259      */
260     public List<XcosDiagram> openedDiagrams() {
261         ArrayList<XcosDiagram> opened = new ArrayList<>();
262         diagrams.values().stream().forEach(l -> opened.addAll(l));
263         return opened;
264     }
265
266     /**
267      * Opened diagrams
268      *
269      * @param root
270      *            the root diagram
271      * @return the opened diagrams list
272      */
273     public List<XcosDiagram> openedDiagrams(ScicosObjectOwner root) {
274         return diagrams.getOrDefault(root, Collections.emptyList());
275     }
276
277     public ScicosObjectOwner openedDiagram(File f) {
278         ScicosObjectOwner opened = null;
279         if (f == null) {
280             return opened;
281         }
282
283         for (ScicosObjectOwner root : diagrams.keySet()) {
284             List<XcosDiagram> diags = diagrams.getOrDefault(root, Collections.emptyList());
285
286             if (!diags.isEmpty() && f.equals(diags.get(0).getSavedFile())) {
287                 opened = root;
288                 break;
289             }
290         }
291
292         return opened;
293     }
294
295     /**
296      * Check if the in memory file representation is modified
297      *
298      * @param root
299      *            the root diagram
300      * @return is modified
301      */
302     public boolean isModified(ScicosObjectOwner root) {
303         for (XcosDiagram d : diagrams.get(root)) {
304             if (d.isModified()) {
305                 return true;
306             }
307         }
308
309         return false;
310     }
311
312     /**
313      * Set the full diagram as modified
314      * @param root the root diagram
315      * @param modified the modified status
316      */
317     public void setModified(ScicosObjectOwner root, boolean modified) {
318         for (XcosDiagram d : diagrams.get(root)) {
319             d.setModified(modified);
320         }
321     }
322
323     /**
324      * Popup a dialog to ask for a file creation
325      *
326      * @param f
327      *            the file to create
328      * @return true if creation is has been performed
329      */
330     public boolean askForFileCreation(final XcosDiagram diag, final File f) {
331         AnswerOption answer;
332         try {
333             answer = ScilabModalDialog.show(diag.getAsComponent(), new String[] { String.format(XcosMessages.FILE_DOESNT_EXIST, f.getCanonicalFile()) },
334                                             XcosMessages.XCOS, IconType.QUESTION_ICON, ButtonType.YES_NO);
335         } catch (final IOException e) {
336             LOG.severe(e.toString());
337             answer = AnswerOption.YES_OPTION;
338         }
339
340         if (answer == AnswerOption.YES_OPTION) {
341             return diag.saveDiagramAs(f);
342         }
343
344         return false;
345     }
346
347     /**
348      * @return the global shared styleSheet
349      */
350     public mxStylesheet getStyleSheet() {
351         return styleSheet;
352     }
353
354     /**
355      * Open a file from it's filename.
356      *
357      * This method must be called on the EDT thread. For other use, please use the {@link #xcos(String, String)} method.
358      *
359      * @param file
360      *            the file to open. If null an empty diagram is created.
361      * @param diagramId
362      *            the MVC ID to track. If 0 no association is performed.
363      */
364     public void open(final String file, final long diagramId) {
365         if (!SwingUtilities.isEventDispatchThread()) {
366             LOG.severe(CALLED_OUTSIDE_THE_EDT_THREAD);
367         }
368
369         /*
370          * If it is the first window opened, then open the palette first.
371          */
372         boolean openThePalette = openedDiagrams().isEmpty();
373
374         JavaController controller = new JavaController();
375         XcosDiagram diag = null;
376         final File f;
377         if (file != null) {
378             f = new File(file);
379         } else {
380             f = null;
381         }
382
383         if (f != null && f.exists()) {
384             configuration.addToRecentFiles(f);
385         }
386
387         /*
388          * looking for an already opened diagram
389          */
390         ScicosObjectOwner root = openedDiagram(f);
391         if (root != null) {
392             diag = diagrams.get(root).iterator().next();
393         }
394         // if unsaved and empty, reuse it. Allocate otherwise.
395         if (f == null && diag != null && diag.getModel().getChildCount(diag.getDefaultParent()) > 0) {
396             diag = null;
397         }
398
399         // looking for an empty, unsaved diagram to use if opening a new file
400         // if not found an already open instance of the file
401         if (diag == null) {
402             // traverse through the key set of all the opened diagrams
403             for (Map.Entry<ScicosObjectOwner, List<XcosDiagram>> entry : diagrams.entrySet()) {
404                 List<XcosDiagram> diagramsWithKey = entry.getValue();
405                 XcosDiagram diagramWithKey = diagramsWithKey.get(0); // get the diagram that maps to that key
406                 int childCount = diagramWithKey.countChildren(); //count the number of children in the diagram
407                 // if empty, unsaved and unused
408                 if (childCount == 0 && diagramWithKey.getSavedFile() == null && !diagramWithKey.isModified()) {
409                     // use that open diagram
410                     diag = diagramWithKey;
411                     diag.transformAndLoadFile(controller, file);
412                 }
413             }
414         }
415         // if reuse then request focus
416         if (diag != null) {
417             XcosTab tab = XcosTab.get(diag);
418             if (tab != null) {
419                 tab.setCurrent();
420                 tab.requestFocus();
421             }
422         }
423
424         long currentId;
425         if (diagramId != 0) {
426             currentId = diagramId;
427         } else {
428             currentId = controller.createObject(Kind.DIAGRAM);
429         }
430
431         if (diag != null) {
432             // loading disabled, unlock
433             synchronized (this) {
434                 setLastError("");
435                 notify();
436             }
437         } else {
438             // loading enable, unlock will be performed later, on another thread
439
440             /*
441              * Allocate and setup a new diagram
442              */
443             Kind currentKind = controller.getKind(currentId);
444             if (Kind.BLOCK.equals(currentKind)) {
445                 long[] rootId = new long[1];
446                 controller.getObjectProperty(currentId, currentKind, ObjectProperties.PARENT_DIAGRAM, rootId);
447
448                 if (rootId[0] == 0) {
449                     // a SuperBlock without a parent diagram, duplicate it as a root diagram
450                     VectorOfScicosID children = new VectorOfScicosID();
451                     controller.getObjectProperty(currentId, currentKind, ObjectProperties.CHILDREN, children);
452
453                     currentKind = Kind.DIAGRAM;
454                     currentId = controller.createObject(Kind.DIAGRAM);
455                     controller.setObjectProperty(currentId, currentKind, ObjectProperties.CHILDREN, children);
456
457                     for (int i = 0; i < children.size(); i++) {
458                         controller.referenceObject(children.get(i));
459                     }
460                 }
461             }
462
463             diag = new XcosDiagram(controller, currentId, currentKind, "");
464             diag.installListeners();
465
466             root = findRoot(controller, diag);
467             addDiagram(root, diag);
468
469             /*
470              * Ask for file creation
471              */
472             if (f != null && !f.exists()) {
473                 if (!askForFileCreation(diag, f)) {
474                     // loading disabled, unlock
475                     synchronized (this) {
476                         setLastError("");
477                         notify();
478                     }
479
480                     // return now, to avoid tab creation
481                     diagrams.remove(root);
482                     return;
483                 }
484             }
485
486
487             /*
488              * Create a visible window before loading
489              */
490             if (XcosTab.get(diag) == null) {
491                 XcosTab.restore(diag);
492
493             }
494             if (openThePalette) {
495                 PaletteManager.setVisible(true);
496             }
497
498             /*
499              * Load the file
500              */
501             diag.transformAndLoadFile(controller, file);
502         }
503     }
504
505     /**
506      * Log a loading error
507      *
508      * @param lastError
509      *            the error description
510      */
511     public void setLastError(String error) {
512         this.lastError = error;
513     }
514
515     /**
516      * @return the Xcos view
517      */
518     public XcosView getXcosView() {
519         return view;
520     }
521
522     /**
523      * @return the Browser view
524      */
525     public BrowserView getBrowser() {
526         if (browser == null) {
527             browser = new BrowserView();
528             JavaController.register_view(BrowserView.class.getSimpleName(), browser);
529         }
530         return browser;
531     }
532
533     /**
534      * Clear the browser state and unregister the current view.
535      */
536     public void clearBrowser() {
537         if (browser != null) {
538             JavaController.unregister_view(browser);
539             browser = null;
540         }
541     }
542
543     /**
544      * Get an unmodifiable view of the diagrams for an UID
545      *
546      * @param root
547      *            the root diagram
548      * @return the diagram collection
549      */
550     public Collection<XcosDiagram> getDiagrams(final ScicosObjectOwner root) {
551         final Collection<XcosDiagram> diags = diagrams.get(root);
552         if (diags == null) {
553             return null;
554         }
555         return Collections.unmodifiableCollection(diags);
556     }
557
558     /**
559      * Add a diagram to the diagram list for a file. Be sure to set the right opened status on the diagram before calling this method.
560      *
561      * @param root
562      *            the root diagram
563      * @param diag
564      *            the diag
565      */
566     public void addDiagram(final ScicosObjectOwner root, final XcosDiagram diag) {
567         if (onDiagramIteration) {
568             throw new RuntimeException();
569         }
570         if (root == null) {
571             throw new IllegalArgumentException();
572         }
573
574         /*
575          * Create the collection if it does not exist
576          */
577         List<XcosDiagram> diags = diagrams.get(root);
578         if (diags == null) {
579             diags = createDiagramCollection();
580             diagrams.put(root, diags);
581         }
582
583         // insert the diagram
584         diags.add(diag);
585     }
586
587     /**
588      * Add a diagram to the opened  list
589      *
590      * This method manage both super-block and root diagrams.
591      * @param diag the diagram to add
592      */
593     public void addDiagram(final XcosDiagram diag) {
594         ScicosObjectOwner root = findRoot(diag);
595         addDiagram(root, diag);
596     }
597
598     /**
599      * Create a diagram collections (sorted List)
600      *
601      * @return the diagram collection
602      */
603     @SuppressWarnings("serial")
604     public List<XcosDiagram> createDiagramCollection() {
605         return new ArrayList<XcosDiagram>() {
606             @Override
607             public boolean add(XcosDiagram element) {
608                 final boolean status = super.add(element);
609                 DiagramComparator.sort(this);
610                 return status;
611             }
612
613             @Override
614             public boolean addAll(Collection<? extends XcosDiagram> c) {
615                 final boolean status = super.addAll(c);
616                 DiagramComparator.sort(this);
617                 return status;
618             }
619         };
620     }
621
622     /**
623      * Try to close the graph (popup save dialog)
624      *
625      * @param graph
626      *            the graph to close
627      * @return if we can (or not) close the graph
628      */
629     public boolean canClose(final XcosDiagram graph) {
630         boolean canClose = false;
631
632         ScicosObjectOwner root = findRoot(graph);
633
634         final boolean wasLastOpened = openedDiagrams(root).size() <= 1;
635         final boolean isModified = isModified(root);
636         if (!(wasLastOpened && isModified)) {
637             canClose = true;
638         }
639
640         if (!canClose) {
641             final AnswerOption ans = ScilabModalDialog.show(XcosTab.get(graph), XcosMessages.DIAGRAM_MODIFIED, XcosMessages.XCOS, IconType.QUESTION_ICON,
642                                      ButtonType.YES_NO_CANCEL);
643
644             switch (ans) {
645                 case YES_OPTION:
646                     canClose = diagrams.get(root).iterator().next().saveDiagram();
647                     break;
648                 case NO_OPTION:
649                     canClose = true; // can close
650                     break;
651                 default:
652                     canClose = false; // operation canceled
653                     break;
654             }
655         }
656
657         /*
658          * Update configuration before the destroy call to validate the uuid
659          */
660         if (canClose) {
661             configuration.addToRecentTabs(graph);
662             configuration.saveConfig();
663         }
664         return canClose;
665     }
666
667     /**
668      * Close a diagram.
669      *
670      * This method must be called on the EDT thread.
671      *
672      * @param graph
673      *            the diagram to close
674      */
675     public void destroy(XcosDiagram graph) {
676         ScicosObjectOwner root = findRoot(graph);
677
678         final boolean wasLastOpenedForFile = openedDiagrams(root).size() <= 1;
679         if (wasLastOpenedForFile) {
680             diagrams.remove(root);
681         } else {
682             diagrams.get(root).remove(graph);
683         }
684
685         if (openedDiagrams().size() <= 0) {
686             JavaController.end_simulation();
687         }
688     }
689
690     /**
691      * @param graph
692      *            the graph to handle
693      * @param list
694      *            the diagram to check
695      * @return diagram name for the "Are your sure ?" dialog
696      */
697     public String askForClosing(final XcosDiagram graph, final List<SwingScilabDockablePanel> list) {
698         final String msg;
699
700         if (wasLastOpened(list)) {
701             msg = TRADENAME;
702         } else {
703             msg = null;
704         }
705
706         return msg;
707     }
708
709     /**
710      * Does Xcos will close or not ?
711      *
712      * @param list
713      *            the list to be closed
714      * @return true if all files will be close on tabs close.
715      */
716     public boolean wasLastOpened(final List<SwingScilabDockablePanel> list) {
717         final HashSet<String> opened = new HashSet<String>();
718         for (XcosDiagram diag : openedDiagrams()) {
719             opened.add(diag.getGraphTab());
720         }
721
722         final HashSet<String> tabs = new HashSet<String>();
723         for (SwingScilabDockablePanel tab : list) {
724             if (tab != null) {
725                 tabs.add(tab.getPersistentId());
726             }
727         }
728
729         opened.removeAll(tabs);
730
731         return opened.isEmpty();
732     }
733
734     /**
735      * @return the external action list
736      */
737     public List<ExternalAction> getExternalActions() {
738         return externalActions;
739     }
740
741     /**
742      * Close the current xcos session.
743      *
744      * This method must be called on the EDT thread. For other use, please use the {@link #closeXcosFromScilab()} method.
745      */
746     public static synchronized void closeSession(final boolean ask) {
747         if (!SwingUtilities.isEventDispatchThread()) {
748             LOG.severe(CALLED_OUTSIDE_THE_EDT_THREAD);
749         }
750
751         /* Doesn't instantiate xcos on close operation */
752         if (sharedInstance == null) {
753             return;
754         }
755
756         /*
757          * Try to close all opened files
758          */
759         final Xcos instance = sharedInstance;
760
761         // get all tabs
762         final List<SwingScilabDockablePanel> tabs = new ArrayList<SwingScilabDockablePanel>();
763         for (final Collection<XcosDiagram> diags : instance.diagrams.values()) {
764             for (final XcosDiagram diag : diags) {
765                 final SwingScilabDockablePanel tab = XcosTab.get(diag);
766                 if (tab != null) {
767                     tabs.add(tab);
768                 }
769             }
770         }
771
772         // ask to close
773         final boolean status = ClosingOperationsManager.startClosingOperation(tabs, ask, ask);
774
775         // clear states
776         if (status) {
777             /* reset the shared instance state */
778             instance.diagrams.clear();
779
780             /* terminate any remaining simulation */
781             JavaController.end_simulation();
782
783             /* Saving modified data */
784             instance.palette.saveConfig();
785             instance.configuration.saveConfig();
786         }
787     }
788
789     /*
790      * Scilab exported methods.
791      *
792      * All the following methods must use SwingUtilities method to assert that the operations will be called on the EDT thread.
793      *
794      * @see modules/xcos/src/jni/Xcos.giws.xml
795      *
796      * @see sci_gateway/xcos_gateway.xml
797      *
798      * @see modules/xcos/sci_gateway/cpp/sci_*.cpp
799      */
800
801     /**
802      * Main entry point
803      *
804      * This method invoke Xcos operation on the EDT thread.
805      *
806      * @param file
807      *            The filename (can be null)
808      * @param diagramId
809      *            The Xcos DIAGRAM model ID (can be 0)
810      */
811     @ScilabExported(module = "xcos", filename = "Xcos.giws.xml")
812     public static void xcos(final String file, final long diagramId) {
813         final Xcos instance = getInstance();
814         instance.lastError = null;
815
816         /* load scicos libraries (macros) */
817         InterpreterManagement.requestScilabExec(LOAD_XCOS_LIBS_LOAD_SCICOS);
818
819         synchronized (instance) {
820             /*
821              * Open the file
822              */
823             SwingUtilities.invokeLater(new Runnable() {
824                 @Override
825                 public void run() {
826                     // open on EDT
827                     instance.open(file, diagramId);
828                 }
829             });
830         }
831         if (instance.lastError != null && !instance.lastError.isEmpty()) {
832             throw new RuntimeException(instance.lastError);
833         }
834     }
835
836     /**
837      * Load or Save an xcos diagram without using Scilab at all.
838      *
839      * <P>
840      * This support a reduced number of format and should be mainly used to test
841      *
842      * @param file
843      *            the file
844      * @param diagramId
845      *            the diagram to load into
846      * @param export
847      *            flag used to indicate an export (true == export ; false == import)
848      * @throws Exception
849      *             on loading error
850      */
851     @ScilabExported(module = "xcos", filename = "Xcos.giws.xml")
852     public static void xcosDiagramToScilab(String file, long diagramId, boolean export) throws Exception {
853         XcosFileType filetype = XcosFileType.findFileType(file);
854         if (filetype == null) {
855             throw new IllegalArgumentException("not handled filetype");
856         }
857
858         if (XcosFileType.getAvailableSaveFormats().contains(filetype)) {
859             if (export) {
860                 filetype.save(file, new XcosDiagram(new JavaController(), diagramId, Kind.DIAGRAM, ""));
861             } else {
862                 filetype.load(file, new XcosDiagram(new JavaController(), diagramId, Kind.DIAGRAM, ""));
863             }
864         } else {
865             throw new IllegalArgumentException("not handled filetype");
866         }
867     }
868
869     /**
870      * Close the current xcos session from any thread.
871      *
872      * This method invoke Xcos operation on the EDT thread. Please prefer using {@link #closeSession()} when the caller is on the EDT thread.
873      */
874     @ScilabExported(module = "xcos", filename = "Xcos.giws.xml")
875     public static void closeXcosFromScilab() {
876         try {
877             SwingUtilities.invokeAndWait(new Runnable() {
878                 @Override
879                 public void run() {
880                     closeSession(false);
881                     clearInstance();
882                 }
883             });
884         } catch (final InterruptedException e) {
885             e.printStackTrace();
886         } catch (final InvocationTargetException e) {
887             e.printStackTrace();
888
889             Throwable throwable = e;
890             String firstMessage = null;
891             while (throwable != null) {
892                 firstMessage = throwable.getLocalizedMessage();
893                 throwable = throwable.getCause();
894             }
895
896             throw new RuntimeException(firstMessage, e);
897         }
898     }
899
900     /**
901      * Look in each diagram to find the block corresponding to the given uid and display a warning message.
902      *
903      * This method invoke Xcos operation on the EDT thread.
904      *
905      * @param uid
906      *            A String as UID.
907      * @param message
908      *            The message to display.
909      */
910     @ScilabExported(module = "xcos", filename = "Xcos.giws.xml")
911     public static void warnCellByUID(final String[] uids, final String message) {
912         try {
913             SwingUtilities.invokeAndWait(new Runnable() {
914                 @Override
915                 public void run() {
916                     getInstance().warnCell(uids, message);
917                 }
918             });
919         } catch (final InterruptedException e) {
920             LOG.severe(e.toString());
921         } catch (final InvocationTargetException e) {
922             Throwable throwable = e;
923             String firstMessage = null;
924             while (throwable != null) {
925                 firstMessage = throwable.getLocalizedMessage();
926                 throwable = throwable.getCause();
927             }
928
929             throw new RuntimeException(firstMessage, e);
930         }
931     }
932
933     private void warnCell(final String[] uids, final String message) {
934
935         final mxCell[] cells  = new mxCell[uids.length];
936         final XcosDiagram[] diags  = new XcosDiagram[uids.length];
937
938         lookupForCells(uids, cells, diags);
939         for (int i = cells.length - 1; i >= 0; --i) {
940             mxCell cell = cells[i];
941
942             // perform the action on the last visible block
943             if (cell != null) {
944                 final XcosDiagram parent = diags[i];
945                 parent.warnCellByUID(cell.getId(), message);
946
947                 // Focus on an existing diagram
948                 SwingUtilities.invokeLater(() -> {
949                     XcosTab.get(parent).setCurrent();
950                 });
951
952                 return;
953             }
954
955         }
956     }
957
958     public void lookupForCells(final String[] uid, final mxCell[] cells, final XcosDiagram[] diags) {
959         final List<XcosDiagram> all = openedDiagrams();
960         for (int i = 0; i < uid.length; i++) {
961             final String id = uid[i];
962             diags[i] = all.stream().filter(d -> ((XcosGraphModel) d.getModel()).getCell(id) != null)
963                        .findFirst().orElse(null);
964             if (diags[i] != null) {
965                 cells[i] = (mxCell) ((XcosGraphModel) diags[i].getModel()).getCell(id);
966             }
967         }
968     }
969
970     /**
971      * Add a menu into xcos
972      *
973      * @param label
974      *            the label to use
975      * @param command
976      *            the callback (as a Scilab executable String)
977      */
978     @ScilabExported(module = "xcos", filename = "Xcos.giws.xml")
979     public static void addToolsMenu(final String label, final String command) {
980         final ExternalAction action = new ExternalAction(null, command);
981         action.putValue(Action.NAME, label);
982         final Xcos instance = Xcos.getInstance();
983
984         /*
985          * Store for future tabs
986          */
987         instance.externalActions.add(action);
988
989         /*
990          * Update opened tabs
991          */
992         for (final XcosDiagram d : instance.openedDiagrams()) {
993             final String uuid = d.getGraphTab();
994             final SwingScilabDockablePanel tab = ScilabTabFactory.getInstance().getFromCache(uuid);
995
996             if (tab != null) {
997                 final SwingScilabMenuBar bar = ((SwingScilabMenuBar) tab.getMenuBar().getAsSimpleMenuBar());
998
999                 final Component[] comps = bar.getComponents();
1000                 for (Component component : comps) {
1001                     if (component instanceof SwingScilabMenu) {
1002                         final SwingScilabMenu menu = (SwingScilabMenu) component;
1003
1004                         if (menu.getText() == XcosMessages.TOOLS) {
1005                             menu.add(new ExternalAction(action, d));
1006                         }
1007                     }
1008                 }
1009
1010                 // Also update the parent window toolbar
1011                 BarUpdater.updateBars(tab.getParentWindowId(), tab.getMenuBar(), tab.getToolBar(), tab.getInfoBar(), tab.getName(), tab.getWindowIcon());
1012             }
1013         }
1014     }
1015
1016     /**
1017      * Inform Xcos the simulator has just started
1018      *
1019      */
1020     @ScilabExported(module = "xcos", filename = "Xcos.giws.xml")
1021     public static void xcosSimulationStarted() {
1022         SwingUtilities.invokeLater(new Runnable() {
1023             @Override
1024             public void run() {
1025                 GraphActionManager.setEnable(StopAction.class, true);
1026             }
1027         });
1028     }
1029
1030     /**
1031      * Look for the diagram that match the model object in the diagram hierarchy.
1032      * @param uid
1033      *            the UID to search for
1034      * @return the associated diagram
1035      */
1036     public static XcosDiagram findDiagram(long uid) {
1037         final Xcos instance = getInstance();
1038
1039         for (Collection<XcosDiagram> diags : instance.diagrams.values()) {
1040             for (XcosDiagram diag : diags) {
1041                 final Object defaultParent = diag.getDefaultParent();
1042
1043                 if (defaultParent instanceof XcosCell && ((XcosCell) defaultParent).getUID() == uid) {
1044                     return diag;
1045                 }
1046             }
1047         }
1048
1049         return null;
1050     }
1051
1052     /**
1053      * Look for the parent diagram of the cell in the diagram hierarchy.
1054      *
1055      * @param cell
1056      *            the cell to search for
1057      * @return the associated diagram
1058      */
1059     public static XcosDiagram findParent(Object cell) {
1060         final Xcos instance = getInstance();
1061
1062         for (Collection<XcosDiagram> diags : instance.diagrams.values()) {
1063             for (XcosDiagram diag : diags) {
1064                 final mxGraphModel model = (mxGraphModel) diag.getModel();
1065
1066                 // use the O(1) lookup
1067                 if (cell instanceof mxICell && model.getCell(((mxICell) cell).getId()) != null) {
1068                     return diag;
1069                 }
1070             }
1071         }
1072
1073         return null;
1074     }
1075
1076     /**
1077      * Look for the root object of the whole graph hierarchy
1078      * @param graph the graph
1079      * @return the root MVC object with Kind.DIAGRAM
1080      */
1081     public static ScicosObjectOwner findRoot(XcosDiagram graph) {
1082         return findRoot(new JavaController(), graph);
1083     }
1084
1085     /**
1086      * Look for the root object of the whole graph hierarchy
1087      * @param  controller the shared controller
1088      * @param graph the graph
1089      * @return the root MVC object with Kind.DIAGRAM
1090      */
1091     public static ScicosObjectOwner findRoot(JavaController controller, XcosDiagram graph) {
1092         ScicosObjectOwner root;
1093         if (graph.getKind() == Kind.DIAGRAM) {
1094             root = new ScicosObjectOwner(controller, graph.getUID(), graph.getKind());
1095         } else {
1096             long[] rootDiagram = new long[1];
1097             controller.getObjectProperty(graph.getUID(), graph.getKind(), ObjectProperties.PARENT_DIAGRAM, rootDiagram);
1098             root = new ScicosObjectOwner(controller, rootDiagram[0], Kind.DIAGRAM);
1099         }
1100
1101         return root;
1102
1103     }
1104
1105     /*
1106      * @see org.scilab.modules.gui.tabfactory.AbstractScilabTabFactory
1107      */
1108     public static class XcosTabFactory extends AbstractScilabTabFactory {
1109
1110         /*
1111          * Cache
1112          */
1113         private DocumentType cachedDocumentType;
1114
1115         /**
1116          * Default constructor
1117          */
1118         public XcosTabFactory() {
1119             this(true);
1120         }
1121
1122         private XcosTabFactory(boolean instanciateXcos) {
1123             if (instanciateXcos) {
1124                 getInstance(this);
1125             }
1126         }
1127
1128         /**
1129          * Create/restore a tab for a given uuid
1130          *
1131          * @param uuid
1132          *            the specific uuid
1133          * @return the tab instance
1134          */
1135         @Override
1136         public synchronized SwingScilabDockablePanel getTab(final String uuid) {
1137             if (uuid == null) {
1138                 return null;
1139             }
1140
1141             SwingScilabDockablePanel tab = ScilabTabFactory.getInstance().getFromCache(uuid);
1142
1143             // Palette manager restore
1144             if (tab == null) {
1145                 if (PaletteManagerView.DEFAULT_TAB_UUID.equals(uuid)) {
1146                     PaletteManagerView.restore(null, false);
1147                     tab = PaletteManagerView.get();
1148                 }
1149             }
1150
1151             // diagram (tab or viewport) restore
1152             if (tab == null) {
1153                 cache(uuid);
1154                 if (cachedDocumentType == null) {
1155                     return null;
1156                 }
1157
1158                 final boolean isTab = uuid.equals(cachedDocumentType.getUuid());
1159                 final boolean isViewport = uuid.equals(cachedDocumentType.getViewport());
1160
1161                 final XcosDiagram graph = getDiagram(isTab, isViewport);
1162                 if (graph != null && isTab) {
1163                     XcosTab.restore(graph, false);
1164                     graph.fireEvent(new mxEventObject(mxEvent.ROOT));
1165                     tab = XcosTab.get(graph);
1166                 } else if (graph != null && isViewport) {
1167                     ViewPortTab.restore(graph, false);
1168                     tab = ViewPortTab.get(graph);
1169
1170                     ClosingOperationsManager.addDependency(XcosTab.get(graph), tab);
1171                     WindowsConfigurationManager.makeDependency(graph.getGraphTab(), tab.getPersistentId());
1172                 } else {
1173                     return null;
1174                 }
1175             }
1176
1177             WindowsConfigurationManager.restorationFinished(tab);
1178             ScilabTabFactory.getInstance().addToCache(tab);
1179
1180             return tab;
1181         }
1182
1183         private XcosDiagram getDiagram(boolean isTab, boolean isViewport) {
1184             final Xcos instance = getInstance();
1185             XcosDiagram graph = null;
1186
1187             if (isTab) {
1188                 // load a new diagram
1189                 graph = getInstance().configuration.loadDiagram(cachedDocumentType);
1190             } else if (isViewport) {
1191                 // get the cached diagram
1192                 final File f = instance.configuration.getFile(cachedDocumentType);
1193                 final ScicosObjectOwner root = getInstance().openedDiagram(f);
1194
1195                 Collection<XcosDiagram> diags = instance.diagrams.getOrDefault(root, Collections.emptyList());
1196                 for (XcosDiagram d : diags) {
1197                     final String id = d.getGraphTab();
1198                     if (id != null && id.equals(cachedDocumentType.getUuid())) {
1199                         graph = d;
1200                         break;
1201                     }
1202                 }
1203             }
1204
1205             return graph;
1206         }
1207
1208         @Override
1209         public synchronized boolean isAValidUUID(String uuid) {
1210             // check the Palette manager view (static uuid)
1211             if (PaletteManagerView.DEFAULT_TAB_UUID.equals(uuid)) {
1212                 return true;
1213             }
1214
1215             /*
1216              * Cache and check against cache to ease next getTab(uuid) call
1217              */
1218             cache(uuid);
1219             return cachedDocumentType != null;
1220         }
1221
1222         /**
1223          * Cache the {@link DocumentType} for the specific uuid
1224          *
1225          * @param uuid
1226          *            the uuid
1227          */
1228         private void cache(String uuid) {
1229             /*
1230              * Handle a non null cache
1231              */
1232             if (cachedDocumentType != null) {
1233                 final boolean isTab = uuid.equals(cachedDocumentType.getUuid());
1234                 final boolean isViewport = uuid.equals(cachedDocumentType.getViewport());
1235
1236                 if (isTab || isViewport) {
1237                     return;
1238                 } else {
1239                     cachedDocumentType = null;
1240                 }
1241             }
1242
1243             /*
1244              * Invalid cache, look for the right one
1245              */
1246             final ConfigurationManager config = getInstance().configuration;
1247             final List<DocumentType> docs = config.getSettings().getTab();
1248             for (DocumentType d : docs) {
1249                 final boolean isTab = uuid.equals(d.getUuid());
1250                 final boolean isViewport = uuid.equals(d.getViewport());
1251
1252                 if (isTab || isViewport) {
1253                     cachedDocumentType = d;
1254                     break;
1255                 }
1256             }
1257         }
1258
1259         @Override
1260         public String getPackage() {
1261             return TRADENAME;
1262         }
1263
1264         @Override
1265         public String getClassName() {
1266             return XcosTabFactory.class.getName();
1267         }
1268
1269         @Override
1270         public String getApplication() {
1271             return TRADENAME;
1272         }
1273     }
1274 }
1275 // CSON: ClassDataAbstractionCoupling
1276 // CSON: ClassFanOutComplexity