package jalview.jbgui; import java.io.File; import java.util.Date; import javax.swing.JFrame; import javax.swing.JOptionPane; import com.formdev.flatlaf.extras.FlatDesktop; import jalview.bin.Console; import jalview.io.BackupFiles; import jalview.util.MessageManager; import jalview.util.Platform; public class QuitHandler { public static enum QResponse { QUIT, CANCEL_QUIT, FORCE_QUIT }; public static void setQuitHandler() { FlatDesktop.setQuitHandler(response -> { QResponse qresponse = getQuitResponse(); switch (qresponse) { case QUIT: response.performQuit(); break; case CANCEL_QUIT: response.cancelQuit(); break; case FORCE_QUIT: response.performQuit(); break; default: response.cancelQuit(); } }); } private static QResponse gotQuitResponse = QResponse.CANCEL_QUIT; private static QResponse returnResponse(QResponse qresponse) { gotQuitResponse = qresponse; return qresponse; } public static QResponse gotQuitResponse() { return gotQuitResponse; } public static QResponse getQuitResponse() { return getQuitResponse(true); } public static QResponse getQuitResponse(boolean ui) { if (gotQuitResponse() != QResponse.CANCEL_QUIT) { return returnResponse(getQuitResponse()); } boolean interactive = ui && !Platform.isHeadless(); // confirm quit if needed and wanted boolean confirmQuit = true; if (!interactive) { Console.debug("Non interactive quit -- not confirming"); confirmQuit = false; } /* else if (undostack is empty) { Console.debug("Nothing changed -- not confirming quit"); confirmQuit = false } */ else { confirmQuit = jalview.bin.Cache .getDefault(jalview.gui.Desktop.CONFIRM_KEYBOARD_QUIT, true); Console.debug("Jalview property '" + jalview.gui.Desktop.CONFIRM_KEYBOARD_QUIT + "' is/defaults to " + confirmQuit + " -- " + (confirmQuit ? "" : "not ") + "confirming quit"); } int answer = JOptionPane.OK_OPTION; // if going to confirm, do it before the save in progress check to give // the save time to finish! if (confirmQuit) { answer = frameOnTop(MessageManager.getString("label.quit_jalview"), MessageManager.getString("action.quit"), JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE); } if (answer == JOptionPane.CANCEL_OPTION) { Console.debug("QuitHandler: Quit action cancelled by user"); return returnResponse(QResponse.CANCEL_QUIT); } // check for saves in progress int waitForSave = 5000; // MAKE THIS BETTER int waitIncrement = 2000; long startTime = new Date().getTime(); boolean saving = BackupFiles.hasSavesInProgress(); if (saving) { boolean waiting = (new Date().getTime() - startTime) < waitForSave; while (saving && waiting) { saving = !waitForSave(waitIncrement); waiting = (new Date().getTime() - startTime) < waitForSave; } if (saving) // still saving after a wait { StringBuilder messageSB = new StringBuilder( MessageManager.getString("label.save_in_progress")); for (File file : BackupFiles.savesInProgressFiles()) { messageSB.append("\n"); messageSB.append(file.getName()); } int waitLonger = interactive ? JOptionPane.YES_OPTION : JOptionPane.NO_OPTION; while (saving && waitLonger == JOptionPane.YES_OPTION) { waitLonger = waitForceQuitCancelQuitOptionDialog( messageSB.toString(), MessageManager.getString("action.wait")); if (waitLonger == JOptionPane.YES_OPTION) // wait { Console.debug("*** YES answer=" + waitLonger); // do wait stuff saving = !waitForSave(waitIncrement); } else if (waitLonger == JOptionPane.NO_OPTION) // force quit { Console.debug("*** NO answer=" + waitLonger); // do a force quit return returnResponse(QResponse.FORCE_QUIT); // shouldn't reach this } else if (waitLonger == JOptionPane.CANCEL_OPTION) // cancel quit { Console.debug("*** CANCEL answer=" + waitLonger); return returnResponse(QResponse.CANCEL_QUIT); } else { Console.debug("**** Shouldn't have got here!"); } } } } // not cancelled and not saving return returnResponse(QResponse.QUIT); } public static int frameOnTop(String label, String actionString, int JOPTIONPANE_OPTION, int JOPTIONPANE_MESSAGETYPE) { return frameOnTop(new JFrame(), label, actionString, JOPTIONPANE_OPTION, JOPTIONPANE_MESSAGETYPE); } public static int frameOnTop(JFrame dialogParent, String label, String actionString, int JOPTIONPANE_OPTION, int JOPTIONPANE_MESSAGETYPE) { // ensure Jalview window is brought to front for Quit confirmation // window to be visible // this method of raising the Jalview window is broken in java // jalviewDesktop.setVisible(true); // jalviewDesktop.toFront(); // a better hack which works instead dialogParent.setAlwaysOnTop(true); int answer = JOptionPane.showConfirmDialog(dialogParent, label, actionString, JOPTIONPANE_OPTION, JOPTIONPANE_MESSAGETYPE); dialogParent.setAlwaysOnTop(false); dialogParent.dispose(); return answer; } private static int waitForceQuitCancelQuitOptionDialog(Object message, String title) { JFrame dialogParent = new JFrame(); dialogParent.setAlwaysOnTop(true); String wait = MessageManager.getString("action.wait"); Object[] options = { wait, MessageManager.getString("action.force_quit"), MessageManager.getString("action.cancel_quit") }; int answer = JOptionPane.showOptionDialog(dialogParent, message, title, JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE, null, options, wait); return answer; } private static boolean waitForSave(long t) { boolean ret = false; try { Console.debug("Wait for save to complete: " + t + "ms"); long c = 0; int i = 100; while (c < t) { Thread.sleep(i); c += i; ret = !BackupFiles.hasSavesInProgress(); if (ret) { Console.debug( "Save completed whilst waiting (" + c + "/" + t + "ms)"); return ret; } if (c % 1000 < i) // just gone over another second { Console.debug("...waiting (" + c + "/" + t + "ms]"); } } } catch (InterruptedException e) { Console.debug("Wait for save interrupted"); } Console.debug("Save has " + (ret ? "" : "not ") + "completed"); return ret; } }