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.AWTEvent;
25 import java.awt.ActiveEvent;
26 import java.awt.Component;
27 import java.awt.Container;
28 import java.awt.Dialog.ModalityType;
29 import java.awt.EventQueue;
30 import java.awt.HeadlessException;
31 import java.awt.MenuComponent;
32 import java.awt.Toolkit;
33 import java.awt.Window;
34 import java.awt.event.ActionEvent;
35 import java.awt.event.ActionListener;
36 import java.awt.event.MouseAdapter;
37 import java.awt.event.MouseMotionAdapter;
38 import java.beans.PropertyChangeEvent;
39 import java.beans.PropertyChangeListener;
40 import java.util.ArrayList;
41 import java.util.Arrays;
42 import java.util.HashMap;
43 import java.util.List;
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.JInternalFrame;
51 import javax.swing.JLayeredPane;
52 import javax.swing.JOptionPane;
53 import javax.swing.JPanel;
54 import javax.swing.UIManager;
55 import javax.swing.event.InternalFrameEvent;
56 import javax.swing.event.InternalFrameListener;
58 import jalview.util.Platform;
59 import jalview.util.dialogrunner.DialogRunnerI;
61 public class JvOptionPane extends JOptionPane
62 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, Runnable> callbacks = new HashMap<>();
75 * JalviewJS reports user choice in the dialog as the selected
76 * option (text); this list allows conversion to index (int)
78 List<Object> ourOptions;
80 public JvOptionPane(final Component parent)
82 this.parentComponent = Platform.isJS() ? this : parent;
86 public static int showConfirmDialog(Component parentComponent,
87 Object message) throws HeadlessException
89 // only called by test
90 return isInteractiveMode()
91 ? JOptionPane.showConfirmDialog(parentComponent, message)
92 : (int) getMockResponse();
96 * Message, title, optionType
98 * @param parentComponent
103 * @throws HeadlessException
105 public static int showConfirmDialog(Component parentComponent,
106 Object message, String title, int optionType)
107 throws HeadlessException
109 if (!isInteractiveMode())
111 return (int) getMockResponse();
115 case JvOptionPane.YES_NO_CANCEL_OPTION:
116 // FeatureRenderer amendFeatures ?? TODO ??
121 case JvOptionPane.YES_NO_OPTION:
122 // PromptUserConfig usage stats
123 // for now treated as "OK CANCEL"
125 case JvOptionPane.OK_CANCEL_OPTION:
126 // will fall back to simple HTML
127 return JOptionPane.showConfirmDialog(parentComponent, message, title,
133 * Adds a message type. Fallback is to just add it in the beginning.
135 * @param parentComponent
141 * @throws HeadlessException
143 public static int showConfirmDialog(Component parentComponent,
144 Object message, String title, int optionType, int messageType)
145 throws HeadlessException
147 // JalviewServicesChanged
148 // PromptUserConfig raiseDialog
149 return isInteractiveMode()
150 ? JOptionPane.showConfirmDialog(parentComponent, message, title,
151 optionType, messageType)
152 : (int) getMockResponse();
158 * @param parentComponent
165 * @throws HeadlessException
167 public static int showConfirmDialog(Component parentComponent,
168 Object message, String title, int optionType, int messageType,
169 Icon icon) throws HeadlessException
171 // JvOptionPaneTest only
172 return isInteractiveMode()
173 ? JOptionPane.showConfirmDialog(parentComponent, message, title,
174 optionType, messageType, icon)
175 : (int) getMockResponse();
179 * Internal version "OK"
181 * @param parentComponent
185 public static int showInternalConfirmDialog(Component parentComponent,
188 // JvOptionPaneTest only;
189 return isInteractiveMode()
190 ? JOptionPane.showInternalConfirmDialog(parentComponent,
192 : (int) getMockResponse();
196 * Internal version -- changed to standard version for now
198 * @param parentComponent
204 public static int showInternalConfirmDialog(Component parentComponent,
205 String message, String title, int optionType)
207 if (!isInteractiveMode())
209 return (int) getMockResponse();
213 case JvOptionPane.YES_NO_CANCEL_OPTION:
214 // ColourMenuHelper.addMenuItmers.offerRemoval TODO
215 case JvOptionPane.YES_NO_OPTION:
216 // UserDefinedColoursSave -- relevant? TODO
219 case JvOptionPane.OK_CANCEL_OPTION:
221 // EditNameDialog --- uses panel for messsage TODO
223 // Desktop.inputURLMenuItem
225 return JOptionPane.showConfirmDialog(parentComponent, message, title,
232 * @param parentComponent
239 public static int showInternalConfirmDialog(Component parentComponent,
240 Object message, String title, int optionType, int messageType)
242 if (!isInteractiveMode())
244 return (int) getMockResponse();
248 case JvOptionPane.YES_NO_CANCEL_OPTION:
249 case JvOptionPane.YES_NO_OPTION:
250 // UserQuestionanaireCheck
254 case JvOptionPane.OK_CANCEL_OPTION:
255 // will fall back to simple HTML
256 return JOptionPane.showConfirmDialog(parentComponent, message, title,
257 optionType, messageType);
262 * adds icon; no longer internal
264 * @param parentComponent
272 public static int showInternalConfirmDialog(Component parentComponent,
273 Object message, String title, int optionType, int messageType,
276 if (!isInteractiveMode())
278 return (int) getMockResponse();
282 case JvOptionPane.YES_NO_CANCEL_OPTION:
283 case JvOptionPane.YES_NO_OPTION:
286 case JvOptionPane.OK_CANCEL_OPTION:
287 // Preferences editLink/newLink
288 return JOptionPane.showConfirmDialog(parentComponent, message, title,
289 optionType, messageType, icon);
295 * custom options full-featured
297 * @param parentComponent
304 * @param initialValue
306 * @throws HeadlessException
308 public static int showOptionDialog(Component parentComponent,
309 String message, String title, int optionType, int messageType,
310 Icon icon, Object[] options, Object initialValue)
311 throws HeadlessException
313 if (!isInteractiveMode())
315 return (int) getMockResponse();
321 // 1) AlignViewport for openLinkedAlignment
323 // Show a dialog with the option to open and link (cDNA <-> protein) as a
325 // alignment, either as a standalone alignment or in a split frame. Returns
326 // true if the new alignment was opened, false if not, because the user
327 // declined the offer.
329 // 2) UserDefinedColors warning about saving over a name already defined
331 return JOptionPane.showOptionDialog(parentComponent, message, title,
332 optionType, messageType, icon, options, initialValue);
339 * @throws HeadlessException
341 public static void showMessageDialog(Component parentComponent,
342 String message) throws HeadlessException
344 if (!isInteractiveMode())
346 outputMessage(message);
352 JOptionPane.showMessageDialog(parentComponent, message);
356 * OK with message, title, and type
358 * @param parentComponent
362 * @throws HeadlessException
364 public static void showMessageDialog(Component parentComponent,
365 String message, String title, int messageType)
366 throws HeadlessException
368 // 30 implementations -- all just fine.
370 if (!isInteractiveMode())
372 outputMessage(message);
376 JOptionPane.showMessageDialog(parentComponent,
377 getPrefix(messageType) + message, title, messageType);
381 * adds title and icon
383 * @param parentComponent
388 * @throws HeadlessException
390 public static void showMessageDialog(Component parentComponent,
391 String message, String title, int messageType, Icon icon)
392 throws HeadlessException
397 if (!isInteractiveMode())
399 outputMessage(message);
403 JOptionPane.showMessageDialog(parentComponent, message, title,
411 public static void showInternalMessageDialog(Component parentComponent,
415 // WsPreferences only
417 if (!isInteractiveMode())
419 outputMessage(message);
423 JOptionPane.showMessageDialog(parentComponent, message);
427 * Adds title and messageType
429 * @param parentComponent
434 public static void showInternalMessageDialog(Component parentComponent,
435 String message, String title, int messageType)
440 if (!isInteractiveMode())
442 outputMessage(message);
446 JOptionPane.showMessageDialog(parentComponent,
447 getPrefix(messageType) + message, title, messageType);
452 * @param parentComponent
458 public static void showInternalMessageDialog(Component parentComponent,
459 Object message, String title, int messageType, Icon icon)
464 if (!isInteractiveMode())
466 outputMessage(message);
470 JOptionPane.showMessageDialog(parentComponent, message, title,
478 * @throws HeadlessException
480 public static String showInputDialog(Object message)
481 throws HeadlessException
485 if (!isInteractiveMode())
487 return getMockResponse().toString();
490 return JOptionPane.showInputDialog(message);
494 * adds inital selection value
497 * @param initialSelectionValue
500 public static String showInputDialog(String message,
501 String initialSelectionValue)
503 if (!isInteractiveMode())
505 return getMockResponse().toString();
508 // AnnotationPanel character option
510 return JOptionPane.showInputDialog(message, initialSelectionValue);
514 * adds inital selection value
517 * @param initialSelectionValue
520 public static String showInputDialog(Object message,
521 Object initialSelectionValue)
523 if (!isInteractiveMode())
525 return getMockResponse().toString();
528 // AnnotationPanel character option
530 return JOptionPane.showInputDialog(message, initialSelectionValue);
536 * @param parentComponent
539 * @throws HeadlessException
541 public static String showInputDialog(Component parentComponent,
542 String message) throws HeadlessException
546 return isInteractiveMode()
547 ? JOptionPane.showInputDialog(parentComponent, message)
548 : getMockResponse().toString();
552 * input with initial selection
554 * @param parentComponent
556 * @param initialSelectionValue
559 public static String showInputDialog(Component parentComponent,
560 String message, String initialSelectionValue)
565 return isInteractiveMode()
566 ? JOptionPane.showInputDialog(parentComponent, message,
567 initialSelectionValue)
568 : getMockResponse().toString();
572 * input with initial selection
574 * @param parentComponent
576 * @param initialSelectionValue
579 public static String showInputDialog(Component parentComponent,
580 Object message, Object initialSelectionValue)
585 return isInteractiveMode()
586 ? JOptionPane.showInputDialog(parentComponent, message,
587 initialSelectionValue)
588 : getMockResponse().toString();
593 * @param parentComponent
598 * @throws HeadlessException
600 public static String showInputDialog(Component parentComponent,
601 String message, String title, int messageType)
602 throws HeadlessException
607 return isInteractiveMode()
608 ? JOptionPane.showInputDialog(parentComponent, message, title,
610 : getMockResponse().toString();
614 * Customized input option
616 * @param parentComponent
621 * @param selectionValues
622 * @param initialSelectionValue
624 * @throws HeadlessException
626 public static Object showInputDialog(Component parentComponent,
627 Object message, String title, int messageType, Icon icon,
628 Object[] selectionValues, Object initialSelectionValue)
629 throws HeadlessException
634 return isInteractiveMode()
635 ? JOptionPane.showInputDialog(parentComponent, message, title,
636 messageType, icon, selectionValues,
637 initialSelectionValue)
638 : getMockResponse().toString();
644 * @param parentComponent
648 public static String showInternalInputDialog(Component parentComponent,
653 return isInteractiveMode()
654 ? JOptionPane.showInternalInputDialog(parentComponent, message)
655 : getMockResponse().toString();
659 * internal with title and messageType
661 * @param parentComponent
667 public static String showInternalInputDialog(Component parentComponent,
668 String message, String title, int messageType)
671 // AlignFrame tabbedPane_mousePressed
673 return isInteractiveMode()
674 ? JOptionPane.showInternalInputDialog(parentComponent,
675 getPrefix(messageType) + message, title, messageType)
676 : getMockResponse().toString();
680 * customized internal
682 * @param parentComponent
687 * @param selectionValues
688 * @param initialSelectionValue
691 public static Object showInternalInputDialog(Component parentComponent,
692 String message, String title, int messageType, Icon icon,
693 Object[] selectionValues, Object initialSelectionValue)
697 return isInteractiveMode()
698 ? JOptionPane.showInternalInputDialog(parentComponent, message,
699 title, messageType, icon, selectionValues,
700 initialSelectionValue)
701 : getMockResponse().toString();
704 ///////////// end of options ///////////////
706 private static void outputMessage(Object message)
708 System.out.println(">>> JOption Message : " + message.toString());
711 public static Object getMockResponse()
716 public static void setMockResponse(Object mockOption)
718 JvOptionPane.mockResponse = mockOption;
721 public static void resetMock()
723 setMockResponse(JvOptionPane.CANCEL_OPTION);
724 setInteractiveMode(true);
727 public static boolean isInteractiveMode()
729 return interactiveMode;
732 public static void setInteractiveMode(boolean interactive)
734 JvOptionPane.interactiveMode = interactive;
737 private static String getPrefix(int messageType)
746 case JvOptionPane.WARNING_MESSAGE:
747 prefix = "WARNING! ";
749 case JvOptionPane.ERROR_MESSAGE:
760 * create a new option dialog that can be used to register responses - along
761 * lines of showOptionDialog
766 * @param defaultOption
767 * @param plainMessage
773 public static JvOptionPane newOptionDialog(Component parentComponent)
775 return new JvOptionPane(parentComponent);
778 public void showDialog(String message, String title, int optionType,
779 int messageType, Icon icon, Object[] options, Object initialValue)
781 showDialog(message, title, optionType, messageType, icon, options,
785 public void showDialog(String message, String title, int optionType,
786 int messageType, Icon icon, Object[] options, Object initialValue,
789 if (!isInteractiveMode())
791 handleResponse(getMockResponse());
797 // 1) AlignViewport for openLinkedAlignment
799 // Show a dialog with the option to open and link (cDNA <-> protein) as a
801 // alignment, either as a standalone alignment or in a split frame. Returns
802 // true if the new alignment was opened, false if not, because the user
803 // declined the offer.
805 // 2) UserDefinedColors warning about saving over a name already defined
808 ourOptions = Arrays.asList(options);
812 // use a JOptionPane as usual
813 int response = JOptionPane.showOptionDialog(parentComponent, message,
814 title, optionType, messageType, icon, options, initialValue);
817 * In Java, the response is returned to this thread and handled here;
818 * (for Javascript, see propertyChange)
820 if (!Platform.isJS())
827 handleResponse(response);
833 * This is java similar to the swingjs handling, with the callbacks
834 * attached to the button press of the dialog. This means we can use
835 * a non-modal JDialog for the confirmation without blocking the GUI.
837 JOptionPane joptionpane = new JOptionPane();
838 // Make button options
839 int[] buttonActions = { JvOptionPane.YES_OPTION,
840 JvOptionPane.NO_OPTION, JvOptionPane.CANCEL_OPTION };
842 // we need the strings to make the buttons with actionEventListener
845 ArrayList<String> options_default = new ArrayList<>();
847 .add(UIManager.getString("OptionPane.yesButtonText"));
848 if (optionType == JvOptionPane.YES_NO_OPTION
849 || optionType == JvOptionPane.YES_NO_CANCEL_OPTION)
852 .add(UIManager.getString("OptionPane.noButtonText"));
854 if (optionType == JvOptionPane.YES_NO_CANCEL_OPTION)
857 .add(UIManager.getString("OptionPane.cancelButtonText"));
859 options = options_default.toArray();
862 ArrayList<JButton> options_btns = new ArrayList<>();
863 Object initialValue_btn = null;
864 if (!Platform.isJS()) // JalviewJS already uses callback, don't need to
867 for (int i = 0; i < options.length && i < 3; i++)
869 Object o = options[i];
870 int buttonAction = buttonActions[i];
871 Runnable action = callbacks.get(buttonAction);
872 JButton jb = new JButton();
873 jb.setText((String) o);
874 jb.addActionListener(new ActionListener()
877 public void actionPerformed(ActionEvent e)
879 joptionpane.setValue(buttonAction);
881 Executors.defaultThreadFactory().newThread(action).start();
882 // joptionpane.transferFocusBackward();
883 joptionpane.transferFocusBackward();
884 joptionpane.setVisible(false);
885 // put focus and raise parent window if possible, unless cancel
887 boolean raiseParent = (parentComponent != null);
888 if (buttonAction == JvOptionPane.CANCEL_OPTION)
890 if (optionType == JvOptionPane.YES_NO_OPTION
891 && buttonAction == JvOptionPane.NO_OPTION)
895 parentComponent.requestFocus();
896 if (parentComponent instanceof JInternalFrame)
898 JInternalFrame jif = (JInternalFrame) parentComponent;
903 else if (parentComponent instanceof Window)
905 Window w = (Window) parentComponent;
910 joptionpane.setVisible(false);
913 options_btns.add(jb);
914 if (o.equals(initialValue))
915 initialValue_btn = jb;
918 joptionpane.setMessage(message);
919 joptionpane.setMessageType(messageType);
920 joptionpane.setOptionType(optionType);
921 joptionpane.setIcon(icon);
922 joptionpane.setOptions(
923 Platform.isJS() ? options : options_btns.toArray());
924 joptionpane.setInitialValue(
925 Platform.isJS() ? initialValue : initialValue_btn);
927 JDialog dialog = joptionpane.createDialog(parentComponent, title);
928 dialog.setIconImage(null);
929 dialog.setModalityType(modal ? ModalityType.APPLICATION_MODAL
930 : ModalityType.MODELESS);
931 dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
932 dialog.setVisible(true);
936 public void showInternalDialog(JPanel mainPanel, String title,
937 int yesNoCancelOption, int questionMessage, Icon icon,
938 Object[] options, String initresponse)
940 if (!isInteractiveMode())
942 handleResponse(getMockResponse());
945 // need to set these separately so we can set the title bar icon later
946 this.setOptionType(yesNoCancelOption);
947 this.setMessageType(questionMessage);
949 this.setInitialValue(initresponse);
950 this.setOptions(options);
951 this.setMessage(mainPanel);
953 ourOptions = Arrays.asList(options);
954 if (parentComponent != this)
956 JInternalFrame jif = this.createInternalFrame(parentComponent, title);
957 jif.setFrameIcon(null);
958 jif.addInternalFrameListener(new InternalFrameListener()
961 public void internalFrameActivated(InternalFrameEvent arg0)
966 public void internalFrameClosed(InternalFrameEvent arg0)
968 JvOptionPane.this.internalDialogHandleResponse();
972 public void internalFrameClosing(InternalFrameEvent arg0)
977 public void internalFrameDeactivated(InternalFrameEvent arg0)
982 public void internalFrameDeiconified(InternalFrameEvent arg0)
987 public void internalFrameIconified(InternalFrameEvent arg0)
992 public void internalFrameOpened(InternalFrameEvent arg0)
996 jif.setVisible(true);
1002 JDialog dialog = this.createDialog(parentComponent, title);
1003 dialog.setIconImage(null);
1004 dialog.setVisible(true); // blocking
1005 this.internalDialogHandleResponse();
1010 private void internalDialogHandleResponse()
1012 String responseString = (String) this.getValue();
1013 int response = ourOptions.indexOf(responseString);
1015 if (!Platform.isJS())
1022 handleResponse(response);
1027 public JvOptionPane setResponseHandler(Object response, Runnable action)
1029 callbacks.put(response, action);
1034 * JalviewJS signals option selection by a property change event for the
1035 * option e.g. "OK". This methods responds to that by running the response
1036 * action that corresponds to that option.
1041 public void propertyChange(PropertyChangeEvent evt)
1043 Object newValue = evt.getNewValue();
1044 int ourOption = ourOptions.indexOf(newValue);
1047 handleResponse(ourOption);
1052 handleResponse(newValue);
1057 public void handleResponse(Object response)
1060 * this test is for NaN in Chrome
1062 if (response != null && !response.equals(response))
1066 Runnable action = callbacks.get(response);
1070 parentComponent.requestFocus();
1075 * This helper method makes the JInternalFrame wait until it is notified by an
1076 * InternalFrameClosing event. This method also adds the given JOptionPane to
1077 * the JInternalFrame and sizes it according to the JInternalFrame's preferred
1081 * The JInternalFrame to make modal.
1083 private static void startModal(JInternalFrame f)
1085 // We need to add an additional glasspane-like component directly
1086 // below the frame, which intercepts all mouse events that are not
1087 // directed at the frame itself.
1088 JPanel modalInterceptor = new JPanel();
1089 modalInterceptor.setOpaque(false);
1090 JLayeredPane lp = JLayeredPane.getLayeredPaneAbove(f);
1091 lp.setLayer(modalInterceptor, JLayeredPane.MODAL_LAYER.intValue());
1092 modalInterceptor.setBounds(0, 0, lp.getWidth(), lp.getHeight());
1093 modalInterceptor.addMouseListener(new MouseAdapter()
1096 modalInterceptor.addMouseMotionListener(new MouseMotionAdapter()
1099 lp.add(modalInterceptor);
1102 // We need to explicitly dispatch events when we are blocking the event
1104 EventQueue queue = Toolkit.getDefaultToolkit().getSystemEventQueue();
1107 while (!f.isClosed())
1109 if (EventQueue.isDispatchThread())
1111 // The getNextEventMethod() issues wait() when no
1112 // event is available, so we don't need do explicitly wait().
1113 AWTEvent ev = queue.getNextEvent();
1114 // This mimics EventQueue.dispatchEvent(). We can't use
1115 // EventQueue.dispatchEvent() directly, because it is
1116 // protected, unfortunately.
1117 if (ev instanceof ActiveEvent)
1118 ((ActiveEvent) ev).dispatch();
1119 else if (ev.getSource() instanceof Component)
1120 ((Component) ev.getSource()).dispatchEvent(ev);
1121 else if (ev.getSource() instanceof MenuComponent)
1122 ((MenuComponent) ev.getSource()).dispatchEvent(ev);
1123 // Other events are ignored as per spec in
1124 // EventQueue.dispatchEvent
1128 // Give other threads a chance to become active.
1132 } catch (InterruptedException ex)
1134 // If we get interrupted, then leave the modal state.
1137 // Clean up the modal interceptor.
1138 lp.remove(modalInterceptor);
1140 // Remove the internal frame from its parent, so it is no longer
1141 // lurking around and clogging memory.
1142 Container parent = f.getParent();