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