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.ChannelProperties;
59 import jalview.util.Platform;
60 import jalview.util.dialogrunner.DialogRunnerI;
62 public class JvOptionPane extends JOptionPane
63 implements DialogRunnerI, PropertyChangeListener
65 private static final long serialVersionUID = -3019167117756785229L;
67 private static Object mockResponse = JvOptionPane.CANCEL_OPTION;
69 private static boolean interactiveMode = true;
71 private Component parentComponent;
73 private Map<Object, Runnable> callbacks = new HashMap<>();
76 * JalviewJS reports user choice in the dialog as the selected
77 * option (text); this list allows conversion to index (int)
79 List<Object> ourOptions;
81 public JvOptionPane(final Component parent)
83 this.parentComponent = Platform.isJS() ? this : parent;
87 public static int showConfirmDialog(Component parentComponent,
88 Object message) throws HeadlessException
90 // only called by test
91 return isInteractiveMode()
92 ? JOptionPane.showConfirmDialog(parentComponent, message)
93 : (int) getMockResponse();
97 * Message, title, optionType
99 * @param parentComponent
104 * @throws HeadlessException
106 public static int showConfirmDialog(Component parentComponent,
107 Object message, String title, int optionType)
108 throws HeadlessException
110 if (!isInteractiveMode())
112 return (int) getMockResponse();
116 case JvOptionPane.YES_NO_CANCEL_OPTION:
117 // FeatureRenderer amendFeatures ?? TODO ??
122 case JvOptionPane.YES_NO_OPTION:
123 // PromptUserConfig usage stats
124 // for now treated as "OK CANCEL"
126 case JvOptionPane.OK_CANCEL_OPTION:
127 // will fall back to simple HTML
128 return JOptionPane.showConfirmDialog(parentComponent, message, title,
134 * Adds a message type. Fallback is to just add it in the beginning.
136 * @param parentComponent
142 * @throws HeadlessException
144 public static int showConfirmDialog(Component parentComponent,
145 Object message, String title, int optionType, int messageType)
146 throws HeadlessException
148 // JalviewServicesChanged
149 // PromptUserConfig raiseDialog
150 return isInteractiveMode()
151 ? JOptionPane.showConfirmDialog(parentComponent, message, title,
152 optionType, messageType)
153 : (int) getMockResponse();
159 * @param parentComponent
166 * @throws HeadlessException
168 public static int showConfirmDialog(Component parentComponent,
169 Object message, String title, int optionType, int messageType,
170 Icon icon) throws HeadlessException
172 // JvOptionPaneTest only
173 return isInteractiveMode()
174 ? JOptionPane.showConfirmDialog(parentComponent, message, title,
175 optionType, messageType, icon)
176 : (int) getMockResponse();
180 * Internal version "OK"
182 * @param parentComponent
186 public static int showInternalConfirmDialog(Component parentComponent,
189 // JvOptionPaneTest only;
190 return isInteractiveMode()
191 ? JOptionPane.showInternalConfirmDialog(parentComponent,
193 : (int) getMockResponse();
197 * Internal version -- changed to standard version for now
199 * @param parentComponent
205 public static int showInternalConfirmDialog(Component parentComponent,
206 String message, String title, int optionType)
208 if (!isInteractiveMode())
210 return (int) getMockResponse();
214 case JvOptionPane.YES_NO_CANCEL_OPTION:
215 // ColourMenuHelper.addMenuItmers.offerRemoval TODO
216 case JvOptionPane.YES_NO_OPTION:
217 // UserDefinedColoursSave -- relevant? TODO
220 case JvOptionPane.OK_CANCEL_OPTION:
222 // EditNameDialog --- uses panel for messsage TODO
224 // Desktop.inputURLMenuItem
226 return JOptionPane.showConfirmDialog(parentComponent, message, title,
233 * @param parentComponent
240 public static int showInternalConfirmDialog(Component parentComponent,
241 Object message, String title, int optionType, int messageType)
243 if (!isInteractiveMode())
245 return (int) getMockResponse();
249 case JvOptionPane.YES_NO_CANCEL_OPTION:
250 case JvOptionPane.YES_NO_OPTION:
251 // UserQuestionanaireCheck
255 case JvOptionPane.OK_CANCEL_OPTION:
256 // will fall back to simple HTML
257 return JOptionPane.showConfirmDialog(parentComponent, message, title,
258 optionType, messageType);
263 * adds icon; no longer internal
265 * @param parentComponent
273 public static int showInternalConfirmDialog(Component parentComponent,
274 Object message, String title, int optionType, int messageType,
277 if (!isInteractiveMode())
279 return (int) getMockResponse();
283 case JvOptionPane.YES_NO_CANCEL_OPTION:
284 case JvOptionPane.YES_NO_OPTION:
287 case JvOptionPane.OK_CANCEL_OPTION:
288 // Preferences editLink/newLink
289 return JOptionPane.showConfirmDialog(parentComponent, message, title,
290 optionType, messageType, icon);
296 * custom options full-featured
298 * @param parentComponent
305 * @param initialValue
307 * @throws HeadlessException
309 public static int showOptionDialog(Component parentComponent,
310 String message, String title, int optionType, int messageType,
311 Icon icon, Object[] options, Object initialValue)
312 throws HeadlessException
314 if (!isInteractiveMode())
316 return (int) getMockResponse();
322 // 1) AlignViewport for openLinkedAlignment
324 // Show a dialog with the option to open and link (cDNA <-> protein) as a
326 // alignment, either as a standalone alignment or in a split frame. Returns
327 // true if the new alignment was opened, false if not, because the user
328 // declined the offer.
330 // 2) UserDefinedColors warning about saving over a name already defined
332 return JOptionPane.showOptionDialog(parentComponent, message, title,
333 optionType, messageType, icon, options, initialValue);
340 * @throws HeadlessException
342 public static void showMessageDialog(Component parentComponent,
343 String message) throws HeadlessException
345 if (!isInteractiveMode())
347 outputMessage(message);
353 JOptionPane.showMessageDialog(parentComponent, message);
357 * OK with message, title, and type
359 * @param parentComponent
363 * @throws HeadlessException
365 public static void showMessageDialog(Component parentComponent,
366 String message, String title, int messageType)
367 throws HeadlessException
369 // 30 implementations -- all just fine.
371 if (!isInteractiveMode())
373 outputMessage(message);
377 JOptionPane.showMessageDialog(parentComponent,
378 getPrefix(messageType) + message, title, messageType);
382 * adds title and icon
384 * @param parentComponent
389 * @throws HeadlessException
391 public static void showMessageDialog(Component parentComponent,
392 String message, String title, int messageType, Icon icon)
393 throws HeadlessException
398 if (!isInteractiveMode())
400 outputMessage(message);
404 JOptionPane.showMessageDialog(parentComponent, message, title,
412 public static void showInternalMessageDialog(Component parentComponent,
416 // WsPreferences only
418 if (!isInteractiveMode())
420 outputMessage(message);
424 JOptionPane.showMessageDialog(parentComponent, message);
428 * Adds title and messageType
430 * @param parentComponent
435 public static void showInternalMessageDialog(Component parentComponent,
436 String message, String title, int messageType)
441 if (!isInteractiveMode())
443 outputMessage(message);
447 JOptionPane.showMessageDialog(parentComponent,
448 getPrefix(messageType) + message, title, messageType);
453 * @param parentComponent
459 public static void showInternalMessageDialog(Component parentComponent,
460 Object message, String title, int messageType, Icon icon)
465 if (!isInteractiveMode())
467 outputMessage(message);
471 JOptionPane.showMessageDialog(parentComponent, message, title,
479 * @throws HeadlessException
481 public static String showInputDialog(Object message)
482 throws HeadlessException
486 if (!isInteractiveMode())
488 return getMockResponse().toString();
491 return JOptionPane.showInputDialog(message);
495 * adds inital selection value
498 * @param initialSelectionValue
501 public static String showInputDialog(String message,
502 String initialSelectionValue)
504 if (!isInteractiveMode())
506 return getMockResponse().toString();
509 // AnnotationPanel character option
511 return JOptionPane.showInputDialog(message, initialSelectionValue);
515 * adds inital selection value
518 * @param initialSelectionValue
521 public static String showInputDialog(Object message,
522 Object initialSelectionValue)
524 if (!isInteractiveMode())
526 return getMockResponse().toString();
529 // AnnotationPanel character option
531 return JOptionPane.showInputDialog(message, initialSelectionValue);
537 * @param parentComponent
540 * @throws HeadlessException
542 public static String showInputDialog(Component parentComponent,
543 String message) throws HeadlessException
547 return isInteractiveMode()
548 ? JOptionPane.showInputDialog(parentComponent, message)
549 : getMockResponse().toString();
553 * input with initial selection
555 * @param parentComponent
557 * @param initialSelectionValue
560 public static String showInputDialog(Component parentComponent,
561 String message, String initialSelectionValue)
566 return isInteractiveMode()
567 ? JOptionPane.showInputDialog(parentComponent, message,
568 initialSelectionValue)
569 : getMockResponse().toString();
573 * input with initial selection
575 * @param parentComponent
577 * @param initialSelectionValue
580 public static String showInputDialog(Component parentComponent,
581 Object message, Object initialSelectionValue)
586 return isInteractiveMode()
587 ? JOptionPane.showInputDialog(parentComponent, message,
588 initialSelectionValue)
589 : getMockResponse().toString();
594 * @param parentComponent
599 * @throws HeadlessException
601 public static String showInputDialog(Component parentComponent,
602 String message, String title, int messageType)
603 throws HeadlessException
608 return isInteractiveMode()
609 ? JOptionPane.showInputDialog(parentComponent, message, title,
611 : getMockResponse().toString();
615 * Customized input option
617 * @param parentComponent
622 * @param selectionValues
623 * @param initialSelectionValue
625 * @throws HeadlessException
627 public static Object showInputDialog(Component parentComponent,
628 Object message, String title, int messageType, Icon icon,
629 Object[] selectionValues, Object initialSelectionValue)
630 throws HeadlessException
635 return isInteractiveMode()
636 ? JOptionPane.showInputDialog(parentComponent, message, title,
637 messageType, icon, selectionValues,
638 initialSelectionValue)
639 : getMockResponse().toString();
645 * @param parentComponent
649 public static String showInternalInputDialog(Component parentComponent,
654 return isInteractiveMode()
655 ? JOptionPane.showInternalInputDialog(parentComponent, message)
656 : getMockResponse().toString();
660 * internal with title and messageType
662 * @param parentComponent
668 public static String showInternalInputDialog(Component parentComponent,
669 String message, String title, int messageType)
672 // AlignFrame tabbedPane_mousePressed
674 return isInteractiveMode()
675 ? JOptionPane.showInternalInputDialog(parentComponent,
676 getPrefix(messageType) + message, title, messageType)
677 : getMockResponse().toString();
681 * customized internal
683 * @param parentComponent
688 * @param selectionValues
689 * @param initialSelectionValue
692 public static Object showInternalInputDialog(Component parentComponent,
693 String message, String title, int messageType, Icon icon,
694 Object[] selectionValues, Object initialSelectionValue)
698 return isInteractiveMode()
699 ? JOptionPane.showInternalInputDialog(parentComponent, message,
700 title, messageType, icon, selectionValues,
701 initialSelectionValue)
702 : getMockResponse().toString();
705 ///////////// end of options ///////////////
707 private static void outputMessage(Object message)
709 System.out.println(">>> JOption Message : " + message.toString());
712 public static Object getMockResponse()
717 public static void setMockResponse(Object mockOption)
719 JvOptionPane.mockResponse = mockOption;
722 public static void resetMock()
724 setMockResponse(JvOptionPane.CANCEL_OPTION);
725 setInteractiveMode(true);
728 public static boolean isInteractiveMode()
730 return interactiveMode;
733 public static void setInteractiveMode(boolean interactive)
735 JvOptionPane.interactiveMode = interactive;
738 private static String getPrefix(int messageType)
747 case JvOptionPane.WARNING_MESSAGE:
748 prefix = "WARNING! ";
750 case JvOptionPane.ERROR_MESSAGE:
761 * create a new option dialog that can be used to register responses - along
762 * lines of showOptionDialog
767 * @param defaultOption
768 * @param plainMessage
774 public static JvOptionPane newOptionDialog(Component parentComponent)
776 return new JvOptionPane(parentComponent);
779 public void showDialog(String message, String title, int optionType,
780 int messageType, Icon icon, Object[] options, Object initialValue)
782 showDialog(message, title, optionType, messageType, icon, options,
786 public void showDialog(String message, String title, int optionType,
787 int messageType, Icon icon, Object[] options, Object initialValue,
790 if (!isInteractiveMode())
792 handleResponse(getMockResponse());
798 // 1) AlignViewport for openLinkedAlignment
800 // Show a dialog with the option to open and link (cDNA <-> protein) as a
802 // alignment, either as a standalone alignment or in a split frame. Returns
803 // true if the new alignment was opened, false if not, because the user
804 // declined the offer.
806 // 2) UserDefinedColors warning about saving over a name already defined
809 ourOptions = Arrays.asList(options);
813 // use a JOptionPane as usual
814 int response = JOptionPane.showOptionDialog(parentComponent, message,
815 title, optionType, messageType, icon, options, initialValue);
818 * In Java, the response is returned to this thread and handled here;
819 * (for Javascript, see propertyChange)
821 if (!Platform.isJS())
828 handleResponse(response);
834 * This is java similar to the swingjs handling, with the callbacks
835 * attached to the button press of the dialog. This means we can use
836 * a non-modal JDialog for the confirmation without blocking the GUI.
838 JOptionPane joptionpane = new JOptionPane();
839 // Make button options
840 int[] buttonActions = { JvOptionPane.YES_OPTION,
841 JvOptionPane.NO_OPTION, JvOptionPane.CANCEL_OPTION };
843 // we need the strings to make the buttons with actionEventListener
846 ArrayList<String> options_default = new ArrayList<>();
848 .add(UIManager.getString("OptionPane.yesButtonText"));
849 if (optionType == JvOptionPane.YES_NO_OPTION
850 || optionType == JvOptionPane.YES_NO_CANCEL_OPTION)
853 .add(UIManager.getString("OptionPane.noButtonText"));
855 if (optionType == JvOptionPane.YES_NO_CANCEL_OPTION)
858 .add(UIManager.getString("OptionPane.cancelButtonText"));
860 options = options_default.toArray();
863 ArrayList<JButton> options_btns = new ArrayList<>();
864 Object initialValue_btn = null;
865 if (!Platform.isJS()) // JalviewJS already uses callback, don't need to
868 for (int i = 0; i < options.length && i < 3; i++)
870 Object o = options[i];
871 int buttonAction = buttonActions[i];
872 Runnable action = callbacks.get(buttonAction);
873 JButton jb = new JButton();
874 jb.setText((String) o);
875 jb.addActionListener(new ActionListener()
878 public void actionPerformed(ActionEvent e)
880 joptionpane.setValue(buttonAction);
882 Executors.defaultThreadFactory().newThread(action).start();
883 // joptionpane.transferFocusBackward();
884 joptionpane.transferFocusBackward();
885 joptionpane.setVisible(false);
886 // put focus and raise parent window if possible, unless cancel
888 boolean raiseParent = (parentComponent != null);
889 if (buttonAction == JvOptionPane.CANCEL_OPTION)
891 if (optionType == JvOptionPane.YES_NO_OPTION
892 && buttonAction == JvOptionPane.NO_OPTION)
896 parentComponent.requestFocus();
897 if (parentComponent instanceof JInternalFrame)
899 JInternalFrame jif = (JInternalFrame) parentComponent;
904 else if (parentComponent instanceof Window)
906 Window w = (Window) parentComponent;
911 joptionpane.setVisible(false);
914 options_btns.add(jb);
915 if (o.equals(initialValue))
916 initialValue_btn = jb;
919 joptionpane.setMessage(message);
920 joptionpane.setMessageType(messageType);
921 joptionpane.setOptionType(optionType);
922 joptionpane.setIcon(icon);
923 joptionpane.setOptions(
924 Platform.isJS() ? options : options_btns.toArray());
925 joptionpane.setInitialValue(
926 Platform.isJS() ? initialValue : initialValue_btn);
928 JDialog dialog = joptionpane.createDialog(parentComponent, title);
929 dialog.setIconImages(ChannelProperties.getIconList());
930 dialog.setModalityType(modal ? ModalityType.APPLICATION_MODAL
931 : ModalityType.MODELESS);
932 dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
933 dialog.setVisible(true);
937 public void showInternalDialog(JPanel mainPanel, String title,
938 int yesNoCancelOption, int questionMessage, Icon icon,
939 Object[] options, String initresponse)
941 if (!isInteractiveMode())
943 handleResponse(getMockResponse());
946 // need to set these separately so we can set the title bar icon later
947 this.setOptionType(yesNoCancelOption);
948 this.setMessageType(questionMessage);
950 this.setInitialValue(initresponse);
951 this.setOptions(options);
952 this.setMessage(mainPanel);
954 ourOptions = Arrays.asList(options);
955 if (parentComponent != this)
957 JInternalFrame jif = this.createInternalFrame(parentComponent, title);
958 jif.setFrameIcon(null);
959 jif.addInternalFrameListener(new InternalFrameListener()
962 public void internalFrameActivated(InternalFrameEvent arg0)
967 public void internalFrameClosed(InternalFrameEvent arg0)
969 JvOptionPane.this.internalDialogHandleResponse();
973 public void internalFrameClosing(InternalFrameEvent arg0)
978 public void internalFrameDeactivated(InternalFrameEvent arg0)
983 public void internalFrameDeiconified(InternalFrameEvent arg0)
988 public void internalFrameIconified(InternalFrameEvent arg0)
993 public void internalFrameOpened(InternalFrameEvent arg0)
997 jif.setVisible(true);
1003 JDialog dialog = this.createDialog(parentComponent, title);
1004 dialog.setIconImages(ChannelProperties.getIconList());
1005 dialog.setVisible(true); // blocking
1006 this.internalDialogHandleResponse();
1011 private void internalDialogHandleResponse()
1013 String responseString = (String) this.getValue();
1014 int response = ourOptions.indexOf(responseString);
1016 if (!Platform.isJS())
1023 handleResponse(response);
1028 public JvOptionPane setResponseHandler(Object response, Runnable action)
1030 callbacks.put(response, action);
1035 * JalviewJS signals option selection by a property change event for the
1036 * option e.g. "OK". This methods responds to that by running the response
1037 * action that corresponds to that option.
1042 public void propertyChange(PropertyChangeEvent evt)
1044 Object newValue = evt.getNewValue();
1045 int ourOption = ourOptions.indexOf(newValue);
1048 handleResponse(ourOption);
1053 handleResponse(newValue);
1058 public void handleResponse(Object response)
1061 * this test is for NaN in Chrome
1063 if (response != null && !response.equals(response))
1067 Runnable action = callbacks.get(response);
1071 parentComponent.requestFocus();
1076 * This helper method makes the JInternalFrame wait until it is notified by an
1077 * InternalFrameClosing event. This method also adds the given JOptionPane to
1078 * the JInternalFrame and sizes it according to the JInternalFrame's preferred
1082 * The JInternalFrame to make modal.
1084 private static void startModal(JInternalFrame f)
1086 // We need to add an additional glasspane-like component directly
1087 // below the frame, which intercepts all mouse events that are not
1088 // directed at the frame itself.
1089 JPanel modalInterceptor = new JPanel();
1090 modalInterceptor.setOpaque(false);
1091 JLayeredPane lp = JLayeredPane.getLayeredPaneAbove(f);
1092 lp.setLayer(modalInterceptor, JLayeredPane.MODAL_LAYER.intValue());
1093 modalInterceptor.setBounds(0, 0, lp.getWidth(), lp.getHeight());
1094 modalInterceptor.addMouseListener(new MouseAdapter()
1097 modalInterceptor.addMouseMotionListener(new MouseMotionAdapter()
1100 lp.add(modalInterceptor);
1103 // We need to explicitly dispatch events when we are blocking the event
1105 EventQueue queue = Toolkit.getDefaultToolkit().getSystemEventQueue();
1108 while (!f.isClosed())
1110 if (EventQueue.isDispatchThread())
1112 // The getNextEventMethod() issues wait() when no
1113 // event is available, so we don't need do explicitly wait().
1114 AWTEvent ev = queue.getNextEvent();
1115 // This mimics EventQueue.dispatchEvent(). We can't use
1116 // EventQueue.dispatchEvent() directly, because it is
1117 // protected, unfortunately.
1118 if (ev instanceof ActiveEvent)
1119 ((ActiveEvent) ev).dispatch();
1120 else if (ev.getSource() instanceof Component)
1121 ((Component) ev.getSource()).dispatchEvent(ev);
1122 else if (ev.getSource() instanceof MenuComponent)
1123 ((MenuComponent) ev.getSource()).dispatchEvent(ev);
1124 // Other events are ignored as per spec in
1125 // EventQueue.dispatchEvent
1129 // Give other threads a chance to become active.
1133 } catch (InterruptedException ex)
1135 // If we get interrupted, then leave the modal state.
1138 // Clean up the modal interceptor.
1139 lp.remove(modalInterceptor);
1141 // Remove the internal frame from its parent, so it is no longer
1142 // lurking around and clogging memory.
1143 Container parent = f.getParent();