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