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