2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
7 * Jalview is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation, either version 3
10 * of the License, or (at your option) any later version.
12 * Jalview is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19 * The Jalview Authors are detailed in the 'AUTHORS' file.
23 import java.awt.AWTEvent;
24 import java.awt.ActiveEvent;
25 import java.awt.Component;
26 import java.awt.Container;
27 import java.awt.Dialog.ModalityType;
28 import java.awt.EventQueue;
29 import java.awt.HeadlessException;
30 import java.awt.MenuComponent;
31 import java.awt.Toolkit;
32 import java.awt.Window;
33 import java.awt.event.ActionEvent;
34 import java.awt.event.ActionListener;
35 import java.awt.event.MouseAdapter;
36 import java.awt.event.MouseMotionAdapter;
37 import java.beans.PropertyChangeEvent;
38 import java.beans.PropertyChangeListener;
39 import java.util.ArrayList;
40 import java.util.Arrays;
41 import java.util.HashMap;
42 import java.util.List;
44 import java.util.concurrent.Callable;
45 import java.util.concurrent.ExecutorService;
46 import java.util.concurrent.Executors;
48 import javax.swing.Icon;
49 import javax.swing.JButton;
50 import javax.swing.JDialog;
51 import javax.swing.JFrame;
52 import javax.swing.JInternalFrame;
53 import javax.swing.JLayeredPane;
54 import javax.swing.JOptionPane;
55 import javax.swing.JPanel;
56 import javax.swing.SwingUtilities;
57 import javax.swing.UIManager;
58 import javax.swing.event.InternalFrameEvent;
59 import javax.swing.event.InternalFrameListener;
61 import jalview.bin.Console;
62 import jalview.util.ChannelProperties;
63 import jalview.util.Platform;
64 import jalview.util.dialogrunner.DialogRunnerI;
66 public class JvOptionPane extends JOptionPane
67 implements DialogRunnerI, PropertyChangeListener
69 private static final long serialVersionUID = -3019167117756785229L;
71 private static Object mockResponse = JvOptionPane.CANCEL_OPTION;
73 private static boolean interactiveMode = true;
75 private Component parentComponent;
77 private ExecutorService executor = Executors.newCachedThreadPool();
79 private JDialog dialog = null;
81 private Map<Object, Callable<Void>> callbacks = new HashMap<>();
84 * JalviewJS reports user choice in the dialog as the selected option (text);
85 * this list allows conversion to index (int)
87 List<Object> ourOptions;
89 public JvOptionPane(final Component parent)
91 this.parentComponent = Platform.isJS() ? this : parent;
95 public static int showConfirmDialog(Component parentComponent,
96 Object message) throws HeadlessException
98 // only called by test
99 return isInteractiveMode()
100 ? JOptionPane.showConfirmDialog(parentComponent, message)
101 : (int) getMockResponse();
105 * Message, title, optionType
107 * @param parentComponent
112 * @throws HeadlessException
114 public static int showConfirmDialog(Component parentComponent,
115 Object message, String title, int optionType)
116 throws HeadlessException
118 if (!isInteractiveMode())
120 return (int) getMockResponse();
124 case JvOptionPane.YES_NO_CANCEL_OPTION:
125 // FeatureRenderer amendFeatures ?? TODO ??
130 case JvOptionPane.YES_NO_OPTION:
131 // PromptUserConfig usage stats
132 // for now treated as "OK CANCEL"
134 case JvOptionPane.OK_CANCEL_OPTION:
135 // will fall back to simple HTML
136 return JOptionPane.showConfirmDialog(parentComponent, message, title,
142 * Adds a message type. Fallback is to just add it in the beginning.
144 * @param parentComponent
150 * @throws HeadlessException
152 public static int showConfirmDialog(Component parentComponent,
153 Object message, String title, int optionType, int messageType)
154 throws HeadlessException
156 // JalviewServicesChanged
157 // PromptUserConfig raiseDialog
158 return isInteractiveMode()
159 ? JOptionPane.showConfirmDialog(parentComponent, message, title,
160 optionType, messageType)
161 : (int) getMockResponse();
167 * @param parentComponent
174 * @throws HeadlessException
176 public static int showConfirmDialog(Component parentComponent,
177 Object message, String title, int optionType, int messageType,
178 Icon icon) throws HeadlessException
180 // JvOptionPaneTest only
181 return isInteractiveMode()
182 ? JOptionPane.showConfirmDialog(parentComponent, message, title,
183 optionType, messageType, icon)
184 : (int) getMockResponse();
188 * Internal version "OK"
190 * @param parentComponent
194 public static int showInternalConfirmDialog(Component parentComponent,
197 // JvOptionPaneTest only;
198 return isInteractiveMode()
199 ? JOptionPane.showInternalConfirmDialog(parentComponent,
201 : (int) getMockResponse();
205 * Internal version -- changed to standard version for now
207 * @param parentComponent
213 public static int showInternalConfirmDialog(Component parentComponent,
214 String message, String title, int optionType)
216 if (!isInteractiveMode())
218 return (int) getMockResponse();
222 case JvOptionPane.YES_NO_CANCEL_OPTION:
223 // ColourMenuHelper.addMenuItmers.offerRemoval TODO
224 case JvOptionPane.YES_NO_OPTION:
225 // UserDefinedColoursSave -- relevant? TODO
228 case JvOptionPane.OK_CANCEL_OPTION:
230 // EditNameDialog --- uses panel for messsage TODO
232 // Desktop.inputURLMenuItem
234 return JOptionPane.showConfirmDialog(parentComponent, message, title,
241 * @param parentComponent
248 public static int showInternalConfirmDialog(Component parentComponent,
249 Object message, String title, int optionType, int messageType)
251 if (!isInteractiveMode())
253 return (int) getMockResponse();
257 case JvOptionPane.YES_NO_CANCEL_OPTION:
258 case JvOptionPane.YES_NO_OPTION:
259 // UserQuestionanaireCheck
263 case JvOptionPane.OK_CANCEL_OPTION:
264 // will fall back to simple HTML
265 return JOptionPane.showConfirmDialog(parentComponent, message, title,
266 optionType, messageType);
271 * adds icon; no longer internal
273 * @param parentComponent
281 public static int showInternalConfirmDialog(Component parentComponent,
282 Object message, String title, int optionType, int messageType,
285 if (!isInteractiveMode())
287 return (int) getMockResponse();
291 case JvOptionPane.YES_NO_CANCEL_OPTION:
292 case JvOptionPane.YES_NO_OPTION:
295 case JvOptionPane.OK_CANCEL_OPTION:
296 // Preferences editLink/newLink
297 return JOptionPane.showConfirmDialog(parentComponent, message, title,
298 optionType, messageType, icon);
304 * custom options full-featured
306 * @param parentComponent
313 * @param initialValue
315 * @throws HeadlessException
317 public static int showOptionDialog(Component parentComponent,
318 String message, String title, int optionType, int messageType,
319 Icon icon, Object[] options, Object initialValue)
320 throws HeadlessException
322 if (!isInteractiveMode())
324 return (int) getMockResponse();
330 // 1) AlignViewport for openLinkedAlignment
332 // Show a dialog with the option to open and link (cDNA <-> protein) as a
334 // alignment, either as a standalone alignment or in a split frame. Returns
335 // true if the new alignment was opened, false if not, because the user
336 // declined the offer.
338 // 2) UserDefinedColors warning about saving over a name already defined
340 return JOptionPane.showOptionDialog(parentComponent, message, title,
341 optionType, messageType, icon, options, initialValue);
348 * @throws HeadlessException
350 public static void showMessageDialog(Component parentComponent,
351 String message) throws HeadlessException
353 if (!isInteractiveMode())
355 outputMessage(message);
361 JOptionPane.showMessageDialog(parentComponent, message);
365 * OK with message, title, and type
367 * @param parentComponent
371 * @throws HeadlessException
373 public static void showMessageDialog(Component parentComponent,
374 String message, String title, int messageType)
375 throws HeadlessException
377 // 30 implementations -- all just fine.
379 if (!isInteractiveMode())
381 outputMessage(message);
385 JOptionPane.showMessageDialog(parentComponent,
386 getPrefix(messageType) + message, title, messageType);
390 * adds title and icon
392 * @param parentComponent
397 * @throws HeadlessException
399 public static void showMessageDialog(Component parentComponent,
400 String message, String title, int messageType, Icon icon)
401 throws HeadlessException
406 if (!isInteractiveMode())
408 outputMessage(message);
412 JOptionPane.showMessageDialog(parentComponent, message, title,
420 public static void showInternalMessageDialog(Component parentComponent,
424 // WsPreferences only
426 if (!isInteractiveMode())
428 outputMessage(message);
432 JOptionPane.showMessageDialog(parentComponent, message);
436 * Adds title and messageType
438 * @param parentComponent
443 public static void showInternalMessageDialog(Component parentComponent,
444 String message, String title, int messageType)
449 if (!isInteractiveMode())
451 outputMessage(message);
455 JOptionPane.showMessageDialog(parentComponent,
456 getPrefix(messageType) + message, title, messageType);
461 * @param parentComponent
467 public static void showInternalMessageDialog(Component parentComponent,
468 Object message, String title, int messageType, Icon icon)
473 if (!isInteractiveMode())
475 outputMessage(message);
479 JOptionPane.showMessageDialog(parentComponent, message, title,
487 * @throws HeadlessException
489 public static String showInputDialog(Object message)
490 throws HeadlessException
494 if (!isInteractiveMode())
496 return getMockResponse().toString();
499 return JOptionPane.showInputDialog(message);
503 * adds inital selection value
506 * @param initialSelectionValue
509 public static String showInputDialog(String message,
510 String initialSelectionValue)
512 if (!isInteractiveMode())
514 return getMockResponse().toString();
517 // AnnotationPanel character option
519 return JOptionPane.showInputDialog(message, initialSelectionValue);
523 * adds inital selection value
526 * @param initialSelectionValue
529 public static String showInputDialog(Object message,
530 Object initialSelectionValue)
532 if (!isInteractiveMode())
534 return getMockResponse().toString();
537 // AnnotationPanel character option
539 return JOptionPane.showInputDialog(message, initialSelectionValue);
545 * @param parentComponent
548 * @throws HeadlessException
550 public static String showInputDialog(Component parentComponent,
551 String message) throws HeadlessException
555 return isInteractiveMode()
556 ? JOptionPane.showInputDialog(parentComponent, message)
557 : getMockResponse().toString();
561 * input with initial selection
563 * @param parentComponent
565 * @param initialSelectionValue
568 public static String showInputDialog(Component parentComponent,
569 String message, String initialSelectionValue)
574 return isInteractiveMode()
575 ? JOptionPane.showInputDialog(parentComponent, message,
576 initialSelectionValue)
577 : getMockResponse().toString();
581 * input with initial selection
583 * @param parentComponent
585 * @param initialSelectionValue
588 public static String showInputDialog(Component parentComponent,
589 Object message, Object initialSelectionValue)
594 return isInteractiveMode()
595 ? JOptionPane.showInputDialog(parentComponent, message,
596 initialSelectionValue)
597 : getMockResponse().toString();
602 * @param parentComponent
607 * @throws HeadlessException
609 public static String showInputDialog(Component parentComponent,
610 String message, String title, int messageType)
611 throws HeadlessException
616 return isInteractiveMode()
617 ? JOptionPane.showInputDialog(parentComponent, message, title,
619 : getMockResponse().toString();
623 * Customized input option
625 * @param parentComponent
630 * @param selectionValues
631 * @param initialSelectionValue
633 * @throws HeadlessException
635 public static Object showInputDialog(Component parentComponent,
636 Object message, String title, int messageType, Icon icon,
637 Object[] selectionValues, Object initialSelectionValue)
638 throws HeadlessException
643 return isInteractiveMode()
644 ? JOptionPane.showInputDialog(parentComponent, message, title,
645 messageType, icon, selectionValues,
646 initialSelectionValue)
647 : getMockResponse().toString();
653 * @param parentComponent
657 public static String showInternalInputDialog(Component parentComponent,
662 return isInteractiveMode()
663 ? JOptionPane.showInternalInputDialog(parentComponent, message)
664 : getMockResponse().toString();
668 * internal with title and messageType
670 * @param parentComponent
676 public static String showInternalInputDialog(Component parentComponent,
677 String message, String title, int messageType)
680 // AlignFrame tabbedPane_mousePressed
682 return isInteractiveMode()
683 ? JOptionPane.showInternalInputDialog(parentComponent,
684 getPrefix(messageType) + message, title, messageType)
685 : getMockResponse().toString();
689 * customized internal
691 * @param parentComponent
696 * @param selectionValues
697 * @param initialSelectionValue
700 public static Object showInternalInputDialog(Component parentComponent,
701 String message, String title, int messageType, Icon icon,
702 Object[] selectionValues, Object initialSelectionValue)
706 return isInteractiveMode()
707 ? JOptionPane.showInternalInputDialog(parentComponent, message,
708 title, messageType, icon, selectionValues,
709 initialSelectionValue)
710 : getMockResponse().toString();
713 ///////////// end of options ///////////////
715 private static void outputMessage(Object message)
717 System.out.println(">>> JOption Message : " + message.toString());
720 public static Object getMockResponse()
725 public static void setMockResponse(Object mockOption)
727 JvOptionPane.mockResponse = mockOption;
730 public static void resetMock()
732 setMockResponse(JvOptionPane.CANCEL_OPTION);
733 setInteractiveMode(true);
736 public static boolean isInteractiveMode()
738 return interactiveMode;
741 public static void setInteractiveMode(boolean interactive)
743 JvOptionPane.interactiveMode = interactive;
746 private static String getPrefix(int messageType)
755 case JvOptionPane.WARNING_MESSAGE:
756 prefix = "WARNING! ";
758 case JvOptionPane.ERROR_MESSAGE:
769 * create a new option dialog that can be used to register responses - along
770 * lines of showOptionDialog
775 * @param defaultOption
776 * @param plainMessage
782 public static JvOptionPane newOptionDialog()
784 return new JvOptionPane(null);
787 public static JvOptionPane newOptionDialog(Component parentComponent)
789 return new JvOptionPane(parentComponent);
792 public void showDialog(String message, String title, int optionType,
793 int messageType, Icon icon, Object[] options, Object initialValue)
795 showDialog(message, title, optionType, messageType, icon, options,
799 public void showDialog(Object message, String title, int optionType,
800 int messageType, Icon icon, Object[] options, Object initialValue,
803 showDialog(message, title, optionType, messageType, icon, options,
804 initialValue, modal, null);
807 public void showDialog(Object message, String title, int optionType,
808 int messageType, Icon icon, Object[] options, Object initialValue,
809 boolean modal, JButton[] buttons)
811 if (!isInteractiveMode())
813 handleResponse(getMockResponse());
820 // 1) AlignViewport for openLinkedAlignment
822 // Show a dialog with the option to open and link (cDNA <-> protein) as a
824 // alignment, either as a standalone alignment or in a split frame. Returns
825 // true if the new alignment was opened, false if not, because the user
826 // declined the offer.
828 // 2) UserDefinedColors warning about saving over a name already defined
831 ourOptions = Arrays.asList(options);
835 boolean useButtons = false;
836 Object initialValueButton = null;
837 NOTNULL: if (buttons != null)
839 if (buttons.length != options.length)
841 jalview.bin.Console.error(
842 "Supplied buttons array not the same length as supplied options array.");
845 int[] buttonActions = { JOptionPane.YES_OPTION,
846 JOptionPane.NO_OPTION, JOptionPane.CANCEL_OPTION };
847 for (int i = 0; i < options.length; i++)
849 Object o = options[i];
850 jalview.bin.Console.debug(
851 "Setting button " + i + " to '" + o.toString() + "'");
852 JButton jb = buttons[i];
854 if (o.equals(initialValue))
855 initialValueButton = jb;
857 int buttonAction = buttonActions[i];
858 Callable<Void> action = callbacks.get(buttonAction);
859 jb.setText((String) o);
860 jb.addActionListener(new ActionListener()
863 public void actionPerformed(ActionEvent e)
866 Object obj = e.getSource();
867 if (obj == null || !(obj instanceof Component))
869 jalview.bin.Console.debug(
870 "Could not find Component source of event object "
874 Object joptionpaneObject = SwingUtilities.getAncestorOfClass(
875 JOptionPane.class, (Component) obj);
876 if (joptionpaneObject == null
877 || !(joptionpaneObject instanceof JOptionPane))
879 jalview.bin.Console.debug(
880 "Could not find JOptionPane ancestor of event object "
884 JOptionPane joptionpane = (JOptionPane) joptionpaneObject;
885 joptionpane.setValue(buttonAction);
887 getExecutor().submit(action);
888 joptionpane.transferFocusBackward();
889 joptionpane.setVisible(false);
890 // put focus and raise parent window if possible, unless cancel or
892 boolean raiseParent = (parentComponent != null);
893 if (buttonAction == JOptionPane.CANCEL_OPTION)
895 if (optionType == JOptionPane.YES_NO_OPTION
896 && buttonAction == JOptionPane.NO_OPTION)
900 parentComponent.requestFocus();
901 if (parentComponent instanceof JInternalFrame)
903 JInternalFrame jif = (JInternalFrame) parentComponent;
908 else if (parentComponent instanceof Window)
910 Window w = (Window) parentComponent;
915 joptionpane.setVisible(false);
922 // use a JOptionPane as usual
923 int response = JOptionPane.showOptionDialog(parentComponent, message,
924 title, optionType, messageType, icon,
925 useButtons ? buttons : options,
926 useButtons ? initialValueButton : initialValue);
929 * In Java, the response is returned to this thread and handled here; (for
930 * Javascript, see propertyChange)
932 if (!Platform.isJS())
939 handleResponse(response);
945 * This is java similar to the swingjs handling, with the callbacks attached to
946 * the button press of the dialog. This means we can use a non-modal JDialog for
947 * the confirmation without blocking the GUI.
949 JOptionPane joptionpane = new JOptionPane();
950 // Make button options
951 int[] buttonActions = { JvOptionPane.YES_OPTION,
952 JvOptionPane.NO_OPTION, JvOptionPane.CANCEL_OPTION };
954 // we need the strings to make the buttons with actionEventListener
957 ArrayList<String> options_default = new ArrayList<>();
959 .add(UIManager.getString("OptionPane.yesButtonText"));
960 if (optionType == JvOptionPane.YES_NO_OPTION
961 || optionType == JvOptionPane.YES_NO_CANCEL_OPTION)
964 .add(UIManager.getString("OptionPane.noButtonText"));
966 if (optionType == JvOptionPane.YES_NO_CANCEL_OPTION)
969 .add(UIManager.getString("OptionPane.cancelButtonText"));
971 options = options_default.toArray();
974 ArrayList<JButton> options_btns = new ArrayList<>();
975 Object initialValue_btn = null;
976 if (!Platform.isJS()) // JalviewJS already uses callback, don't need to
979 for (int i = 0; i < options.length && i < 3; i++)
981 Object o = options[i];
982 int buttonAction = buttonActions[i];
983 Callable<Void> action = callbacks.get(buttonAction);
984 JButton jb = new JButton();
985 jb.setText((String) o);
986 jb.addActionListener(new ActionListener()
990 public void actionPerformed(ActionEvent e)
992 joptionpane.setValue(buttonAction);
994 getExecutor().submit(action);
995 // joptionpane.transferFocusBackward();
996 joptionpane.transferFocusBackward();
997 joptionpane.setVisible(false);
998 // put focus and raise parent window if possible, unless cancel
1000 boolean raiseParent = (parentComponent != null);
1001 if (buttonAction == JvOptionPane.CANCEL_OPTION)
1002 raiseParent = false;
1003 if (optionType == JvOptionPane.YES_NO_OPTION
1004 && buttonAction == JvOptionPane.NO_OPTION)
1005 raiseParent = false;
1008 parentComponent.requestFocus();
1009 if (parentComponent instanceof JInternalFrame)
1011 JInternalFrame jif = (JInternalFrame) parentComponent;
1016 else if (parentComponent instanceof Window)
1018 Window w = (Window) parentComponent;
1023 joptionpane.setVisible(false);
1026 options_btns.add(jb);
1027 if (o.equals(initialValue))
1028 initialValue_btn = jb;
1031 joptionpane.setMessage(message);
1032 joptionpane.setMessageType(messageType);
1033 joptionpane.setOptionType(optionType);
1034 joptionpane.setIcon(icon);
1035 joptionpane.setOptions(
1036 Platform.isJS() ? options : options_btns.toArray());
1037 joptionpane.setInitialValue(
1038 Platform.isJS() ? initialValue : initialValue_btn);
1040 JDialog dialog = joptionpane.createDialog(parentComponent, title);
1041 dialog.setIconImages(ChannelProperties.getIconList());
1042 dialog.setModalityType(modal ? ModalityType.APPLICATION_MODAL
1043 : ModalityType.MODELESS);
1044 dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
1045 dialog.setVisible(true);
1050 public void showInternalDialog(JPanel mainPanel, String title,
1051 int yesNoCancelOption, int questionMessage, Icon icon,
1052 Object[] options, String initresponse)
1054 if (!isInteractiveMode())
1056 handleResponse(getMockResponse());
1059 // need to set these separately so we can set the title bar icon later
1060 this.setOptionType(yesNoCancelOption);
1061 this.setMessageType(questionMessage);
1063 this.setInitialValue(initresponse);
1064 this.setOptions(options);
1065 this.setMessage(mainPanel);
1067 ourOptions = Arrays.asList(options);
1069 if (parentComponent != this)
1071 JInternalFrame jif = this.createInternalFrame(parentComponent, title);
1072 jif.setFrameIcon(null);
1073 jif.addInternalFrameListener(new InternalFrameListener()
1076 public void internalFrameActivated(InternalFrameEvent arg0)
1081 public void internalFrameClosed(InternalFrameEvent arg0)
1083 JvOptionPane.this.internalDialogHandleResponse();
1087 public void internalFrameClosing(InternalFrameEvent arg0)
1092 public void internalFrameDeactivated(InternalFrameEvent arg0)
1097 public void internalFrameDeiconified(InternalFrameEvent arg0)
1102 public void internalFrameIconified(InternalFrameEvent arg0)
1107 public void internalFrameOpened(InternalFrameEvent arg0)
1111 jif.setVisible(true);
1117 JDialog dialog = this.createDialog(parentComponent, title);
1118 dialog.setIconImages(ChannelProperties.getIconList());
1119 dialog.setVisible(true); // blocking
1120 this.internalDialogHandleResponse();
1125 private void internalDialogHandleResponse()
1127 String responseString = (String) this.getValue();
1128 int response = ourOptions.indexOf(responseString);
1130 if (!Platform.isJS())
1137 handleResponse(response);
1142 * @Override public JvOptionPane setResponseHandler(Object response, Runnable
1143 * action) { callbacks.put(response, new Callable<Void>() {
1145 * @Override public Void call() { action.run(); return null; } }); return this;
1149 public JvOptionPane setResponseHandler(Object response,
1150 Callable<Void> action)
1152 callbacks.put(response, action);
1156 public ExecutorService getExecutor()
1158 if (executor == null)
1159 executor = Executors.newSingleThreadExecutor();
1163 public void setExecutor(ExecutorService es)
1168 public void setDialog(JDialog d)
1173 public JDialog getDialog()
1179 * showDialogOnTop will create a dialog that (attempts to) come to top of OS
1182 public static int showDialogOnTop(String label, String actionString,
1183 int JOPTIONPANE_OPTION, int JOPTIONPANE_MESSAGETYPE)
1185 return showDialogOnTop(null, label, actionString, JOPTIONPANE_OPTION,
1186 JOPTIONPANE_MESSAGETYPE);
1189 public static int showDialogOnTop(Component dialogParentComponent,
1190 String label, String actionString, int JOPTIONPANE_OPTION,
1191 int JOPTIONPANE_MESSAGETYPE)
1193 if (!isInteractiveMode())
1195 return (int) getMockResponse();
1197 // Ensure Jalview window is brought to front (primarily for Quit
1198 // confirmation window to be visible)
1200 // This method of raising the Jalview window is broken in java
1201 // jalviewDesktop.setVisible(true);
1202 // jalviewDesktop.toFront();
1204 // A better hack which works is to create a new JFrame parent with
1205 // setAlwaysOnTop(true)
1206 JFrame dialogParent = new JFrame();
1207 if (dialogParentComponent == null)
1209 dialogParent.setIconImages(ChannelProperties.getIconList());
1210 dialogParent.setAlwaysOnTop(true);
1213 int answer = JOptionPane.showConfirmDialog(
1214 dialogParentComponent == null ? dialogParent
1215 : dialogParentComponent,
1216 label, actionString, JOPTIONPANE_OPTION,
1217 JOPTIONPANE_MESSAGETYPE);
1219 if (dialogParentComponent == null)
1221 dialogParent.setAlwaysOnTop(false);
1222 dialogParent.dispose();
1228 public void showDialogOnTopAsync(String label, String actionString,
1229 int JOPTIONPANE_OPTION, int JOPTIONPANE_MESSAGETYPE, Icon icon,
1230 Object[] options, Object initialValue, boolean modal)
1232 JFrame frame = new JFrame();
1233 frame.setIconImages(ChannelProperties.getIconList());
1234 showDialogOnTopAsync(frame, label, actionString, JOPTIONPANE_OPTION,
1235 JOPTIONPANE_MESSAGETYPE, icon, options, initialValue, modal);
1238 public void showDialogOnTopAsync(JFrame dialogParent, Object label,
1239 String actionString, int JOPTIONPANE_OPTION,
1240 int JOPTIONPANE_MESSAGETYPE, Icon icon, Object[] options,
1241 Object initialValue, boolean modal)
1243 showDialogOnTopAsync(dialogParent, label, actionString,
1244 JOPTIONPANE_OPTION, JOPTIONPANE_MESSAGETYPE, icon, options,
1245 initialValue, modal, null);
1248 public void showDialogOnTopAsync(JFrame dialogParent, Object label,
1249 String actionString, int JOPTIONPANE_OPTION,
1250 int JOPTIONPANE_MESSAGETYPE, Icon icon, Object[] options,
1251 Object initialValue, boolean modal, JButton[] buttons)
1253 if (!isInteractiveMode())
1255 handleResponse(getMockResponse());
1258 // Ensure Jalview window is brought to front (primarily for Quit
1259 // confirmation window to be visible)
1261 // This method of raising the Jalview window is broken in java
1262 // jalviewDesktop.setVisible(true);
1263 // jalviewDesktop.toFront();
1265 // A better hack which works is to create a new JFrame parent with
1266 // setAlwaysOnTop(true)
1267 dialogParent.setAlwaysOnTop(true);
1268 parentComponent = dialogParent;
1270 showDialog(label, actionString, JOPTIONPANE_OPTION,
1271 JOPTIONPANE_MESSAGETYPE, icon, options, initialValue, modal,
1274 dialogParent.setAlwaysOnTop(false);
1275 dialogParent.dispose();
1279 * JalviewJS signals option selection by a property change event for the
1280 * option e.g. "OK". This methods responds to that by running the response
1281 * action that corresponds to that option.
1286 public void propertyChange(PropertyChangeEvent evt)
1288 Object newValue = evt.getNewValue();
1289 int ourOption = ourOptions.indexOf(newValue);
1292 handleResponse(ourOption);
1297 handleResponse(newValue);
1302 public void handleResponse(Object response)
1305 * this test is for NaN in Chrome
1307 if (response != null && !response.equals(response))
1311 Callable<Void> action = callbacks.get(response);
1316 getExecutor().submit(action).get();
1318 } catch (Exception e)
1320 e.printStackTrace();
1322 if (parentComponent != null)
1323 parentComponent.requestFocus();
1328 * Create a non-modal confirm dialog
1330 public JDialog createDialog(Component parentComponent, Object message,
1331 String title, int optionType, int messageType, Icon icon,
1332 Object[] options, Object initialValue, boolean modal)
1334 return createDialog(parentComponent, message, title, optionType,
1335 messageType, icon, options, initialValue, modal, null);
1338 public JDialog createDialog(Component parentComponent, Object message,
1339 String title, int optionType, int messageType, Icon icon,
1340 Object[] options, Object initialValue, boolean modal,
1343 if (!isInteractiveMode())
1345 handleResponse(getMockResponse());
1348 JButton[] optionsButtons = null;
1349 Object initialValueButton = null;
1350 JOptionPane joptionpane = new JOptionPane();
1351 // Make button options
1352 int[] buttonActions = { JOptionPane.YES_OPTION, JOptionPane.NO_OPTION,
1353 JOptionPane.CANCEL_OPTION };
1355 // we need the strings to make the buttons with actionEventListener
1356 if (options == null)
1358 ArrayList<String> options_default = new ArrayList<>();
1359 options_default.add(UIManager.getString("OptionPane.yesButtonText"));
1360 if (optionType == JOptionPane.YES_NO_OPTION
1361 || optionType == JOptionPane.YES_NO_CANCEL_OPTION)
1363 options_default.add(UIManager.getString("OptionPane.noButtonText"));
1365 if (optionType == JOptionPane.YES_NO_CANCEL_OPTION)
1368 .add(UIManager.getString("OptionPane.cancelButtonText"));
1370 options = options_default.toArray();
1372 if (!Platform.isJS()) // JalviewJS already uses callback, don't need to
1375 if (((optionType == JOptionPane.YES_OPTION
1376 || optionType == JOptionPane.NO_OPTION
1377 || optionType == JOptionPane.CANCEL_OPTION
1378 || optionType == JOptionPane.OK_OPTION
1379 || optionType == JOptionPane.DEFAULT_OPTION)
1380 && options.length < 1)
1381 || ((optionType == JOptionPane.YES_NO_OPTION
1382 || optionType == JOptionPane.OK_CANCEL_OPTION)
1383 && options.length < 2)
1384 || (optionType == JOptionPane.YES_NO_CANCEL_OPTION
1385 && options.length < 3))
1388 .debug("JvOptionPane: not enough options for dialog type");
1390 optionsButtons = new JButton[options.length];
1391 for (int i = 0; i < options.length && i < 3; i++)
1393 Object o = options[i];
1394 int buttonAction = buttonActions[i];
1395 Callable<Void> action = callbacks.get(buttonAction);
1397 if (buttons != null && buttons.length > i && buttons[i] != null)
1405 jb.setText((String) o);
1406 jb.addActionListener(new ActionListener()
1409 public void actionPerformed(ActionEvent e)
1411 joptionpane.setValue(buttonAction);
1413 getExecutor().submit(action);
1414 // joptionpane.transferFocusBackward();
1415 joptionpane.transferFocusBackward();
1416 joptionpane.setVisible(false);
1417 // put focus and raise parent window if possible, unless cancel
1419 boolean raiseParent = (parentComponent != null);
1420 if (buttonAction == JOptionPane.CANCEL_OPTION)
1421 raiseParent = false;
1422 if (optionType == JOptionPane.YES_NO_OPTION
1423 && buttonAction == JOptionPane.NO_OPTION)
1424 raiseParent = false;
1427 parentComponent.requestFocus();
1428 if (parentComponent instanceof JInternalFrame)
1430 JInternalFrame jif = (JInternalFrame) parentComponent;
1435 else if (parentComponent instanceof Window)
1437 Window w = (Window) parentComponent;
1442 joptionpane.setVisible(false);
1445 optionsButtons[i] = jb;
1446 if (o.equals(initialValue))
1447 initialValueButton = jb;
1450 joptionpane.setMessage(message);
1451 joptionpane.setMessageType(messageType);
1452 joptionpane.setOptionType(optionType);
1453 joptionpane.setIcon(icon);
1454 joptionpane.setOptions(Platform.isJS() ? options : optionsButtons);
1455 joptionpane.setInitialValue(
1456 Platform.isJS() ? initialValue : initialValueButton);
1458 JDialog dialog = joptionpane.createDialog(parentComponent, title);
1459 dialog.setIconImages(ChannelProperties.getIconList());
1460 dialog.setModalityType(
1461 modal ? ModalityType.APPLICATION_MODAL : ModalityType.MODELESS);
1462 dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
1468 * Utility to programmatically click a button on a JOptionPane (as a JFrame)
1470 * returns true if button was found
1472 public static boolean clickButton(JFrame frame, int buttonType)
1479 * This helper method makes the JInternalFrame wait until it is notified by an
1480 * InternalFrameClosing event. This method also adds the given JOptionPane to
1481 * the JInternalFrame and sizes it according to the JInternalFrame's preferred
1485 * The JInternalFrame to make modal.
1487 private static void startModal(JInternalFrame f)
1489 // We need to add an additional glasspane-like component directly
1490 // below the frame, which intercepts all mouse events that are not
1491 // directed at the frame itself.
1492 JPanel modalInterceptor = new JPanel();
1493 modalInterceptor.setOpaque(false);
1494 JLayeredPane lp = JLayeredPane.getLayeredPaneAbove(f);
1495 lp.setLayer(modalInterceptor, JLayeredPane.MODAL_LAYER.intValue());
1496 modalInterceptor.setBounds(0, 0, lp.getWidth(), lp.getHeight());
1497 modalInterceptor.addMouseListener(new MouseAdapter()
1500 modalInterceptor.addMouseMotionListener(new MouseMotionAdapter()
1503 lp.add(modalInterceptor);
1506 // We need to explicitly dispatch events when we are blocking the event
1508 EventQueue queue = Toolkit.getDefaultToolkit().getSystemEventQueue();
1511 while (!f.isClosed())
1513 if (EventQueue.isDispatchThread())
1515 // The getNextEventMethod() issues wait() when no
1516 // event is available, so we don't need do explicitly wait().
1517 AWTEvent ev = queue.getNextEvent();
1518 // This mimics EventQueue.dispatchEvent(). We can't use
1519 // EventQueue.dispatchEvent() directly, because it is
1520 // protected, unfortunately.
1521 if (ev instanceof ActiveEvent)
1522 ((ActiveEvent) ev).dispatch();
1523 else if (ev.getSource() instanceof Component)
1524 ((Component) ev.getSource()).dispatchEvent(ev);
1525 else if (ev.getSource() instanceof MenuComponent)
1526 ((MenuComponent) ev.getSource()).dispatchEvent(ev);
1527 // Other events are ignored as per spec in
1528 // EventQueue.dispatchEvent
1532 // Give other threads a chance to become active.
1536 } catch (InterruptedException ex)
1538 // If we get interrupted, then leave the modal state.
1541 // Clean up the modal interceptor.
1542 lp.remove(modalInterceptor);
1544 // Remove the internal frame from its parent, so it is no longer
1545 // lurking around and clogging memory.
1546 Container parent = f.getParent();
1552 public static JvOptionPane frameDialog(String message, String title,
1553 int messageType, String[] buttonsText, String defaultButton,
1554 Callable<Void>[] handlers, boolean modal)
1556 JFrame parent = new JFrame();
1557 JvOptionPane jvop = JvOptionPane.newOptionDialog();
1558 JButton[] buttons = new JButton[buttonsText.length];
1559 for (int i = 0; i < buttonsText.length; i++)
1561 buttons[i] = new JButton();
1562 buttons[i].setText(buttonsText[i]);
1563 Console.debug("DISABLING BUTTON " + buttons[i].getText());
1564 buttons[i].setEnabled(false);
1565 buttons[i].setVisible(false);
1568 int dialogType = -1;
1569 if (buttonsText.length == 1)
1571 dialogType = JOptionPane.OK_OPTION;
1573 else if (buttonsText.length == 2)
1575 dialogType = JOptionPane.YES_NO_OPTION;
1579 dialogType = JOptionPane.YES_NO_CANCEL_OPTION;
1581 jvop.setResponseHandler(JOptionPane.YES_OPTION, handlers[0]);
1582 if (dialogType == JOptionPane.YES_NO_OPTION
1583 || dialogType == JOptionPane.YES_NO_CANCEL_OPTION)
1585 jvop.setResponseHandler(JOptionPane.NO_OPTION, handlers[1]);
1587 if (dialogType == JOptionPane.YES_NO_CANCEL_OPTION)
1589 jvop.setResponseHandler(JOptionPane.CANCEL_OPTION, handlers[2]);
1592 final int dt = dialogType;
1593 jvop.getExecutor().execute(() -> {
1594 jvop.showDialog(message, title, dt, messageType, null, buttonsText,
1595 defaultButton, modal, buttons);