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