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