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