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