JAL-4034 Allow request for non-modal JDialog, which puts the Runnable action into...
[jalview.git] / src / jalview / gui / JvOptionPane.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
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.
11  *  
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.
16  * 
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.
20  */
21
22 package jalview.gui;
23
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;
36 import java.util.Map;
37 import java.util.concurrent.Executors;
38
39 import javax.swing.Icon;
40 import javax.swing.JButton;
41 import javax.swing.JDialog;
42 import javax.swing.JInternalFrame;
43 import javax.swing.JOptionPane;
44 import javax.swing.JPanel;
45 import javax.swing.UIManager;
46
47 import jalview.util.Platform;
48 import jalview.util.dialogrunner.DialogRunnerI;
49
50 public class JvOptionPane extends JOptionPane
51         implements DialogRunnerI, PropertyChangeListener
52 {
53   private static final long serialVersionUID = -3019167117756785229L;
54
55   private static Object mockResponse = JvOptionPane.CANCEL_OPTION;
56
57   private static boolean interactiveMode = true;
58
59   private Component parentComponent;
60
61   private Map<Object, Runnable> callbacks = new HashMap<>();
62
63   /*
64    * JalviewJS reports user choice in the dialog as the selected
65    * option (text); this list allows conversion to index (int)
66    */
67   List<Object> ourOptions;
68
69   public JvOptionPane(final Component parent)
70   {
71     this.parentComponent = Platform.isJS() ? this : parent;
72   }
73
74   public static int showConfirmDialog(Component parentComponent,
75           Object message) throws HeadlessException
76   {
77     // only called by test
78     return isInteractiveMode()
79             ? JOptionPane.showConfirmDialog(parentComponent, message)
80             : (int) getMockResponse();
81   }
82
83   /**
84    * Message, title, optionType
85    * 
86    * @param parentComponent
87    * @param message
88    * @param title
89    * @param optionType
90    * @return
91    * @throws HeadlessException
92    */
93   public static int showConfirmDialog(Component parentComponent,
94           Object message, String title, int optionType)
95           throws HeadlessException
96   {
97     if (!isInteractiveMode())
98     {
99       return (int) getMockResponse();
100     }
101     switch (optionType)
102     {
103     case JvOptionPane.YES_NO_CANCEL_OPTION:
104       // FeatureRenderer amendFeatures ?? TODO ??
105       // Chimera close
106       // PromptUserConfig
107       // $FALL-THROUGH$
108     default:
109     case JvOptionPane.YES_NO_OPTION:
110       // PromptUserConfig usage stats
111       // for now treated as "OK CANCEL"
112       // $FALL-THROUGH$
113     case JvOptionPane.OK_CANCEL_OPTION:
114       // will fall back to simple HTML
115       return JOptionPane.showConfirmDialog(parentComponent, message, title,
116               optionType);
117     }
118   }
119
120   /**
121    * Adds a message type. Fallback is to just add it in the beginning.
122    * 
123    * @param parentComponent
124    * @param message
125    * @param title
126    * @param optionType
127    * @param messageType
128    * @return
129    * @throws HeadlessException
130    */
131   public static int showConfirmDialog(Component parentComponent,
132           Object message, String title, int optionType, int messageType)
133           throws HeadlessException
134   {
135     // JalviewServicesChanged
136     // PromptUserConfig raiseDialog
137     return isInteractiveMode()
138             ? JOptionPane.showConfirmDialog(parentComponent, message, title,
139                     optionType, messageType)
140             : (int) getMockResponse();
141   }
142
143   /**
144    * Adds an icon
145    * 
146    * @param parentComponent
147    * @param message
148    * @param title
149    * @param optionType
150    * @param messageType
151    * @param icon
152    * @return
153    * @throws HeadlessException
154    */
155   public static int showConfirmDialog(Component parentComponent,
156           Object message, String title, int optionType, int messageType,
157           Icon icon) throws HeadlessException
158   {
159     // JvOptionPaneTest only
160     return isInteractiveMode()
161             ? JOptionPane.showConfirmDialog(parentComponent, message, title,
162                     optionType, messageType, icon)
163             : (int) getMockResponse();
164   }
165
166   /**
167    * Internal version "OK"
168    * 
169    * @param parentComponent
170    * @param message
171    * @return
172    */
173   public static int showInternalConfirmDialog(Component parentComponent,
174           Object message)
175   {
176     // JvOptionPaneTest only;
177     return isInteractiveMode()
178             ? JOptionPane.showInternalConfirmDialog(parentComponent,
179                     message)
180             : (int) getMockResponse();
181   }
182
183   /**
184    * Internal version -- changed to standard version for now
185    * 
186    * @param parentComponent
187    * @param message
188    * @param title
189    * @param optionType
190    * @return
191    */
192   public static int showInternalConfirmDialog(Component parentComponent,
193           String message, String title, int optionType)
194   {
195     if (!isInteractiveMode())
196     {
197       return (int) getMockResponse();
198     }
199     switch (optionType)
200     {
201     case JvOptionPane.YES_NO_CANCEL_OPTION:
202       // ColourMenuHelper.addMenuItmers.offerRemoval TODO
203     case JvOptionPane.YES_NO_OPTION:
204       // UserDefinedColoursSave -- relevant? TODO
205       // $FALL-THROUGH$
206     default:
207     case JvOptionPane.OK_CANCEL_OPTION:
208
209       // EditNameDialog --- uses panel for messsage TODO
210
211       // Desktop.inputURLMenuItem
212       // WsPreferenses
213       return JOptionPane.showConfirmDialog(parentComponent, message, title,
214               optionType);
215     }
216   }
217
218   /**
219    * 
220    * @param parentComponent
221    * @param message
222    * @param title
223    * @param optionType
224    * @param messageType
225    * @return
226    */
227   public static int showInternalConfirmDialog(Component parentComponent,
228           Object message, String title, int optionType, int messageType)
229   {
230     if (!isInteractiveMode())
231     {
232       return (int) getMockResponse();
233     }
234     switch (optionType)
235     {
236     case JvOptionPane.YES_NO_CANCEL_OPTION:
237     case JvOptionPane.YES_NO_OPTION:
238       // UserQuestionanaireCheck
239       // VamsasApplication
240       // $FALL-THROUGH$
241     default:
242     case JvOptionPane.OK_CANCEL_OPTION:
243       // will fall back to simple HTML
244       return JOptionPane.showConfirmDialog(parentComponent, message, title,
245               optionType, messageType);
246     }
247   }
248
249   /**
250    * adds icon; no longer internal
251    * 
252    * @param parentComponent
253    * @param message
254    * @param title
255    * @param optionType
256    * @param messageType
257    * @param icon
258    * @return
259    */
260   public static int showInternalConfirmDialog(Component parentComponent,
261           Object message, String title, int optionType, int messageType,
262           Icon icon)
263   {
264     if (!isInteractiveMode())
265     {
266       return (int) getMockResponse();
267     }
268     switch (optionType)
269     {
270     case JvOptionPane.YES_NO_CANCEL_OPTION:
271     case JvOptionPane.YES_NO_OPTION:
272       //$FALL-THROUGH$
273     default:
274     case JvOptionPane.OK_CANCEL_OPTION:
275       // Preferences editLink/newLink
276       return JOptionPane.showConfirmDialog(parentComponent, message, title,
277               optionType, messageType, icon);
278     }
279
280   }
281
282   /**
283    * custom options full-featured
284    * 
285    * @param parentComponent
286    * @param message
287    * @param title
288    * @param optionType
289    * @param messageType
290    * @param icon
291    * @param options
292    * @param initialValue
293    * @return
294    * @throws HeadlessException
295    */
296   public static int showOptionDialog(Component parentComponent,
297           String message, String title, int optionType, int messageType,
298           Icon icon, Object[] options, Object initialValue)
299           throws HeadlessException
300   {
301     if (!isInteractiveMode())
302     {
303       return (int) getMockResponse();
304     }
305     // two uses:
306     //
307     // TODO
308     //
309     // 1) AlignViewport for openLinkedAlignment
310     //
311     // Show a dialog with the option to open and link (cDNA <-> protein) as a
312     // new
313     // alignment, either as a standalone alignment or in a split frame. Returns
314     // true if the new alignment was opened, false if not, because the user
315     // declined the offer.
316     //
317     // 2) UserDefinedColors warning about saving over a name already defined
318     //
319     return JOptionPane.showOptionDialog(parentComponent, message, title,
320             optionType, messageType, icon, options, initialValue);
321   }
322
323   /**
324    * Just an OK message
325    * 
326    * @param message
327    * @throws HeadlessException
328    */
329   public static void showMessageDialog(Component parentComponent,
330           String message) throws HeadlessException
331   {
332     if (!isInteractiveMode())
333     {
334       outputMessage(message);
335       return;
336     }
337
338     // test class only
339
340     JOptionPane.showMessageDialog(parentComponent, message);
341   }
342
343   /**
344    * OK with message, title, and type
345    * 
346    * @param parentComponent
347    * @param message
348    * @param title
349    * @param messageType
350    * @throws HeadlessException
351    */
352   public static void showMessageDialog(Component parentComponent,
353           String message, String title, int messageType)
354           throws HeadlessException
355   {
356     // 30 implementations -- all just fine.
357
358     if (!isInteractiveMode())
359     {
360       outputMessage(message);
361       return;
362     }
363
364     JOptionPane.showMessageDialog(parentComponent,
365             getPrefix(messageType) + message, title, messageType);
366   }
367
368   /**
369    * adds title and icon
370    * 
371    * @param parentComponent
372    * @param message
373    * @param title
374    * @param messageType
375    * @param icon
376    * @throws HeadlessException
377    */
378   public static void showMessageDialog(Component parentComponent,
379           String message, String title, int messageType, Icon icon)
380           throws HeadlessException
381   {
382
383     // test only
384
385     if (!isInteractiveMode())
386     {
387       outputMessage(message);
388       return;
389     }
390
391     JOptionPane.showMessageDialog(parentComponent, message, title,
392             messageType, icon);
393   }
394
395   /**
396    * was internal
397    * 
398    */
399   public static void showInternalMessageDialog(Component parentComponent,
400           Object message)
401   {
402
403     // WsPreferences only
404
405     if (!isInteractiveMode())
406     {
407       outputMessage(message);
408       return;
409     }
410
411     JOptionPane.showMessageDialog(parentComponent, message);
412   }
413
414   /**
415    * Adds title and messageType
416    * 
417    * @param parentComponent
418    * @param message
419    * @param title
420    * @param messageType
421    */
422   public static void showInternalMessageDialog(Component parentComponent,
423           String message, String title, int messageType)
424   {
425
426     // 41 references
427
428     if (!isInteractiveMode())
429     {
430       outputMessage(message);
431       return;
432     }
433
434     JOptionPane.showMessageDialog(parentComponent,
435             getPrefix(messageType) + message, title, messageType);
436   }
437
438   /**
439    * 
440    * @param parentComponent
441    * @param message
442    * @param title
443    * @param messageType
444    * @param icon
445    */
446   public static void showInternalMessageDialog(Component parentComponent,
447           Object message, String title, int messageType, Icon icon)
448   {
449
450     // test only
451
452     if (!isInteractiveMode())
453     {
454       outputMessage(message);
455       return;
456     }
457
458     JOptionPane.showMessageDialog(parentComponent, message, title,
459             messageType, icon);
460   }
461
462   /**
463    * 
464    * @param message
465    * @return
466    * @throws HeadlessException
467    */
468   public static String showInputDialog(Object message)
469           throws HeadlessException
470   {
471     // test only
472
473     if (!isInteractiveMode())
474     {
475       return getMockResponse().toString();
476     }
477
478     return JOptionPane.showInputDialog(message);
479   }
480
481   /**
482    * adds inital selection value
483    * 
484    * @param message
485    * @param initialSelectionValue
486    * @return
487    */
488   public static String showInputDialog(String message,
489           String initialSelectionValue)
490   {
491     if (!isInteractiveMode())
492     {
493       return getMockResponse().toString();
494     }
495
496     // AnnotationPanel character option
497
498     return JOptionPane.showInputDialog(message, initialSelectionValue);
499   }
500
501   /**
502    * adds inital selection value
503    * 
504    * @param message
505    * @param initialSelectionValue
506    * @return
507    */
508   public static String showInputDialog(Object message,
509           Object initialSelectionValue)
510   {
511     if (!isInteractiveMode())
512     {
513       return getMockResponse().toString();
514     }
515
516     // AnnotationPanel character option
517
518     return JOptionPane.showInputDialog(message, initialSelectionValue);
519   }
520
521   /**
522    * centered on parent
523    * 
524    * @param parentComponent
525    * @param message
526    * @return
527    * @throws HeadlessException
528    */
529   public static String showInputDialog(Component parentComponent,
530           String message) throws HeadlessException
531   {
532     // test only
533
534     return isInteractiveMode()
535             ? JOptionPane.showInputDialog(parentComponent, message)
536             : getMockResponse().toString();
537   }
538
539   /**
540    * input with initial selection
541    * 
542    * @param parentComponent
543    * @param message
544    * @param initialSelectionValue
545    * @return
546    */
547   public static String showInputDialog(Component parentComponent,
548           String message, String initialSelectionValue)
549   {
550
551     // AnnotationPanel
552
553     return isInteractiveMode()
554             ? JOptionPane.showInputDialog(parentComponent, message,
555                     initialSelectionValue)
556             : getMockResponse().toString();
557   }
558
559   /**
560    * input with initial selection
561    * 
562    * @param parentComponent
563    * @param message
564    * @param initialSelectionValue
565    * @return
566    */
567   public static String showInputDialog(Component parentComponent,
568           Object message, Object initialSelectionValue)
569   {
570
571     // AnnotationPanel
572
573     return isInteractiveMode()
574             ? JOptionPane.showInputDialog(parentComponent, message,
575                     initialSelectionValue)
576             : getMockResponse().toString();
577   }
578
579   /**
580    * 
581    * @param parentComponent
582    * @param message
583    * @param title
584    * @param messageType
585    * @return
586    * @throws HeadlessException
587    */
588   public static String showInputDialog(Component parentComponent,
589           String message, String title, int messageType)
590           throws HeadlessException
591   {
592
593     // test only
594
595     return isInteractiveMode()
596             ? JOptionPane.showInputDialog(parentComponent, message, title,
597                     messageType)
598             : getMockResponse().toString();
599   }
600
601   /**
602    * Customized input option
603    * 
604    * @param parentComponent
605    * @param message
606    * @param title
607    * @param messageType
608    * @param icon
609    * @param selectionValues
610    * @param initialSelectionValue
611    * @return
612    * @throws HeadlessException
613    */
614   public static Object showInputDialog(Component parentComponent,
615           Object message, String title, int messageType, Icon icon,
616           Object[] selectionValues, Object initialSelectionValue)
617           throws HeadlessException
618   {
619
620     // test only
621
622     return isInteractiveMode()
623             ? JOptionPane.showInputDialog(parentComponent, message, title,
624                     messageType, icon, selectionValues,
625                     initialSelectionValue)
626             : getMockResponse().toString();
627   }
628
629   /**
630    * internal version
631    * 
632    * @param parentComponent
633    * @param message
634    * @return
635    */
636   public static String showInternalInputDialog(Component parentComponent,
637           String message)
638   {
639     // test only
640
641     return isInteractiveMode()
642             ? JOptionPane.showInternalInputDialog(parentComponent, message)
643             : getMockResponse().toString();
644   }
645
646   /**
647    * internal with title and messageType
648    * 
649    * @param parentComponent
650    * @param message
651    * @param title
652    * @param messageType
653    * @return
654    */
655   public static String showInternalInputDialog(Component parentComponent,
656           String message, String title, int messageType)
657   {
658
659     // AlignFrame tabbedPane_mousePressed
660
661     return isInteractiveMode()
662             ? JOptionPane.showInternalInputDialog(parentComponent,
663                     getPrefix(messageType) + message, title, messageType)
664             : getMockResponse().toString();
665   }
666
667   /**
668    * customized internal
669    * 
670    * @param parentComponent
671    * @param message
672    * @param title
673    * @param messageType
674    * @param icon
675    * @param selectionValues
676    * @param initialSelectionValue
677    * @return
678    */
679   public static Object showInternalInputDialog(Component parentComponent,
680           String message, String title, int messageType, Icon icon,
681           Object[] selectionValues, Object initialSelectionValue)
682   {
683     // test only
684
685     return isInteractiveMode()
686             ? JOptionPane.showInternalInputDialog(parentComponent, message,
687                     title, messageType, icon, selectionValues,
688                     initialSelectionValue)
689             : getMockResponse().toString();
690   }
691
692   ///////////// end of options ///////////////
693
694   private static void outputMessage(Object message)
695   {
696     System.out.println(">>> JOption Message : " + message.toString());
697   }
698
699   public static Object getMockResponse()
700   {
701     return mockResponse;
702   }
703
704   public static void setMockResponse(Object mockOption)
705   {
706     JvOptionPane.mockResponse = mockOption;
707   }
708
709   public static void resetMock()
710   {
711     setMockResponse(JvOptionPane.CANCEL_OPTION);
712     setInteractiveMode(true);
713   }
714
715   public static boolean isInteractiveMode()
716   {
717     return interactiveMode;
718   }
719
720   public static void setInteractiveMode(boolean interactive)
721   {
722     JvOptionPane.interactiveMode = interactive;
723   }
724
725   private static String getPrefix(int messageType)
726   {
727     String prefix = "";
728
729     // JavaScript only
730     if (Platform.isJS())
731     {
732       switch (messageType)
733       {
734       case JvOptionPane.WARNING_MESSAGE:
735         prefix = "WARNING! ";
736         break;
737       case JvOptionPane.ERROR_MESSAGE:
738         prefix = "ERROR! ";
739         break;
740       default:
741         prefix = "Note: ";
742       }
743     }
744     return prefix;
745   }
746
747   /**
748    * create a new option dialog that can be used to register responses - along
749    * lines of showOptionDialog
750    * 
751    * @param desktop
752    * @param question
753    * @param string
754    * @param defaultOption
755    * @param plainMessage
756    * @param object
757    * @param options
758    * @param string2
759    * @return
760    */
761   public static JvOptionPane newOptionDialog(Component parentComponent)
762   {
763     return new JvOptionPane(parentComponent);
764   }
765
766   public void showDialog(String message, String title, int optionType,
767           int messageType, Icon icon, Object[] options, Object initialValue)
768   {
769     showDialog(message, title, optionType, messageType, icon, options,
770             initialValue, true);
771   }
772
773   public void showDialog(String message, String title, int optionType,
774           int messageType, Icon icon, Object[] options, Object initialValue,
775           boolean modal)
776   {
777     if (!isInteractiveMode())
778     {
779       handleResponse(getMockResponse());
780     }
781     // two uses:
782     //
783     // TODO
784     //
785     // 1) AlignViewport for openLinkedAlignment
786     //
787     // Show a dialog with the option to open and link (cDNA <-> protein) as a
788     // new
789     // alignment, either as a standalone alignment or in a split frame. Returns
790     // true if the new alignment was opened, false if not, because the user
791     // declined the offer.
792     //
793     // 2) UserDefinedColors warning about saving over a name already defined
794     //
795
796     ourOptions = Arrays.asList(options);
797
798     if (modal)
799     {
800       // use a JOptionPane as usual
801       int response = JOptionPane.showOptionDialog(parentComponent, message,
802               title, optionType, messageType, icon, options, initialValue);
803
804       /*
805        * In Java, the response is returned to this thread and handled here;
806        * (for Javascript, see propertyChange)
807        */
808       if (!Platform.isJS())
809       /**
810        * Java only
811        * 
812        * @j2sIgnore
813        */
814       {
815         handleResponse(response);
816       }
817     }
818     else
819     {
820       /*
821        * This is java similar to the swingjs handling, with the callbacks
822        * attached to the button press of the dialog.  This means we can use
823        * a non-modal JDialog for the confirmation without blocking the GUI.
824        */
825       JOptionPane joptionpane = new JOptionPane();
826       // Make button options
827       int[] buttonActions = { JvOptionPane.YES_OPTION,
828           JvOptionPane.NO_OPTION, JvOptionPane.CANCEL_OPTION };
829
830       // we need the strings to make the buttons with actionEventListener
831       if (options == null)
832       {
833         ArrayList<String> options_default = new ArrayList<>();
834         options_default
835                 .add(UIManager.getString("OptionPane.yesButtonText"));
836         if (optionType == JvOptionPane.YES_NO_OPTION
837                 || optionType == JvOptionPane.YES_NO_CANCEL_OPTION)
838         {
839           options_default
840                   .add(UIManager.getString("OptionPane.noButtonText"));
841         }
842         if (optionType == JvOptionPane.YES_NO_CANCEL_OPTION)
843         {
844           options_default
845                   .add(UIManager.getString("OptionPane.cancelButtonText"));
846         }
847         options = options_default.toArray();
848       }
849
850       ArrayList<JButton> options_btns = new ArrayList<>();
851       Object initialValue_btn = null;
852       if (!Platform.isJS()) // JalviewJS already uses callback, don't need to add them here
853       {
854         for (int i = 0; i < options.length && i < 3; i++)
855         {
856           Object o = options[i];
857           int buttonAction = buttonActions[i];
858           Runnable action = callbacks.get(buttonAction);
859           JButton jb = new JButton();
860           jb.setText((String) o);
861           jb.addActionListener(new ActionListener()
862           {
863             @Override
864             public void actionPerformed(ActionEvent e)
865             {
866               joptionpane.setValue(buttonAction);
867               if (action != null)
868                 Executors.defaultThreadFactory().newThread(action).start();
869               // joptionpane.transferFocusBackward();
870               joptionpane.transferFocusBackward();
871               joptionpane.setVisible(false);
872               // put focus and raise parent window if possible, unless cancel
873               // button pressed
874               boolean raiseParent = (parentComponent != null);
875               if (buttonAction == JvOptionPane.CANCEL_OPTION)
876                 raiseParent = false;
877               if (optionType == JvOptionPane.YES_NO_OPTION
878                       && buttonAction == JvOptionPane.NO_OPTION)
879                 raiseParent = false;
880               if (raiseParent)
881               {
882                 parentComponent.requestFocus();
883                 if (parentComponent instanceof JInternalFrame)
884                 {
885                   JInternalFrame jif = (JInternalFrame) parentComponent;
886                   jif.show();
887                   jif.moveToFront();
888                   jif.grabFocus();
889                 }
890                 else if (parentComponent instanceof Window)
891                 {
892                   Window w = (Window) parentComponent;
893                   w.toFront();
894                   w.requestFocus();
895                 }
896               }
897               joptionpane.setVisible(false);
898             }
899           });
900           options_btns.add(jb);
901           if (o.equals(initialValue))
902             initialValue_btn = jb;
903         }
904       }
905       joptionpane.setMessage(message);
906       joptionpane.setMessageType(messageType);
907       joptionpane.setOptionType(optionType);
908       joptionpane.setIcon(icon);
909       joptionpane.setOptions(
910               Platform.isJS() ? options : options_btns.toArray());
911       joptionpane.setInitialValue(
912               Platform.isJS() ? initialValue : initialValue_btn);
913
914       JDialog dialog = joptionpane.createDialog(parentComponent, title);
915       dialog.setModalityType(modal ? ModalityType.APPLICATION_MODAL
916               : ModalityType.MODELESS);
917       dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
918       dialog.setVisible(true);
919     }
920   }
921
922   public void showInternalDialog(JPanel mainPanel, String title,
923           int yesNoCancelOption, int questionMessage, Icon icon,
924           Object[] options, String initresponse)
925   {
926     if (!isInteractiveMode())
927     {
928       handleResponse(getMockResponse());
929     }
930
931     ourOptions = Arrays.asList(options);
932     int response;
933     if (parentComponent != this)
934     {
935       response = JOptionPane.showInternalOptionDialog(parentComponent,
936               mainPanel, title, yesNoCancelOption, questionMessage, icon,
937               options, initresponse);
938     }
939     else
940     {
941       response = JOptionPane.showOptionDialog(parentComponent, mainPanel,
942               title, yesNoCancelOption, questionMessage, icon, options,
943               initresponse);
944     }
945     if (!Platform.isJS())
946     /**
947      * Java only
948      * 
949      * @j2sIgnore
950      */
951     {
952       handleResponse(response);
953     }
954   }
955
956   @Override
957   public JvOptionPane setResponseHandler(Object response, Runnable action)
958   {
959     callbacks.put(response, action);
960     return this;
961   }
962
963   /**
964    * JalviewJS signals option selection by a property change event for the
965    * option e.g. "OK". This methods responds to that by running the response
966    * action that corresponds to that option.
967    * 
968    * @param evt
969    */
970   @Override
971   public void propertyChange(PropertyChangeEvent evt)
972   {
973     Object newValue = evt.getNewValue();
974     int ourOption = ourOptions.indexOf(newValue);
975     if (ourOption >= 0)
976     {
977       handleResponse(ourOption);
978     }
979     else
980     {
981       // try our luck..
982       handleResponse(newValue);
983     }
984   }
985
986   @Override
987   public void handleResponse(Object response)
988   {
989     /*
990     * this test is for NaN in Chrome
991     */
992     if (response != null && !response.equals(response))
993     {
994       return;
995     }
996     Runnable action = callbacks.get(response);
997     if (action != null)
998     {
999       action.run();
1000       parentComponent.requestFocus();
1001     }
1002   }
1003 }