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