JAL-1988 JAL-3772 Fixes highlighted by tests
[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.Callable;
38 import java.util.concurrent.Executors;
39
40 import javax.swing.Icon;
41 import javax.swing.JButton;
42 import javax.swing.JDialog;
43 import javax.swing.JFrame;
44 import javax.swing.JInternalFrame;
45 import javax.swing.JOptionPane;
46 import javax.swing.JPanel;
47 import javax.swing.SwingUtilities;
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<Void>> 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       return;
797     }
798     // two uses:
799     //
800     // TODO
801     //
802     // 1) AlignViewport for openLinkedAlignment
803     //
804     // Show a dialog with the option to open and link (cDNA <-> protein) as a
805     // new
806     // alignment, either as a standalone alignment or in a split frame. Returns
807     // true if the new alignment was opened, false if not, because the user
808     // declined the offer.
809     //
810     // 2) UserDefinedColors warning about saving over a name already defined
811     //
812
813     ourOptions = Arrays.asList(options);
814
815     if (modal)
816     {
817       boolean useButtons = false;
818       Object initialValueButton = null;
819       NOTNULL: if (buttons != null)
820       {
821         if (buttons.length != options.length)
822         {
823           jalview.bin.Console.error(
824                   "Supplied buttons array not the same length as supplied options array.");
825           break NOTNULL;
826         }
827         int[] buttonActions = { JOptionPane.YES_OPTION,
828             JOptionPane.NO_OPTION, JOptionPane.CANCEL_OPTION };
829         for (int i = 0; i < options.length; i++)
830         {
831           Object o = options[i];
832           jalview.bin.Console.debug(
833                   "Setting button " + i + " to '" + o.toString() + "'");
834           JButton jb = buttons[i];
835
836           if (o.equals(initialValue))
837             initialValueButton = jb;
838
839           int buttonAction = buttonActions[i];
840           Callable<Void> action = callbacks.get(buttonAction);
841           jb.setText((String) o);
842           jb.addActionListener(new ActionListener()
843           {
844             @Override
845             public void actionPerformed(ActionEvent e)
846             {
847
848               Object obj = e.getSource();
849               if (obj == null || !(obj instanceof Component))
850               {
851                 jalview.bin.Console.debug(
852                         "Could not find Component source of event object "
853                                 + obj);
854                 return;
855               }
856               Object joptionpaneObject = SwingUtilities.getAncestorOfClass(
857                       JOptionPane.class, (Component) obj);
858               if (joptionpaneObject == null
859                       || !(joptionpaneObject instanceof JOptionPane))
860               {
861                 jalview.bin.Console.debug(
862                         "Could not find JOptionPane ancestor of event object "
863                                 + obj);
864                 return;
865               }
866               JOptionPane joptionpane = (JOptionPane) joptionpaneObject;
867               joptionpane.setValue(buttonAction);
868               if (action != null)
869                 Executors.newSingleThreadExecutor().submit(action);
870               joptionpane.transferFocusBackward();
871               joptionpane.setVisible(false);
872               // put focus and raise parent window if possible, unless cancel or
873               // no button pressed
874               boolean raiseParent = (parentComponent != null);
875               if (buttonAction == JOptionPane.CANCEL_OPTION)
876                 raiseParent = false;
877               if (optionType == JOptionPane.YES_NO_OPTION
878                       && buttonAction == JOptionPane.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
901         }
902         useButtons = true;
903       }
904       // use a JOptionPane as usual
905       int response = JOptionPane.showOptionDialog(parentComponent, message,
906               title, optionType, messageType, icon,
907               useButtons ? buttons : options,
908               useButtons ? initialValueButton : initialValue);
909
910       /*
911        * In Java, the response is returned to this thread and handled here;
912        * (for Javascript, see propertyChange)
913        */
914       if (!Platform.isJS())
915       /**
916        * Java only
917        * 
918        * @j2sIgnore
919        */
920       {
921         handleResponse(response);
922       }
923     }
924     else
925     {
926       /*
927        * This is java similar to the swingjs handling, with the callbacks
928        * attached to the button press of the dialog.  This means we can use
929        * a non-modal JDialog for the confirmation without blocking the GUI.
930        */
931
932       JDialog dialog = createDialog(parentComponent, message, title,
933               optionType, messageType, icon, options, initialValue, modal,
934               buttons);
935       jalview.bin.Console.debug("About to setVisible(true)");
936       dialog.setVisible(true);
937       jalview.bin.Console.debug("Just setVisible(true)");
938     }
939   }
940
941   public void showInternalDialog(JPanel mainPanel, String title,
942           int yesNoCancelOption, int questionMessage, Icon icon,
943           Object[] options, String initresponse)
944   {
945     if (!isInteractiveMode())
946     {
947       handleResponse(getMockResponse());
948     }
949
950     ourOptions = Arrays.asList(options);
951     int response;
952     if (parentComponent != this)
953     {
954       response = JOptionPane.showInternalOptionDialog(parentComponent,
955               mainPanel, title, yesNoCancelOption, questionMessage, icon,
956               options, initresponse);
957     }
958     else
959     {
960       response = JOptionPane.showOptionDialog(parentComponent, mainPanel,
961               title, yesNoCancelOption, questionMessage, icon, options,
962               initresponse);
963     }
964     if (!Platform.isJS())
965     /**
966      * Java only
967      * 
968      * @j2sIgnore
969      */
970     {
971       handleResponse(response);
972     }
973   }
974
975   /*
976   @Override
977   public JvOptionPane setResponseHandler(Object response, Runnable action)
978   {
979     callbacks.put(response, new Callable<Void>()
980     {
981       @Override
982       public Void call()
983       {
984         action.run();
985         return null;
986       }
987     });
988     return this;
989   }
990   */
991   @Override
992   public JvOptionPane setResponseHandler(Object response,
993           Callable<Void> action)
994   {
995     callbacks.put(response, action);
996     return this;
997   }
998
999   /**
1000    * showDialogOnTop will create a dialog that (attempts to) come to top of OS
1001    * desktop windows
1002    */
1003   public static int showDialogOnTop(String label, String actionString,
1004           int JOPTIONPANE_OPTION, int JOPTIONPANE_MESSAGETYPE)
1005   {
1006     if (!isInteractiveMode())
1007     {
1008       return (int) getMockResponse();
1009     }
1010     // Ensure Jalview window is brought to front (primarily for Quit
1011     // confirmation window to be visible)
1012
1013     // This method of raising the Jalview window is broken in java
1014     // jalviewDesktop.setVisible(true);
1015     // jalviewDesktop.toFront();
1016
1017     // A better hack which works is to create a new JFrame parent with
1018     // setAlwaysOnTop(true)
1019     JFrame dialogParent = new JFrame();
1020     dialogParent.setAlwaysOnTop(true);
1021
1022     int answer = JOptionPane.showConfirmDialog(dialogParent, label,
1023             actionString, JOPTIONPANE_OPTION, JOPTIONPANE_MESSAGETYPE);
1024
1025     dialogParent.setAlwaysOnTop(false);
1026     dialogParent.dispose();
1027
1028     return answer;
1029   }
1030
1031   public void showDialogOnTopAsync(String label, String actionString,
1032           int JOPTIONPANE_OPTION, int JOPTIONPANE_MESSAGETYPE, Icon icon,
1033           Object[] options, Object initialValue, boolean modal)
1034   {
1035     showDialogOnTopAsync(new JFrame(), label, actionString,
1036             JOPTIONPANE_OPTION, JOPTIONPANE_MESSAGETYPE, icon, options,
1037             initialValue, modal);
1038   }
1039
1040   public void showDialogOnTopAsync(JFrame dialogParent, Object label,
1041           String actionString, int JOPTIONPANE_OPTION,
1042           int JOPTIONPANE_MESSAGETYPE, Icon icon, Object[] options,
1043           Object initialValue, boolean modal)
1044   {
1045     showDialogOnTopAsync(dialogParent, label, actionString,
1046             JOPTIONPANE_OPTION, JOPTIONPANE_MESSAGETYPE, icon, options,
1047             initialValue, modal, null);
1048   }
1049
1050   public void showDialogOnTopAsync(JFrame dialogParent, Object label,
1051           String actionString, int JOPTIONPANE_OPTION,
1052           int JOPTIONPANE_MESSAGETYPE, Icon icon, Object[] options,
1053           Object initialValue, boolean modal, JButton[] buttons)
1054   {
1055     if (!isInteractiveMode())
1056     {
1057       handleResponse(getMockResponse());
1058       return;
1059     }
1060     // Ensure Jalview window is brought to front (primarily for Quit
1061     // confirmation window to be visible)
1062
1063     // This method of raising the Jalview window is broken in java
1064     // jalviewDesktop.setVisible(true);
1065     // jalviewDesktop.toFront();
1066
1067     // A better hack which works is to create a new JFrame parent with
1068     // setAlwaysOnTop(true)
1069     dialogParent.setAlwaysOnTop(true);
1070     parentComponent = dialogParent;
1071
1072     showDialog(label, actionString, JOPTIONPANE_OPTION,
1073             JOPTIONPANE_MESSAGETYPE, icon, options, initialValue, modal,
1074             buttons);
1075
1076     dialogParent.setAlwaysOnTop(false);
1077     dialogParent.dispose();
1078   }
1079
1080   /**
1081    * JalviewJS signals option selection by a property change event for the
1082    * option e.g. "OK". This methods responds to that by running the response
1083    * action that corresponds to that option.
1084    * 
1085    * @param evt
1086    */
1087   @Override
1088   public void propertyChange(PropertyChangeEvent evt)
1089   {
1090     Object newValue = evt.getNewValue();
1091     int ourOption = ourOptions.indexOf(newValue);
1092     if (ourOption >= 0)
1093     {
1094       handleResponse(ourOption);
1095     }
1096     else
1097     {
1098       // try our luck..
1099       handleResponse(newValue);
1100     }
1101   }
1102
1103   @Override
1104   public void handleResponse(Object response)
1105   {
1106     /*
1107     * this test is for NaN in Chrome
1108     */
1109     if (response != null && !response.equals(response))
1110     {
1111       return;
1112     }
1113     Callable<Void> action = callbacks.get(response);
1114     if (action != null)
1115     {
1116       try
1117       {
1118         action.call();
1119       } catch (Exception e)
1120       {
1121         e.printStackTrace();
1122       }
1123       if (parentComponent != null)
1124         parentComponent.requestFocus();
1125     }
1126   }
1127
1128   /**
1129    * Create a non-modal confirm dialog
1130    */
1131   public JDialog createDialog(Component parentComponent, Object message,
1132           String title, int optionType, int messageType, Icon icon,
1133           Object[] options, Object initialValue, boolean modal)
1134   {
1135     return createDialog(parentComponent, message, title, optionType,
1136             messageType, icon, options, initialValue, modal, null);
1137   }
1138
1139   public JDialog createDialog(Component parentComponent, Object message,
1140           String title, int optionType, int messageType, Icon icon,
1141           Object[] options, Object initialValue, boolean modal,
1142           JButton[] buttons)
1143   {
1144     if (!isInteractiveMode())
1145     {
1146       handleResponse(getMockResponse());
1147       return null;
1148     }
1149     JButton[] optionsButtons = null;
1150     Object initialValueButton = null;
1151     JOptionPane joptionpane = new JOptionPane();
1152     // Make button options
1153     int[] buttonActions = { JOptionPane.YES_OPTION, JOptionPane.NO_OPTION,
1154         JOptionPane.CANCEL_OPTION };
1155
1156     // we need the strings to make the buttons with actionEventListener
1157     if (options == null)
1158     {
1159       ArrayList<String> options_default = new ArrayList<>();
1160       options_default.add(UIManager.getString("OptionPane.yesButtonText"));
1161       if (optionType == JOptionPane.YES_NO_OPTION
1162               || optionType == JOptionPane.YES_NO_CANCEL_OPTION)
1163       {
1164         options_default.add(UIManager.getString("OptionPane.noButtonText"));
1165       }
1166       if (optionType == JOptionPane.YES_NO_CANCEL_OPTION)
1167       {
1168         options_default
1169                 .add(UIManager.getString("OptionPane.cancelButtonText"));
1170       }
1171       options = options_default.toArray();
1172     }
1173     if (!Platform.isJS()) // JalviewJS already uses callback, don't need to
1174                           // add them here
1175     {
1176       if (((optionType == JOptionPane.YES_OPTION
1177               || optionType == JOptionPane.NO_OPTION
1178               || optionType == JOptionPane.CANCEL_OPTION
1179               || optionType == JOptionPane.OK_OPTION
1180               || optionType == JOptionPane.DEFAULT_OPTION)
1181               && options.length < 1)
1182               || ((optionType == JOptionPane.YES_NO_OPTION
1183                       || optionType == JOptionPane.OK_CANCEL_OPTION)
1184                       && options.length < 2)
1185               || (optionType == JOptionPane.YES_NO_CANCEL_OPTION
1186                       && options.length < 3))
1187       {
1188         jalview.bin.Console
1189                 .debug("JvOptionPane: not enough options for dialog type");
1190       }
1191       optionsButtons = new JButton[options.length];
1192       for (int i = 0; i < options.length && i < 3; i++)
1193       {
1194         Object o = options[i];
1195         int buttonAction = buttonActions[i];
1196         Callable<Void> action = callbacks.get(buttonAction);
1197         JButton jb;
1198         if (buttons != null && buttons.length > i && buttons[i] != null)
1199         {
1200           jb = buttons[i];
1201         }
1202         else
1203         {
1204           jb = new JButton();
1205         }
1206         jb.setText((String) o);
1207         jb.addActionListener(new ActionListener()
1208         {
1209           @Override
1210           public void actionPerformed(ActionEvent e)
1211           {
1212             joptionpane.setValue(buttonAction);
1213             if (action != null)
1214               Executors.newSingleThreadExecutor().submit(action);
1215             // joptionpane.transferFocusBackward();
1216             joptionpane.transferFocusBackward();
1217             joptionpane.setVisible(false);
1218             // put focus and raise parent window if possible, unless cancel
1219             // button pressed
1220             boolean raiseParent = (parentComponent != null);
1221             if (buttonAction == JOptionPane.CANCEL_OPTION)
1222               raiseParent = false;
1223             if (optionType == JOptionPane.YES_NO_OPTION
1224                     && buttonAction == JOptionPane.NO_OPTION)
1225               raiseParent = false;
1226             if (raiseParent)
1227             {
1228               parentComponent.requestFocus();
1229               if (parentComponent instanceof JInternalFrame)
1230               {
1231                 JInternalFrame jif = (JInternalFrame) parentComponent;
1232                 jif.show();
1233                 jif.moveToFront();
1234                 jif.grabFocus();
1235               }
1236               else if (parentComponent instanceof Window)
1237               {
1238                 Window w = (Window) parentComponent;
1239                 w.toFront();
1240                 w.requestFocus();
1241               }
1242             }
1243             joptionpane.setVisible(false);
1244           }
1245         });
1246         optionsButtons[i] = jb;
1247         if (o.equals(initialValue))
1248           initialValueButton = jb;
1249       }
1250     }
1251     joptionpane.setMessage(message);
1252     joptionpane.setMessageType(messageType);
1253     joptionpane.setOptionType(optionType);
1254     joptionpane.setIcon(icon);
1255     joptionpane.setOptions(Platform.isJS() ? options : optionsButtons);
1256     joptionpane.setInitialValue(
1257             Platform.isJS() ? initialValue : initialValueButton);
1258
1259     JDialog dialog = joptionpane.createDialog(parentComponent, title);
1260     dialog.setModalityType(
1261             modal ? ModalityType.APPLICATION_MODAL : ModalityType.MODELESS);
1262     dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
1263     return dialog;
1264   }
1265
1266   /**
1267    * Utility to programmatically click a button on a JOptionPane (as a JFrame)
1268    * 
1269    * returns true if button was found
1270    */
1271   public static boolean clickButton(JFrame frame, int buttonType)
1272   {
1273
1274     return false;
1275   }
1276 }