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