From 62d4f5d6877f5f36c67f472dabebef3129600191 Mon Sep 17 00:00:00 2001 From: Ben Soares Date: Sat, 29 Oct 2022 00:16:20 +0100 Subject: [PATCH] JAL-1988 JAL-3772 Removed 'Wait' button from waitDialog, no iterations for GUI. Added bulletpoint to files saving list. Added use of passed in button objects to modal JvOptionPane, so buttons can be controlled (e.g. setEnabled, setVisible) outside of JvOptionPane. --- resources/lang/Messages.properties | 4 +- resources/lang/Messages_es.properties | 2 + src/jalview/gui/Desktop.java | 4 - src/jalview/gui/JvOptionPane.java | 183 ++++++++++++++++++++++++++++++--- src/jalview/jbgui/QuitHandler.java | 113 +++++++++----------- 5 files changed, 222 insertions(+), 84 deletions(-) diff --git a/resources/lang/Messages.properties b/resources/lang/Messages.properties index 1f57096..a406f33 100644 --- a/resources/lang/Messages.properties +++ b/resources/lang/Messages.properties @@ -32,11 +32,13 @@ action.load_project = Load Project action.save_project = Save Project action.save_project_as = Save Project as... action.quit = Quit -action.force_quit = Force Quit +action.force_quit = Force quit label.quit_jalview = Are you sure you want to quit Jalview? +label.wait_for_save = Wait for save label.unsaved_changes = There are unsaved changes. label.save_in_progress = Some files are still saving: label.unknown = Unknown +label.quit_after_saving = Jalview will quit after saving. label.all_saved = All files saved. label.quitting_bye = Quitting, bye! action.wait = Wait diff --git a/resources/lang/Messages_es.properties b/resources/lang/Messages_es.properties index d1b6b4e..b50226a 100644 --- a/resources/lang/Messages_es.properties +++ b/resources/lang/Messages_es.properties @@ -34,9 +34,11 @@ action.save_project_as = Guardar proyecto como... action.quit = Salir action.force_quit = Forzar la salida label.quit_jalview = ¿Estás seguro de que quieres salir de Jalview? +label.wait_for_save = Esperar a guardar label.unsaved_changes = Hay cambios sin guardar. label.save_in_progress = Algunos archivos aún se están guardando: label.unknown = desconocido +label.quit_after_saving = Jalview se cerrará después de guardar. label.all_saved = Todos los archivos guardados. label.quitting_bye = Saliendo ¡chao! action.wait = Espere diff --git a/src/jalview/gui/Desktop.java b/src/jalview/gui/Desktop.java index 13253c3..72c9106 100644 --- a/src/jalview/gui/Desktop.java +++ b/src/jalview/gui/Desktop.java @@ -1396,11 +1396,7 @@ public class Desktop extends jalview.jbgui.GDesktop instance.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); // instance.dispose(); } - jalview.bin.Console.debug("**** BEFORE quit"); - jalview.bin.Console.debug("**** QuitHandler.gotQuitResponse=" - + QuitHandler.gotQuitResponse()); instance.quit(); - jalview.bin.Console.debug("**** AFTER quit"); return QuitHandler.gotQuitResponse(); }; diff --git a/src/jalview/gui/JvOptionPane.java b/src/jalview/gui/JvOptionPane.java index 02bca5a..10dbfcf 100644 --- a/src/jalview/gui/JvOptionPane.java +++ b/src/jalview/gui/JvOptionPane.java @@ -22,6 +22,7 @@ package jalview.gui; import java.awt.Component; +import java.awt.Container; import java.awt.Dialog.ModalityType; import java.awt.HeadlessException; import java.awt.Window; @@ -781,6 +782,14 @@ public class JvOptionPane extends JOptionPane int messageType, Icon icon, Object[] options, Object initialValue, boolean modal) { + showDialog(message, title, optionType, messageType, icon, options, + initialValue, modal, null); + } + + public void showDialog(Object message, String title, int optionType, + int messageType, Icon icon, Object[] options, Object initialValue, + boolean modal, JButton[] buttons) + { if (!isInteractiveMode()) { handleResponse(getMockResponse()); @@ -804,9 +813,92 @@ public class JvOptionPane extends JOptionPane if (modal) { + boolean useButtons = false; + Object initialValueButton = null; + NOTNULL: if (buttons != null) + { + if (buttons.length != options.length) + { + jalview.bin.Console.error( + "Supplied buttons array not the same length as supplied options array."); + break NOTNULL; + } + int[] buttonActions = { JOptionPane.YES_OPTION, + JOptionPane.NO_OPTION, JOptionPane.CANCEL_OPTION }; + for (int i = 0; i < options.length; i++) + { + Object o = options[i]; + jalview.bin.Console.debug( + "Setting button " + i + " to '" + o.toString() + "'"); + JButton jb = buttons[i]; + + if (o.equals(initialValue)) + initialValueButton = jb; + + int buttonAction = buttonActions[i]; + Callable action = callbacks.get(buttonAction); + jb.setText((String) o); + jb.addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + + Object obj = e.getSource(); + Object joptionpaneObject = getAncestorClass(obj, + JOptionPane.class); + if (joptionpaneObject == null + || !(joptionpaneObject instanceof JOptionPane)) + { + jalview.bin.Console.debug( + "Could not find JOptionPane ancestor of event object " + + obj); + return; + } + JOptionPane joptionpane = (JOptionPane) joptionpaneObject; + joptionpane.setValue(buttonAction); + if (action != null) + Executors.newSingleThreadExecutor().submit(action); + // joptionpane.transferFocusBackward(); + joptionpane.transferFocusBackward(); + joptionpane.setVisible(false); + // put focus and raise parent window if possible, unless cancel + // button pressed + boolean raiseParent = (parentComponent != null); + if (buttonAction == JOptionPane.CANCEL_OPTION) + raiseParent = false; + if (optionType == JOptionPane.YES_NO_OPTION + && buttonAction == JOptionPane.NO_OPTION) + raiseParent = false; + if (raiseParent) + { + parentComponent.requestFocus(); + if (parentComponent instanceof JInternalFrame) + { + JInternalFrame jif = (JInternalFrame) parentComponent; + jif.show(); + jif.moveToFront(); + jif.grabFocus(); + } + else if (parentComponent instanceof Window) + { + Window w = (Window) parentComponent; + w.toFront(); + w.requestFocus(); + } + } + joptionpane.setVisible(false); + } + }); + + } + useButtons = true; + } // use a JOptionPane as usual int response = JOptionPane.showOptionDialog(parentComponent, message, - title, optionType, messageType, icon, options, initialValue); + title, optionType, messageType, icon, + useButtons ? buttons : options, + useButtons ? initialValueButton : initialValue); /* * In Java, the response is returned to this thread and handled here; @@ -830,8 +922,9 @@ public class JvOptionPane extends JOptionPane * a non-modal JDialog for the confirmation without blocking the GUI. */ - JDialog dialog = this.createDialog(parentComponent, message, title, - optionType, messageType, icon, options, initialValue, modal); + JDialog dialog = createDialog(parentComponent, message, title, + optionType, messageType, icon, options, initialValue, modal, + buttons); jalview.bin.Console.debug("About to setVisible(true)"); dialog.setVisible(true); jalview.bin.Console.debug("Just setVisible(true)"); @@ -918,9 +1011,7 @@ public class JvOptionPane extends JOptionPane actionString, JOPTIONPANE_OPTION, JOPTIONPANE_MESSAGETYPE); dialogParent.setAlwaysOnTop(false); - jalview.bin.Console.debug("*********** BEFORE dialogParent.dispose()"); dialogParent.dispose(); - jalview.bin.Console.debug("*********** BEFORE dialogParent.dispose()"); return answer; } @@ -939,6 +1030,16 @@ public class JvOptionPane extends JOptionPane int JOPTIONPANE_MESSAGETYPE, Icon icon, Object[] options, Object initialValue, boolean modal) { + showDialogOnTopAsync(dialogParent, label, actionString, + JOPTIONPANE_OPTION, JOPTIONPANE_MESSAGETYPE, icon, options, + initialValue, modal, null); + } + + public void showDialogOnTopAsync(JFrame dialogParent, Object label, + String actionString, int JOPTIONPANE_OPTION, + int JOPTIONPANE_MESSAGETYPE, Icon icon, Object[] options, + Object initialValue, boolean modal, JButton[] buttons) + { // Ensure Jalview window is brought to front (primarily for Quit // confirmation window to be visible) @@ -952,7 +1053,8 @@ public class JvOptionPane extends JOptionPane parentComponent = dialogParent; showDialog(label, actionString, JOPTIONPANE_OPTION, - JOPTIONPANE_MESSAGETYPE, icon, options, initialValue, modal); + JOPTIONPANE_MESSAGETYPE, icon, options, initialValue, modal, + buttons); dialogParent.setAlwaysOnTop(false); dialogParent.dispose(); @@ -1013,6 +1115,17 @@ public class JvOptionPane extends JOptionPane String title, int optionType, int messageType, Icon icon, Object[] options, Object initialValue, boolean modal) { + return createDialog(parentComponent, message, title, optionType, + messageType, icon, options, initialValue, modal, null); + } + + public JDialog createDialog(Component parentComponent, Object message, + String title, int optionType, int messageType, Icon icon, + Object[] options, Object initialValue, boolean modal, + JButton[] buttons) + { + JButton[] optionsButtons = null; + Object initialValueButton = null; JOptionPane joptionpane = new JOptionPane(); // Make button options int[] buttonActions = { JOptionPane.YES_OPTION, JOptionPane.NO_OPTION, @@ -1035,26 +1148,39 @@ public class JvOptionPane extends JOptionPane } options = options_default.toArray(); } - ArrayList options_btns = new ArrayList<>(); - Object initialValue_btn = null; if (!Platform.isJS()) // JalviewJS already uses callback, don't need to // add them here { - if (((optionType == JOptionPane.YES_NO_OPTION - || optionType == JOptionPane.OK_CANCEL_OPTION) - && options.length < 2) + if (((optionType == JOptionPane.YES_OPTION + || optionType == JOptionPane.NO_OPTION + || optionType == JOptionPane.CANCEL_OPTION + || optionType == JOptionPane.OK_OPTION + || optionType == JOptionPane.DEFAULT_OPTION) + && options.length < 1) + || ((optionType == JOptionPane.YES_NO_OPTION + || optionType == JOptionPane.OK_CANCEL_OPTION) + && options.length < 2) || (optionType == JOptionPane.YES_NO_CANCEL_OPTION && options.length < 3)) { jalview.bin.Console .debug("JvOptionPane: not enough options for dialog type"); } + optionsButtons = new JButton[options.length]; for (int i = 0; i < options.length && i < 3; i++) { Object o = options[i]; int buttonAction = buttonActions[i]; Callable action = callbacks.get(buttonAction); - JButton jb = new JButton(); + JButton jb; + if (buttons != null && buttons.length > i && buttons[i] != null) + { + jb = buttons[i]; + } + else + { + jb = new JButton(); + } jb.setText((String) o); jb.addActionListener(new ActionListener() { @@ -1095,19 +1221,18 @@ public class JvOptionPane extends JOptionPane joptionpane.setVisible(false); } }); - options_btns.add(jb); + optionsButtons[i] = jb; if (o.equals(initialValue)) - initialValue_btn = jb; + initialValueButton = jb; } } joptionpane.setMessage(message); joptionpane.setMessageType(messageType); joptionpane.setOptionType(optionType); joptionpane.setIcon(icon); - joptionpane - .setOptions(Platform.isJS() ? options : options_btns.toArray()); + joptionpane.setOptions(Platform.isJS() ? options : optionsButtons); joptionpane.setInitialValue( - Platform.isJS() ? initialValue : initialValue_btn); + Platform.isJS() ? initialValue : initialValueButton); JDialog dialog = joptionpane.createDialog(parentComponent, title); dialog.setModalityType( @@ -1126,4 +1251,28 @@ public class JvOptionPane extends JOptionPane return false; } + + /** + * Get parent JOptionPane if there is one + */ + public static Container getAncestorClass(Object o, Class cl) + { + Container c; + if (o instanceof Container) + c = (Container) o; + else + return null; + Container p = c.getParent(); + if (p == null) + return null; + if (p.getClass() == cl) + { + return p; + } + else if (p instanceof Component) + { + return getAncestorClass((Component) p, cl); + } + return null; + } } diff --git a/src/jalview/jbgui/QuitHandler.java b/src/jalview/jbgui/QuitHandler.java index ef8ec55..0d73006 100644 --- a/src/jalview/jbgui/QuitHandler.java +++ b/src/jalview/jbgui/QuitHandler.java @@ -10,6 +10,7 @@ import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JOptionPane; import javax.swing.JTextPane; @@ -31,7 +32,7 @@ import jalview.util.Platform; public class QuitHandler { - private static final int MIN_WAIT_FOR_SAVE = 5000; + private static final int MIN_WAIT_FOR_SAVE = 3000; private static final int MAX_WAIT_FOR_SAVE = 20000; @@ -247,7 +248,8 @@ public class QuitHandler QResponse waitResponse = QResponse.NULL; int iteration = 0; - boolean doIterations = true; + boolean doIterations = true; // note iterations not used in the gui now, + // only one pass without the "Wait" button while (doIterations && BackupFiles.hasSavesInProgress() && iteration++ < (interactive ? 100 : 5)) { @@ -281,24 +283,11 @@ public class QuitHandler if (interactive && BackupFiles.hasSavesInProgress()) { - boolean allowForceQuit = iteration > 0; // iteration > 1 to not show - // force quit the first time + boolean showForceQuit = iteration > 0; // iteration > 1 to not show + // force quit the first time JFrame parent = new JFrame(); + JButton[] buttons = { new JButton(), new JButton() }; JvOptionPane waitDialog = JvOptionPane.newOptionDialog(); - if (allowForceQuit) - { - waitDialog - .setResponseHandler(JOptionPane.YES_OPTION, defaultOkQuit) - .setResponseHandler(JOptionPane.NO_OPTION, forceQuit) - .setResponseHandler(JOptionPane.CANCEL_OPTION, - cancelQuit); - } - else - { - waitDialog - .setResponseHandler(JOptionPane.YES_OPTION, defaultOkQuit) - .setResponseHandler(JOptionPane.NO_OPTION, cancelQuit); - } JTextPane messagePane = new JTextPane(); messagePane.setBackground(waitDialog.getBackground()); messagePane.setBorder(null); @@ -309,48 +298,64 @@ public class QuitHandler { cf.whenComplete((ret, e) -> { if (BackupFiles.hasSavesInProgress()) + { // update the list of saving files as they save too messagePane.setText(waitingForSaveMessage()); + } else { - // if this is the last one then close the dialog - messagePane.setText(new StringBuilder() - .append(MessageManager.getString("label.all_saved")) - .append("\n").append(MessageManager - .getString("label.quitting_bye")) - .toString()); - try - { - Console.debug("WAITING FOR MESSAGE"); - Thread.sleep(500); - } catch (InterruptedException e1) + if (!(QuitHandler.gotQuitResponse() == QResponse.CANCEL_QUIT + || QuitHandler.gotQuitResponse() == QResponse.NULL)) { + for (int i = 0; i < buttons.length; i++) + { + Console.debug("DISABLING BUTTON " + buttons[i].getText()); + buttons[i].setEnabled(false); + buttons[i].setVisible(false); + } + // if this is the last one then close the dialog + messagePane.setText(new StringBuilder() + .append(MessageManager.getString("label.all_saved")) + .append("\n") + .append(MessageManager + .getString("label.quitting_bye")) + .toString()); + messagePane.setEditable(false); + try + { + Thread.sleep(1500); + } catch (InterruptedException e1) + { + } + parent.dispose(); } - // like a click on Wait button - waitDialog.setValue(JOptionPane.YES_OPTION); - parent.dispose(); } }); } - String[] options = new String[allowForceQuit ? 3 : 2]; - if (allowForceQuit) + String[] options; + int dialogType = -1; + if (showForceQuit) { - options[0] = MessageManager.getString("action.wait"); - options[1] = MessageManager.getString("action.force_quit"); - options[2] = MessageManager.getString("action.cancel_quit"); + options = new String[2]; + options[0] = MessageManager.getString("action.force_quit"); + options[1] = MessageManager.getString("action.cancel_quit"); + dialogType = JOptionPane.YES_NO_OPTION; + waitDialog.setResponseHandler(JOptionPane.YES_OPTION, forceQuit) + .setResponseHandler(JOptionPane.NO_OPTION, cancelQuit); } else { - options[0] = MessageManager.getString("action.wait"); - options[1] = MessageManager.getString("action.cancel_quit"); + options = new String[1]; + options[0] = MessageManager.getString("action.cancel_quit"); + dialogType = JOptionPane.YES_OPTION; + waitDialog.setResponseHandler(JOptionPane.YES_OPTION, cancelQuit); } waitDialog.showDialogOnTopAsync(parent, messagePane, - MessageManager.getString("action.wait"), - allowForceQuit ? JOptionPane.YES_NO_CANCEL_OPTION - : JOptionPane.YES_NO_OPTION, + MessageManager.getString("label.wait_for_save"), dialogType, JOptionPane.WARNING_MESSAGE, null, options, - MessageManager.getString("action.wait"), true); + MessageManager.getString("action.cancel_quit"), true, + buttons); parent.dispose(); final QResponse thisWaitResponse = gotQuitResponse(); @@ -377,23 +382,6 @@ public class QuitHandler return waitResponse; }; - 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 String waitingForSaveMessage() { StringBuilder messageSB = new StringBuilder(); @@ -405,14 +393,15 @@ public class QuitHandler { for (File file : files) { - messageSB.append("\n- ").append(file.getName()); + messageSB.append("\n\u2022 ").append(file.getName()); } } else { messageSB.append(MessageManager.getString("label.unknown")); } + messageSB.append("\n\n") + .append(MessageManager.getString("label.quit_after_saving")); return messageSB.toString(); } - } \ No newline at end of file -- 1.7.10.2