Docking: bug when saving the configuration
[scilab.git] / scilab / modules / gui / src / java / org / scilab / modules / gui / utils / ClosingOperationsManager.java
1 /*
2  * Scilab ( http://www.scilab.org/ ) - This file is part of Scilab
3  * Copyright (C) 2011 - Calixte DENIZET
4  *
5  * This file must be used under the terms of the CeCILL.
6  * This source file is licensed as described in the file COPYING, which
7  * you should have received as part of this distribution.  The terms
8  * are also available at
9  * http://www.cecill.info/licences/Licence_CeCILL_V2-en.txt
10  *
11  */
12
13 package org.scilab.modules.gui.utils;
14
15 import java.awt.GraphicsEnvironment;
16 import java.awt.event.ActionEvent;
17 import java.util.ArrayList;
18 import java.util.Collections;
19 import java.util.HashMap;
20 import java.util.List;
21 import java.util.ListIterator;
22 import java.util.Map;
23 import java.util.UUID;
24
25 import javax.swing.AbstractAction;
26 import javax.swing.Action;
27 import javax.swing.JCheckBox;
28 import javax.swing.SwingUtilities;
29
30 import org.flexdock.docking.DockingConstants;
31 import org.scilab.modules.gui.bridge.tab.SwingScilabTab;
32 import org.scilab.modules.gui.bridge.window.SwingScilabWindow;
33 import org.scilab.modules.gui.messagebox.ScilabModalDialog;
34 import org.scilab.modules.gui.messagebox.ScilabModalDialog.AnswerOption;
35 import org.scilab.modules.gui.messagebox.ScilabModalDialog.ButtonType;
36 import org.scilab.modules.gui.messagebox.ScilabModalDialog.IconType;
37 import org.scilab.modules.gui.tab.Tab;
38 import org.scilab.modules.gui.tabfactory.ScilabTabFactory;
39 import org.scilab.modules.gui.window.Window;
40 import org.scilab.modules.localization.Messages;
41
42 import static org.scilab.modules.commons.xml.XConfiguration.XConfAttribute;
43 import org.scilab.modules.commons.xml.XConfiguration;
44
45 /**
46  * Class to handle the different closing operations.
47  *
48  * @author Calixte DENIZET
49  */
50
51 @SuppressWarnings(value = { "serial" })
52 public class ClosingOperationsManager {
53
54     private static final String EXIT_CONFIRM = Messages.gettext("Are you sure you want to close %s ?");
55     private static final String EXIT_CONFIRM_AND = Messages.gettext("Are you sure you want to close %s and %s ?");
56     private static final String DONT_SHOW = Messages.gettext("Do not show this message again");
57     private static final String CONFIRMATION_PATH = "//general/confirmation-dialogs/body/tools/tool[@id='console-exit']";
58     private static final String EXIT = Messages.gettext("Exit");
59     private static final String NULLUUID = new UUID(0L, 0L).toString();
60     private static final Map<SwingScilabTab, ClosingOperation> closingOps = new HashMap<SwingScilabTab, ClosingOperation>();
61     private static final Map<SwingScilabTab, List<SwingScilabTab>> deps = new HashMap<SwingScilabTab, List<SwingScilabTab>>();
62
63     private static final List<SwingScilabTab> dunnoList = new ArrayList<SwingScilabTab>();
64     private static List<SwingScilabTab> savedList;
65     private static boolean savedMustSave;
66
67     private static SwingScilabTab root;
68
69     static {
70         deps.put(null, new ArrayList<SwingScilabTab>());
71     }
72
73     /**
74      * Register a closing operation for a tab
75      *
76      * @param tab
77      *            the associated tab
78      * @param op
79      *            the closing operation
80      */
81     public static void registerClosingOperation(SwingScilabTab tab,
82                                                 ClosingOperation op) {
83         if (tab != null) {
84             closingOps.put(tab, op);
85         }
86     }
87
88     /**
89      * Register a closing operation for a tab
90      *
91      * @param tab
92      *            the associated tab
93      * @param op
94      *            the closing operation
95      */
96     public static void registerClosingOperation(Tab tab, ClosingOperation op) {
97         if (tab != null) {
98             registerClosingOperation((SwingScilabTab) tab.getAsSimpleTab(), op);
99         }
100     }
101
102     /**
103      * Unregister a closing operation for a tab
104      *
105      * @param tab the associated tab
106      */
107     public static void unregisterClosingOperation(SwingScilabTab tab) {
108         if (tab != null) {
109             closingOps.remove(tab);
110         }
111     }
112
113     public static void checkTabForClosing(SwingScilabTab tab) {
114         if (tab != null && !dunnoList.isEmpty()) {
115             if (dunnoList.contains(tab)) {
116                 dunnoList.remove(tab);
117             }
118             if (dunnoList.isEmpty() && savedList != null) {
119                 close(savedList, null, false, savedMustSave);
120                 savedList = null;
121                 savedMustSave = false;
122             }
123         }
124     }
125
126     public static void removeFromDunnoList(SwingScilabTab tab) {
127         if (tab != null && !dunnoList.isEmpty() && dunnoList.contains(tab)) {
128             dunnoList.remove(tab);
129         }
130     }
131
132     /**
133      * Start a closing operation on root
134      *
135      * @return true if the closing operation succeeded
136      */
137     public static boolean startClosingOperationOnRoot() {
138         if (!GraphicsEnvironment.isHeadless()) {
139             if (root != null) {
140                 // STD mode
141                 SwingScilabWindow win = getWindow(root);
142                 if (win == null) {
143                     return true;
144                 }
145                 return startClosingOperation(win, true, true);
146             } else if (deps.get(null).size() != 0) {
147                 // NW mode
148                 List<SwingScilabTab> list = new ArrayList<SwingScilabTab>();
149                 for (SwingScilabTab tab : deps.get(null)) {
150                     collectTabsToClose(tab, list);
151                 }
152                 return close(list, null, true, true);
153             } else {
154                 return true;
155             }
156         } else {
157             return true;
158         }
159     }
160
161     /**
162      * Force a closing operation on root to dispose resources
163      */
164     public static void forceClosingOperationOnRoot() {
165         if (root != null) {
166             // STD mode
167             SwingScilabWindow win = getWindow(root);
168             if (win == null) {
169                 return;
170             }
171             startClosingOperation(win, false, false);
172         } else if (deps.get(null).size() != 0) {
173             // NW mode
174             List<SwingScilabTab> list = new ArrayList<SwingScilabTab>();
175             for (SwingScilabTab tab : deps.get(null)) {
176                 collectTabsToClose(tab, list);
177             }
178             close(list, null, false, false);
179         }
180     }
181
182     /**
183      * Start a closing operation on a tab
184      *
185      * @param tab
186      *            the tab to close
187      * @return true if the closing operation succeeded
188      */
189     public static boolean startClosingOperation(SwingScilabTab tab) {
190         return close(collectTabsToClose(tab), getWindow(tab), true, true);
191     }
192
193     /**
194      * Start a closing operation on a tab
195      *
196      * @param tab
197      *            the tab to close
198      * @return true if the closing operation succeeded
199      */
200     public static boolean startClosingOperation(Tab tab) {
201         return startClosingOperation((SwingScilabTab) tab.getAsSimpleTab());
202     }
203
204     /**
205      * Start a closing operation on a tab
206      *
207      * @param tab
208      *            the tab to close
209      * @return true if the closing operation succeeded
210      */
211     public static boolean startClosingOperation(SwingScilabTab tab, boolean askToExit, boolean mustSave) {
212         return close(collectTabsToClose(tab), getWindow(tab), askToExit, mustSave);
213     }
214
215     /**
216      * Start a closing operation on multiple tabs
217      *
218      * @param tabs
219      *            the tabs to close
220      * @return true if the closing operation succeeded
221      */
222     public static boolean startClosingOperation(List<SwingScilabTab> tabs, boolean askToExit, boolean mustSave) {
223         final SwingScilabWindow win;
224         if (tabs.isEmpty()) {
225             // use the null window to select the console tab.
226             win = null;
227         } else {
228             win = getWindow(tabs.get(0));
229         }
230         return close(collectTabsToClose(tabs), win, askToExit, mustSave);
231     }
232
233     /**
234      * Start a closing operation on a tab
235      *
236      * @param tab
237      *            the tab to close
238      * @return true if the closing operation succeeded
239      */
240     public static boolean startClosingOperation(Tab tab, boolean askToExit, boolean mustSave) {
241         return startClosingOperation((SwingScilabTab) tab.getAsSimpleTab(), askToExit, mustSave);
242     }
243
244     /**
245      * Start a closing operation on a tab
246      *
247      * @param tab
248      *            the tab to close
249      * @return true if the closing operation succeeded
250      */
251     public static boolean startClosingOperationWithoutSave(SwingScilabTab tab) {
252         return close(collectTabsToClose(tab), getWindow(tab), true, false);
253     }
254
255     /**
256      * Start a closing operation on a tab
257      *
258      * @param tab
259      *            the tab to close
260      * @return true if the closing operation succeeded
261      */
262     public static boolean startClosingOperationWithoutSave(Tab tab) {
263         return startClosingOperationWithoutSave((SwingScilabTab) tab.getAsSimpleTab());
264     }
265
266     /**
267      * Start a closing operation on a window
268      *
269      * Configured to ask for close and store configuration.
270      *
271      * @return true if the closing operation succeeded
272      * @param window
273      *            the window to close
274      */
275     public static boolean startClosingOperation(SwingScilabWindow window) {
276         return startClosingOperation(window, true, true);
277     }
278
279     /**
280      * Start a closing operation on a window
281      *
282      * @return true if the closing operation succeeded
283      * @param window
284      *            the window to close
285      * @param askToExit
286      *            ask to exit ?
287      * @param mustSave
288      *            store the configuration ?
289      */
290     public static boolean startClosingOperation(SwingScilabWindow window, boolean askToExit, boolean mustSave) {
291         // Put the closing operation in a try/catch to avoid that an exception
292         // blocks the shutting down. If it is not done, the Scilab process could stay alive.
293         try {
294             if (window != null) {
295                 List<SwingScilabTab> list = new ArrayList<SwingScilabTab>();
296                 if (window.getDockingPort() != null) {
297                     Object[] dockArray = window.getDockingPort().getDockables().toArray();
298                     for (int i = 0; i < dockArray.length; i++) {
299                         collectTabsToClose((SwingScilabTab) dockArray[i], list);
300                     }
301                     return close(list, window, askToExit, mustSave);
302                 } else {
303                     return true;
304                 }
305             }
306         } catch (Exception e) {
307             e.printStackTrace();
308         }
309
310         return true;
311     }
312
313     /**
314      * Start a closing operation on a window
315      *
316      * @param window
317      *            the window to close
318      * @param askToExit
319      *            ask to exit ?
320      * @param mustSave
321      *            store the configuration ?
322      * @return true if the closing operation succeeded
323      */
324     public static boolean startClosingOperation(Window window, boolean askToExit, boolean mustSave) {
325         return startClosingOperation((SwingScilabWindow) window.getAsSimpleWindow(), askToExit, mustSave);
326     }
327
328     /**
329      * Add a dependency between two tabs
330      *
331      * @param parent
332      *            the parent tab
333      * @param child
334      *            the child tab
335      */
336     public static void addDependency(SwingScilabTab parent, SwingScilabTab child) {
337         if (parent != null && child != null) {
338             List<SwingScilabTab> children = deps.get(parent);
339             if (children == null) {
340                 children = new ArrayList<SwingScilabTab>();
341                 deps.put(parent, children);
342             }
343             children.add(child);
344         }
345     }
346
347     /**
348      * Remove the given children from its parent
349      * @param child teh child to remove
350      */
351     public static void removeDependency(SwingScilabTab child) {
352         for (SwingScilabTab tab : deps.keySet()) {
353             List<SwingScilabTab> children = deps.get(tab);
354             if (children != null) {
355                 children.remove(child);
356             }
357         }
358     }
359
360     /**
361      * Add a dependency between two tabs
362      *
363      * @param parent
364      *            the parent tab
365      * @param child
366      *            the child tab
367      */
368     public static void addDependency(Tab parent, Tab child) {
369         if (parent != null && child != null) {
370             addDependency((SwingScilabTab) parent.getAsSimpleTab(), (SwingScilabTab) child.getAsSimpleTab());
371         }
372     }
373
374     /**
375      * Add a dependency between two tabs
376      *
377      * @param parent
378      *            the parent tab
379      * @param child
380      *            the child tab
381      */
382     public static void addDependency(SwingScilabTab parent, Tab child) {
383         if (parent != null && child != null) {
384             addDependency(parent, (SwingScilabTab) child.getAsSimpleTab());
385         }
386     }
387
388     /**
389      * Add a dependency between two tabs
390      *
391      * @param parent
392      *            the parent tab
393      * @param child
394      *            the child tab
395      */
396     public static void addDependency(Tab parent, SwingScilabTab child) {
397         if (parent != null && child != null) {
398             addDependency((SwingScilabTab) parent.getAsSimpleTab(), child);
399         }
400     }
401
402     /**
403      * Add a dependency with the root tab
404      *
405      * @param child
406      *            the child tab
407      */
408     public static void addDependencyWithRoot(SwingScilabTab child) {
409         if (child != null) {
410             addDependency(root, child);
411         }
412     }
413
414     /**
415      * Add a dependency with the root tab
416      *
417      * @param child
418      *            the child tab
419      */
420     public static void addDependencyWithRoot(Tab child) {
421         if (child != null) {
422             addDependency(root, (SwingScilabTab) child.getAsSimpleTab());
423         }
424     }
425
426     /**
427      * Set the root element (normally the console)
428      *
429      * @param root
430      *            the root element
431      */
432     public static void setRoot(SwingScilabTab tab) {
433         List<SwingScilabTab> list = deps.get(root);
434         deps.remove(root);
435         deps.put(tab, list);
436         root = tab;
437     }
438
439     /**
440      * Set the root element (normally the console)
441      *
442      * @param root
443      *            the root element
444      */
445     public static void setRoot(Tab tab) {
446         setRoot((SwingScilabTab) tab.getAsSimpleTab());
447     }
448
449     /**
450      * Return the parent tab
451      *
452      * @param tab
453      *            the child
454      * @return the parent tab
455      */
456     private static SwingScilabTab getParent(SwingScilabTab tab) {
457         for (SwingScilabTab key : deps.keySet()) {
458             List<SwingScilabTab> list = deps.get(key);
459             if (list != null && list.contains(tab)) {
460                 return key;
461             }
462         }
463         return null;
464     }
465
466     /**
467      * Return the parent tab
468      *
469      * @param tab
470      *            the child
471      * @return the parent tab
472      */
473     public static SwingScilabTab getElderTab(List<SwingScilabTab> tabs) {
474         if (tabs == null || tabs.size() == 0) {
475             return null;
476         }
477
478         int min = Integer.MAX_VALUE;
479         SwingScilabTab elder = null;
480         for (SwingScilabTab tab : tabs) {
481             int level = 0;
482             SwingScilabTab t = getParent(tab);
483             while (t != null) {
484                 level++;
485                 t = getParent(t);
486             }
487             if (level < min) {
488                 elder = tab;
489                 min = level;
490             }
491         }
492
493         return elder;
494     }
495
496     /**
497      * Close a list of tabs
498      *
499      * @param list
500      *            the list
501      * @param window
502      *            the window to use to center the modal dialog
503      * @param askToExit
504      *            if true, then ask to exit
505      * @param mustSave
506      *            if true, the configuration is saved
507      * @return true if the closing operation succeeded
508      */
509     private static final boolean close(List<SwingScilabTab> list, SwingScilabWindow window, boolean askToExit, boolean mustSave) {
510         boolean ret = false;
511         if (!askToExit || canClose(list, window, mustSave)) {
512             ret = true;
513             SwingScilabTab console = null;
514             try {
515                 // First thing we get the console (if it is here) to be sure to
516                 // kill it !
517                 for (SwingScilabTab tab : list) {
518                     if (tab.getPersistentId().equals(NULLUUID)) {
519                         console = tab;
520                         break;
521                     }
522                 }
523
524                 // We remove the tabs which have a callback and no
525                 // ClosingOperation
526                 // To avoid annoying situations the tab will be undocked and
527                 // closed
528                 List<SwingScilabTab> tabsToRemove = new ArrayList<SwingScilabTab>();
529                 for (SwingScilabTab tab : list) {
530                     if (closingOps.get(tab) == null) {
531                         tab.setVisible(false);
532                         tab.getActionButton("undock").getAction().actionPerformed(null);
533                         Action action = ((SciClosingAction) tab.getActionButton(DockingConstants.CLOSE_ACTION).getAction()).getAction();
534                         if (action == null) {
535                             SwingScilabWindow win = getWindow(tab);
536                             if (win != null) {
537                                 win.removeTabs(new SwingScilabTab[] { tab });
538                             }
539                         } else {
540                             action.actionPerformed(null);
541                         }
542                         tabsToRemove.add(tab);
543                     }
544                 }
545                 list.removeAll(tabsToRemove);
546
547                 // we group the tabs by win
548                 Map<SwingScilabWindow, List<SwingScilabTab>> map = new HashMap<SwingScilabWindow, List<SwingScilabTab>>();
549                 for (SwingScilabTab tab : list) {
550                     SwingScilabWindow win = getWindow(tab);
551                     if (win != null) {
552                         if (!map.containsKey(win)) {
553                             map.put(win, new ArrayList<SwingScilabTab>());
554                         }
555                         map.get(win).add(tab);
556                     }
557                 }
558
559                 List<SwingScilabWindow> winsWithOneTab = new ArrayList<SwingScilabWindow>();
560                 List<SwingScilabWindow> windowsToClose = new ArrayList<SwingScilabWindow>();
561                 for (SwingScilabWindow win : map.keySet()) {
562                     List<SwingScilabTab> listTabs = map.get(win);
563                     int nbDockedTabs = win.getNbDockedObjects();
564                     if (nbDockedTabs == listTabs.size()) {
565                         // all the tabs in the window are removed so we save the
566                         // win state
567                         if (mustSave) {
568                             WindowsConfigurationManager.saveWindowProperties(win);
569                         }
570                         windowsToClose.add(win);
571                     } else {
572                         if (nbDockedTabs - listTabs.size() == 1) {
573                             winsWithOneTab.add(win);
574                         }
575                         // the window will stay opened
576                         if (mustSave) {
577                             for (SwingScilabTab tab : listTabs) {
578                                 WindowsConfigurationManager.saveTabProperties(tab, true);
579                             }
580                         }
581                     }
582                 }
583
584                 // If a parent and a child are removed, we make a dependency
585                 // between them
586                 // The parent restoration will imply the child one
587                 for (SwingScilabTab tab : list) {
588                     SwingScilabTab parent = getParent(tab);
589                     if (list.contains(parent) || parent == null) {
590                         if (parent != null) {
591                             WindowsConfigurationManager.makeDependency(parent.getPersistentId(), tab.getPersistentId());
592                         } else if (!tab.getPersistentId().equals(NULLUUID)) {
593                             // if the parent is null, we make a dependency with
594                             // the console which is the default root
595                             WindowsConfigurationManager.makeDependency(NULLUUID, tab.getPersistentId());
596                         }
597                     } else {
598                         WindowsConfigurationManager.removeDependency(tab.getPersistentId());
599                     }
600                 }
601
602                 WindowsConfigurationManager.clean();
603                 // We destroy all the tabs: children before parents.
604                 for (SwingScilabTab tab : list) {
605                     tab.setVisible(false);
606                     if (!tab.getPersistentId().equals(NULLUUID)) {
607                         try {
608                             closingOps.get(tab).destroy();
609                         } catch (Exception e) {
610                             // An error can occurred during the destroy operation
611                             // We show it but it mustn't avoid the window
612                             // destruction
613                             e.printStackTrace();
614                         }
615                     }
616                 }
617
618                 // We remove the tabs in each window
619                 // The tabs are removed in one time to avoid that the
620                 // ActiveDockableTracker tryes to give the activation to a
621                 // removed tab
622                 for (SwingScilabWindow win : map.keySet()) {
623                     win.removeTabs(map.get(win).toArray(new SwingScilabTab[0]));
624                 }
625
626                 // It stays one docked tab so we remove close and undock action
627                 for (SwingScilabWindow win : winsWithOneTab) {
628                     Object[] dockArray = win.getDockingPort().getDockables().toArray();
629                     SwingScilabTab.removeActions((SwingScilabTab) dockArray[0]);
630                 }
631
632                 // We wait until all the windows are definitly closed
633                 while (windowsToClose.size() != 0) {
634                     List<SwingScilabWindow> toRemove = new ArrayList<SwingScilabWindow>();
635                     for (SwingScilabWindow win : windowsToClose) {
636                         WindowsConfigurationManager.removeWin(win.getUUID());
637                         if (win.isDisplayable()) {
638                             Thread.yield();
639                         } else {
640                             toRemove.add(win);
641                         }
642                     }
643                     windowsToClose.removeAll(toRemove);
644                 }
645
646                 // We remove the tabs from the cache
647                 for (SwingScilabTab tab : list) {
648                     ScilabTabFactory.getInstance().removeFromCache(tab.getPersistentId());
649                     SwingScilabTab parent = getParent(tab);
650                     List<SwingScilabTab> l = deps.get(parent);
651                     if (l != null) {
652                         l.remove(tab);
653                     }
654                     deps.remove(tab);
655                 }
656             } catch (Exception e) {
657                 e.printStackTrace();
658             } finally {
659                 if (console != null) {
660                     try {
661                         closingOps.get(console).destroy();
662                     } catch (Exception e) {
663                         e.printStackTrace();
664                     }
665                 }
666             }
667         }
668
669         return ret;
670     }
671
672     /**
673      * Check if the tabs of the liste are closable or not
674      *
675      * @param list
676      *            the list of tabs
677      * @param window
678      *            the window to use to center the modal dialog
679      * @return true if all the tabs can be closed
680      */
681     private static final boolean canClose(List<SwingScilabTab> list,
682                                           SwingScilabWindow window,
683                                           boolean mustSave) {
684         CheckExitConfirmation cec = XConfiguration.get(CheckExitConfirmation.class, XConfiguration.getXConfigurationDocument(), CONFIRMATION_PATH)[0];
685         dunnoList.clear();
686         savedList = null;
687         savedMustSave = false;
688
689         if (cec.checked) {
690             String question = makeQuestion(list);
691             final boolean[] checked = new boolean[1];
692             final Action action = new AbstractAction() {
693                     public void actionPerformed(ActionEvent e) {
694                         checked[0] = ((JCheckBox) e.getSource()).isSelected();
695                     }
696                 };
697
698             if (question != null) {
699                 if (ScilabModalDialog.show(window, new String[] { question }, EXIT, IconType.WARNING_ICON, ButtonType.YES_NO, DONT_SHOW, action) == AnswerOption.NO_OPTION) {
700                     if (checked[0]) {
701                         XConfiguration.set(XConfiguration.getXConfigurationDocument(), CONFIRMATION_PATH + "/@state", "unchecked");
702                     }
703                     return false;
704                 }
705             }
706
707             if (checked[0]) {
708                 XConfiguration.set(XConfiguration.getXConfigurationDocument(), CONFIRMATION_PATH + "/@state", "unchecked");
709             }
710         }
711
712         for (SwingScilabTab t : list) {
713             ClosingOperation op = closingOps.get(t);
714             if (op != null) {
715                 int ret = op.canClose();
716                 if (ret == 0) {
717                     dunnoList.clear();
718                     return false;
719                 }
720                 if (ret == -1) {
721                     dunnoList.add(t);
722                 }
723             }
724         }
725
726         if (dunnoList.isEmpty()) {
727             return true;
728         }
729
730         for (SwingScilabTab tab : dunnoList) {
731             list.remove(tab);
732         }
733
734         savedList = list;
735         savedMustSave = mustSave;
736
737         return false;
738     }
739
740     /**
741      * Make the question to ask to exit
742      *
743      * @param list
744      *            the list of the tabs to close
745      * @return the question
746      */
747     private static final String makeQuestion(List<SwingScilabTab> list) {
748         List<String> apps = new ArrayList<String>();
749         List<SwingScilabTab> toBeRemoved = Collections.unmodifiableList(list);
750         for (SwingScilabTab t : list) {
751             ClosingOperation op = closingOps.get(t);
752             if (op != null) {
753                 String name = op.askForClosing(toBeRemoved);
754                 if (name != null && !apps.contains(name)) {
755                     apps.add(name);
756                 }
757             }
758         }
759         switch (apps.size()) {
760         case 0:
761             return null;
762         case 1:
763             return String.format(EXIT_CONFIRM, apps.get(0));
764         }
765
766         String str = apps.remove(0);
767         String last = apps.remove(apps.size() - 1);
768         for (String s : apps) {
769             str += ", " + s;
770         }
771
772         return String.format(EXIT_CONFIRM_AND, str, last);
773     }
774
775     /**
776      * Collect the tabs and their children to close (recursive function)
777      *
778      * @param tab
779      *            the current tab
780      * @param list
781      *            the list
782      */
783     private static final void collectTabsToClose(SwingScilabTab tab,
784                                                  List<SwingScilabTab> list) {
785         List<SwingScilabTab> children = deps.get(tab);
786         if (children != null) {
787             for (SwingScilabTab t : children) {
788                 collectTabsToClose(t, list);
789             }
790         }
791         if (!list.contains(tab)) {
792             list.add(tab);
793         }
794
795         /*
796          * Update the tab list in case of hidden (eg. dynamic) dependencies
797          */
798         final List<SwingScilabTab> ro = Collections.unmodifiableList(list);
799         for (ListIterator<SwingScilabTab> it = list.listIterator(); it.hasNext();) {
800             final SwingScilabTab t = it.next();
801
802             final ClosingOperation op = closingOps.get(t);
803             if (op != null) {
804                 op.updateDependencies(ro, it);
805             }
806         }
807     }
808
809     /**
810      * Collect the tabs and their children to close (recursive function)
811      *
812      * @param tab
813      *            the current tab
814      * @return the list of the tabs to close
815      */
816     private static final List<SwingScilabTab> collectTabsToClose(
817         SwingScilabTab tab) {
818         final List<SwingScilabTab> list = new ArrayList<SwingScilabTab>();
819         collectTabsToClose(tab, list);
820         return list;
821     }
822
823     /**
824      * Collect the tabs and their children to close (recursive function)
825      *
826      * @param tabs
827      *            the current tabs
828      * @return the list of the tabs to close
829      */
830     private static final List<SwingScilabTab> collectTabsToClose(List<SwingScilabTab> tabs) {
831         final List<SwingScilabTab> list = new ArrayList<SwingScilabTab>();
832         for (final SwingScilabTab tab : tabs) {
833             collectTabsToClose(tab, list);
834         }
835         return list;
836     }
837
838     /**
839      * Get the window containing the given tab
840      *
841      * @param tab
842      *            the tab
843      * @return the corresponding window
844      */
845     private static final SwingScilabWindow getWindow(SwingScilabTab tab) {
846         return (SwingScilabWindow) SwingUtilities.getAncestorOfClass(SwingScilabWindow.class, tab);
847     }
848
849     /**
850      * Inner interface to handle a closing operation Must be registered with the
851      * static method ClosingOperationsManager.registerClosingOperation
852      */
853     public interface ClosingOperation {
854
855         /**
856          * @return 0 or 1 if the associated tab can be closed or not, and -1 if the answer is unknown
857          */
858         public int canClose();
859
860         /**
861          * Destroy the resources associated to the tab
862          */
863         public void destroy();
864
865         /**
866          * @return non null String if the tab requires a
867          *         "Are you sure you want to close FOO ?..."
868          */
869         public String askForClosing(final List<SwingScilabTab> list);
870
871         /**
872          * Update the dependency list to handle specific dependency
873          *
874          * @param list
875          *            the tab list to update
876          * @param it
877          *            the iterator to update
878          */
879         public void updateDependencies(final List<SwingScilabTab> list, final ListIterator<SwingScilabTab> it);
880     }
881
882     @XConfAttribute
883     private static class CheckExitConfirmation {
884
885         public boolean checked;
886
887         private CheckExitConfirmation() { }
888
889         @XConfAttribute(attributes = {"state"})
890         private void set(String checked) {
891             this.checked = checked.equals("checked");
892         }
893     }
894 }