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