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