10dbfcf402acd47bea4e8b373889273480f26fb7
[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.Container;
26 import java.awt.Dialog.ModalityType;
27 import java.awt.HeadlessException;
28 import java.awt.Window;
29 import java.awt.event.ActionEvent;
30 import java.awt.event.ActionListener;
31 import java.beans.PropertyChangeEvent;
32 import java.beans.PropertyChangeListener;
33 import java.util.ArrayList;
34 import java.util.Arrays;
35 import java.util.HashMap;
36 import java.util.List;
37 import java.util.Map;
38 import java.util.concurrent.Callable;
39 import java.util.concurrent.Executors;
40
41 import javax.swing.Icon;
42 import javax.swing.JButton;
43 import javax.swing.JDialog;
44 import javax.swing.JFrame;
45 import javax.swing.JInternalFrame;
46 import javax.swing.JOptionPane;
47 import javax.swing.JPanel;
48 import javax.swing.UIManager;
49
50 import jalview.util.Platform;
51 import jalview.util.dialogrunner.DialogRunnerI;
52
53 public class JvOptionPane extends JOptionPane
54         implements DialogRunnerI, PropertyChangeListener
55 {
56   private static final long serialVersionUID = -3019167117756785229L;
57
58   private static Object mockResponse = JOptionPane.CANCEL_OPTION;
59
60   private static boolean interactiveMode = true;
61
62   private Component parentComponent;
63
64   private Map<Object, Callable> callbacks = new HashMap<>();
65
66   /*
67    * JalviewJS reports user choice in the dialog as the selected
68    * option (text); this list allows conversion to index (int)
69    */
70   List<Object> ourOptions;
71
72   public JvOptionPane(final Component parent)
73   {
74     this.parentComponent = Platform.isJS() ? this : parent;
75   }
76
77   public static int showConfirmDialog(Component parentComponent,
78           Object message) throws HeadlessException
79   {
80     // only called by test
81     return isInteractiveMode()
82             ? JOptionPane.showConfirmDialog(parentComponent, message)
83             : (int) getMockResponse();
84   }
85
86   /**
87    * Message, title, optionType
88    * 
89    * @param parentComponent
90    * @param message
91    * @param title
92    * @param optionType
93    * @return
94    * @throws HeadlessException
95    */
96   public static int showConfirmDialog(Component parentComponent,
97           Object message, String title, int optionType)
98           throws HeadlessException
99   {
100     if (!isInteractiveMode())
101     {
102       return (int) getMockResponse();
103     }
104     switch (optionType)
105     {
106     case JOptionPane.YES_NO_CANCEL_OPTION:
107       // FeatureRenderer amendFeatures ?? TODO ??
108       // Chimera close
109       // PromptUserConfig
110       // $FALL-THROUGH$
111     default:
112     case JOptionPane.YES_NO_OPTION:
113       // PromptUserConfig usage stats
114       // for now treated as "OK CANCEL"
115       // $FALL-THROUGH$
116     case JOptionPane.OK_CANCEL_OPTION:
117       // will fall back to simple HTML
118       return JOptionPane.showConfirmDialog(parentComponent, message, title,
119               optionType);
120     }
121   }
122
123   /**
124    * Adds a message type. Fallback is to just add it in the beginning.
125    * 
126    * @param parentComponent
127    * @param message
128    * @param title
129    * @param optionType
130    * @param messageType
131    * @return
132    * @throws HeadlessException
133    */
134   public static int showConfirmDialog(Component parentComponent,
135           Object message, String title, int optionType, int messageType)
136           throws HeadlessException
137   {
138     // JalviewServicesChanged
139     // PromptUserConfig raiseDialog
140     return isInteractiveMode()
141             ? JOptionPane.showConfirmDialog(parentComponent, message, title,
142                     optionType, messageType)
143             : (int) getMockResponse();
144   }
145
146   /**
147    * Adds an icon
148    * 
149    * @param parentComponent
150    * @param message
151    * @param title
152    * @param optionType
153    * @param messageType
154    * @param icon
155    * @return
156    * @throws HeadlessException
157    */
158   public static int showConfirmDialog(Component parentComponent,
159           Object message, String title, int optionType, int messageType,
160           Icon icon) throws HeadlessException
161   {
162     // JvOptionPaneTest only
163     return isInteractiveMode()
164             ? JOptionPane.showConfirmDialog(parentComponent, message, title,
165                     optionType, messageType, icon)
166             : (int) getMockResponse();
167   }
168
169   /**
170    * Internal version "OK"
171    * 
172    * @param parentComponent
173    * @param message
174    * @return
175    */
176   public static int showInternalConfirmDialog(Component parentComponent,
177           Object message)
178   {
179     // JvOptionPaneTest only;
180     return isInteractiveMode()
181             ? JOptionPane.showInternalConfirmDialog(parentComponent,
182                     message)
183             : (int) getMockResponse();
184   }
185
186   /**
187    * Internal version -- changed to standard version for now
188    * 
189    * @param parentComponent
190    * @param message
191    * @param title
192    * @param optionType
193    * @return
194    */
195   public static int showInternalConfirmDialog(Component parentComponent,
196           String message, String title, int optionType)
197   {
198     if (!isInteractiveMode())
199     {
200       return (int) getMockResponse();
201     }
202     switch (optionType)
203     {
204     case JOptionPane.YES_NO_CANCEL_OPTION:
205       // ColourMenuHelper.addMenuItmers.offerRemoval TODO
206     case JOptionPane.YES_NO_OPTION:
207       // UserDefinedColoursSave -- relevant? TODO
208       // $FALL-THROUGH$
209     default:
210     case JOptionPane.OK_CANCEL_OPTION:
211
212       // EditNameDialog --- uses panel for messsage TODO
213
214       // Desktop.inputURLMenuItem
215       // WsPreferenses
216       return JOptionPane.showConfirmDialog(parentComponent, message, title,
217               optionType);
218     }
219   }
220
221   /**
222    * 
223    * @param parentComponent
224    * @param message
225    * @param title
226    * @param optionType
227    * @param messageType
228    * @return
229    */
230   public static int showInternalConfirmDialog(Component parentComponent,
231           Object message, String title, int optionType, int messageType)
232   {
233     if (!isInteractiveMode())
234     {
235       return (int) getMockResponse();
236     }
237     switch (optionType)
238     {
239     case JOptionPane.YES_NO_CANCEL_OPTION:
240     case JOptionPane.YES_NO_OPTION:
241       // UserQuestionanaireCheck
242       // VamsasApplication
243       // $FALL-THROUGH$
244     default:
245     case JOptionPane.OK_CANCEL_OPTION:
246       // will fall back to simple HTML
247       return JOptionPane.showConfirmDialog(parentComponent, message, title,
248               optionType, messageType);
249     }
250   }
251
252   /**
253    * adds icon; no longer internal
254    * 
255    * @param parentComponent
256    * @param message
257    * @param title
258    * @param optionType
259    * @param messageType
260    * @param icon
261    * @return
262    */
263   public static int showInternalConfirmDialog(Component parentComponent,
264           Object message, String title, int optionType, int messageType,
265           Icon icon)
266   {
267     if (!isInteractiveMode())
268     {
269       return (int) getMockResponse();
270     }
271     switch (optionType)
272     {
273     case JOptionPane.YES_NO_CANCEL_OPTION:
274     case JOptionPane.YES_NO_OPTION:
275       //$FALL-THROUGH$
276     default:
277     case JOptionPane.OK_CANCEL_OPTION:
278       // Preferences editLink/newLink
279       return JOptionPane.showConfirmDialog(parentComponent, message, title,
280               optionType, messageType, icon);
281     }
282
283   }
284
285   /**
286    * custom options full-featured
287    * 
288    * @param parentComponent
289    * @param message
290    * @param title
291    * @param optionType
292    * @param messageType
293    * @param icon
294    * @param options
295    * @param initialValue
296    * @return
297    * @throws HeadlessException
298    */
299   public static int showOptionDialog(Component parentComponent,
300           String message, String title, int optionType, int messageType,
301           Icon icon, Object[] options, Object initialValue)
302           throws HeadlessException
303   {
304     if (!isInteractiveMode())
305     {
306       return (int) getMockResponse();
307     }
308     // two uses:
309     //
310     // TODO
311     //
312     // 1) AlignViewport for openLinkedAlignment
313     //
314     // Show a dialog with the option to open and link (cDNA <-> protein) as a
315     // new
316     // alignment, either as a standalone alignment or in a split frame. Returns
317     // true if the new alignment was opened, false if not, because the user
318     // declined the offer.
319     //
320     // 2) UserDefinedColors warning about saving over a name already defined
321     //
322     return JOptionPane.showOptionDialog(parentComponent, message, title,
323             optionType, messageType, icon, options, initialValue);
324   }
325
326   /**
327    * Just an OK message
328    * 
329    * @param message
330    * @throws HeadlessException
331    */
332   public static void showMessageDialog(Component parentComponent,
333           String message) throws HeadlessException
334   {
335     if (!isInteractiveMode())
336     {
337       outputMessage(message);
338       return;
339     }
340
341     // test class only
342
343     JOptionPane.showMessageDialog(parentComponent, message);
344   }
345
346   /**
347    * OK with message, title, and type
348    * 
349    * @param parentComponent
350    * @param message
351    * @param title
352    * @param messageType
353    * @throws HeadlessException
354    */
355   public static void showMessageDialog(Component parentComponent,
356           String message, String title, int messageType)
357           throws HeadlessException
358   {
359     // 30 implementations -- all just fine.
360
361     if (!isInteractiveMode())
362     {
363       outputMessage(message);
364       return;
365     }
366
367     JOptionPane.showMessageDialog(parentComponent,
368             getPrefix(messageType) + message, title, messageType);
369   }
370
371   /**
372    * adds title and icon
373    * 
374    * @param parentComponent
375    * @param message
376    * @param title
377    * @param messageType
378    * @param icon
379    * @throws HeadlessException
380    */
381   public static void showMessageDialog(Component parentComponent,
382           String message, String title, int messageType, Icon icon)
383           throws HeadlessException
384   {
385
386     // test only
387
388     if (!isInteractiveMode())
389     {
390       outputMessage(message);
391       return;
392     }
393
394     JOptionPane.showMessageDialog(parentComponent, message, title,
395             messageType, icon);
396   }
397
398   /**
399    * was internal
400    * 
401    */
402   public static void showInternalMessageDialog(Component parentComponent,
403           Object message)
404   {
405
406     // WsPreferences only
407
408     if (!isInteractiveMode())
409     {
410       outputMessage(message);
411       return;
412     }
413
414     JOptionPane.showMessageDialog(parentComponent, message);
415   }
416
417   /**
418    * Adds title and messageType
419    * 
420    * @param parentComponent
421    * @param message
422    * @param title
423    * @param messageType
424    */
425   public static void showInternalMessageDialog(Component parentComponent,
426           String message, String title, int messageType)
427   {
428
429     // 41 references
430
431     if (!isInteractiveMode())
432     {
433       outputMessage(message);
434       return;
435     }
436
437     JOptionPane.showMessageDialog(parentComponent,
438             getPrefix(messageType) + message, title, messageType);
439   }
440
441   /**
442    * 
443    * @param parentComponent
444    * @param message
445    * @param title
446    * @param messageType
447    * @param icon
448    */
449   public static void showInternalMessageDialog(Component parentComponent,
450           Object message, String title, int messageType, Icon icon)
451   {
452
453     // test only
454
455     if (!isInteractiveMode())
456     {
457       outputMessage(message);
458       return;
459     }
460
461     JOptionPane.showMessageDialog(parentComponent, message, title,
462             messageType, icon);
463   }
464
465   /**
466    * 
467    * @param message
468    * @return
469    * @throws HeadlessException
470    */
471   public static String showInputDialog(Object message)
472           throws HeadlessException
473   {
474     // test only
475
476     if (!isInteractiveMode())
477     {
478       return getMockResponse().toString();
479     }
480
481     return JOptionPane.showInputDialog(message);
482   }
483
484   /**
485    * adds inital selection value
486    * 
487    * @param message
488    * @param initialSelectionValue
489    * @return
490    */
491   public static String showInputDialog(String message,
492           String initialSelectionValue)
493   {
494     if (!isInteractiveMode())
495     {
496       return getMockResponse().toString();
497     }
498
499     // AnnotationPanel character option
500
501     return JOptionPane.showInputDialog(message, initialSelectionValue);
502   }
503
504   /**
505    * adds inital selection value
506    * 
507    * @param message
508    * @param initialSelectionValue
509    * @return
510    */
511   public static String showInputDialog(Object message,
512           Object initialSelectionValue)
513   {
514     if (!isInteractiveMode())
515     {
516       return getMockResponse().toString();
517     }
518
519     // AnnotationPanel character option
520
521     return JOptionPane.showInputDialog(message, initialSelectionValue);
522   }
523
524   /**
525    * centered on parent
526    * 
527    * @param parentComponent
528    * @param message
529    * @return
530    * @throws HeadlessException
531    */
532   public static String showInputDialog(Component parentComponent,
533           String message) throws HeadlessException
534   {
535     // test only
536
537     return isInteractiveMode()
538             ? JOptionPane.showInputDialog(parentComponent, message)
539             : getMockResponse().toString();
540   }
541
542   /**
543    * input with initial selection
544    * 
545    * @param parentComponent
546    * @param message
547    * @param initialSelectionValue
548    * @return
549    */
550   public static String showInputDialog(Component parentComponent,
551           String message, String initialSelectionValue)
552   {
553
554     // AnnotationPanel
555
556     return isInteractiveMode()
557             ? JOptionPane.showInputDialog(parentComponent, message,
558                     initialSelectionValue)
559             : getMockResponse().toString();
560   }
561
562   /**
563    * input with initial selection
564    * 
565    * @param parentComponent
566    * @param message
567    * @param initialSelectionValue
568    * @return
569    */
570   public static String showInputDialog(Component parentComponent,
571           Object message, Object initialSelectionValue)
572   {
573
574     // AnnotationPanel
575
576     return isInteractiveMode()
577             ? JOptionPane.showInputDialog(parentComponent, message,
578                     initialSelectionValue)
579             : getMockResponse().toString();
580   }
581
582   /**
583    * 
584    * @param parentComponent
585    * @param message
586    * @param title
587    * @param messageType
588    * @return
589    * @throws HeadlessException
590    */
591   public static String showInputDialog(Component parentComponent,
592           String message, String title, int messageType)
593           throws HeadlessException
594   {
595
596     // test only
597
598     return isInteractiveMode()
599             ? JOptionPane.showInputDialog(parentComponent, message, title,
600                     messageType)
601             : getMockResponse().toString();
602   }
603
604   /**
605    * Customized input option
606    * 
607    * @param parentComponent
608    * @param message
609    * @param title
610    * @param messageType
611    * @param icon
612    * @param selectionValues
613    * @param initialSelectionValue
614    * @return
615    * @throws HeadlessException
616    */
617   public static Object showInputDialog(Component parentComponent,
618           Object message, String title, int messageType, Icon icon,
619           Object[] selectionValues, Object initialSelectionValue)
620           throws HeadlessException
621   {
622
623     // test only
624
625     return isInteractiveMode()
626             ? JOptionPane.showInputDialog(parentComponent, message, title,
627                     messageType, icon, selectionValues,
628                     initialSelectionValue)
629             : getMockResponse().toString();
630   }
631
632   /**
633    * internal version
634    * 
635    * @param parentComponent
636    * @param message
637    * @return
638    */
639   public static String showInternalInputDialog(Component parentComponent,
640           String message)
641   {
642     // test only
643
644     return isInteractiveMode()
645             ? JOptionPane.showInternalInputDialog(parentComponent, message)
646             : getMockResponse().toString();
647   }
648
649   /**
650    * internal with title and messageType
651    * 
652    * @param parentComponent
653    * @param message
654    * @param title
655    * @param messageType
656    * @return
657    */
658   public static String showInternalInputDialog(Component parentComponent,
659           String message, String title, int messageType)
660   {
661
662     // AlignFrame tabbedPane_mousePressed
663
664     return isInteractiveMode()
665             ? JOptionPane.showInternalInputDialog(parentComponent,
666                     getPrefix(messageType) + message, title, messageType)
667             : getMockResponse().toString();
668   }
669
670   /**
671    * customized internal
672    * 
673    * @param parentComponent
674    * @param message
675    * @param title
676    * @param messageType
677    * @param icon
678    * @param selectionValues
679    * @param initialSelectionValue
680    * @return
681    */
682   public static Object showInternalInputDialog(Component parentComponent,
683           String message, String title, int messageType, Icon icon,
684           Object[] selectionValues, Object initialSelectionValue)
685   {
686     // test only
687
688     return isInteractiveMode()
689             ? JOptionPane.showInternalInputDialog(parentComponent, message,
690                     title, messageType, icon, selectionValues,
691                     initialSelectionValue)
692             : getMockResponse().toString();
693   }
694
695   ///////////// end of options ///////////////
696
697   private static void outputMessage(Object message)
698   {
699     System.out.println(">>> JOption Message : " + message.toString());
700   }
701
702   public static Object getMockResponse()
703   {
704     return mockResponse;
705   }
706
707   public static void setMockResponse(Object mockOption)
708   {
709     JvOptionPane.mockResponse = mockOption;
710   }
711
712   public static void resetMock()
713   {
714     setMockResponse(JOptionPane.CANCEL_OPTION);
715     setInteractiveMode(true);
716   }
717
718   public static boolean isInteractiveMode()
719   {
720     return interactiveMode;
721   }
722
723   public static void setInteractiveMode(boolean interactive)
724   {
725     JvOptionPane.interactiveMode = interactive;
726   }
727
728   private static String getPrefix(int messageType)
729   {
730     String prefix = "";
731
732     // JavaScript only
733     if (Platform.isJS())
734     {
735       switch (messageType)
736       {
737       case JOptionPane.WARNING_MESSAGE:
738         prefix = "WARNING! ";
739         break;
740       case JOptionPane.ERROR_MESSAGE:
741         prefix = "ERROR! ";
742         break;
743       default:
744         prefix = "Note: ";
745       }
746     }
747     return prefix;
748   }
749
750   /**
751    * create a new option dialog that can be used to register responses - along
752    * lines of showOptionDialog
753    * 
754    * @param desktop
755    * @param question
756    * @param string
757    * @param defaultOption
758    * @param plainMessage
759    * @param object
760    * @param options
761    * @param string2
762    * @return
763    */
764   public static JvOptionPane newOptionDialog()
765   {
766     return new JvOptionPane(null);
767   }
768
769   public static JvOptionPane newOptionDialog(Component parentComponent)
770   {
771     return new JvOptionPane(parentComponent);
772   }
773
774   public void showDialog(String message, String title, int optionType,
775           int messageType, Icon icon, Object[] options, Object initialValue)
776   {
777     showDialog(message, title, optionType, messageType, icon, options,
778             initialValue, true);
779   }
780
781   public void showDialog(Object message, String title, int optionType,
782           int messageType, Icon icon, Object[] options, Object initialValue,
783           boolean modal)
784   {
785     showDialog(message, title, optionType, messageType, icon, options,
786             initialValue, modal, null);
787   }
788
789   public void showDialog(Object message, String title, int optionType,
790           int messageType, Icon icon, Object[] options, Object initialValue,
791           boolean modal, JButton[] buttons)
792   {
793     if (!isInteractiveMode())
794     {
795       handleResponse(getMockResponse());
796     }
797     // two uses:
798     //
799     // TODO
800     //
801     // 1) AlignViewport for openLinkedAlignment
802     //
803     // Show a dialog with the option to open and link (cDNA <-> protein) as a
804     // new
805     // alignment, either as a standalone alignment or in a split frame. Returns
806     // true if the new alignment was opened, false if not, because the user
807     // declined the offer.
808     //
809     // 2) UserDefinedColors warning about saving over a name already defined
810     //
811
812     ourOptions = Arrays.asList(options);
813
814     if (modal)
815     {
816       boolean useButtons = false;
817       Object initialValueButton = null;
818       NOTNULL: if (buttons != null)
819       {
820         if (buttons.length != options.length)
821         {
822           jalview.bin.Console.error(
823                   "Supplied buttons array not the same length as supplied options array.");
824           break NOTNULL;
825         }
826         int[] buttonActions = { JOptionPane.YES_OPTION,
827             JOptionPane.NO_OPTION, JOptionPane.CANCEL_OPTION };
828         for (int i = 0; i < options.length; i++)
829         {
830           Object o = options[i];
831           jalview.bin.Console.debug(
832                   "Setting button " + i + " to '" + o.toString() + "'");
833           JButton jb = buttons[i];
834
835           if (o.equals(initialValue))
836             initialValueButton = jb;
837
838           int buttonAction = buttonActions[i];
839           Callable action = callbacks.get(buttonAction);
840           jb.setText((String) o);
841           jb.addActionListener(new ActionListener()
842           {
843             @Override
844             public void actionPerformed(ActionEvent e)
845             {
846
847               Object obj = e.getSource();
848               Object joptionpaneObject = getAncestorClass(obj,
849                       JOptionPane.class);
850               if (joptionpaneObject == null
851                       || !(joptionpaneObject instanceof JOptionPane))
852               {
853                 jalview.bin.Console.debug(
854                         "Could not find JOptionPane ancestor of event object "
855                                 + obj);
856                 return;
857               }
858               JOptionPane joptionpane = (JOptionPane) joptionpaneObject;
859               joptionpane.setValue(buttonAction);
860               if (action != null)
861                 Executors.newSingleThreadExecutor().submit(action);
862               // joptionpane.transferFocusBackward();
863               joptionpane.transferFocusBackward();
864               joptionpane.setVisible(false);
865               // put focus and raise parent window if possible, unless cancel
866               // button pressed
867               boolean raiseParent = (parentComponent != null);
868               if (buttonAction == JOptionPane.CANCEL_OPTION)
869                 raiseParent = false;
870               if (optionType == JOptionPane.YES_NO_OPTION
871                       && buttonAction == JOptionPane.NO_OPTION)
872                 raiseParent = false;
873               if (raiseParent)
874               {
875                 parentComponent.requestFocus();
876                 if (parentComponent instanceof JInternalFrame)
877                 {
878                   JInternalFrame jif = (JInternalFrame) parentComponent;
879                   jif.show();
880                   jif.moveToFront();
881                   jif.grabFocus();
882                 }
883                 else if (parentComponent instanceof Window)
884                 {
885                   Window w = (Window) parentComponent;
886                   w.toFront();
887                   w.requestFocus();
888                 }
889               }
890               joptionpane.setVisible(false);
891             }
892           });
893
894         }
895         useButtons = true;
896       }
897       // use a JOptionPane as usual
898       int response = JOptionPane.showOptionDialog(parentComponent, message,
899               title, optionType, messageType, icon,
900               useButtons ? buttons : options,
901               useButtons ? initialValueButton : initialValue);
902
903       /*
904        * In Java, the response is returned to this thread and handled here;
905        * (for Javascript, see propertyChange)
906        */
907       if (!Platform.isJS())
908       /**
909        * Java only
910        * 
911        * @j2sIgnore
912        */
913       {
914         handleResponse(response);
915       }
916     }
917     else
918     {
919       /*
920        * This is java similar to the swingjs handling, with the callbacks
921        * attached to the button press of the dialog.  This means we can use
922        * a non-modal JDialog for the confirmation without blocking the GUI.
923        */
924
925       JDialog dialog = createDialog(parentComponent, message, title,
926               optionType, messageType, icon, options, initialValue, modal,
927               buttons);
928       jalview.bin.Console.debug("About to setVisible(true)");
929       dialog.setVisible(true);
930       jalview.bin.Console.debug("Just setVisible(true)");
931     }
932   }
933
934   public void showInternalDialog(JPanel mainPanel, String title,
935           int yesNoCancelOption, int questionMessage, Icon icon,
936           Object[] options, String initresponse)
937   {
938     if (!isInteractiveMode())
939     {
940       handleResponse(getMockResponse());
941     }
942
943     ourOptions = Arrays.asList(options);
944     int response;
945     if (parentComponent != this)
946     {
947       response = JOptionPane.showInternalOptionDialog(parentComponent,
948               mainPanel, title, yesNoCancelOption, questionMessage, icon,
949               options, initresponse);
950     }
951     else
952     {
953       response = JOptionPane.showOptionDialog(parentComponent, mainPanel,
954               title, yesNoCancelOption, questionMessage, icon, options,
955               initresponse);
956     }
957     if (!Platform.isJS())
958     /**
959      * Java only
960      * 
961      * @j2sIgnore
962      */
963     {
964       handleResponse(response);
965     }
966   }
967
968   /*
969   @Override
970   public JvOptionPane setResponseHandler(Object response, Runnable action)
971   {
972     callbacks.put(response, new Callable<Void>()
973     {
974       @Override
975       public Void call()
976       {
977         action.run();
978         return null;
979       }
980     });
981     return this;
982   }
983   */
984   @Override
985   public JvOptionPane setResponseHandler(Object response, Callable action)
986   {
987     callbacks.put(response, action);
988     return this;
989   }
990
991   /**
992    * showDialogOnTop will create a dialog that (attempts to) come to top of OS
993    * desktop windows
994    */
995   public static int showDialogOnTop(String label, String actionString,
996           int JOPTIONPANE_OPTION, int JOPTIONPANE_MESSAGETYPE)
997   {
998     // Ensure Jalview window is brought to front (primarily for Quit
999     // confirmation window to be visible)
1000
1001     // This method of raising the Jalview window is broken in java
1002     // jalviewDesktop.setVisible(true);
1003     // jalviewDesktop.toFront();
1004
1005     // A better hack which works is to create a new JFrame parent with
1006     // setAlwaysOnTop(true)
1007     JFrame dialogParent = new JFrame();
1008     dialogParent.setAlwaysOnTop(true);
1009
1010     int answer = JOptionPane.showConfirmDialog(dialogParent, label,
1011             actionString, JOPTIONPANE_OPTION, JOPTIONPANE_MESSAGETYPE);
1012
1013     dialogParent.setAlwaysOnTop(false);
1014     dialogParent.dispose();
1015
1016     return answer;
1017   }
1018
1019   public void showDialogOnTopAsync(String label, String actionString,
1020           int JOPTIONPANE_OPTION, int JOPTIONPANE_MESSAGETYPE, Icon icon,
1021           Object[] options, Object initialValue, boolean modal)
1022   {
1023     showDialogOnTopAsync(new JFrame(), label, actionString,
1024             JOPTIONPANE_OPTION, JOPTIONPANE_MESSAGETYPE, icon, options,
1025             initialValue, modal);
1026   }
1027
1028   public void showDialogOnTopAsync(JFrame dialogParent, Object label,
1029           String actionString, int JOPTIONPANE_OPTION,
1030           int JOPTIONPANE_MESSAGETYPE, Icon icon, Object[] options,
1031           Object initialValue, boolean modal)
1032   {
1033     showDialogOnTopAsync(dialogParent, label, actionString,
1034             JOPTIONPANE_OPTION, JOPTIONPANE_MESSAGETYPE, icon, options,
1035             initialValue, modal, null);
1036   }
1037
1038   public void showDialogOnTopAsync(JFrame dialogParent, Object label,
1039           String actionString, int JOPTIONPANE_OPTION,
1040           int JOPTIONPANE_MESSAGETYPE, Icon icon, Object[] options,
1041           Object initialValue, boolean modal, JButton[] buttons)
1042   {
1043     // Ensure Jalview window is brought to front (primarily for Quit
1044     // confirmation window to be visible)
1045
1046     // This method of raising the Jalview window is broken in java
1047     // jalviewDesktop.setVisible(true);
1048     // jalviewDesktop.toFront();
1049
1050     // A better hack which works is to create a new JFrame parent with
1051     // setAlwaysOnTop(true)
1052     dialogParent.setAlwaysOnTop(true);
1053     parentComponent = dialogParent;
1054
1055     showDialog(label, actionString, JOPTIONPANE_OPTION,
1056             JOPTIONPANE_MESSAGETYPE, icon, options, initialValue, modal,
1057             buttons);
1058
1059     dialogParent.setAlwaysOnTop(false);
1060     dialogParent.dispose();
1061   }
1062
1063   /**
1064    * JalviewJS signals option selection by a property change event for the
1065    * option e.g. "OK". This methods responds to that by running the response
1066    * action that corresponds to that option.
1067    * 
1068    * @param evt
1069    */
1070   @Override
1071   public void propertyChange(PropertyChangeEvent evt)
1072   {
1073     Object newValue = evt.getNewValue();
1074     int ourOption = ourOptions.indexOf(newValue);
1075     if (ourOption >= 0)
1076     {
1077       handleResponse(ourOption);
1078     }
1079     else
1080     {
1081       // try our luck..
1082       handleResponse(newValue);
1083     }
1084   }
1085
1086   @Override
1087   public void handleResponse(Object response)
1088   {
1089     /*
1090     * this test is for NaN in Chrome
1091     */
1092     if (response != null && !response.equals(response))
1093     {
1094       return;
1095     }
1096     Callable<Void> action = callbacks.get(response);
1097     if (action != null)
1098     {
1099       try
1100       {
1101         action.call();
1102       } catch (Exception e)
1103       {
1104         e.printStackTrace();
1105       }
1106       if (parentComponent != null)
1107         parentComponent.requestFocus();
1108     }
1109   }
1110
1111   /**
1112    * Create a non-modal confirm dialog
1113    */
1114   public JDialog createDialog(Component parentComponent, Object message,
1115           String title, int optionType, int messageType, Icon icon,
1116           Object[] options, Object initialValue, boolean modal)
1117   {
1118     return createDialog(parentComponent, message, title, optionType,
1119             messageType, icon, options, initialValue, modal, null);
1120   }
1121
1122   public JDialog createDialog(Component parentComponent, Object message,
1123           String title, int optionType, int messageType, Icon icon,
1124           Object[] options, Object initialValue, boolean modal,
1125           JButton[] buttons)
1126   {
1127     JButton[] optionsButtons = null;
1128     Object initialValueButton = null;
1129     JOptionPane joptionpane = new JOptionPane();
1130     // Make button options
1131     int[] buttonActions = { JOptionPane.YES_OPTION, JOptionPane.NO_OPTION,
1132         JOptionPane.CANCEL_OPTION };
1133
1134     // we need the strings to make the buttons with actionEventListener
1135     if (options == null)
1136     {
1137       ArrayList<String> options_default = new ArrayList<>();
1138       options_default.add(UIManager.getString("OptionPane.yesButtonText"));
1139       if (optionType == JOptionPane.YES_NO_OPTION
1140               || optionType == JOptionPane.YES_NO_CANCEL_OPTION)
1141       {
1142         options_default.add(UIManager.getString("OptionPane.noButtonText"));
1143       }
1144       if (optionType == JOptionPane.YES_NO_CANCEL_OPTION)
1145       {
1146         options_default
1147                 .add(UIManager.getString("OptionPane.cancelButtonText"));
1148       }
1149       options = options_default.toArray();
1150     }
1151     if (!Platform.isJS()) // JalviewJS already uses callback, don't need to
1152                           // add them here
1153     {
1154       if (((optionType == JOptionPane.YES_OPTION
1155               || optionType == JOptionPane.NO_OPTION
1156               || optionType == JOptionPane.CANCEL_OPTION
1157               || optionType == JOptionPane.OK_OPTION
1158               || optionType == JOptionPane.DEFAULT_OPTION)
1159               && options.length < 1)
1160               || ((optionType == JOptionPane.YES_NO_OPTION
1161                       || optionType == JOptionPane.OK_CANCEL_OPTION)
1162                       && options.length < 2)
1163               || (optionType == JOptionPane.YES_NO_CANCEL_OPTION
1164                       && options.length < 3))
1165       {
1166         jalview.bin.Console
1167                 .debug("JvOptionPane: not enough options for dialog type");
1168       }
1169       optionsButtons = new JButton[options.length];
1170       for (int i = 0; i < options.length && i < 3; i++)
1171       {
1172         Object o = options[i];
1173         int buttonAction = buttonActions[i];
1174         Callable action = callbacks.get(buttonAction);
1175         JButton jb;
1176         if (buttons != null && buttons.length > i && buttons[i] != null)
1177         {
1178           jb = buttons[i];
1179         }
1180         else
1181         {
1182           jb = new JButton();
1183         }
1184         jb.setText((String) o);
1185         jb.addActionListener(new ActionListener()
1186         {
1187           @Override
1188           public void actionPerformed(ActionEvent e)
1189           {
1190             joptionpane.setValue(buttonAction);
1191             if (action != null)
1192               Executors.newSingleThreadExecutor().submit(action);
1193             // joptionpane.transferFocusBackward();
1194             joptionpane.transferFocusBackward();
1195             joptionpane.setVisible(false);
1196             // put focus and raise parent window if possible, unless cancel
1197             // button pressed
1198             boolean raiseParent = (parentComponent != null);
1199             if (buttonAction == JOptionPane.CANCEL_OPTION)
1200               raiseParent = false;
1201             if (optionType == JOptionPane.YES_NO_OPTION
1202                     && buttonAction == JOptionPane.NO_OPTION)
1203               raiseParent = false;
1204             if (raiseParent)
1205             {
1206               parentComponent.requestFocus();
1207               if (parentComponent instanceof JInternalFrame)
1208               {
1209                 JInternalFrame jif = (JInternalFrame) parentComponent;
1210                 jif.show();
1211                 jif.moveToFront();
1212                 jif.grabFocus();
1213               }
1214               else if (parentComponent instanceof Window)
1215               {
1216                 Window w = (Window) parentComponent;
1217                 w.toFront();
1218                 w.requestFocus();
1219               }
1220             }
1221             joptionpane.setVisible(false);
1222           }
1223         });
1224         optionsButtons[i] = jb;
1225         if (o.equals(initialValue))
1226           initialValueButton = jb;
1227       }
1228     }
1229     joptionpane.setMessage(message);
1230     joptionpane.setMessageType(messageType);
1231     joptionpane.setOptionType(optionType);
1232     joptionpane.setIcon(icon);
1233     joptionpane.setOptions(Platform.isJS() ? options : optionsButtons);
1234     joptionpane.setInitialValue(
1235             Platform.isJS() ? initialValue : initialValueButton);
1236
1237     JDialog dialog = joptionpane.createDialog(parentComponent, title);
1238     dialog.setModalityType(
1239             modal ? ModalityType.APPLICATION_MODAL : ModalityType.MODELESS);
1240     dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
1241     return dialog;
1242   }
1243
1244   /**
1245    * Utility to programmatically click a button on a JOptionPane (as a JFrame)
1246    * 
1247    * returns true if button was found
1248    */
1249   public static boolean clickButton(JFrame frame, int buttonType)
1250   {
1251
1252     return false;
1253   }
1254
1255   /**
1256    * Get parent JOptionPane if there is one
1257    */
1258   public static Container getAncestorClass(Object o, Class cl)
1259   {
1260     Container c;
1261     if (o instanceof Container)
1262       c = (Container) o;
1263     else
1264       return null;
1265     Container p = c.getParent();
1266     if (p == null)
1267       return null;
1268     if (p.getClass() == cl)
1269     {
1270       return p;
1271     }
1272     else if (p instanceof Component)
1273     {
1274       return getAncestorClass((Component) p, cl);
1275     }
1276     return null;
1277   }
1278 }