2 * Scilab ( http://www.scilab.org/ ) - This file is part of Scilab
3 * Copyright (C) 2011 - Calixte DENIZET
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
13 package org.scilab.modules.gui.utils;
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;
23 import java.util.UUID;
25 import javax.swing.AbstractAction;
26 import javax.swing.Action;
27 import javax.swing.JCheckBox;
28 import javax.swing.SwingUtilities;
30 import org.flexdock.docking.DockingConstants;
31 import org.scilab.modules.gui.bridge.tab.SwingScilabTab;
32 import org.scilab.modules.gui.bridge.window.SwingScilabWindow;
33 import org.scilab.modules.gui.messagebox.ScilabModalDialog;
34 import org.scilab.modules.gui.messagebox.ScilabModalDialog.AnswerOption;
35 import org.scilab.modules.gui.messagebox.ScilabModalDialog.ButtonType;
36 import org.scilab.modules.gui.messagebox.ScilabModalDialog.IconType;
37 import org.scilab.modules.gui.tab.Tab;
38 import org.scilab.modules.gui.tabfactory.ScilabTabFactory;
39 import org.scilab.modules.gui.window.Window;
40 import org.scilab.modules.localization.Messages;
42 import static org.scilab.modules.commons.xml.XConfiguration.XConfAttribute;
43 import org.scilab.modules.commons.xml.XConfiguration;
46 * Class to handle the different closing operations.
48 * @author Calixte DENIZET
51 @SuppressWarnings(value = { "serial" })
52 public class ClosingOperationsManager {
54 private static final String EXIT_CONFIRM = Messages.gettext("Are you sure you want to close %s ?");
55 private static final String EXIT_CONFIRM_AND = Messages.gettext("Are you sure you want to close %s and %s ?");
56 private static final String DONT_SHOW = Messages.gettext("Do not show this message again");
57 private static final String CONFIRMATION_PATH = "//general/confirmation-dialogs/body/tools/tool[@id='console-exit']";
58 private static final String EXIT = Messages.gettext("Exit");
59 private static final String NULLUUID = new UUID(0L, 0L).toString();
60 private static final Map<SwingScilabTab, ClosingOperation> closingOps = new HashMap<SwingScilabTab, ClosingOperation>();
61 private static final Map<SwingScilabTab, List<SwingScilabTab>> deps = new HashMap<SwingScilabTab, List<SwingScilabTab>>();
63 private static final List<SwingScilabTab> dunnoList = new ArrayList<SwingScilabTab>();
64 private static List<SwingScilabTab> savedList;
65 private static boolean savedMustSave;
67 private static SwingScilabTab root;
70 deps.put(null, new ArrayList<SwingScilabTab>());
74 * Register a closing operation for a tab
79 * the closing operation
81 public static void registerClosingOperation(SwingScilabTab tab,
82 ClosingOperation op) {
84 closingOps.put(tab, op);
89 * Register a closing operation for a tab
94 * the closing operation
96 public static void registerClosingOperation(Tab tab, ClosingOperation op) {
98 registerClosingOperation((SwingScilabTab) tab.getAsSimpleTab(), op);
103 * Unregister a closing operation for a tab
105 * @param tab the associated tab
107 public static void unregisterClosingOperation(SwingScilabTab tab) {
109 closingOps.remove(tab);
113 public static void checkTabForClosing(SwingScilabTab tab) {
114 if (tab != null && !dunnoList.isEmpty()) {
115 if (dunnoList.contains(tab)) {
116 dunnoList.remove(tab);
118 if (dunnoList.isEmpty() && savedList != null) {
119 close(savedList, null, false, savedMustSave);
121 savedMustSave = false;
126 public static void removeFromDunnoList(SwingScilabTab tab) {
127 if (tab != null && !dunnoList.isEmpty() && dunnoList.contains(tab)) {
128 dunnoList.remove(tab);
133 * Start a closing operation on root
135 * @return true if the closing operation succeeded
137 public static boolean startClosingOperationOnRoot() {
138 if (!GraphicsEnvironment.isHeadless()) {
141 SwingScilabWindow win = getWindow(root);
145 return startClosingOperation(win, true, true);
146 } else if (deps.get(null).size() != 0) {
148 List<SwingScilabTab> list = new ArrayList<SwingScilabTab>();
149 for (SwingScilabTab tab : deps.get(null)) {
150 collectTabsToClose(tab, list);
152 return close(list, null, true, true);
162 * Force a closing operation on root to dispose resources
164 public static void forceClosingOperationOnRoot() {
167 SwingScilabWindow win = getWindow(root);
171 startClosingOperation(win, false, false);
172 } else if (deps.get(null).size() != 0) {
174 List<SwingScilabTab> list = new ArrayList<SwingScilabTab>();
175 for (SwingScilabTab tab : deps.get(null)) {
176 collectTabsToClose(tab, list);
178 close(list, null, false, false);
183 * Start a closing operation on a tab
187 * @return true if the closing operation succeeded
189 public static boolean startClosingOperation(SwingScilabTab tab) {
190 return close(collectTabsToClose(tab), getWindow(tab), true, true);
194 * Start a closing operation on a tab
198 * @return true if the closing operation succeeded
200 public static boolean startClosingOperation(Tab tab) {
201 return startClosingOperation((SwingScilabTab) tab.getAsSimpleTab());
205 * Start a closing operation on a tab
209 * @return true if the closing operation succeeded
211 public static boolean startClosingOperation(SwingScilabTab tab, boolean askToExit, boolean mustSave) {
212 return close(collectTabsToClose(tab), getWindow(tab), askToExit, mustSave);
216 * Start a closing operation on multiple tabs
220 * @return true if the closing operation succeeded
222 public static boolean startClosingOperation(List<SwingScilabTab> tabs, boolean askToExit, boolean mustSave) {
223 final SwingScilabWindow win;
224 if (tabs.isEmpty()) {
225 // use the null window to select the console tab.
228 win = getWindow(tabs.get(0));
230 return close(collectTabsToClose(tabs), win, askToExit, mustSave);
234 * Start a closing operation on a tab
238 * @return true if the closing operation succeeded
240 public static boolean startClosingOperation(Tab tab, boolean askToExit, boolean mustSave) {
241 return startClosingOperation((SwingScilabTab) tab.getAsSimpleTab(), askToExit, mustSave);
245 * Start a closing operation on a tab
249 * @return true if the closing operation succeeded
251 public static boolean startClosingOperationWithoutSave(SwingScilabTab tab) {
252 return close(collectTabsToClose(tab), getWindow(tab), true, false);
256 * Start a closing operation on a tab
260 * @return true if the closing operation succeeded
262 public static boolean startClosingOperationWithoutSave(Tab tab) {
263 return startClosingOperationWithoutSave((SwingScilabTab) tab.getAsSimpleTab());
267 * Start a closing operation on a window
269 * Configured to ask for close and store configuration.
271 * @return true if the closing operation succeeded
273 * the window to close
275 public static boolean startClosingOperation(SwingScilabWindow window) {
276 return startClosingOperation(window, true, true);
280 * Start a closing operation on a window
282 * @return true if the closing operation succeeded
284 * the window to close
288 * store the configuration ?
290 public static boolean startClosingOperation(SwingScilabWindow window, boolean askToExit, boolean mustSave) {
291 // Put the closing operation in a try/catch to avoid that an exception
292 // blocks the shutting down. If it is not done, the Scilab process could stay alive.
294 if (window != null) {
295 List<SwingScilabTab> list = new ArrayList<SwingScilabTab>();
296 if (window.getDockingPort() != null) {
297 Object[] dockArray = window.getDockingPort().getDockables().toArray();
298 for (int i = 0; i < dockArray.length; i++) {
299 collectTabsToClose((SwingScilabTab) dockArray[i], list);
301 return close(list, window, askToExit, mustSave);
306 } catch (Exception e) {
314 * Start a closing operation on a window
317 * the window to close
321 * store the configuration ?
322 * @return true if the closing operation succeeded
324 public static boolean startClosingOperation(Window window, boolean askToExit, boolean mustSave) {
325 return startClosingOperation((SwingScilabWindow) window.getAsSimpleWindow(), askToExit, mustSave);
329 * Add a dependency between two tabs
336 public static void addDependency(SwingScilabTab parent, SwingScilabTab child) {
337 if (parent != null && child != null) {
338 List<SwingScilabTab> children = deps.get(parent);
339 if (children == null) {
340 children = new ArrayList<SwingScilabTab>();
341 deps.put(parent, children);
348 * Remove the given children from its parent
349 * @param child teh child to remove
351 public static void removeDependency(SwingScilabTab child) {
352 for (SwingScilabTab tab : deps.keySet()) {
353 List<SwingScilabTab> children = deps.get(tab);
354 if (children != null) {
355 children.remove(child);
361 * Add a dependency between two tabs
368 public static void addDependency(Tab parent, Tab child) {
369 if (parent != null && child != null) {
370 addDependency((SwingScilabTab) parent.getAsSimpleTab(), (SwingScilabTab) child.getAsSimpleTab());
375 * Add a dependency between two tabs
382 public static void addDependency(SwingScilabTab parent, Tab child) {
383 if (parent != null && child != null) {
384 addDependency(parent, (SwingScilabTab) child.getAsSimpleTab());
389 * Add a dependency between two tabs
396 public static void addDependency(Tab parent, SwingScilabTab child) {
397 if (parent != null && child != null) {
398 addDependency((SwingScilabTab) parent.getAsSimpleTab(), child);
403 * Add a dependency with the root tab
408 public static void addDependencyWithRoot(SwingScilabTab child) {
410 addDependency(root, child);
415 * Add a dependency with the root tab
420 public static void addDependencyWithRoot(Tab child) {
422 addDependency(root, (SwingScilabTab) child.getAsSimpleTab());
427 * Set the root element (normally the console)
432 public static void setRoot(SwingScilabTab tab) {
433 List<SwingScilabTab> list = deps.get(root);
440 * Set the root element (normally the console)
445 public static void setRoot(Tab tab) {
446 setRoot((SwingScilabTab) tab.getAsSimpleTab());
450 * Return the parent tab
454 * @return the parent tab
456 private static SwingScilabTab getParent(SwingScilabTab tab) {
457 for (SwingScilabTab key : deps.keySet()) {
458 List<SwingScilabTab> list = deps.get(key);
459 if (list != null && list.contains(tab)) {
467 * Return the parent tab
471 * @return the parent tab
473 public static SwingScilabTab getElderTab(List<SwingScilabTab> tabs) {
474 if (tabs == null || tabs.size() == 0) {
478 int min = Integer.MAX_VALUE;
479 SwingScilabTab elder = null;
480 for (SwingScilabTab tab : tabs) {
482 SwingScilabTab t = getParent(tab);
497 * Close a list of tabs
502 * the window to use to center the modal dialog
504 * if true, then ask to exit
506 * if true, the configuration is saved
507 * @return true if the closing operation succeeded
509 private static final boolean close(List<SwingScilabTab> list, SwingScilabWindow window, boolean askToExit, boolean mustSave) {
511 if (!askToExit || canClose(list, window, mustSave)) {
513 SwingScilabTab console = null;
515 // First thing we get the console (if it is here) to be sure to
517 for (SwingScilabTab tab : list) {
518 if (tab.getPersistentId().equals(NULLUUID)) {
524 // We remove the tabs which have a callback and no
526 // To avoid annoying situations the tab will be undocked and
528 List<SwingScilabTab> tabsToRemove = new ArrayList<SwingScilabTab>();
529 for (SwingScilabTab tab : list) {
530 if (closingOps.get(tab) == null) {
531 tab.setVisible(false);
532 tab.getActionButton("undock").getAction().actionPerformed(null);
533 Action action = ((SciClosingAction) tab.getActionButton(DockingConstants.CLOSE_ACTION).getAction()).getAction();
534 if (action == null) {
535 SwingScilabWindow win = getWindow(tab);
537 win.removeTabs(new SwingScilabTab[] { tab });
540 action.actionPerformed(null);
542 tabsToRemove.add(tab);
545 list.removeAll(tabsToRemove);
547 // we group the tabs by win
548 Map<SwingScilabWindow, List<SwingScilabTab>> map = new HashMap<SwingScilabWindow, List<SwingScilabTab>>();
549 for (SwingScilabTab tab : list) {
550 SwingScilabWindow win = getWindow(tab);
552 if (!map.containsKey(win)) {
553 map.put(win, new ArrayList<SwingScilabTab>());
555 map.get(win).add(tab);
559 List<SwingScilabWindow> winsWithOneTab = new ArrayList<SwingScilabWindow>();
560 List<SwingScilabWindow> windowsToClose = new ArrayList<SwingScilabWindow>();
561 for (SwingScilabWindow win : map.keySet()) {
562 List<SwingScilabTab> listTabs = map.get(win);
563 int nbDockedTabs = win.getNbDockedObjects();
564 if (nbDockedTabs == listTabs.size()) {
565 // all the tabs in the window are removed so we save the
568 WindowsConfigurationManager.saveWindowProperties(win);
570 windowsToClose.add(win);
572 if (nbDockedTabs - listTabs.size() == 1) {
573 winsWithOneTab.add(win);
575 // the window will stay opened
577 for (SwingScilabTab tab : listTabs) {
578 WindowsConfigurationManager.saveTabProperties(tab, true);
584 // If a parent and a child are removed, we make a dependency
586 // The parent restoration will imply the child one
587 for (SwingScilabTab tab : list) {
588 SwingScilabTab parent = getParent(tab);
589 if (list.contains(parent) || parent == null) {
590 if (parent != null) {
591 WindowsConfigurationManager.makeDependency(parent.getPersistentId(), tab.getPersistentId());
592 } else if (!tab.getPersistentId().equals(NULLUUID)) {
593 // if the parent is null, we make a dependency with
594 // the console which is the default root
595 WindowsConfigurationManager.makeDependency(NULLUUID, tab.getPersistentId());
598 WindowsConfigurationManager.removeDependency(tab.getPersistentId());
602 WindowsConfigurationManager.clean();
603 // We destroy all the tabs: children before parents.
604 for (SwingScilabTab tab : list) {
605 tab.setVisible(false);
606 if (!tab.getPersistentId().equals(NULLUUID)) {
608 closingOps.get(tab).destroy();
609 } catch (Exception e) {
610 // An error can occurred during the destroy operation
611 // We show it but it mustn't avoid the window
618 // We remove the tabs in each window
619 // The tabs are removed in one time to avoid that the
620 // ActiveDockableTracker tryes to give the activation to a
622 for (SwingScilabWindow win : map.keySet()) {
623 win.removeTabs(map.get(win).toArray(new SwingScilabTab[0]));
626 // It stays one docked tab so we remove close and undock action
627 for (SwingScilabWindow win : winsWithOneTab) {
628 Object[] dockArray = win.getDockingPort().getDockables().toArray();
629 SwingScilabTab.removeActions((SwingScilabTab) dockArray[0]);
632 // We wait until all the windows are definitly closed
633 while (windowsToClose.size() != 0) {
634 List<SwingScilabWindow> toRemove = new ArrayList<SwingScilabWindow>();
635 for (SwingScilabWindow win : windowsToClose) {
636 WindowsConfigurationManager.removeWin(win.getUUID());
637 if (win.isDisplayable()) {
643 windowsToClose.removeAll(toRemove);
646 // We remove the tabs from the cache
647 for (SwingScilabTab tab : list) {
648 ScilabTabFactory.getInstance().removeFromCache(tab.getPersistentId());
649 SwingScilabTab parent = getParent(tab);
650 List<SwingScilabTab> l = deps.get(parent);
656 } catch (Exception e) {
659 if (console != null) {
661 closingOps.get(console).destroy();
662 } catch (Exception e) {
673 * Check if the tabs of the liste are closable or not
678 * the window to use to center the modal dialog
679 * @return true if all the tabs can be closed
681 private static final boolean canClose(List<SwingScilabTab> list,
682 SwingScilabWindow window,
684 CheckExitConfirmation cec = XConfiguration.get(CheckExitConfirmation.class, XConfiguration.getXConfigurationDocument(), CONFIRMATION_PATH)[0];
687 savedMustSave = false;
690 String question = makeQuestion(list);
691 final boolean[] checked = new boolean[1];
692 final Action action = new AbstractAction() {
693 public void actionPerformed(ActionEvent e) {
694 checked[0] = ((JCheckBox) e.getSource()).isSelected();
698 if (question != null) {
699 if (ScilabModalDialog.show(window, new String[] { question }, EXIT, IconType.WARNING_ICON, ButtonType.YES_NO, DONT_SHOW, action) == AnswerOption.NO_OPTION) {
701 XConfiguration.set(XConfiguration.getXConfigurationDocument(), CONFIRMATION_PATH + "/@state", "unchecked");
708 XConfiguration.set(XConfiguration.getXConfigurationDocument(), CONFIRMATION_PATH + "/@state", "unchecked");
712 for (SwingScilabTab t : list) {
713 ClosingOperation op = closingOps.get(t);
715 int ret = op.canClose();
726 if (dunnoList.isEmpty()) {
730 for (SwingScilabTab tab : dunnoList) {
735 savedMustSave = mustSave;
741 * Make the question to ask to exit
744 * the list of the tabs to close
745 * @return the question
747 private static final String makeQuestion(List<SwingScilabTab> list) {
748 List<String> apps = new ArrayList<String>();
749 List<SwingScilabTab> toBeRemoved = Collections.unmodifiableList(list);
750 for (SwingScilabTab t : list) {
751 ClosingOperation op = closingOps.get(t);
753 String name = op.askForClosing(toBeRemoved);
754 if (name != null && !apps.contains(name)) {
759 switch (apps.size()) {
763 return String.format(EXIT_CONFIRM, apps.get(0));
766 String str = apps.remove(0);
767 String last = apps.remove(apps.size() - 1);
768 for (String s : apps) {
772 return String.format(EXIT_CONFIRM_AND, str, last);
776 * Collect the tabs and their children to close (recursive function)
783 private static final void collectTabsToClose(SwingScilabTab tab,
784 List<SwingScilabTab> list) {
785 List<SwingScilabTab> children = deps.get(tab);
786 if (children != null) {
787 for (SwingScilabTab t : children) {
788 collectTabsToClose(t, list);
791 if (!list.contains(tab)) {
796 * Update the tab list in case of hidden (eg. dynamic) dependencies
798 final List<SwingScilabTab> ro = Collections.unmodifiableList(list);
799 for (ListIterator<SwingScilabTab> it = list.listIterator(); it.hasNext();) {
800 final SwingScilabTab t = it.next();
802 final ClosingOperation op = closingOps.get(t);
804 op.updateDependencies(ro, it);
810 * Collect the tabs and their children to close (recursive function)
814 * @return the list of the tabs to close
816 private static final List<SwingScilabTab> collectTabsToClose(
817 SwingScilabTab tab) {
818 final List<SwingScilabTab> list = new ArrayList<SwingScilabTab>();
819 collectTabsToClose(tab, list);
824 * Collect the tabs and their children to close (recursive function)
828 * @return the list of the tabs to close
830 private static final List<SwingScilabTab> collectTabsToClose(List<SwingScilabTab> tabs) {
831 final List<SwingScilabTab> list = new ArrayList<SwingScilabTab>();
832 for (final SwingScilabTab tab : tabs) {
833 collectTabsToClose(tab, list);
839 * Get the window containing the given tab
843 * @return the corresponding window
845 private static final SwingScilabWindow getWindow(SwingScilabTab tab) {
846 return (SwingScilabWindow) SwingUtilities.getAncestorOfClass(SwingScilabWindow.class, tab);
850 * Inner interface to handle a closing operation Must be registered with the
851 * static method ClosingOperationsManager.registerClosingOperation
853 public interface ClosingOperation {
856 * @return 0 or 1 if the associated tab can be closed or not, and -1 if the answer is unknown
858 public int canClose();
861 * Destroy the resources associated to the tab
863 public void destroy();
866 * @return non null String if the tab requires a
867 * "Are you sure you want to close FOO ?..."
869 public String askForClosing(final List<SwingScilabTab> list);
872 * Update the dependency list to handle specific dependency
875 * the tab list to update
877 * the iterator to update
879 public void updateDependencies(final List<SwingScilabTab> list, final ListIterator<SwingScilabTab> it);
883 private static class CheckExitConfirmation {
885 public boolean checked;
887 private CheckExitConfirmation() { }
889 @XConfAttribute(attributes = {"state"})
890 private void set(String checked) {
891 this.checked = checked.equals("checked");