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