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