X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;f=src%2Fjalview%2Fjbgui%2FQuitHandler.java;h=6369b1ae00e6fc4cf616bdeb31d0206e1d81727d;hb=b364e1e6d199002069dab615d1007799b5bb71e1;hp=6a95db7f6f692d0b6bc05d9da8ec9d41f3d64fc3;hpb=0980e87380e2922790d01bde3f616115f1916a43;p=jalview.git diff --git a/src/jalview/jbgui/QuitHandler.java b/src/jalview/jbgui/QuitHandler.java index 6a95db7..6369b1a 100644 --- a/src/jalview/jbgui/QuitHandler.java +++ b/src/jalview/jbgui/QuitHandler.java @@ -1,8 +1,12 @@ package jalview.jbgui; import java.io.File; -import java.util.Date; import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import javax.swing.JFrame; import javax.swing.JOptionPane; @@ -10,11 +14,13 @@ import javax.swing.JOptionPane; import com.formdev.flatlaf.extras.FlatDesktop; import jalview.api.AlignmentViewPanel; +import jalview.bin.Cache; import jalview.bin.Console; import jalview.datamodel.AlignmentI; import jalview.datamodel.SequenceI; import jalview.gui.AlignFrame; import jalview.gui.Desktop; +import jalview.gui.JvOptionPane; import jalview.io.BackupFiles; import jalview.project.Jalview2XML; import jalview.util.MessageManager; @@ -22,37 +28,48 @@ import jalview.util.Platform; public class QuitHandler { + private static final int INITIAL_WAIT_FOR_SAVE = 3000; + + private static final int NON_INTERACTIVE_WAIT_CYCLES = 2; + public static enum QResponse { - QUIT, CANCEL_QUIT, FORCE_QUIT + NULL, QUIT, CANCEL_QUIT, FORCE_QUIT }; - public static void setQuitHandler() + private static ExecutorService executor = Executors.newFixedThreadPool(3); + + public static QResponse setQuitHandler() { FlatDesktop.setQuitHandler(response -> { - QResponse qresponse = getQuitResponse(); - switch (qresponse) - { - case QUIT: + Callable performQuit = () -> { response.performQuit(); - break; - case CANCEL_QUIT: - response.cancelQuit(); - break; - case FORCE_QUIT: + return setResponse(QResponse.QUIT); + }; + Callable performForceQuit = () -> { response.performQuit(); - break; - default: + return setResponse(QResponse.FORCE_QUIT); + }; + Callable cancelQuit = () -> { response.cancelQuit(); - } + // reset + setResponse(QResponse.NULL); + // but return cancel + return QResponse.CANCEL_QUIT; + }; + QResponse qresponse = getQuitResponse(true, performQuit, + performForceQuit, cancelQuit); }); + + return gotQuitResponse(); } - private static QResponse gotQuitResponse = QResponse.CANCEL_QUIT; + private static QResponse gotQuitResponse = QResponse.NULL; - private static QResponse returnResponse(QResponse qresponse) + private static QResponse setResponse(QResponse qresponse) { gotQuitResponse = qresponse; + Console.debug("##### Setting gotQuitResponse to " + qresponse); return qresponse; } @@ -61,19 +78,47 @@ public class QuitHandler return gotQuitResponse; } - public static QResponse getQuitResponse() + public static final Callable defaultCancelQuit = () -> { + Console.debug("QuitHandler: (default) Quit action CANCELLED by user"); + // reset + setResponse(QResponse.NULL); + // and return cancel + return QResponse.CANCEL_QUIT; + }; + + public static final Callable defaultOkQuit = () -> { + Console.debug("QuitHandler: (default) Quit action CONFIRMED by user"); + return setResponse(QResponse.QUIT); + }; + + public static final Callable defaultForceQuit = () -> { + Console.debug("QuitHandler: (default) Quit action FORCED by user"); + // note that shutdown hook will not be run + Runtime.getRuntime().halt(0); + return setResponse(QResponse.FORCE_QUIT); // this line never reached! + }; + + public static QResponse getQuitResponse(boolean ui) { - return getQuitResponse(true); + return getQuitResponse(ui, defaultOkQuit, defaultForceQuit, + defaultCancelQuit); } - public static QResponse getQuitResponse(boolean ui) + private static boolean interactive = true; + + public static QResponse getQuitResponse(boolean ui, + Callable okQuit, Callable forceQuit, + Callable cancelQuit) { - if (gotQuitResponse() != QResponse.CANCEL_QUIT) + QResponse got = gotQuitResponse(); + if (got != QResponse.NULL && got != QResponse.CANCEL_QUIT) { - return returnResponse(getQuitResponse()); + // quit has already been selected, continue with calling quit method + Console.debug("##### getQuitResponse called. gotQuitResponse=" + got); + return got; } - boolean interactive = ui && !Platform.isHeadless(); + interactive = ui && !Platform.isHeadless(); // confirm quit if needed and wanted boolean confirmQuit = true; @@ -96,45 +141,112 @@ public class QuitHandler + "' is/defaults to " + confirmQuit + " -- " + (confirmQuit ? "" : "not ") + "confirming quit"); } + got = confirmQuit ? QResponse.NULL : QResponse.QUIT; + Console.debug("initial calculation, got=" + got); + setResponse(got); - 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( + + Console.debug("********************ABOUT TO CONFIRM QUIT"); + JvOptionPane quitDialog = JvOptionPane.newOptionDialog() + .setResponseHandler(JOptionPane.YES_OPTION, defaultOkQuit) + .setResponseHandler(JOptionPane.NO_OPTION, defaultCancelQuit); + quitDialog.showDialogOnTopAsync( new StringBuilder( MessageManager.getString("label.quit_jalview")) - .append(" ") + .append("\n") .append(MessageManager .getString("label.unsaved_changes")) .toString(), MessageManager.getString("action.quit"), - JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE); + JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, + new Object[] + { MessageManager.getString("action.quit"), + MessageManager.getString("action.cancel") }, + MessageManager.getString("action.quit"), true); } - if (answer == JOptionPane.CANCEL_OPTION) + got = gotQuitResponse(); + Console.debug("first user response, got=" + got); + boolean wait = false; + if (got == QResponse.CANCEL_QUIT) { - Console.debug("QuitHandler: Quit action cancelled by user"); - return returnResponse(QResponse.CANCEL_QUIT); + // reset + setResponse(QResponse.NULL); + // but return cancel + return QResponse.CANCEL_QUIT; + } + else if (got == QResponse.QUIT) + { + if (Cache.getDefault("WAIT_FOR_SAVE", true) + && BackupFiles.hasSavesInProgress()) + { + /* + Future waitGot = executor.submit(waitQuitCall); + try + { + got = waitGot.get(); + } catch (InterruptedException | ExecutionException e) + { + jalview.bin.Console.debug( + "Exception during quit handling (wait for save)", e); + } + */ + QResponse waitResponse = waitQuit(interactive, okQuit, forceQuit, + cancelQuit); + wait = waitResponse == QResponse.QUIT; + } } - // check for saves in progress - int waitForSave = 1000; // MAKE THIS BETTER - AlignFrame[] afArray = Desktop.getAlignFrames(); - if (afArray == null || afArray.length == 0) + Callable next = null; + switch (gotQuitResponse()) { - // no change + case QUIT: + Console.debug("### User selected QUIT"); + next = okQuit; + break; + case FORCE_QUIT: // not actually an option at this stage + Console.debug("### User selected FORCE QUIT"); + next = forceQuit; + break; + default: + Console.debug("### User selected CANCEL QUIT"); + next = cancelQuit; + break; } - else + try + { + got = executor.submit(next).get(); + } catch (InterruptedException | ExecutionException e) + { + jalview.bin.Console + .debug("Exception during quit handling (final choice)", e); + } + jalview.bin.Console.debug("### nextResponse=" + got.toString()); + setResponse(got); + + return gotQuitResponse(); + } + + private static QResponse waitQuit(boolean interactive, + Callable okQuit, Callable forceQuit, + Callable cancelQuit) + { + jalview.bin.Console.debug("#### waitQuit started"); + // check for saves in progress + if (!BackupFiles.hasSavesInProgress()) + return QResponse.QUIT; + + int waitTime = INITIAL_WAIT_FOR_SAVE; // start with 3 second wait + AlignFrame[] afArray = Desktop.getAlignFrames(); + if (!(afArray == null || afArray.length == 0)) { int size = 0; for (int i = 0; i < afArray.length; i++) { AlignFrame af = afArray[i]; - List avpList = (List) af - .getAlignPanels(); + List avpList = af.getAlignPanels(); for (AlignmentViewPanel avp : avpList) { AlignmentI a = avp.getAlignment(); @@ -145,101 +257,174 @@ public class QuitHandler } } } - waitForSave = size; - Console.debug("Set waitForSave to " + waitForSave); + waitTime = Math.max(waitTime, size / 2); + Console.debug("Set waitForSave to " + waitTime); } - int waitIncrement = 3000; - long startTime = new Date().getTime(); - boolean saving = BackupFiles.hasSavesInProgress(); - if (saving) + + // future that returns a Boolean when all files are saved + CompletableFuture filesAllSaved = new CompletableFuture<>(); + + // callback as each file finishes saving + for (CompletableFuture cf : BackupFiles + .savesInProgressCompletableFutures()) + { + // if this is the last one then complete filesAllSaved + cf.whenComplete((ret, e) -> { + if (!BackupFiles.hasSavesInProgress()) + { + filesAllSaved.complete(true); + } + }); + } + + final int waitTimeFinal = waitTime; + // timeout the wait -- will result in another wait button when looped + CompletableFuture waitTimeout = CompletableFuture + .supplyAsync(() -> { + executor.submit(() -> { + try + { + Thread.sleep(waitTimeFinal); + } catch (InterruptedException e) + { + // probably interrupted by all files saving + } + }); + return true; + }); + CompletableFuture waitForSave = CompletableFuture + .anyOf(waitTimeout, filesAllSaved); + Console.debug("##### WAITFORSAVE RUNNING"); + + QResponse waitResponse = QResponse.NULL; + + int iteration = 0; + boolean doIterations = true; + while (doIterations && BackupFiles.hasSavesInProgress() + && iteration++ < (interactive ? 100 : 5)) { - boolean waiting = (new Date().getTime() - startTime) < waitForSave; - while (saving && waiting) + try { - saving = !waitForSave(waitIncrement); - waiting = (new Date().getTime() - startTime) < waitForSave; + waitForSave.get(); + } catch (InterruptedException | ExecutionException e1) + { + Console.debug( + "Exception whilst waiting for files to save before quitting", + e1); } - - if (saving) // still saving after a wait + if (interactive) { - StringBuilder messageSB = new StringBuilder( - MessageManager.getString("label.save_in_progress")); - messageSB.append(":"); - boolean any = false; - for (File file : BackupFiles.savesInProgressFiles()) + Console.debug("********************About to make waitDialog"); + JvOptionPane waitDialog = JvOptionPane.newOptionDialog() + .setResponseHandler(JOptionPane.YES_OPTION, defaultOkQuit) + .setResponseHandler(JOptionPane.NO_OPTION, forceQuit) + .setResponseHandler(JOptionPane.CANCEL_OPTION, cancelQuit); + + // callback as each file finishes saving + for (CompletableFuture cf : BackupFiles + .savesInProgressCompletableFutures()) { - messageSB.append("\n- "); - messageSB.append(file.getName()); - any = true; + // update the list of saving files as they save too + cf.thenRun(() -> { + waitDialog.setMessage(waitingForSaveMessage()); + }); + // if this is the last one then close the dialog + cf.whenComplete((ret, e) -> { + if (!BackupFiles.hasSavesInProgress()) + { + // like a click on Wait button + Console.debug( + "***** TRYING TO MAKE THE WAIT FOR SAVE DIALOG DISAPPEAR!"); + waitDialog.setValue(JOptionPane.YES_OPTION); + } + }); } - if (!any) - { - messageSB.append("\n"); - messageSB.append(MessageManager.getString("label.unknown")); - } - int waitLonger = interactive ? JOptionPane.YES_OPTION - : JOptionPane.NO_OPTION; - while (saving && waitLonger == JOptionPane.YES_OPTION) + + waitDialog.showDialogOnTopAsync(waitingForSaveMessage(), + MessageManager.getString("action.wait"), + JOptionPane.YES_NO_CANCEL_OPTION, + JOptionPane.WARNING_MESSAGE, null, new Object[] + { MessageManager.getString("action.wait"), + MessageManager.getString("action.force_quit"), + MessageManager.getString("action.cancel_quit") }, + MessageManager.getString("action.wait"), true); + Console.debug("********************Finished waitDialog"); + + waitResponse = gotQuitResponse(); + Console.debug("####### WAITFORSAVE SET: " + waitResponse); + switch (waitResponse) { - waitLonger = waitForceQuitCancelQuitOptionDialog( - messageSB.toString(), - MessageManager.getString("action.wait")); - if (waitLonger == JOptionPane.YES_OPTION) // wait - { - // do wait stuff - saving = !waitForSave(waitIncrement); - } - else if (waitLonger == JOptionPane.NO_OPTION) // force quit - { - // do a force quit - return returnResponse(QResponse.FORCE_QUIT); // shouldn't reach this - } - else if (waitLonger == JOptionPane.CANCEL_OPTION) // cancel quit - { - return returnResponse(QResponse.CANCEL_QUIT); - } - else - { - Console.debug("**** Shouldn't have got here!"); - } + case QUIT: // wait -- do another iteration + break; + case FORCE_QUIT: + doIterations = false; + break; + case CANCEL_QUIT: + doIterations = false; + break; + case NULL: // already cancelled + doIterations = false; + break; + default: } - } - } + } // end if interactive - // not cancelled and not saving - return returnResponse(QResponse.QUIT); - } + } + waitResponse = gotQuitResponse(); - public static int frameOnTop(String label, String actionString, - int JOPTIONPANE_OPTION, int JOPTIONPANE_MESSAGETYPE) - { - return frameOnTop(new JFrame(), label, actionString, JOPTIONPANE_OPTION, - JOPTIONPANE_MESSAGETYPE); - } + Console.debug("####### WAITFORSAVE RETURNING: " + waitResponse); + return waitResponse; + }; - public static int frameOnTop(JFrame dialogParent, String label, - String actionString, int JOPTIONPANE_OPTION, - int JOPTIONPANE_MESSAGETYPE) + public static void okk() { - // 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; - } + /* + if (false) + { + if (false) + { + + waitLonger = JOptionPane.showOptionDialog(dialogParent, + waitingForSaveMessage(), + MessageManager.getString("action.wait"), + JOptionPane.YES_NO_CANCEL_OPTION, + JOptionPane.WARNING_MESSAGE, null, options, wait); + } + else + { + // non-interactive + waitLonger = iteration < NON_INTERACTIVE_WAIT_CYCLES + ? JOptionPane.YES_OPTION + : JOptionPane.NO_OPTION; + } + + if (waitLonger == JOptionPane.YES_OPTION) // "wait" + { + saving = !waitForSave(waitIncrement); + } + else if (waitLonger == JOptionPane.NO_OPTION) // "force + // quit" + { + // do a force quit + return setResponse(QResponse.FORCE_QUIT); + } + else if (waitLonger == JOptionPane.CANCEL_OPTION) // cancel quit + { + return setResponse(QResponse.CANCEL_QUIT); + } + else + { + // Most likely got here by user dismissing the dialog with the + // 'x' + // -- treat as a "Cancel" + return setResponse(QResponse.CANCEL_QUIT); + } + } + + // not sure how we got here, best be safe + return QResponse.CANCEL_QUIT; + */ + }; private static int waitForceQuitCancelQuitOptionDialog(Object message, String title) @@ -251,14 +436,38 @@ public class QuitHandler MessageManager.getString("action.force_quit"), MessageManager.getString("action.cancel_quit") }; + // BackupFiles.setWaitForSaveDialog(dialogParent); + int answer = JOptionPane.showOptionDialog(dialogParent, message, title, JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE, null, options, wait); + // BackupFiles.clearWaitForSaveDialog(); + return answer; } - private static boolean waitForSave(long t) + private static String waitingForSaveMessage() + { + StringBuilder messageSB = new StringBuilder( + MessageManager.getString("label.save_in_progress")); + boolean any = false; + for (File file : BackupFiles.savesInProgressFiles()) + { + messageSB.append("\n- "); + messageSB.append(file.getName()); + any = true; + } + if (!any) + { + messageSB.append("\n"); + messageSB.append(MessageManager.getString("label.unknown")); + } + + return messageSB.toString(); + } + + private static Boolean waitForSave(long t) { boolean ret = false; try