JAL-1988 JAL-3772 Quit confirmation dialog boxes with saving files check and wait
[jalview.git] / src / jalview / jbgui / QuitHandler.java
1 package jalview.jbgui;
2
3 import java.io.File;
4 import java.util.Date;
5
6 import javax.swing.JFrame;
7 import javax.swing.JOptionPane;
8
9 import com.formdev.flatlaf.extras.FlatDesktop;
10
11 import jalview.bin.Console;
12 import jalview.io.BackupFiles;
13 import jalview.util.MessageManager;
14 import jalview.util.Platform;
15
16 public class QuitHandler
17 {
18   public static enum QResponse
19   {
20     QUIT, CANCEL_QUIT, FORCE_QUIT
21   };
22
23   public static void setQuitHandler()
24   {
25     FlatDesktop.setQuitHandler(response -> {
26       QResponse qresponse = getQuitResponse();
27       switch (qresponse)
28       {
29       case QUIT:
30         response.performQuit();
31         break;
32       case CANCEL_QUIT:
33         response.cancelQuit();
34         break;
35       case FORCE_QUIT:
36         response.performQuit();
37         break;
38       default:
39         response.cancelQuit();
40       }
41     });
42   }
43
44   private static QResponse gotQuitResponse = QResponse.CANCEL_QUIT;
45
46   private static QResponse returnResponse(QResponse qresponse)
47   {
48     gotQuitResponse = qresponse;
49     return qresponse;
50   }
51
52   public static QResponse gotQuitResponse()
53   {
54     return gotQuitResponse;
55   }
56
57   public static QResponse getQuitResponse()
58   {
59     return getQuitResponse(true);
60   }
61
62   public static QResponse getQuitResponse(boolean ui)
63   {
64     if (gotQuitResponse() != QResponse.CANCEL_QUIT)
65     {
66       return returnResponse(getQuitResponse());
67     }
68
69     boolean interactive = ui && !Platform.isHeadless();
70     // confirm quit if needed and wanted
71     boolean confirmQuit = true;
72
73     if (!interactive)
74     {
75       Console.debug("Non interactive quit -- not confirming");
76       confirmQuit = false;
77     }
78     /* 
79     else if (undostack is empty) {
80       Console.debug("Nothing changed -- not confirming quit");
81       confirmQuit = false
82     }
83     */
84     else
85     {
86       confirmQuit = jalview.bin.Cache
87               .getDefault(jalview.gui.Desktop.CONFIRM_KEYBOARD_QUIT, true);
88       Console.debug("Jalview property '"
89               + jalview.gui.Desktop.CONFIRM_KEYBOARD_QUIT
90               + "' is/defaults to " + confirmQuit + " -- "
91               + (confirmQuit ? "" : "not ") + "confirming quit");
92     }
93
94     int answer = JOptionPane.OK_OPTION;
95
96     // if going to confirm, do it before the save in progress check to give
97     // the save time to finish!
98     if (confirmQuit)
99     {
100       answer = frameOnTop(MessageManager.getString("label.quit_jalview"),
101               MessageManager.getString("action.quit"),
102               JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE);
103     }
104
105     if (answer == JOptionPane.CANCEL_OPTION)
106     {
107       Console.debug("QuitHandler: Quit action cancelled by user");
108       return returnResponse(QResponse.CANCEL_QUIT);
109     }
110
111     // check for saves in progress
112     int waitForSave = 5000; // MAKE THIS BETTER
113     int waitIncrement = 2000;
114     long startTime = new Date().getTime();
115     boolean saving = BackupFiles.hasSavesInProgress();
116     if (saving)
117     {
118       boolean waiting = (new Date().getTime() - startTime) < waitForSave;
119       while (saving && waiting)
120       {
121         saving = !waitForSave(waitIncrement);
122         waiting = (new Date().getTime() - startTime) < waitForSave;
123       }
124
125       if (saving) // still saving after a wait
126       {
127         StringBuilder messageSB = new StringBuilder(
128                 MessageManager.getString("label.save_in_progress"));
129         for (File file : BackupFiles.savesInProgressFiles())
130         {
131           messageSB.append("\n");
132           messageSB.append(file.getName());
133         }
134         int waitLonger = interactive ? JOptionPane.YES_OPTION
135                 : JOptionPane.NO_OPTION;
136         while (saving && waitLonger == JOptionPane.YES_OPTION)
137         {
138           waitLonger = waitForceQuitCancelQuitOptionDialog(
139                   messageSB.toString(),
140                   MessageManager.getString("action.wait"));
141           if (waitLonger == JOptionPane.YES_OPTION) // wait
142           {
143             Console.debug("*** YES answer=" + waitLonger);
144             // do wait stuff
145             saving = !waitForSave(waitIncrement);
146           }
147           else if (waitLonger == JOptionPane.NO_OPTION) // force quit
148           {
149             Console.debug("*** NO answer=" + waitLonger);
150             // do a force quit
151             return returnResponse(QResponse.FORCE_QUIT); // shouldn't reach this
152           }
153           else if (waitLonger == JOptionPane.CANCEL_OPTION) // cancel quit
154           {
155             Console.debug("*** CANCEL answer=" + waitLonger);
156             return returnResponse(QResponse.CANCEL_QUIT);
157           }
158           else
159           {
160             Console.debug("**** Shouldn't have got here!");
161           }
162         }
163       }
164     }
165
166     // not cancelled and not saving
167     return returnResponse(QResponse.QUIT);
168   }
169
170   public static int frameOnTop(String label, String actionString,
171           int JOPTIONPANE_OPTION, int JOPTIONPANE_MESSAGETYPE)
172   {
173     return frameOnTop(new JFrame(), label, actionString, JOPTIONPANE_OPTION,
174             JOPTIONPANE_MESSAGETYPE);
175   }
176
177   public static int frameOnTop(JFrame dialogParent, String label,
178           String actionString, int JOPTIONPANE_OPTION,
179           int JOPTIONPANE_MESSAGETYPE)
180   {
181     // ensure Jalview window is brought to front for Quit confirmation
182     // window to be visible
183
184     // this method of raising the Jalview window is broken in java
185     // jalviewDesktop.setVisible(true);
186     // jalviewDesktop.toFront();
187
188     // a better hack which works instead
189
190     dialogParent.setAlwaysOnTop(true);
191
192     int answer = JOptionPane.showConfirmDialog(dialogParent, label,
193             actionString, JOPTIONPANE_OPTION, JOPTIONPANE_MESSAGETYPE);
194
195     dialogParent.setAlwaysOnTop(false);
196     dialogParent.dispose();
197
198     return answer;
199   }
200
201   private static int waitForceQuitCancelQuitOptionDialog(Object message,
202           String title)
203   {
204     JFrame dialogParent = new JFrame();
205     dialogParent.setAlwaysOnTop(true);
206     String wait = MessageManager.getString("action.wait");
207     Object[] options = { wait,
208         MessageManager.getString("action.force_quit"),
209         MessageManager.getString("action.cancel_quit") };
210
211     int answer = JOptionPane.showOptionDialog(dialogParent, message, title,
212             JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE,
213             null, options, wait);
214
215     return answer;
216   }
217
218   private static boolean waitForSave(long t)
219   {
220     boolean ret = false;
221     try
222     {
223       Console.debug("Wait for save to complete: " + t + "ms");
224       long c = 0;
225       int i = 100;
226       while (c < t)
227       {
228         Thread.sleep(i);
229         c += i;
230         ret = !BackupFiles.hasSavesInProgress();
231         if (ret)
232         {
233           Console.debug(
234                   "Save completed whilst waiting (" + c + "/" + t + "ms)");
235           return ret;
236         }
237         if (c % 1000 < i) // just gone over another second
238         {
239           Console.debug("...waiting (" + c + "/" + t + "ms]");
240         }
241       }
242     } catch (InterruptedException e)
243     {
244       Console.debug("Wait for save interrupted");
245     }
246     Console.debug("Save has " + (ret ? "" : "not ") + "completed");
247     return ret;
248   }
249
250 }