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.Executors;
47 import javax.swing.Icon;
48 import javax.swing.JButton;
49 import javax.swing.JDialog;
50 import javax.swing.JFrame;
51 import javax.swing.JInternalFrame;
52 import javax.swing.JLayeredPane;
53 import javax.swing.JOptionPane;
54 import javax.swing.JPanel;
55 import javax.swing.SwingUtilities;
56 import javax.swing.UIManager;
57 import javax.swing.event.InternalFrameEvent;
58 import javax.swing.event.InternalFrameListener;
60 import jalview.util.Platform;
61 import jalview.util.dialogrunner.DialogRunnerI;
63 public class JvOptionPane extends JOptionPane implements DialogRunnerI, PropertyChangeListener {
64 private static final long serialVersionUID = -3019167117756785229L;
66 private static Object mockResponse = JvOptionPane.CANCEL_OPTION;
68 private static boolean interactiveMode = true;
70 private Component parentComponent;
72 private Map<Object, Callable<Void>> callbacks = new HashMap<>();
75 * JalviewJS reports user choice in the dialog as the selected option (text);
76 * this list allows conversion to index (int)
78 List<Object> ourOptions;
80 public JvOptionPane(final Component parent) {
81 this.parentComponent = Platform.isJS() ? this : parent;
84 public static int showConfirmDialog(Component parentComponent, Object message) throws HeadlessException {
85 // only called by test
86 return isInteractiveMode() ? JOptionPane.showConfirmDialog(parentComponent, message) : (int) getMockResponse();
90 * Message, title, optionType
92 * @param parentComponent
97 * @throws HeadlessException
99 public static int showConfirmDialog(Component parentComponent, Object message, String title, int optionType)
100 throws HeadlessException {
101 if (!isInteractiveMode()) {
102 return (int) getMockResponse();
104 switch (optionType) {
105 case JvOptionPane.YES_NO_CANCEL_OPTION:
106 // FeatureRenderer amendFeatures ?? TODO ??
111 case JvOptionPane.YES_NO_OPTION:
112 // PromptUserConfig usage stats
113 // for now treated as "OK CANCEL"
115 case JvOptionPane.OK_CANCEL_OPTION:
116 // will fall back to simple HTML
117 return JOptionPane.showConfirmDialog(parentComponent, message, title, optionType);
122 * Adds a message type. Fallback is to just add it in the beginning.
124 * @param parentComponent
130 * @throws HeadlessException
132 public static int showConfirmDialog(Component parentComponent, Object message, String title, int optionType,
133 int messageType) throws HeadlessException {
134 // JalviewServicesChanged
135 // PromptUserConfig raiseDialog
136 return isInteractiveMode() ? JOptionPane.showConfirmDialog(parentComponent, message, title, optionType, messageType)
137 : (int) getMockResponse();
143 * @param parentComponent
150 * @throws HeadlessException
152 public static int showConfirmDialog(Component parentComponent, Object message, String title, int optionType,
153 int messageType, Icon icon) throws HeadlessException {
154 // JvOptionPaneTest only
155 return isInteractiveMode()
156 ? JOptionPane.showConfirmDialog(parentComponent, message, title, optionType, messageType, icon)
157 : (int) getMockResponse();
161 * Internal version "OK"
163 * @param parentComponent
167 public static int showInternalConfirmDialog(Component parentComponent, Object message) {
168 // JvOptionPaneTest only;
169 return isInteractiveMode() ? JOptionPane.showInternalConfirmDialog(parentComponent, message)
170 : (int) getMockResponse();
174 * Internal version -- changed to standard version for now
176 * @param parentComponent
182 public static int showInternalConfirmDialog(Component parentComponent, String message, String title, int optionType) {
183 if (!isInteractiveMode()) {
184 return (int) getMockResponse();
186 switch (optionType) {
187 case JvOptionPane.YES_NO_CANCEL_OPTION:
188 // ColourMenuHelper.addMenuItmers.offerRemoval TODO
189 case JvOptionPane.YES_NO_OPTION:
190 // UserDefinedColoursSave -- relevant? TODO
193 case JvOptionPane.OK_CANCEL_OPTION:
195 // EditNameDialog --- uses panel for messsage TODO
197 // Desktop.inputURLMenuItem
199 return JOptionPane.showConfirmDialog(parentComponent, message, title, optionType);
205 * @param parentComponent
212 public static int showInternalConfirmDialog(Component parentComponent, Object message, String title, int optionType,
214 if (!isInteractiveMode()) {
215 return (int) getMockResponse();
217 switch (optionType) {
218 case JvOptionPane.YES_NO_CANCEL_OPTION:
219 case JvOptionPane.YES_NO_OPTION:
220 // UserQuestionanaireCheck
224 case JvOptionPane.OK_CANCEL_OPTION:
225 // will fall back to simple HTML
226 return JOptionPane.showConfirmDialog(parentComponent, message, title, optionType, messageType);
231 * adds icon; no longer internal
233 * @param parentComponent
241 public static int showInternalConfirmDialog(Component parentComponent, Object message, String title, int optionType,
242 int messageType, Icon icon) {
243 if (!isInteractiveMode()) {
244 return (int) getMockResponse();
246 switch (optionType) {
247 case JvOptionPane.YES_NO_CANCEL_OPTION:
248 case JvOptionPane.YES_NO_OPTION:
251 case JvOptionPane.OK_CANCEL_OPTION:
252 // Preferences editLink/newLink
253 return JOptionPane.showConfirmDialog(parentComponent, message, title, optionType, messageType, icon);
259 * custom options full-featured
261 * @param parentComponent
268 * @param initialValue
270 * @throws HeadlessException
272 public static int showOptionDialog(Component parentComponent, String message, String title, int optionType,
273 int messageType, Icon icon, Object[] options, Object initialValue) throws HeadlessException {
274 if (!isInteractiveMode()) {
275 return (int) getMockResponse();
281 // 1) AlignViewport for openLinkedAlignment
283 // Show a dialog with the option to open and link (cDNA <-> protein) as a
285 // alignment, either as a standalone alignment or in a split frame. Returns
286 // true if the new alignment was opened, false if not, because the user
287 // declined the offer.
289 // 2) UserDefinedColors warning about saving over a name already defined
291 return JOptionPane.showOptionDialog(parentComponent, message, title, optionType, messageType, icon, options,
299 * @throws HeadlessException
301 public static void showMessageDialog(Component parentComponent, String message) throws HeadlessException {
302 if (!isInteractiveMode()) {
303 outputMessage(message);
309 JOptionPane.showMessageDialog(parentComponent, message);
313 * OK with message, title, and type
315 * @param parentComponent
319 * @throws HeadlessException
321 public static void showMessageDialog(Component parentComponent, String message, String title, int messageType)
322 throws HeadlessException {
323 // 30 implementations -- all just fine.
325 if (!isInteractiveMode()) {
326 outputMessage(message);
330 JOptionPane.showMessageDialog(parentComponent, getPrefix(messageType) + message, title, messageType);
334 * adds title and icon
336 * @param parentComponent
341 * @throws HeadlessException
343 public static void showMessageDialog(Component parentComponent, String message, String title, int messageType,
344 Icon icon) throws HeadlessException {
348 if (!isInteractiveMode()) {
349 outputMessage(message);
353 JOptionPane.showMessageDialog(parentComponent, message, title, messageType, icon);
360 public static void showInternalMessageDialog(Component parentComponent, Object message) {
362 // WsPreferences only
364 if (!isInteractiveMode()) {
365 outputMessage(message);
369 JOptionPane.showMessageDialog(parentComponent, message);
373 * Adds title and messageType
375 * @param parentComponent
380 public static void showInternalMessageDialog(Component parentComponent, String message, String title,
385 if (!isInteractiveMode()) {
386 outputMessage(message);
390 JOptionPane.showMessageDialog(parentComponent, getPrefix(messageType) + message, title, messageType);
395 * @param parentComponent
401 public static void showInternalMessageDialog(Component parentComponent, Object message, String title, int messageType,
406 if (!isInteractiveMode()) {
407 outputMessage(message);
411 JOptionPane.showMessageDialog(parentComponent, message, title, messageType, icon);
418 * @throws HeadlessException
420 public static String showInputDialog(Object message) throws HeadlessException {
423 if (!isInteractiveMode()) {
424 return getMockResponse().toString();
427 return JOptionPane.showInputDialog(message);
431 * adds inital selection value
434 * @param initialSelectionValue
437 public static String showInputDialog(String message, String initialSelectionValue) {
438 if (!isInteractiveMode()) {
439 return getMockResponse().toString();
442 // AnnotationPanel character option
444 return JOptionPane.showInputDialog(message, initialSelectionValue);
448 * adds inital selection value
451 * @param initialSelectionValue
454 public static String showInputDialog(Object message, Object initialSelectionValue) {
455 if (!isInteractiveMode()) {
456 return getMockResponse().toString();
459 // AnnotationPanel character option
461 return JOptionPane.showInputDialog(message, initialSelectionValue);
467 * @param parentComponent
470 * @throws HeadlessException
472 public static String showInputDialog(Component parentComponent, String message) throws HeadlessException {
475 return isInteractiveMode() ? JOptionPane.showInputDialog(parentComponent, message) : getMockResponse().toString();
479 * input with initial selection
481 * @param parentComponent
483 * @param initialSelectionValue
486 public static String showInputDialog(Component parentComponent, String message, String initialSelectionValue) {
490 return isInteractiveMode() ? JOptionPane.showInputDialog(parentComponent, message, initialSelectionValue)
491 : getMockResponse().toString();
495 * input with initial selection
497 * @param parentComponent
499 * @param initialSelectionValue
502 public static String showInputDialog(Component parentComponent, Object message, Object initialSelectionValue) {
506 return isInteractiveMode() ? JOptionPane.showInputDialog(parentComponent, message, initialSelectionValue)
507 : getMockResponse().toString();
512 * @param parentComponent
517 * @throws HeadlessException
519 public static String showInputDialog(Component parentComponent, String message, String title, int messageType)
520 throws HeadlessException {
524 return isInteractiveMode() ? JOptionPane.showInputDialog(parentComponent, message, title, messageType)
525 : getMockResponse().toString();
529 * Customized input option
531 * @param parentComponent
536 * @param selectionValues
537 * @param initialSelectionValue
539 * @throws HeadlessException
541 public static Object showInputDialog(Component parentComponent, Object message, String title, int messageType,
542 Icon icon, Object[] selectionValues, Object initialSelectionValue) throws HeadlessException {
546 return isInteractiveMode()
547 ? JOptionPane.showInputDialog(parentComponent, message, title, messageType, icon, selectionValues,
548 initialSelectionValue)
549 : getMockResponse().toString();
555 * @param parentComponent
559 public static String showInternalInputDialog(Component parentComponent, String message) {
562 return isInteractiveMode() ? JOptionPane.showInternalInputDialog(parentComponent, message)
563 : getMockResponse().toString();
567 * internal with title and messageType
569 * @param parentComponent
575 public static String showInternalInputDialog(Component parentComponent, String message, String title,
578 // AlignFrame tabbedPane_mousePressed
580 return isInteractiveMode()
581 ? JOptionPane.showInternalInputDialog(parentComponent, getPrefix(messageType) + message, title, messageType)
582 : getMockResponse().toString();
586 * customized internal
588 * @param parentComponent
593 * @param selectionValues
594 * @param initialSelectionValue
597 public static Object showInternalInputDialog(Component parentComponent, String message, String title, int messageType,
598 Icon icon, Object[] selectionValues, Object initialSelectionValue) {
601 return isInteractiveMode() ? JOptionPane.showInternalInputDialog(parentComponent, message, title, messageType, icon,
602 selectionValues, initialSelectionValue) : getMockResponse().toString();
605 ///////////// end of options ///////////////
607 private static void outputMessage(Object message) {
608 System.out.println(">>> JOption Message : " + message.toString());
611 public static Object getMockResponse() {
615 public static void setMockResponse(Object mockOption) {
616 JvOptionPane.mockResponse = mockOption;
619 public static void resetMock() {
620 setMockResponse(JvOptionPane.CANCEL_OPTION);
621 setInteractiveMode(true);
624 public static boolean isInteractiveMode() {
625 return interactiveMode;
628 public static void setInteractiveMode(boolean interactive) {
629 JvOptionPane.interactiveMode = interactive;
632 private static String getPrefix(int messageType) {
636 if (Platform.isJS()) {
637 switch (messageType) {
638 case JvOptionPane.WARNING_MESSAGE:
639 prefix = "WARNING! ";
641 case JvOptionPane.ERROR_MESSAGE:
652 * create a new option dialog that can be used to register responses - along
653 * lines of showOptionDialog
658 * @param defaultOption
659 * @param plainMessage
665 public static JvOptionPane newOptionDialog() {
666 return new JvOptionPane(null);
669 public static JvOptionPane newOptionDialog(Component parentComponent) {
670 return new JvOptionPane(parentComponent);
673 public void showDialog(String message, String title, int optionType, int messageType, Icon icon, Object[] options,
674 Object initialValue) {
675 showDialog(message, title, optionType, messageType, icon, options, initialValue, true);
678 public void showDialog(Object message, String title, int optionType, int messageType, Icon icon, Object[] options,
679 Object initialValue, boolean modal) {
680 showDialog(message, title, optionType, messageType, icon, options, initialValue, modal, null);
683 public void showDialog(Object message, String title, int optionType, int messageType, Icon icon, Object[] options,
684 Object initialValue, boolean modal, JButton[] buttons) {
685 if (!isInteractiveMode()) {
686 handleResponse(getMockResponse());
692 // 1) AlignViewport for openLinkedAlignment
694 // Show a dialog with the option to open and link (cDNA <-> protein) as a
696 // alignment, either as a standalone alignment or in a split frame. Returns
697 // true if the new alignment was opened, false if not, because the user
698 // declined the offer.
700 // 2) UserDefinedColors warning about saving over a name already defined
703 ourOptions = Arrays.asList(options);
706 boolean useButtons = false;
707 Object initialValueButton = null;
708 NOTNULL: if (buttons != null) {
709 if (buttons.length != options.length) {
710 jalview.bin.Console.error("Supplied buttons array not the same length as supplied options array.");
713 int[] buttonActions = { JOptionPane.YES_OPTION, JOptionPane.NO_OPTION, JOptionPane.CANCEL_OPTION };
714 for (int i = 0; i < options.length; i++) {
715 Object o = options[i];
716 jalview.bin.Console.debug("Setting button " + i + " to '" + o.toString() + "'");
717 JButton jb = buttons[i];
719 if (o.equals(initialValue))
720 initialValueButton = jb;
722 int buttonAction = buttonActions[i];
723 Callable<Void> action = callbacks.get(buttonAction);
724 jb.setText((String) o);
725 jb.addActionListener(new ActionListener() {
727 public void actionPerformed(ActionEvent e) {
729 Object obj = e.getSource();
730 if (obj == null || !(obj instanceof Component)) {
731 jalview.bin.Console.debug("Could not find Component source of event object " + obj);
734 Object joptionpaneObject = SwingUtilities.getAncestorOfClass(JOptionPane.class, (Component) obj);
735 if (joptionpaneObject == null || !(joptionpaneObject instanceof JOptionPane)) {
736 jalview.bin.Console.debug("Could not find JOptionPane ancestor of event object " + obj);
739 JOptionPane joptionpane = (JOptionPane) joptionpaneObject;
740 joptionpane.setValue(buttonAction);
742 Executors.newSingleThreadExecutor().submit(action);
743 joptionpane.transferFocusBackward();
744 joptionpane.setVisible(false);
745 // put focus and raise parent window if possible, unless cancel or
747 boolean raiseParent = (parentComponent != null);
748 if (buttonAction == JOptionPane.CANCEL_OPTION)
750 if (optionType == JOptionPane.YES_NO_OPTION && buttonAction == JOptionPane.NO_OPTION)
753 parentComponent.requestFocus();
754 if (parentComponent instanceof JInternalFrame) {
755 JInternalFrame jif = (JInternalFrame) parentComponent;
759 } else if (parentComponent instanceof Window) {
760 Window w = (Window) parentComponent;
765 joptionpane.setVisible(false);
772 // use a JOptionPane as usual
773 int response = JOptionPane.showOptionDialog(parentComponent, message, title, optionType, messageType, icon,
774 useButtons ? buttons : options, useButtons ? initialValueButton : initialValue);
777 * In Java, the response is returned to this thread and handled here; (for
778 * Javascript, see propertyChange)
780 if (!Platform.isJS())
787 handleResponse(response);
791 * This is java similar to the swingjs handling, with the callbacks attached to
792 * the button press of the dialog. This means we can use a non-modal JDialog for
793 * the confirmation without blocking the GUI.
795 JOptionPane joptionpane = new JOptionPane();
796 // Make button options
797 int[] buttonActions = { JvOptionPane.YES_OPTION, JvOptionPane.NO_OPTION, JvOptionPane.CANCEL_OPTION };
799 // we need the strings to make the buttons with actionEventListener
800 if (options == null) {
801 ArrayList<String> options_default = new ArrayList<>();
802 options_default.add(UIManager.getString("OptionPane.yesButtonText"));
803 if (optionType == JvOptionPane.YES_NO_OPTION || optionType == JvOptionPane.YES_NO_CANCEL_OPTION) {
804 options_default.add(UIManager.getString("OptionPane.noButtonText"));
806 if (optionType == JvOptionPane.YES_NO_CANCEL_OPTION) {
807 options_default.add(UIManager.getString("OptionPane.cancelButtonText"));
809 options = options_default.toArray();
812 ArrayList<JButton> options_btns = new ArrayList<>();
813 Object initialValue_btn = null;
814 if (!Platform.isJS()) // JalviewJS already uses callback, don't need to add them here
816 for (int i = 0; i < options.length && i < 3; i++) {
817 Object o = options[i];
818 int buttonAction = buttonActions[i];
819 Callable<Void> action = callbacks.get(buttonAction);
820 JButton jb = new JButton();
821 jb.setText((String) o);
822 jb.addActionListener(new ActionListener() {
824 public void actionPerformed(ActionEvent e) {
825 joptionpane.setValue(buttonAction);
827 Executors.newSingleThreadExecutor().submit(action);
828 // joptionpane.transferFocusBackward();
829 joptionpane.transferFocusBackward();
830 joptionpane.setVisible(false);
831 // put focus and raise parent window if possible, unless cancel
833 boolean raiseParent = (parentComponent != null);
834 if (buttonAction == JvOptionPane.CANCEL_OPTION)
836 if (optionType == JvOptionPane.YES_NO_OPTION && buttonAction == JvOptionPane.NO_OPTION)
839 parentComponent.requestFocus();
840 if (parentComponent instanceof JInternalFrame) {
841 JInternalFrame jif = (JInternalFrame) parentComponent;
845 } else if (parentComponent instanceof Window) {
846 Window w = (Window) parentComponent;
851 joptionpane.setVisible(false);
854 options_btns.add(jb);
855 if (o.equals(initialValue))
856 initialValue_btn = jb;
859 joptionpane.setMessage(message);
860 joptionpane.setMessageType(messageType);
861 joptionpane.setOptionType(optionType);
862 joptionpane.setIcon(icon);
863 joptionpane.setOptions(Platform.isJS() ? options : options_btns.toArray());
864 joptionpane.setInitialValue(Platform.isJS() ? initialValue : initialValue_btn);
866 JDialog dialog = joptionpane.createDialog(parentComponent, title);
867 dialog.setIconImage(WindowIcons.logoIcon.getImage());
868 dialog.setModalityType(modal ? ModalityType.APPLICATION_MODAL : ModalityType.MODELESS);
869 dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
870 dialog.setVisible(true);
874 public void showInternalDialog(JPanel mainPanel, String title, int yesNoCancelOption, int questionMessage, Icon icon,
875 Object[] options, String initresponse) {
876 if (!isInteractiveMode()) {
877 handleResponse(getMockResponse());
880 // need to set these separately so we can set the title bar icon later
881 this.setOptionType(yesNoCancelOption);
882 this.setMessageType(questionMessage);
884 this.setInitialValue(initresponse);
885 this.setOptions(options);
886 this.setMessage(mainPanel);
888 ourOptions = Arrays.asList(options);
890 if (parentComponent != this) {
891 JInternalFrame jif = this.createInternalFrame(parentComponent, title);
892 jif.setFrameIcon(WindowIcons.logoIcon);
893 jif.addInternalFrameListener(new InternalFrameListener() {
895 public void internalFrameActivated(InternalFrameEvent arg0) {
899 public void internalFrameClosed(InternalFrameEvent arg0) {
900 JvOptionPane.this.internalDialogHandleResponse();
904 public void internalFrameClosing(InternalFrameEvent arg0) {
908 public void internalFrameDeactivated(InternalFrameEvent arg0) {
912 public void internalFrameDeiconified(InternalFrameEvent arg0) {
916 public void internalFrameIconified(InternalFrameEvent arg0) {
920 public void internalFrameOpened(InternalFrameEvent arg0) {
923 jif.setVisible(true);
927 JDialog dialog = this.createDialog(parentComponent, title);
928 dialog.setIconImage(WindowIcons.logoIcon.getImage());
929 dialog.setVisible(true); // blocking
930 this.internalDialogHandleResponse();
935 private void internalDialogHandleResponse() {
936 String responseString = (String) this.getValue();
937 int response = ourOptions.indexOf(responseString);
939 if (!Platform.isJS())
946 handleResponse(response);
951 * @Override public JvOptionPane setResponseHandler(Object response, Runnable
952 * action) { callbacks.put(response, new Callable<Void>() {
954 * @Override public Void call() { action.run(); return null; } }); return this;
958 public JvOptionPane setResponseHandler(Object response, Callable<Void> action) {
959 callbacks.put(response, action);
964 * showDialogOnTop will create a dialog that (attempts to) come to top of OS
967 public static int showDialogOnTop(String label, String actionString, int JOPTIONPANE_OPTION,
968 int JOPTIONPANE_MESSAGETYPE) {
969 // Ensure Jalview window is brought to front (primarily for Quit
970 // confirmation window to be visible)
972 // This method of raising the Jalview window is broken in java
973 // jalviewDesktop.setVisible(true);
974 // jalviewDesktop.toFront();
976 // A better hack which works is to create a new JFrame parent with
977 // setAlwaysOnTop(true)
978 JFrame dialogParent = new JFrame();
979 dialogParent.setIconImage(WindowIcons.logoIcon.getImage());
980 dialogParent.setAlwaysOnTop(true);
982 int answer = JOptionPane.showConfirmDialog(dialogParent, label, actionString, JOPTIONPANE_OPTION,
983 JOPTIONPANE_MESSAGETYPE);
985 dialogParent.setAlwaysOnTop(false);
986 dialogParent.dispose();
991 public void showDialogOnTopAsync(String label, String actionString, int JOPTIONPANE_OPTION,
992 int JOPTIONPANE_MESSAGETYPE, Icon icon, Object[] options, Object initialValue, boolean modal) {
993 JFrame frame = new JFrame();
994 frame.setIconImage(WindowIcons.logoIcon.getImage());
995 showDialogOnTopAsync(frame, label, actionString, JOPTIONPANE_OPTION, JOPTIONPANE_MESSAGETYPE, icon, options,
996 initialValue, modal);
999 public void showDialogOnTopAsync(JFrame dialogParent, Object label, String actionString, int JOPTIONPANE_OPTION,
1000 int JOPTIONPANE_MESSAGETYPE, Icon icon, Object[] options, Object initialValue, boolean modal) {
1001 showDialogOnTopAsync(dialogParent, label, actionString, JOPTIONPANE_OPTION, JOPTIONPANE_MESSAGETYPE, icon, options,
1002 initialValue, modal, null);
1005 public void showDialogOnTopAsync(JFrame dialogParent, Object label, String actionString, int JOPTIONPANE_OPTION,
1006 int JOPTIONPANE_MESSAGETYPE, Icon icon, Object[] options, Object initialValue, boolean modal, JButton[] buttons) {
1007 // Ensure Jalview window is brought to front (primarily for Quit
1008 // confirmation window to be visible)
1010 // This method of raising the Jalview window is broken in java
1011 // jalviewDesktop.setVisible(true);
1012 // jalviewDesktop.toFront();
1014 // A better hack which works is to create a new JFrame parent with
1015 // setAlwaysOnTop(true)
1016 dialogParent.setAlwaysOnTop(true);
1017 parentComponent = dialogParent;
1019 showDialog(label, actionString, JOPTIONPANE_OPTION, JOPTIONPANE_MESSAGETYPE, icon, options, initialValue, modal,
1022 dialogParent.setAlwaysOnTop(false);
1023 dialogParent.dispose();
1027 * JalviewJS signals option selection by a property change event for the option
1028 * e.g. "OK". This methods responds to that by running the response action that
1029 * corresponds to that option.
1034 public void propertyChange(PropertyChangeEvent evt) {
1035 Object newValue = evt.getNewValue();
1036 int ourOption = ourOptions.indexOf(newValue);
1037 if (ourOption >= 0) {
1038 handleResponse(ourOption);
1041 handleResponse(newValue);
1046 public void handleResponse(Object response) {
1048 * this test is for NaN in Chrome
1050 if (response != null && !response.equals(response)) {
1053 Callable<Void> action = callbacks.get(response);
1054 if (action != null) {
1057 } catch (Exception e) {
1058 e.printStackTrace();
1060 if (parentComponent != null)
1061 parentComponent.requestFocus();
1066 * Create a non-modal confirm dialog
1068 public JDialog createDialog(Component parentComponent, Object message, String title, int optionType, int messageType,
1069 Icon icon, Object[] options, Object initialValue, boolean modal) {
1070 return createDialog(parentComponent, message, title, optionType, messageType, icon, options, initialValue, modal,
1074 public JDialog createDialog(Component parentComponent, Object message, String title, int optionType, int messageType,
1075 Icon icon, Object[] options, Object initialValue, boolean modal, JButton[] buttons) {
1076 JButton[] optionsButtons = null;
1077 Object initialValueButton = null;
1078 JOptionPane joptionpane = new JOptionPane();
1079 // Make button options
1080 int[] buttonActions = { JOptionPane.YES_OPTION, JOptionPane.NO_OPTION, JOptionPane.CANCEL_OPTION };
1082 // we need the strings to make the buttons with actionEventListener
1083 if (options == null) {
1084 ArrayList<String> options_default = new ArrayList<>();
1085 options_default.add(UIManager.getString("OptionPane.yesButtonText"));
1086 if (optionType == JOptionPane.YES_NO_OPTION || optionType == JOptionPane.YES_NO_CANCEL_OPTION) {
1087 options_default.add(UIManager.getString("OptionPane.noButtonText"));
1089 if (optionType == JOptionPane.YES_NO_CANCEL_OPTION) {
1090 options_default.add(UIManager.getString("OptionPane.cancelButtonText"));
1092 options = options_default.toArray();
1094 if (!Platform.isJS()) // JalviewJS already uses callback, don't need to
1097 if (((optionType == JOptionPane.YES_OPTION || optionType == JOptionPane.NO_OPTION
1098 || optionType == JOptionPane.CANCEL_OPTION || optionType == JOptionPane.OK_OPTION
1099 || optionType == JOptionPane.DEFAULT_OPTION) && options.length < 1)
1100 || ((optionType == JOptionPane.YES_NO_OPTION || optionType == JOptionPane.OK_CANCEL_OPTION)
1101 && options.length < 2)
1102 || (optionType == JOptionPane.YES_NO_CANCEL_OPTION && options.length < 3)) {
1103 jalview.bin.Console.debug("JvOptionPane: not enough options for dialog type");
1105 optionsButtons = new JButton[options.length];
1106 for (int i = 0; i < options.length && i < 3; i++) {
1107 Object o = options[i];
1108 int buttonAction = buttonActions[i];
1109 Callable<Void> action = callbacks.get(buttonAction);
1111 if (buttons != null && buttons.length > i && buttons[i] != null) {
1116 jb.setText((String) o);
1117 jb.addActionListener(new ActionListener() {
1119 public void actionPerformed(ActionEvent e) {
1120 joptionpane.setValue(buttonAction);
1122 Executors.newSingleThreadExecutor().submit(action);
1123 // joptionpane.transferFocusBackward();
1124 joptionpane.transferFocusBackward();
1125 joptionpane.setVisible(false);
1126 // put focus and raise parent window if possible, unless cancel
1128 boolean raiseParent = (parentComponent != null);
1129 if (buttonAction == JOptionPane.CANCEL_OPTION)
1130 raiseParent = false;
1131 if (optionType == JOptionPane.YES_NO_OPTION && buttonAction == JOptionPane.NO_OPTION)
1132 raiseParent = false;
1134 parentComponent.requestFocus();
1135 if (parentComponent instanceof JInternalFrame) {
1136 JInternalFrame jif = (JInternalFrame) parentComponent;
1140 } else if (parentComponent instanceof Window) {
1141 Window w = (Window) parentComponent;
1146 joptionpane.setVisible(false);
1149 optionsButtons[i] = jb;
1150 if (o.equals(initialValue))
1151 initialValueButton = jb;
1154 joptionpane.setMessage(message);
1155 joptionpane.setMessageType(messageType);
1156 joptionpane.setOptionType(optionType);
1157 joptionpane.setIcon(icon);
1158 joptionpane.setOptions(Platform.isJS() ? options : optionsButtons);
1159 joptionpane.setInitialValue(Platform.isJS() ? initialValue : initialValueButton);
1161 JDialog dialog = joptionpane.createDialog(parentComponent, title);
1162 dialog.setIconImage(WindowIcons.logoIcon.getImage());
1163 dialog.setModalityType(modal ? ModalityType.APPLICATION_MODAL : ModalityType.MODELESS);
1164 dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
1169 * Utility to programmatically click a button on a JOptionPane (as a JFrame)
1171 * returns true if button was found
1173 public static boolean clickButton(JFrame frame, int buttonType) {
1179 * This helper method makes the JInternalFrame wait until it is notified by an
1180 * InternalFrameClosing event. This method also adds the given JOptionPane to
1181 * the JInternalFrame and sizes it according to the JInternalFrame's preferred
1184 * @param f The JInternalFrame to make modal.
1186 private static void startModal(JInternalFrame f) {
1187 // We need to add an additional glasspane-like component directly
1188 // below the frame, which intercepts all mouse events that are not
1189 // directed at the frame itself.
1190 JPanel modalInterceptor = new JPanel();
1191 modalInterceptor.setOpaque(false);
1192 JLayeredPane lp = JLayeredPane.getLayeredPaneAbove(f);
1193 lp.setLayer(modalInterceptor, JLayeredPane.MODAL_LAYER.intValue());
1194 modalInterceptor.setBounds(0, 0, lp.getWidth(), lp.getHeight());
1195 modalInterceptor.addMouseListener(new MouseAdapter() {
1197 modalInterceptor.addMouseMotionListener(new MouseMotionAdapter() {
1199 lp.add(modalInterceptor);
1202 // We need to explicitly dispatch events when we are blocking the event
1204 EventQueue queue = Toolkit.getDefaultToolkit().getSystemEventQueue();
1206 while (!f.isClosed()) {
1207 if (EventQueue.isDispatchThread()) {
1208 // The getNextEventMethod() issues wait() when no
1209 // event is available, so we don't need do explicitly wait().
1210 AWTEvent ev = queue.getNextEvent();
1211 // This mimics EventQueue.dispatchEvent(). We can't use
1212 // EventQueue.dispatchEvent() directly, because it is
1213 // protected, unfortunately.
1214 if (ev instanceof ActiveEvent)
1215 ((ActiveEvent) ev).dispatch();
1216 else if (ev.getSource() instanceof Component)
1217 ((Component) ev.getSource()).dispatchEvent(ev);
1218 else if (ev.getSource() instanceof MenuComponent)
1219 ((MenuComponent) ev.getSource()).dispatchEvent(ev);
1220 // Other events are ignored as per spec in
1221 // EventQueue.dispatchEvent
1223 // Give other threads a chance to become active.
1227 } catch (InterruptedException ex) {
1228 // If we get interrupted, then leave the modal state.
1230 // Clean up the modal interceptor.
1231 lp.remove(modalInterceptor);
1233 // Remove the internal frame from its parent, so it is no longer
1234 // lurking around and clogging memory.
1235 Container parent = f.getParent();