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