Bug fix #11530 & #11363: Raise (help, scinotes...) windows when iconified
[scilab.git] / scilab / modules / gui / src / java / org / scilab / modules / gui / utils / WindowsConfigurationManager.java
1 /*
2  * Scilab ( http://www.scilab.org/ ) - This file is part of Scilab
3  * Copyright (C) 2011 - Calixte DENIZET
4  *
5  * Copyright (C) 2012 - 2016 - Scilab Enterprises
6  *
7  * This file is hereby licensed under the terms of the GNU GPL v2.0,
8  * pursuant to article 5.3.4 of the CeCILL v.2.1.
9  * This file was originally licensed under the terms of the CeCILL v2.1,
10  * and continues to be available under such terms.
11  * For more information, see the COPYING file which you should have received
12  * along with this program.
13  *
14  */
15
16 package org.scilab.modules.gui.utils;
17
18 import java.awt.Dimension;
19 import java.awt.GraphicsDevice;
20 import java.awt.GraphicsEnvironment;
21 import java.awt.Point;
22 import java.awt.Rectangle;
23 import java.io.File;
24 import java.lang.reflect.Method;
25 import java.util.ArrayList;
26 import java.util.Arrays;
27 import java.util.HashMap;
28 import java.util.LinkedHashSet;
29 import java.util.List;
30 import java.util.Map;
31 import java.util.Set;
32 import java.util.UUID;
33
34 import javax.swing.SwingUtilities;
35 import javax.xml.xpath.XPath;
36 import javax.xml.xpath.XPathConstants;
37
38 import org.flexdock.docking.Dockable;
39 import org.flexdock.docking.DockingManager;
40 import org.flexdock.docking.activation.ActiveDockableTracker;
41 import org.flexdock.docking.state.LayoutNode;
42 import org.flexdock.perspective.persist.xml.LayoutNodeSerializer;
43 import org.flexdock.perspective.persist.xml.PersistenceConstants;
44 import org.scilab.modules.commons.ScilabCommonsUtils;
45 import org.scilab.modules.commons.ScilabConstants;
46 import org.scilab.modules.commons.xml.ScilabXPathFactory;
47 import org.scilab.modules.commons.xml.ScilabXMLUtilities;
48 import org.scilab.modules.commons.xml.XConfiguration;
49 import org.scilab.modules.commons.xml.XConfigurationEvent;
50 import org.scilab.modules.commons.xml.XConfigurationListener;
51
52 import static org.scilab.modules.commons.xml.XConfiguration.XConfAttribute;
53
54 import org.scilab.modules.gui.bridge.tab.SwingScilabDockablePanel;
55 import org.scilab.modules.gui.bridge.window.SwingScilabWindow;
56 import org.scilab.modules.gui.console.ScilabConsole;
57 import org.scilab.modules.gui.messagebox.ScilabModalDialog;
58 import org.scilab.modules.gui.messagebox.ScilabModalDialog.IconType;
59 import org.scilab.modules.gui.tab.Tab;
60 import org.scilab.modules.gui.tabfactory.ScilabTabFactory;
61 import org.scilab.modules.localization.Messages;
62 import org.w3c.dom.Document;
63 import org.w3c.dom.Element;
64 import org.w3c.dom.NodeList;
65
66 /**
67  *
68  * Save the windows properties.
69  *
70  * @author Calixte DENIZET
71  */
72 public class WindowsConfigurationManager implements XConfigurationListener {
73
74     private static final String RESTORATION_ERROR = Messages.gettext("Restoration error");
75     private static final String ERROR_IN_LOADING = Messages.gettext("The configuration file has been corrupted and reset to the default one.");
76     private static final String FATAL_ERROR = Messages.gettext("The configuration file has been corrupted. Scilab needs to restart.");
77
78     private static final int DEFAULTX = 0;
79     private static final int DEFAULTY = 0;
80     private static final int DEFAULTHEIGHT = 500;
81     private static final int DEFAULTWIDTH = 500;
82
83     private static final String LAYOUT_PATH = "//general/desktop-layout/body/layouts";
84
85     private static final String SCI = "SCI";
86     private static final String WINDOWS_CONFIG_FILE = System.getenv(SCI) + "/modules/gui/etc/windowsConfiguration.xml";
87     private static final String DEFAULT_WINDOWS_CONFIG_FILE = System.getenv(SCI) + "/modules/gui/etc/integratedConfiguration.xml";
88     private static final String NULLUUID = new UUID(0L, 0L).toString();
89     private static final Map<SwingScilabDockablePanel, EndedRestoration> endedRestoration = new HashMap<SwingScilabDockablePanel, EndedRestoration>();
90     private static final List<String> alreadyRestoredWindows = new ArrayList<String>();
91     private static final Map<String, Object> defaultWinAttributes = new HashMap<String, Object>();
92     private static final List<String> currentlyRestored = new ArrayList<String>();
93
94
95     private static String USER_WINDOWS_CONFIG_FILE = ScilabConstants.SCIHOME.toString() + "/windowsConfiguration.xml";
96
97     private static boolean oneTry;
98     private static Document doc;
99
100     private static boolean mustInvalidate;
101     private static boolean mustSave = true;
102     private static int layoutId;
103
104     static {
105         try {
106             if (ScilabConstants.SCIHOME != null && ScilabConstants.SCIHOME.canRead() && ScilabConstants.SCIHOME.canWrite()) {
107                 USER_WINDOWS_CONFIG_FILE = ScilabConstants.SCIHOME.toString() + "/windowsConfiguration.xml";
108             } else {
109                 USER_WINDOWS_CONFIG_FILE = DEFAULT_WINDOWS_CONFIG_FILE;
110                 mustSave = false;
111             }
112
113             new WindowsConfigurationManager();
114             Runnable runnable = new Runnable() {
115                 public void run() {
116                     if (mustInvalidate) {
117                         File f = new File(USER_WINDOWS_CONFIG_FILE);
118                         if (f.exists() && f.isFile()) {
119                             f.delete();
120                         }
121                     }
122                 }
123             };
124
125             Class scilab = ClassLoader.getSystemClassLoader().loadClass("org.scilab.modules.core.Scilab");
126             Method registerFinalHook = scilab.getDeclaredMethod("registerFinalHook", Runnable.class);
127             registerFinalHook.invoke(null, runnable);
128
129             defaultWinAttributes.put("x", new Integer(DEFAULTX));
130             defaultWinAttributes.put("y", new Integer(DEFAULTY));
131             defaultWinAttributes.put("height", new Integer(DEFAULTHEIGHT));
132             defaultWinAttributes.put("width", new Integer(DEFAULTWIDTH));
133             defaultWinAttributes.put("state", new Integer(SwingScilabWindow.NORMAL));
134             /*
135               Uncomment this code for debugging focus issues
136
137               java.awt.Toolkit.getDefaultToolkit().addAWTEventListener(new java.awt.event.AWTEventListener() {
138               public void eventDispatched(java.awt.AWTEvent e) {
139               System.out.println(e);
140               }
141               }, java.awt.AWTEvent.FOCUS_EVENT_MASK);*/
142         } catch (Exception e) {
143             System.err.println(e);
144         }
145     }
146
147     private WindowsConfigurationManager() {
148         XConfiguration.addXConfigurationListener(this);
149     }
150
151     /**
152      * Reset the layout
153      */
154     public static void resetLayout() {
155         mustInvalidate = true;
156     }
157
158     public void configurationChanged(XConfigurationEvent e) {
159         if (e.getModifiedPaths().contains(LAYOUT_PATH)) {
160             Options options = getOptions();
161             if (options.id != layoutId) {
162                 mustInvalidate = true;
163                 layoutId = options.id;
164             }
165
166             mustSave = options.mustSave;
167         }
168     }
169
170     public static String getLayoutFilePath() {
171         try {
172             Document doc = XConfiguration.getXConfigurationDocument();
173             if (doc != null) {
174                 XPath xp = ScilabXPathFactory.newInstance().newXPath();
175                 NodeList nodes = (NodeList) xp.compile(LAYOUT_PATH + "/layout[@id=../@id]/@path").evaluate(doc, XPathConstants.NODESET);
176                 if (nodes != null && nodes.getLength() > 0) {
177                     return nodes.item(0).getNodeValue().replace("$SCI", System.getenv(SCI));
178                 }
179             }
180         } catch (Exception e) { }
181
182         return WINDOWS_CONFIG_FILE;
183     }
184
185     /**
186      * Create a copy of windows configuration file in the user directory
187      */
188     public static void createUserCopy() {
189         if (isCopyNeeded() && mustSave) {
190             ScilabCommonsUtils.copyFile(new File(getLayoutFilePath()), new File(USER_WINDOWS_CONFIG_FILE));
191             doc = null;
192         }
193     }
194
195     /**
196      * Read the file to modify
197      */
198     private static void readDocument() {
199         if (doc == null) {
200             createUserCopy();
201             doc = ScilabXMLUtilities.readDocument(USER_WINDOWS_CONFIG_FILE);
202             Options options = getOptions();
203             if (mustSave) {
204                 mustSave = options.mustSave;
205             }
206             layoutId = options.id;
207         }
208
209         if (doc == null && !oneTry) {
210             reload();
211         } else if (doc == null && oneTry) {
212             System.err.println("Serious problem to copy and parse the configuration file.");
213             System.err.println("Please check if you have the rights to write the file: " + USER_WINDOWS_CONFIG_FILE);
214             System.err.println("If the previous file exists, please check if it is a valid XML");
215             System.err.println("and if yes, please report a bug: http://bugzilla.scilab.org");
216         } else if (doc != null) {
217             // We check that the file contains a NULLUUID (used for the console) and only one
218             // No console == no Scilab !
219             Element root = doc.getDocumentElement();
220             List<Element> list = ScilabXMLUtilities.getElementsWithAttributeEquals(root, "uuid", NULLUUID);
221             if (list == null || list.isEmpty() || list.size() >= 2) {
222                 reload();
223             } else {
224                 Element console = list.get(0);
225                 // Check the factory
226                 if (console == null || !"org.scilab.modules.core.ConsoleTabFactory".equals(console.getAttribute("factory"))) {
227                     // the factory is not correct => crash so we reload the conf file
228                     reload();
229                 }
230             }
231         }
232     }
233
234     /**
235      * Display an error
236      */
237     private static void displayLoadError() {
238         System.err.println("Try to reload the default configuration file.");
239         ScilabModalDialog.show(null, ERROR_IN_LOADING, RESTORATION_ERROR, IconType.ERROR_ICON);
240     }
241
242     /**
243      * Display a fatal error
244      */
245     private static void displayFatalError() {
246         System.err.println("The configuration file is severely corrupted and cannot be read. It will be deleted.");
247         ScilabModalDialog.show(null, FATAL_ERROR, RESTORATION_ERROR, IconType.ERROR_ICON);
248     }
249
250     /**
251      * Delete configuration file
252      */
253     private static void deleteConfFile() {
254         File f = new File(USER_WINDOWS_CONFIG_FILE);
255         if (f.exists() && f.isFile()) {
256             f.delete();
257         }
258     }
259
260     /**
261      * Reload the xml configuration file
262      */
263     private static void reload() {
264         doc = null;
265         displayLoadError();
266         deleteConfFile();
267         oneTry = true;
268         readDocument();
269     }
270
271     /**
272      * Kill scilab
273      */
274     private static void killScilab() {
275         deleteConfFile();
276         displayFatalError();
277         System.exit(1);
278     }
279
280     /**
281      * Write the document
282      */
283     private static void writeDocument() {
284         if (mustSave) {
285             ScilabXMLUtilities.writeDocument(doc, USER_WINDOWS_CONFIG_FILE);
286         }
287     }
288
289     /**
290      * @return true if a copy is needed
291      */
292     private static final boolean isCopyNeeded() {
293         return !new File(USER_WINDOWS_CONFIG_FILE).exists();
294     }
295
296     /**
297      * Register an EndedRestoration, op.finish() will be executed when the tab restoration will be finished.
298      * @param tab the associated tab
299      * @param ended the closing operation
300      */
301     public static void registerEndedRestoration(SwingScilabDockablePanel tab, EndedRestoration ended) {
302         endedRestoration.put(tab, ended);
303     }
304
305     /**
306      * Unregister an EndedRestoration.
307      * @param tab the associated tab
308      */
309     public static void unregisterEndedRestoration(SwingScilabDockablePanel tab) {
310         endedRestoration.remove(tab);
311     }
312
313     /**
314      * Register an EndedRestoration, op.finish() will be executed when the tab restoration will be finished.
315      * @param tab the associated tab
316      * @param ended the closing operation
317      */
318     public static void registerEndedRestoration(Tab tab, EndedRestoration ended) {
319         registerEndedRestoration((SwingScilabDockablePanel) tab.getAsSimpleTab(), ended);
320     }
321
322     /**
323      * Create a new node with parent element
324      * @param parent the parent element
325      * @param nodeName the node name
326      * @param attr an array containing attribute name followed by its value: "attr1", 1, "attr2", true, ...
327      * @return the created element
328      */
329     public static Element createNode(Element parent, String nodeName, Object[] attr) {
330         readDocument();
331         for (int i = 0; i < attr.length; i += 2) {
332             if (attr[i].equals("uuid")) {
333                 removeNode(parent, nodeName, (String) attr[i + 1]);
334             }
335         }
336
337         return ScilabXMLUtilities.createNode(doc, parent, nodeName, attr);
338     }
339
340     /**
341      * Save the window properties
342      * @param window the window
343      */
344     public static void saveWindowProperties(SwingScilabWindow window) {
345         if (ScilabConstants.isGUI()) {
346             readDocument();
347
348             Element root = doc.getDocumentElement();
349             Element win = createNode(root, "Window", new Object[] {"uuid", window.getUUID(),
350                                      "x", (int) window.getLastPosition().getX(),
351                                      "y", (int) window.getLastPosition().getY(),
352                                      "width", (int) window.getLastDimension().getWidth(),
353                                      "height", (int) window.getLastDimension().getHeight(),
354                                      "state", window.getExtendedState()
355                                                                   });
356             LayoutNode layoutNode = window.getDockingPort().exportLayout();
357             LayoutNodeSerializer serializer = new LayoutNodeSerializer();
358             win.appendChild(serializer.serialize(doc, layoutNode));
359
360             for (Dockable dockable : (Set<Dockable>) window.getDockingPort().getDockables()) {
361                 saveTabProperties((SwingScilabDockablePanel) dockable, false);
362             }
363
364             writeDocument();
365         }
366     }
367
368     /**
369      * Create a window according to the uuid.
370      *
371      * This method can be used to create a reference windows.
372      *
373      * @param uuid
374      *            the reference uuid
375      * @param preserveUUID
376      *            if true the uuid will be used on the new windows, generate a
377      *            new uuid otherwise
378      * @return the window
379      */
380     public static SwingScilabWindow createWindow(final String uuid, final boolean preserveUUID) {
381         readDocument();
382
383         final Element root = doc.getDocumentElement();
384         final boolean nullUUID = uuid.equals(NULLUUID);
385         final Map<String, Object> attrs = new HashMap<String, Object>();
386         Element win = null;
387         boolean containsX = true;
388
389         if (!nullUUID) {
390             win = getElementWithUUID(root, "Window", uuid);
391             if (win == null) {
392                 return null;
393             }
394
395             containsX = !win.getAttribute("x").equals("");
396
397             if (containsX) {
398                 attrs.put("x", int.class);
399                 attrs.put("y", int.class);
400             }
401
402             attrs.put("height", int.class);
403             attrs.put("width", int.class);
404             attrs.put("state", int.class);
405             ScilabXMLUtilities.readNodeAttributes(win, attrs);
406         } else {
407             attrs.putAll(defaultWinAttributes);
408         }
409
410         SwingScilabWindow window = SwingScilabWindow.createWindow(true);
411         window.setVisible(false);
412
413         final String localUUID;
414         if (preserveUUID) {
415             localUUID = uuid;
416         } else {
417             localUUID = UUID.randomUUID().toString();
418         }
419         window.setUUID(localUUID);
420
421         if (containsX) {
422             boolean positionned = false;
423             Point p = new Point(((Integer) attrs.get("x")).intValue(), ((Integer) attrs.get("y")).intValue());
424
425             // We check that the coordinates are valid
426             GraphicsDevice[] gds = GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices();
427             if (gds != null) {
428                 for (GraphicsDevice gd : gds) {
429                     Rectangle r = gd.getDefaultConfiguration().getBounds();
430                     if (r.contains(p)) {
431                         positionned = true;
432                         window.setLocation(p.x, p.y);
433                         break;
434                     }
435                 }
436             }
437
438             if (!positionned) {
439                 window.setLocation(DEFAULTX, DEFAULTY);
440             }
441         }
442
443         window.setSize(((Integer) attrs.get("width")).intValue(), ((Integer) attrs.get("height")).intValue());
444         
445         /* remove ICONIFIED at restoration */
446         int state = ((Integer)attrs.get("state")).intValue();
447         if((state & SwingScilabWindow.ICONIFIED) == SwingScilabWindow.ICONIFIED) {
448             state -= SwingScilabWindow.ICONIFIED;
449         }
450
451         window.setExtendedState(state);
452         return window;
453     }
454
455     public static SwingScilabWindow restoreWindow(String uuid) {
456         String winuuid = UUID.randomUUID().toString();
457         SwingScilabWindow win = SwingScilabWindow.createWindow(true);
458         win.setUUID(winuuid);
459         win.setIsRestoring(true);
460
461         final SwingScilabDockablePanel tab = ScilabTabFactory.getInstance().getTab(uuid);
462         win.addTab(tab);
463         BarUpdater.forceUpdateBars(tab.getParentWindowId(), tab.getMenuBar(), tab.getToolBar(), tab.getInfoBar(), tab.getName(), tab.getWindowIcon());
464
465         win.pack();
466         win.setVisible(true);
467         win.requestFocus();
468         win.toFront();
469         win.setIsRestoring(false);
470
471         endedRestoration.remove(tab);
472
473         SwingUtilities.invokeLater(new Runnable() {
474             @Override
475             public void run() {
476                 ActiveDockableTracker.requestDockableActivation(tab);
477             }
478         });
479
480         return win;
481     }
482
483     /**
484      * Restore a window with a given uuid
485      *
486      * @param uuid
487      *            the uuid
488      * @param restoreTab
489      *            if true the tab is restored too
490      * @return the corresponding window
491      */
492     public static SwingScilabWindow restoreWindow(String uuid, String defaultTabUuid, boolean restoreTab, boolean requestFocus) {
493         readDocument();
494         final boolean nullUUID = uuid.equals(NULLUUID);
495
496         // create the window and preserve the uuid if not null
497         final SwingScilabWindow window = createWindow(uuid, !nullUUID);
498         if (window == null) {
499             return null;
500         }
501
502         window.setIsRestoring(true);
503
504         if (restoreTab) {
505             if (!nullUUID) {
506                 final LayoutNodeSerializer serializer = new LayoutNodeSerializer();
507                 final Element dockingPort = getDockingPort(uuid);
508                 LayoutNode layoutNode = (LayoutNode) serializer.deserialize(dockingPort);
509                 window.getDockingPort().importLayout(layoutNode);
510             } else if (defaultTabUuid != null && !defaultTabUuid.isEmpty()) {
511                 SwingScilabDockablePanel defaultTab = ScilabTabFactory.getInstance().getTab(defaultTabUuid);
512                 defaultTab.setParentWindowId(window.getId());
513                 DockingManager.dock(defaultTab, window.getDockingPort());
514             }
515
516             for (SwingScilabDockablePanel tab : (Set<SwingScilabDockablePanel>) window.getDockingPort().getDockables()) {
517                 tab.setParentWindowId(window.getId());
518             }
519
520             SwingScilabDockablePanel[] tabs = new SwingScilabDockablePanel[window.getNbDockedObjects()];
521             tabs = ((Set<SwingScilabDockablePanel>) window.getDockingPort().getDockables()).toArray(tabs);
522
523             // Be sur that the main tab will have the focus.
524             // Get the elder tab and activate it
525             final SwingScilabDockablePanel mainTab = ClosingOperationsManager.getElderTab(new ArrayList(Arrays.asList(tabs)));
526             BarUpdater.forceUpdateBars(mainTab.getParentWindowId(), mainTab.getMenuBar(), mainTab.getToolBar(), mainTab.getInfoBar(), mainTab.getName(), mainTab.getWindowIcon());
527
528             if (!ScilabConsole.isExistingConsole() && tabs.length == 1 && tabs[0].getPersistentId().equals(NULLUUID)) {
529                 // null uuid is reserved to the console and in NW mode, there is no console.
530                 return null;
531             }
532
533             for (SwingScilabDockablePanel tab : tabs) {
534                 // each tab has now a window so it can be useful for the tab to set an icon window or to center a dialog...
535                 EndedRestoration ended = endedRestoration.get(tab);
536                 if (ended != null) {
537                     ended.finish();
538                     endedRestoration.remove(tab);
539                 }
540             }
541
542             if (tabs.length == 1) {
543                 // we remove undock and close buttons when there is only one View in the DockingPort
544                 SwingScilabDockablePanel.removeActions(tabs[0]);
545             } else {
546                 // we add undock and close buttons
547                 for (SwingScilabDockablePanel tab : tabs) {
548                     SwingScilabDockablePanel.addActions(tab);
549                 }
550             }
551
552             if (requestFocus) {
553                 SwingUtilities.invokeLater(new Runnable() {
554                     @Override
555                     public void run() {
556                         final Thread t = new Thread(new Runnable() {
557                             @Override
558                             public void run() {
559                                 synchronized (currentlyRestored) {
560                                     while (currentlyRestored.size() > 0) {
561                                         try {
562                                             currentlyRestored.wait();
563                                         } catch (InterruptedException e) {
564                                             e.printStackTrace();
565                                         }
566                                     }
567                                 }
568
569                                 SwingUtilities.invokeLater(new Runnable() {
570                                     @Override
571                                     public void run() {
572                                         window.setVisible(true);
573                                         window.requestFocus();
574                                         window.toFront();
575                                         window.setIsRestoring(false);
576
577                                         ActiveDockableTracker.requestDockableActivation(mainTab);
578                                     }
579                                 });
580                             }
581                         });
582                         t.start();
583                     }
584                 });
585             } else {
586                 SwingUtilities.invokeLater(new Runnable() {
587                     @Override
588                     public void run() {
589                         window.setIsRestoring(false);
590                         window.setVisible(true);
591                     }
592                 });
593             }
594         }
595
596         return window;
597     }
598
599     private static final Element getDockingPort(final String winUUID) {
600         readDocument();
601
602         final Element root = doc.getDocumentElement();
603         final Element win = getElementWithUUID(root, "Window", winUUID);
604         if (win == null) {
605             return null;
606         }
607
608         final NodeList children = win.getElementsByTagName(PersistenceConstants.DOCKING_PORT_NODE_ELEMENT_NAME);
609         return (Element) children.item(0);
610     }
611
612
613     /**
614      * Must be called when the restoration is finished
615      * @param tab the tab
616      */
617     public static final void restorationFinished(SwingScilabDockablePanel tab) {
618         synchronized (currentlyRestored) {
619             currentlyRestored.remove(tab.getPersistentId());
620
621             // notify after remove
622             currentlyRestored.notify();
623         }
624     }
625
626     /**
627      * Remove a window from the already restored windows
628      * @param uuid the win uuid
629      */
630     public static final void removeWin(String uuid) {
631         alreadyRestoredWindows.remove(uuid);
632     }
633
634     /**
635      * Find all the dependencies of the given tab. The returned list contains parent before its children.
636      * @param uuid the tab's uuid
637      * @return a list of the elements with the given uuid
638      */
639     public static final Set<Element> getTabDependencies(String uuid) {
640         readDocument();
641         Element root = doc.getDocumentElement();
642
643         // Children
644         List<Element> elements = ScilabXMLUtilities.getElementsWithAttributeEquals(root, "depends", uuid);
645
646         Set<Element> list = new LinkedHashSet<Element>();
647         Element el = getElementWithUUID(doc.getDocumentElement(), uuid);
648         if (el != null) {
649             //We add the parent
650             list.add(el);
651             // We add the children and their own children
652             for (Element e : elements) {
653                 list.addAll(getTabDependencies(e.getAttribute("uuid")));
654             }
655         }
656
657         return list;
658     }
659
660     /**
661      * Create all the tabs depending of the tab with the given uuid.
662      * The creation will respect the convention: parent before children.
663      * @param uuid the tab uuid
664      * @return the list of all the uuids to restore
665      */
666     public static final Set<Element> createDescendantTabs(String uuid) {
667         Set<Element> list = getTabDependencies(uuid);
668         Dimension nullDims = new Dimension(0, 0);
669         for (Element e : list) {
670             // All the tabs created in the factory will be cached so when Flexdock will restore the docking
671             // it will use the same tab as created here.
672             ScilabTabFactory factory = ScilabTabFactory.getInstance();
673             factory.addTabFactory(e.getAttribute("load"), e.getAttribute("factory"));
674             currentlyRestored.add(e.getAttribute("uuid"));
675             SwingScilabDockablePanel tab = factory.getTab(e.getAttribute("uuid"));
676             if (!e.getAttribute("width").isEmpty() && !e.getAttribute("height").isEmpty()) {
677                 tab.setMinimumSize(nullDims);
678                 tab.setPreferredSize(new Dimension(Integer.parseInt(e.getAttribute("width")), Integer.parseInt(e.getAttribute("width"))));
679             }
680         }
681
682         return list;
683     }
684
685     /**
686      * Useful for the following case: you have 3 tabs, A, B and C, C depends of B, A and C are docked in the same window.
687      * You want to restore A, the window containing A needs to restore C too (and finally B).
688      * There is no direct dependency between A and C but the fact that they're in the same dockport
689      * implies a dependency.
690      * @param elems the elems corresponding to the tabs to restore
691      * @return tabs to restore
692      */
693     private static final Set<Element> createAdjacentTabs(Set<Element> elems) {
694         readDocument();
695
696         Element root = doc.getDocumentElement();
697         boolean jobFinished = true;
698         Set<Element> toAdd = new LinkedHashSet<Element>();
699         for (Element e : elems) {
700             String winuuid = e.getAttribute("winuuid");
701             if (!winuuid.isEmpty() && !winuuid.equals(NULLUUID)) {
702                 List<Element> elements = ScilabXMLUtilities.getElementsWithAttributeEquals(root, "winuuid", winuuid);
703                 elements.removeAll(elems);
704                 jobFinished = jobFinished && elements.size() == 0;
705                 toAdd.addAll(elements);
706             }
707         }
708
709         if (jobFinished) {
710             return elems;
711         }
712
713         for (Element ee : toAdd) {
714             elems.addAll(createDescendantTabs(getElderParent(ee).getAttribute("uuid")));
715         }
716
717
718         return createAdjacentTabs(elems);
719     }
720
721     /**
722      * @param e the element
723      * @return the elder parent of this element (elder for the attribute "depends")
724      */
725     private static final Element getElderParent(Element e) {
726         readDocument();
727
728         Element root = doc.getDocumentElement();
729         String dep = e.getAttribute("depends");
730         if (!dep.isEmpty()) {
731             return getElderParent(ScilabXMLUtilities.getElementsWithAttributeEquals(root, "uuid", dep).get(0));
732         }
733
734         return e;
735     }
736
737     /**
738      * Starts the restoration of the tab with the given uuid
739      * @param uuid the tab uuid to restore
740      */
741     private static final void startRestoration(String uuid) {
742         if (!ScilabConstants.isGUI()) {
743             readDocument();
744             Element root = doc.getDocumentElement();
745             Element e = ScilabXMLUtilities.getElementsWithAttributeEquals(root, "uuid", uuid).get(0);
746             if (e != null) {
747                 String winuuid = e.getAttribute("winuuid");
748                 if (!winuuid.isEmpty() && !winuuid.equals(NULLUUID)) {
749                     List<Element> elements = ScilabXMLUtilities.getElementsWithAttributeEquals(root, "winuuid", winuuid);
750                     for (Element ee : elements) {
751                         if (ee.getNodeName().equals("Console")) {
752                             restoreWindow(uuid);
753                             return;
754                         }
755                     }
756                 }
757             }
758         }
759
760         Set<Element> list = createDescendantTabs(uuid);
761         list = createAdjacentTabs(list);
762
763         List<String> wins = new ArrayList<String>();
764         List<String> tabsWithoutWin = new ArrayList<String>();
765         for (Element e : list) {
766             String winuuid = e.getAttribute("winuuid");
767             if (winuuid.equals(NULLUUID) || getElementWithUUID(winuuid) == null || !isDockableIdExisting(winuuid, e.getAttribute("uuid"))) {
768                 tabsWithoutWin.add(e.getAttribute("uuid"));
769             } else if (!wins.contains(winuuid)) {
770                 wins.add(winuuid);
771             }
772         }
773
774         boolean requestFocus = true;
775
776         if (wins.contains(NULLUUID)) {
777             wins.remove(NULLUUID);
778             wins.add(NULLUUID);
779         }
780
781         for (String winuuid : wins) {
782             if (!alreadyRestoredWindows.contains(winuuid)) {
783                 restoreWindow(winuuid, uuid, true, requestFocus);
784                 alreadyRestoredWindows.add(winuuid);
785                 if (requestFocus) {
786                     requestFocus = false;
787                 }
788             }
789         }
790
791         for (String u : tabsWithoutWin) {
792             SwingScilabWindow window = restoreWindow(NULLUUID, u, true, requestFocus);
793             alreadyRestoredWindows.add(window.getUUID());
794             if (requestFocus) {
795                 requestFocus = false;
796             }
797         }
798     }
799
800     /**
801      * Search a node (child of root) with name nodeName and with a given uuid
802      * @param root the root element
803      * @param nodeName the node name
804      * @param uuid the uuid
805      * @return the corresponding element or null if it does not exist
806      */
807     public static final Element getElementWithUUID(Element root, String nodeName, String uuid) {
808         if (uuid == null || uuid.isEmpty()) {
809             return null;
810         }
811         NodeList list = root.getElementsByTagName(nodeName);
812         int length = getNodeListLength(list);
813         for (int i = 0; i < length; i++) {
814             Element elem = (Element) list.item(i);
815             if (elem.getAttribute("uuid").equals(uuid)) {
816                 return elem;
817             }
818         }
819
820         return null;
821     }
822
823     /**
824      * Search a node with a given uuid
825      * @param uuid the uuid
826      * @return the corresponding element or null if it does not exist
827      */
828     public static final Element getElementWithUUID(String uuid) {
829         readDocument();
830
831         return getElementWithUUID(doc.getDocumentElement(), uuid);
832     }
833
834     /**
835      * Search a node (child of root) with name nodeName and with a given uuid
836      * @param root the root element
837      * @param uuid the uuid
838      * @return the corresponding element or null if it does not exist
839      */
840     public static final Element getElementWithUUID(Element root, String uuid) {
841         if (uuid == null || uuid.isEmpty()) {
842             return null;
843         }
844         List<Element> list = ScilabXMLUtilities.getElementsWithAttributeEquals(root, "uuid", uuid);
845         if (list.size() != 0) {
846             return list.get(0);
847         }
848
849         return null;
850     }
851
852     /**
853      * Check if there is a window which has a dockableID equal to the given uuid
854      * @param winuuid the uuid of the window
855      * @param uuid the uuid to test
856      * @return true if a dockableId exists
857      */
858     public static final boolean isDockableIdExisting(String winuuid, String uuid) {
859         if (winuuid == null || winuuid.isEmpty() || uuid == null || uuid.isEmpty()) {
860             return false;
861         }
862
863         Element win = getElementWithUUID(winuuid);
864         if (win != null) {
865             List<Element> list = ScilabXMLUtilities.getElementsWithAttributeEquals(win, "dockableId", uuid);
866             if (list.size() != 0) {
867                 return true;
868             }
869         }
870
871         return false;
872     }
873
874     /**
875      * Validate all the windows in checking if all the dockable nodes are ok.
876      * If a split node contains an invalid uuid, then the invalid dockable is removed
877      * and the split node is replaced by the valid dockable.
878      */
879     private static final void validateWindows() {
880         // We remove all the blanks and carriage return
881         try {
882             XPath xp = ScilabXPathFactory.newInstance().newXPath();
883             NodeList nodes = (NodeList) xp.compile("//text()").evaluate(doc, XPathConstants.NODESET);
884             for (int i = 0; i < nodes.getLength(); i++) {
885                 nodes.item(i).getParentNode().removeChild(nodes.item(i));
886             }
887         } catch (Exception e) {
888             e.printStackTrace();
889         }
890
891         Element root = doc.getDocumentElement();
892         NodeList list = root.getElementsByTagName("Window");
893
894         int length = getNodeListLength(list);
895         List<Element> elems = new ArrayList<Element>(length);
896         for (int i = 0; i < length; i++) {
897             elems.add((Element) list.item(i));
898         }
899
900         for (Element e : elems) {
901             validateWindow(e.getAttribute("uuid"));
902         }
903     }
904
905     /**
906      * Validate a window element.
907      * The window element in the DOM is eventually replaced by a valid one.
908      * @param winuuid the window uuid
909      */
910     private static final void validateWindow(final String winuuid) {
911         if (winuuid == null || winuuid.isEmpty()) {
912             return;
913         }
914
915         Element win = getElementWithUUID(winuuid);
916         if (win == null) {
917             return;
918         }
919
920         Element dp = (Element) win.getFirstChild();
921         if (dp == null) {
922             win.getParentNode().removeChild(win);
923             return;
924         }
925
926         Element e = validateDockingPortNode(winuuid, dp);
927         if (e == null) {
928             win.removeChild(dp);
929             return;
930         }
931
932         win.removeChild(dp);
933         win.appendChild(e);
934     }
935
936     /**
937      * Validate a dockingport node element.
938      * @param winuuid the window uuid
939      * @param e the element to validate
940      * @return a valid element
941      */
942     private static final Element validateDockingPortNode(final String winuuid, final Element e) {
943         Element ee = (Element) e.getFirstChild();
944         if (ee == null) {
945             return null;
946         }
947
948         if (ee.getTagName().equals("DockableNode")) {
949             ee = validateDockableNode(winuuid, ee);
950             if (ee == null) {
951                 return null;
952             }
953             return e;
954         } else {
955             ee = validateSplitNode(winuuid, ee);
956             if (ee == null) {
957                 return null;
958             }
959             Element eee = (Element) e.cloneNode(false);
960             eee.appendChild(ee);
961
962             return eee;
963         }
964     }
965
966     /**
967      * Validate a dockable node element.
968      * @param winuuid the window uuid
969      * @param e the element to validate
970      * @return a valid element
971      */
972     private static final Element validateDockableNode(final String winuuid, final Element e) {
973         String id = e.getAttribute("dockableId");
974         Element ee = getElementWithUUID(id);
975         if (ee == null || !ee.getAttribute("winuuid").equals(winuuid)) {
976             return null;
977         }
978
979         return e;
980     }
981
982     /**
983      * Validate a split node element.
984      * @param winuuid the window uuid
985      * @param e the element to validate
986      * @return a valid element
987      */
988     private static final Element validateSplitNode(final String winuuid, final Element e) {
989         NodeList set = e.getChildNodes();
990         Element c1 = validateDockingPortNode(winuuid, (Element) set.item(0));
991         Element c2 = validateDockingPortNode(winuuid, (Element) set.item(1));
992
993         if (c1 != null && c2 != null) {
994             Element ee = (Element) e.cloneNode(false);
995             ee.appendChild(c1);
996             ee.appendChild(c2);
997             return ee;
998         }
999
1000         if (c1 == null && c2 == null) {
1001             return null;
1002         }
1003
1004         if (c1 == null) {
1005             return (Element) c2.getFirstChild().cloneNode(true);
1006         }
1007
1008         return (Element) c1.getFirstChild().cloneNode(true);
1009     }
1010
1011     /**
1012      * Remove a node with a given uuid
1013      * @param parent the parent element
1014      * @param nodeName the node name
1015      * @param uuid the uuid
1016      */
1017     private static final void removeNode(Element parent, String nodeName, String uuid) {
1018         if (uuid == null || uuid.isEmpty()) {
1019             return;
1020         }
1021         Element e = getElementWithUUID(parent, nodeName, uuid);
1022         if (e != null) {
1023             parent.removeChild(e);
1024         }
1025     }
1026
1027     /**
1028      * Remove a node with a given uuid
1029      * @param nodeName the node name
1030      * @param uuid the uuid
1031      */
1032     public static final void removeNode(String uuid) {
1033         if (uuid == null || uuid.isEmpty()) {
1034             return;
1035         }
1036         Element e = getElementWithUUID(uuid);
1037         if (e != null && e.getParentNode() != null) {
1038             e.getParentNode().removeChild(e);
1039         }
1040     }
1041
1042     /**
1043      * Save the tab properties
1044      * @param tab the tab
1045      * @param nullWin if true, the winuuid will be set to 0 (the tab is not docked)
1046      */
1047     public static void saveTabProperties(SwingScilabDockablePanel tab, boolean nullWin) {
1048         if (ScilabConstants.isGUI()) {
1049             readDocument();
1050
1051             ScilabTabFactory factory = ScilabTabFactory.getInstance();
1052             String uuid = tab.getPersistentId();
1053             Element root = doc.getDocumentElement();
1054
1055             String app = factory.getApplication(uuid);
1056             if (app.isEmpty()) {
1057                 return;
1058             }
1059
1060             String winuuid;
1061             if (nullWin) {
1062                 winuuid = NULLUUID;
1063             } else {
1064                 winuuid = tab.getParentWindowUUID();
1065             }
1066
1067             Dimension dim = tab.getSize();
1068
1069             createNode(root, app, new Object[] {"winuuid", winuuid,
1070                                                 "uuid", uuid,
1071                                                 "load", factory.getPackage(uuid),
1072                                                 "factory", factory.getClassName(uuid),
1073                                                 "width", (int) dim.getWidth(),
1074                                                 "height", (int) dim.getHeight()
1075                                                });
1076             writeDocument();
1077         }
1078     }
1079
1080     /**
1081      * Clean the document in removing the useless tags
1082      * and validate the different windows.
1083      */
1084     public static void clean() {
1085         if (ScilabConstants.isGUI()) {
1086             readDocument();
1087
1088             validateWindows();
1089
1090             Element root = doc.getDocumentElement();
1091             NodeList list = root.getElementsByTagName("Window");
1092             int len = getNodeListLength(list);
1093             for (int i = 0; i < len; i++) {
1094                 if (list.item(i) instanceof Element) {
1095                     String uuid = ((Element) list.item(i)).getAttribute("uuid");
1096                     List<Element> elements = ScilabXMLUtilities.getElementsWithAttributeEquals(root, "winuuid", uuid);
1097                     if (elements == null || elements.size() == 0) {
1098                         root.removeChild(list.item(i));
1099                         removeWin(uuid);
1100                     }
1101                 }
1102             }
1103             writeDocument();
1104         }
1105     }
1106
1107     /**
1108      * Make a dependency between two tabs
1109      * @param parentUUID the parent tab uuid
1110      * @param childUUID the child tab uuid
1111      */
1112     public static final void makeDependency(String parentUUID, String childUUID) {
1113         if (ScilabConstants.isGUI()) {
1114             readDocument();
1115
1116             Element e = getElementWithUUID(doc.getDocumentElement(), childUUID);
1117             if (e != null) {
1118                 e.setAttribute("depends", parentUUID);
1119             }
1120             writeDocument();
1121         }
1122     }
1123
1124     /**
1125      * Remove a dependency with the parent tab
1126      * @param childUUID the child tab uuid
1127      */
1128     public static void removeDependency(String childUUID) {
1129         if (ScilabConstants.isGUI()) {
1130             readDocument();
1131
1132             Element e = getElementWithUUID(doc.getDocumentElement(), childUUID);
1133             if (e != null) {
1134                 e.removeAttribute("depends");
1135             }
1136             writeDocument();
1137         }
1138     }
1139
1140     /**
1141      * Restore an application by its uuid
1142      * @param uuid the application uuid
1143      * @return true if the operation succeded
1144      */
1145     public static boolean restoreUUID(final String uuid) {
1146         readDocument();
1147         clean();
1148
1149         Element elem = getElementWithUUID(uuid);
1150         if (elem == null) {
1151             return false;
1152         }
1153
1154         if (SwingUtilities.isEventDispatchThread()) {
1155             try {
1156                 startRestoration(uuid);
1157             } catch (Exception e) {
1158                 killScilab();
1159             }
1160         } else {
1161             try {
1162                 SwingUtilities.invokeAndWait(new Runnable() {
1163                     public void run() {
1164                         startRestoration(uuid);
1165                     }
1166                 });
1167             } catch (Exception e) {
1168                 killScilab();
1169             }
1170         }
1171
1172         if (ScilabConstants.isGUI()) {
1173             writeDocument();
1174         }
1175
1176         doc = null;
1177
1178         return true;
1179     }
1180
1181     /**
1182      * Get the uuids of an application
1183      * @param name the application anem
1184      * @return the corresponding uuids
1185      */
1186     public static String[] getApplicationUUIDs(String name) {
1187         readDocument();
1188
1189         NodeList list = doc.getDocumentElement().getElementsByTagName(name);
1190         String[] uuids = new String[getNodeListLength(list)];
1191         for (int i = 0; i < uuids.length; i++) {
1192             if (list.item(i) instanceof Element) {
1193                 Element elem = (Element) list.item(i);
1194                 uuids[i] = elem.getAttribute("uuid");
1195             }
1196         }
1197
1198         return uuids;
1199     }
1200
1201     /**
1202      * Get the length of a NodeList (this is a workaround for a f... java bug)
1203      */
1204     private static final int getNodeListLength(NodeList list) {
1205         int length = 0;
1206         try {
1207             length = list.getLength();
1208         } catch (NullPointerException e) { }
1209
1210         return length;
1211     }
1212
1213     private static final Options getOptions() {
1214         return XConfiguration.get(Options.class, XConfiguration.getXConfigurationDocument(), LAYOUT_PATH)[0];
1215     }
1216
1217
1218     /**
1219      * Inner interface used to have something to execute when the restoration is finished
1220      */
1221     public interface EndedRestoration {
1222
1223         /**
1224          * Stuff to do when the restoration is ended
1225          */
1226         public void finish();
1227     }
1228
1229     @XConfAttribute
1230     private static class Options {
1231
1232         public boolean mustSave;
1233         public int id;
1234
1235         @XConfAttribute(tag = "layouts", attributes = {"id", "save-desktop"})
1236         private void set(int id, boolean mustSave) {
1237             this.id = id;
1238             this.mustSave = mustSave;
1239         }
1240     }
1241 }