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