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.
24 import java.awt.Component;
25 import java.awt.Dialog.ModalityType;
26 import java.awt.HeadlessException;
27 import java.awt.Window;
28 import java.awt.event.ActionEvent;
29 import java.awt.event.ActionListener;
30 import java.beans.PropertyChangeEvent;
31 import java.beans.PropertyChangeListener;
32 import java.util.ArrayList;
33 import java.util.Arrays;
34 import java.util.HashMap;
35 import java.util.List;
37 import java.util.concurrent.Callable;
38 import java.util.concurrent.Executors;
40 import javax.swing.Icon;
41 import javax.swing.JButton;
42 import javax.swing.JDialog;
43 import javax.swing.JFrame;
44 import javax.swing.JInternalFrame;
45 import javax.swing.JOptionPane;
46 import javax.swing.JPanel;
47 import javax.swing.UIManager;
49 import jalview.util.Platform;
50 import jalview.util.dialogrunner.DialogRunnerI;
52 public class JvOptionPane extends JOptionPane
53 implements DialogRunnerI, PropertyChangeListener
55 private static final long serialVersionUID = -3019167117756785229L;
57 private static Object mockResponse = JOptionPane.CANCEL_OPTION;
59 private static boolean interactiveMode = true;
61 private Component parentComponent;
63 private Map<Object, Callable> callbacks = new HashMap<>();
66 * JalviewJS reports user choice in the dialog as the selected
67 * option (text); this list allows conversion to index (int)
69 List<Object> ourOptions;
71 public JvOptionPane(final Component parent)
73 this.parentComponent = Platform.isJS() ? this : parent;
76 public static int showConfirmDialog(Component parentComponent,
77 Object message) throws HeadlessException
79 // only called by test
80 return isInteractiveMode()
81 ? JOptionPane.showConfirmDialog(parentComponent, message)
82 : (int) getMockResponse();
86 * Message, title, optionType
88 * @param parentComponent
93 * @throws HeadlessException
95 public static int showConfirmDialog(Component parentComponent,
96 Object message, String title, int optionType)
97 throws HeadlessException
99 if (!isInteractiveMode())
101 return (int) getMockResponse();
105 case JOptionPane.YES_NO_CANCEL_OPTION:
106 // FeatureRenderer amendFeatures ?? TODO ??
111 case JOptionPane.YES_NO_OPTION:
112 // PromptUserConfig usage stats
113 // for now treated as "OK CANCEL"
115 case JOptionPane.OK_CANCEL_OPTION:
116 // will fall back to simple HTML
117 return JOptionPane.showConfirmDialog(parentComponent, message, title,
123 * Adds a message type. Fallback is to just add it in the beginning.
125 * @param parentComponent
131 * @throws HeadlessException
133 public static int showConfirmDialog(Component parentComponent,
134 Object message, String title, int optionType, int messageType)
135 throws HeadlessException
137 // JalviewServicesChanged
138 // PromptUserConfig raiseDialog
139 return isInteractiveMode()
140 ? JOptionPane.showConfirmDialog(parentComponent, message, title,
141 optionType, messageType)
142 : (int) getMockResponse();
148 * @param parentComponent
155 * @throws HeadlessException
157 public static int showConfirmDialog(Component parentComponent,
158 Object message, String title, int optionType, int messageType,
159 Icon icon) throws HeadlessException
161 // JvOptionPaneTest only
162 return isInteractiveMode()
163 ? JOptionPane.showConfirmDialog(parentComponent, message, title,
164 optionType, messageType, icon)
165 : (int) getMockResponse();
169 * Internal version "OK"
171 * @param parentComponent
175 public static int showInternalConfirmDialog(Component parentComponent,
178 // JvOptionPaneTest only;
179 return isInteractiveMode()
180 ? JOptionPane.showInternalConfirmDialog(parentComponent,
182 : (int) getMockResponse();
186 * Internal version -- changed to standard version for now
188 * @param parentComponent
194 public static int showInternalConfirmDialog(Component parentComponent,
195 String message, String title, int optionType)
197 if (!isInteractiveMode())
199 return (int) getMockResponse();
203 case JOptionPane.YES_NO_CANCEL_OPTION:
204 // ColourMenuHelper.addMenuItmers.offerRemoval TODO
205 case JOptionPane.YES_NO_OPTION:
206 // UserDefinedColoursSave -- relevant? TODO
209 case JOptionPane.OK_CANCEL_OPTION:
211 // EditNameDialog --- uses panel for messsage TODO
213 // Desktop.inputURLMenuItem
215 return JOptionPane.showConfirmDialog(parentComponent, message, title,
222 * @param parentComponent
229 public static int showInternalConfirmDialog(Component parentComponent,
230 Object message, String title, int optionType, int messageType)
232 if (!isInteractiveMode())
234 return (int) getMockResponse();
238 case JOptionPane.YES_NO_CANCEL_OPTION:
239 case JOptionPane.YES_NO_OPTION:
240 // UserQuestionanaireCheck
244 case JOptionPane.OK_CANCEL_OPTION:
245 // will fall back to simple HTML
246 return JOptionPane.showConfirmDialog(parentComponent, message, title,
247 optionType, messageType);
252 * adds icon; no longer internal
254 * @param parentComponent
262 public static int showInternalConfirmDialog(Component parentComponent,
263 Object message, String title, int optionType, int messageType,
266 if (!isInteractiveMode())
268 return (int) getMockResponse();
272 case JOptionPane.YES_NO_CANCEL_OPTION:
273 case JOptionPane.YES_NO_OPTION:
276 case JOptionPane.OK_CANCEL_OPTION:
277 // Preferences editLink/newLink
278 return JOptionPane.showConfirmDialog(parentComponent, message, title,
279 optionType, messageType, icon);
285 * custom options full-featured
287 * @param parentComponent
294 * @param initialValue
296 * @throws HeadlessException
298 public static int showOptionDialog(Component parentComponent,
299 String message, String title, int optionType, int messageType,
300 Icon icon, Object[] options, Object initialValue)
301 throws HeadlessException
303 if (!isInteractiveMode())
305 return (int) getMockResponse();
311 // 1) AlignViewport for openLinkedAlignment
313 // Show a dialog with the option to open and link (cDNA <-> protein) as a
315 // alignment, either as a standalone alignment or in a split frame. Returns
316 // true if the new alignment was opened, false if not, because the user
317 // declined the offer.
319 // 2) UserDefinedColors warning about saving over a name already defined
321 return JOptionPane.showOptionDialog(parentComponent, message, title,
322 optionType, messageType, icon, options, initialValue);
329 * @throws HeadlessException
331 public static void showMessageDialog(Component parentComponent,
332 String message) throws HeadlessException
334 if (!isInteractiveMode())
336 outputMessage(message);
342 JOptionPane.showMessageDialog(parentComponent, message);
346 * OK with message, title, and type
348 * @param parentComponent
352 * @throws HeadlessException
354 public static void showMessageDialog(Component parentComponent,
355 String message, String title, int messageType)
356 throws HeadlessException
358 // 30 implementations -- all just fine.
360 if (!isInteractiveMode())
362 outputMessage(message);
366 JOptionPane.showMessageDialog(parentComponent,
367 getPrefix(messageType) + message, title, messageType);
371 * adds title and icon
373 * @param parentComponent
378 * @throws HeadlessException
380 public static void showMessageDialog(Component parentComponent,
381 String message, String title, int messageType, Icon icon)
382 throws HeadlessException
387 if (!isInteractiveMode())
389 outputMessage(message);
393 JOptionPane.showMessageDialog(parentComponent, message, title,
401 public static void showInternalMessageDialog(Component parentComponent,
405 // WsPreferences only
407 if (!isInteractiveMode())
409 outputMessage(message);
413 JOptionPane.showMessageDialog(parentComponent, message);
417 * Adds title and messageType
419 * @param parentComponent
424 public static void showInternalMessageDialog(Component parentComponent,
425 String message, String title, int messageType)
430 if (!isInteractiveMode())
432 outputMessage(message);
436 JOptionPane.showMessageDialog(parentComponent,
437 getPrefix(messageType) + message, title, messageType);
442 * @param parentComponent
448 public static void showInternalMessageDialog(Component parentComponent,
449 Object message, String title, int messageType, Icon icon)
454 if (!isInteractiveMode())
456 outputMessage(message);
460 JOptionPane.showMessageDialog(parentComponent, message, title,
468 * @throws HeadlessException
470 public static String showInputDialog(Object message)
471 throws HeadlessException
475 if (!isInteractiveMode())
477 return getMockResponse().toString();
480 return JOptionPane.showInputDialog(message);
484 * adds inital selection value
487 * @param initialSelectionValue
490 public static String showInputDialog(String message,
491 String initialSelectionValue)
493 if (!isInteractiveMode())
495 return getMockResponse().toString();
498 // AnnotationPanel character option
500 return JOptionPane.showInputDialog(message, initialSelectionValue);
504 * adds inital selection value
507 * @param initialSelectionValue
510 public static String showInputDialog(Object message,
511 Object initialSelectionValue)
513 if (!isInteractiveMode())
515 return getMockResponse().toString();
518 // AnnotationPanel character option
520 return JOptionPane.showInputDialog(message, initialSelectionValue);
526 * @param parentComponent
529 * @throws HeadlessException
531 public static String showInputDialog(Component parentComponent,
532 String message) throws HeadlessException
536 return isInteractiveMode()
537 ? JOptionPane.showInputDialog(parentComponent, message)
538 : getMockResponse().toString();
542 * input with initial selection
544 * @param parentComponent
546 * @param initialSelectionValue
549 public static String showInputDialog(Component parentComponent,
550 String message, String initialSelectionValue)
555 return isInteractiveMode()
556 ? JOptionPane.showInputDialog(parentComponent, message,
557 initialSelectionValue)
558 : getMockResponse().toString();
562 * input with initial selection
564 * @param parentComponent
566 * @param initialSelectionValue
569 public static String showInputDialog(Component parentComponent,
570 Object message, Object initialSelectionValue)
575 return isInteractiveMode()
576 ? JOptionPane.showInputDialog(parentComponent, message,
577 initialSelectionValue)
578 : getMockResponse().toString();
583 * @param parentComponent
588 * @throws HeadlessException
590 public static String showInputDialog(Component parentComponent,
591 String message, String title, int messageType)
592 throws HeadlessException
597 return isInteractiveMode()
598 ? JOptionPane.showInputDialog(parentComponent, message, title,
600 : getMockResponse().toString();
604 * Customized input option
606 * @param parentComponent
611 * @param selectionValues
612 * @param initialSelectionValue
614 * @throws HeadlessException
616 public static Object showInputDialog(Component parentComponent,
617 Object message, String title, int messageType, Icon icon,
618 Object[] selectionValues, Object initialSelectionValue)
619 throws HeadlessException
624 return isInteractiveMode()
625 ? JOptionPane.showInputDialog(parentComponent, message, title,
626 messageType, icon, selectionValues,
627 initialSelectionValue)
628 : getMockResponse().toString();
634 * @param parentComponent
638 public static String showInternalInputDialog(Component parentComponent,
643 return isInteractiveMode()
644 ? JOptionPane.showInternalInputDialog(parentComponent, message)
645 : getMockResponse().toString();
649 * internal with title and messageType
651 * @param parentComponent
657 public static String showInternalInputDialog(Component parentComponent,
658 String message, String title, int messageType)
661 // AlignFrame tabbedPane_mousePressed
663 return isInteractiveMode()
664 ? JOptionPane.showInternalInputDialog(parentComponent,
665 getPrefix(messageType) + message, title, messageType)
666 : getMockResponse().toString();
670 * customized internal
672 * @param parentComponent
677 * @param selectionValues
678 * @param initialSelectionValue
681 public static Object showInternalInputDialog(Component parentComponent,
682 String message, String title, int messageType, Icon icon,
683 Object[] selectionValues, Object initialSelectionValue)
687 return isInteractiveMode()
688 ? JOptionPane.showInternalInputDialog(parentComponent, message,
689 title, messageType, icon, selectionValues,
690 initialSelectionValue)
691 : getMockResponse().toString();
694 ///////////// end of options ///////////////
696 private static void outputMessage(Object message)
698 System.out.println(">>> JOption Message : " + message.toString());
701 public static Object getMockResponse()
706 public static void setMockResponse(Object mockOption)
708 JvOptionPane.mockResponse = mockOption;
711 public static void resetMock()
713 setMockResponse(JOptionPane.CANCEL_OPTION);
714 setInteractiveMode(true);
717 public static boolean isInteractiveMode()
719 return interactiveMode;
722 public static void setInteractiveMode(boolean interactive)
724 JvOptionPane.interactiveMode = interactive;
727 private static String getPrefix(int messageType)
736 case JOptionPane.WARNING_MESSAGE:
737 prefix = "WARNING! ";
739 case JOptionPane.ERROR_MESSAGE:
750 * create a new option dialog that can be used to register responses - along
751 * lines of showOptionDialog
756 * @param defaultOption
757 * @param plainMessage
763 public static JvOptionPane newOptionDialog()
765 return new JvOptionPane(null);
768 public static JvOptionPane newOptionDialog(Component parentComponent)
770 return new JvOptionPane(parentComponent);
773 public void showDialog(String message, String title, int optionType,
774 int messageType, Icon icon, Object[] options, Object initialValue)
776 showDialog(message, title, optionType, messageType, icon, options,
780 public void showDialog(String message, String title, int optionType,
781 int messageType, Icon icon, Object[] options, Object initialValue,
784 if (!isInteractiveMode())
786 handleResponse(getMockResponse());
792 // 1) AlignViewport for openLinkedAlignment
794 // Show a dialog with the option to open and link (cDNA <-> protein) as a
796 // alignment, either as a standalone alignment or in a split frame. Returns
797 // true if the new alignment was opened, false if not, because the user
798 // declined the offer.
800 // 2) UserDefinedColors warning about saving over a name already defined
803 ourOptions = Arrays.asList(options);
807 // use a JOptionPane as usual
808 int response = JOptionPane.showOptionDialog(parentComponent, message,
809 title, optionType, messageType, icon, options, initialValue);
812 * In Java, the response is returned to this thread and handled here;
813 * (for Javascript, see propertyChange)
815 if (!Platform.isJS())
822 handleResponse(response);
828 * This is java similar to the swingjs handling, with the callbacks
829 * attached to the button press of the dialog. This means we can use
830 * a non-modal JDialog for the confirmation without blocking the GUI.
833 JDialog dialog = this.createDialog(parentComponent, message, title,
834 optionType, messageType, icon, options, initialValue, modal);
835 jalview.bin.Console.debug("About to setVisible(true)");
836 dialog.setVisible(true);
837 jalview.bin.Console.debug("Just setVisible(true)");
841 public void showInternalDialog(JPanel mainPanel, String title,
842 int yesNoCancelOption, int questionMessage, Icon icon,
843 Object[] options, String initresponse)
845 if (!isInteractiveMode())
847 handleResponse(getMockResponse());
850 ourOptions = Arrays.asList(options);
852 if (parentComponent != this)
854 response = JOptionPane.showInternalOptionDialog(parentComponent,
855 mainPanel, title, yesNoCancelOption, questionMessage, icon,
856 options, initresponse);
860 response = JOptionPane.showOptionDialog(parentComponent, mainPanel,
861 title, yesNoCancelOption, questionMessage, icon, options,
864 if (!Platform.isJS())
871 handleResponse(response);
877 public JvOptionPane setResponseHandler(Object response, Runnable action)
879 callbacks.put(response, new Callable<Void>()
892 public JvOptionPane setResponseHandler(Object response, Callable action)
894 callbacks.put(response, action);
899 * showDialogOnTop will create a dialog that (attempts to) come to top of OS
902 public static int showDialogOnTop(String label, String actionString,
903 int JOPTIONPANE_OPTION, int JOPTIONPANE_MESSAGETYPE)
905 // Ensure Jalview window is brought to front (primarily for Quit
906 // confirmation window to be visible)
908 // This method of raising the Jalview window is broken in java
909 // jalviewDesktop.setVisible(true);
910 // jalviewDesktop.toFront();
912 // A better hack which works is to create a new JFrame parent with
913 // setAlwaysOnTop(true)
914 JFrame dialogParent = new JFrame();
915 dialogParent.setAlwaysOnTop(true);
917 int answer = JOptionPane.showConfirmDialog(dialogParent, label,
918 actionString, JOPTIONPANE_OPTION, JOPTIONPANE_MESSAGETYPE);
920 dialogParent.setAlwaysOnTop(false);
921 jalview.bin.Console.debug("*********** BEFORE dialogParent.dispose()");
922 dialogParent.dispose();
923 jalview.bin.Console.debug("*********** BEFORE dialogParent.dispose()");
928 public void showDialogOnTopAsync(String label, String actionString,
929 int JOPTIONPANE_OPTION, int JOPTIONPANE_MESSAGETYPE, Icon icon,
930 Object[] options, Object initialValue, boolean modal)
932 // Ensure Jalview window is brought to front (primarily for Quit
933 // confirmation window to be visible)
935 // This method of raising the Jalview window is broken in java
936 // jalviewDesktop.setVisible(true);
937 // jalviewDesktop.toFront();
939 // A better hack which works is to create a new JFrame parent with
940 // setAlwaysOnTop(true)
941 JFrame dialogParent = new JFrame();
942 dialogParent.setAlwaysOnTop(true);
943 parentComponent = dialogParent;
945 showDialog(label, actionString, JOPTIONPANE_OPTION,
946 JOPTIONPANE_MESSAGETYPE, icon, options, initialValue, modal);
948 dialogParent.setAlwaysOnTop(false);
949 dialogParent.dispose();
953 * JalviewJS signals option selection by a property change event for the
954 * option e.g. "OK". This methods responds to that by running the response
955 * action that corresponds to that option.
960 public void propertyChange(PropertyChangeEvent evt)
962 Object newValue = evt.getNewValue();
963 int ourOption = ourOptions.indexOf(newValue);
966 handleResponse(ourOption);
971 handleResponse(newValue);
976 public void handleResponse(Object response)
979 * this test is for NaN in Chrome
981 if (response != null && !response.equals(response))
985 Callable<Void> action = callbacks.get(response);
991 } catch (Exception e)
995 if (parentComponent != null)
996 parentComponent.requestFocus();
1001 * Create a non-modal confirm dialog
1003 public JDialog createDialog(Component parentComponent, String message,
1004 String title, int optionType, int messageType, Icon icon,
1005 Object[] options, Object initialValue, boolean modal)
1007 JOptionPane joptionpane = new JOptionPane();
1008 // Make button options
1009 int[] buttonActions = { JOptionPane.YES_OPTION, JOptionPane.NO_OPTION,
1010 JOptionPane.CANCEL_OPTION };
1012 // we need the strings to make the buttons with actionEventListener
1013 if (options == null)
1015 ArrayList<String> options_default = new ArrayList<>();
1016 options_default.add(UIManager.getString("OptionPane.yesButtonText"));
1017 if (optionType == JOptionPane.YES_NO_OPTION
1018 || optionType == JOptionPane.YES_NO_CANCEL_OPTION)
1020 options_default.add(UIManager.getString("OptionPane.noButtonText"));
1022 if (optionType == JOptionPane.YES_NO_CANCEL_OPTION)
1025 .add(UIManager.getString("OptionPane.cancelButtonText"));
1027 options = options_default.toArray();
1029 ArrayList<JButton> options_btns = new ArrayList<>();
1030 Object initialValue_btn = null;
1031 if (!Platform.isJS()) // JalviewJS already uses callback, don't need to
1034 if (((optionType == JOptionPane.YES_NO_OPTION
1035 || optionType == JOptionPane.OK_CANCEL_OPTION)
1036 && options.length < 2)
1037 || (optionType == JOptionPane.YES_NO_CANCEL_OPTION
1038 && options.length < 3))
1041 .debug("JvOptionPane: not enough options for dialog type");
1043 for (int i = 0; i < options.length && i < 3; i++)
1045 Object o = options[i];
1046 int buttonAction = buttonActions[i];
1047 Callable action = callbacks.get(buttonAction);
1048 JButton jb = new JButton();
1049 jb.setText((String) o);
1050 jb.addActionListener(new ActionListener()
1053 public void actionPerformed(ActionEvent e)
1055 joptionpane.setValue(buttonAction);
1057 Executors.newSingleThreadExecutor().submit(action);
1058 // joptionpane.transferFocusBackward();
1059 joptionpane.transferFocusBackward();
1060 joptionpane.setVisible(false);
1061 // put focus and raise parent window if possible, unless cancel
1063 boolean raiseParent = (parentComponent != null);
1064 if (buttonAction == JOptionPane.CANCEL_OPTION)
1065 raiseParent = false;
1066 if (optionType == JOptionPane.YES_NO_OPTION
1067 && buttonAction == JOptionPane.NO_OPTION)
1068 raiseParent = false;
1071 parentComponent.requestFocus();
1072 if (parentComponent instanceof JInternalFrame)
1074 JInternalFrame jif = (JInternalFrame) parentComponent;
1079 else if (parentComponent instanceof Window)
1081 Window w = (Window) parentComponent;
1086 joptionpane.setVisible(false);
1089 options_btns.add(jb);
1090 if (o.equals(initialValue))
1091 initialValue_btn = jb;
1094 joptionpane.setMessage(message);
1095 joptionpane.setMessageType(messageType);
1096 joptionpane.setOptionType(optionType);
1097 joptionpane.setIcon(icon);
1099 .setOptions(Platform.isJS() ? options : options_btns.toArray());
1100 joptionpane.setInitialValue(
1101 Platform.isJS() ? initialValue : initialValue_btn);
1103 JDialog dialog = joptionpane.createDialog(parentComponent, title);
1104 dialog.setModalityType(
1105 modal ? ModalityType.APPLICATION_MODAL : ModalityType.MODELESS);
1106 dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
1111 * Utility to programmatically click a button on a JOptionPane (as a JFrame)
1113 * returns true if button was found
1115 public static boolean clickButton(JFrame frame, int buttonType)