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