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