JAL-3416 make flatlaf default for linux
[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
22 package jalview.gui;
23
24 import java.awt.AWTEvent;
25 import java.awt.ActiveEvent;
26 import java.awt.Component;
27 import java.awt.Container;
28 import java.awt.Dialog.ModalityType;
29 import java.awt.EventQueue;
30 import java.awt.HeadlessException;
31 import java.awt.MenuComponent;
32 import java.awt.Toolkit;
33 import java.awt.Window;
34 import java.awt.event.ActionEvent;
35 import java.awt.event.ActionListener;
36 import java.awt.event.MouseAdapter;
37 import java.awt.event.MouseMotionAdapter;
38 import java.beans.PropertyChangeEvent;
39 import java.beans.PropertyChangeListener;
40 import java.util.ArrayList;
41 import java.util.Arrays;
42 import java.util.HashMap;
43 import java.util.List;
44 import java.util.Map;
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.JInternalFrame;
51 import javax.swing.JLayeredPane;
52 import javax.swing.JOptionPane;
53 import javax.swing.JPanel;
54 import javax.swing.UIManager;
55 import javax.swing.event.InternalFrameEvent;
56 import javax.swing.event.InternalFrameListener;
57
58 import jalview.util.ChannelProperties;
59 import jalview.util.Platform;
60 import jalview.util.dialogrunner.DialogRunnerI;
61
62 public class JvOptionPane extends JOptionPane
63         implements DialogRunnerI, PropertyChangeListener
64 {
65   private static final long serialVersionUID = -3019167117756785229L;
66
67   private static Object mockResponse = JvOptionPane.CANCEL_OPTION;
68
69   private static boolean interactiveMode = true;
70
71   private Component parentComponent;
72
73   private Map<Object, Runnable> callbacks = new HashMap<>();
74
75   /*
76    * JalviewJS reports user choice in the dialog as the selected
77    * option (text); this list allows conversion to index (int)
78    */
79   List<Object> ourOptions;
80
81   public JvOptionPane(final Component parent)
82   {
83     this.parentComponent = Platform.isJS() ? this : parent;
84     this.setIcon(null);
85   }
86
87   public static int showConfirmDialog(Component parentComponent,
88           Object message) throws HeadlessException
89   {
90     // only called by test
91     return isInteractiveMode()
92             ? JOptionPane.showConfirmDialog(parentComponent, message)
93             : (int) getMockResponse();
94   }
95
96   /**
97    * Message, title, optionType
98    * 
99    * @param parentComponent
100    * @param message
101    * @param title
102    * @param optionType
103    * @return
104    * @throws HeadlessException
105    */
106   public static int showConfirmDialog(Component parentComponent,
107           Object message, String title, int optionType)
108           throws HeadlessException
109   {
110     if (!isInteractiveMode())
111     {
112       return (int) getMockResponse();
113     }
114     switch (optionType)
115     {
116     case JvOptionPane.YES_NO_CANCEL_OPTION:
117       // FeatureRenderer amendFeatures ?? TODO ??
118       // Chimera close
119       // PromptUserConfig
120       // $FALL-THROUGH$
121     default:
122     case JvOptionPane.YES_NO_OPTION:
123       // PromptUserConfig usage stats
124       // for now treated as "OK CANCEL"
125       // $FALL-THROUGH$
126     case JvOptionPane.OK_CANCEL_OPTION:
127       // will fall back to simple HTML
128       return JOptionPane.showConfirmDialog(parentComponent, message, title,
129               optionType);
130     }
131   }
132
133   /**
134    * Adds a message type. Fallback is to just add it in the beginning.
135    * 
136    * @param parentComponent
137    * @param message
138    * @param title
139    * @param optionType
140    * @param messageType
141    * @return
142    * @throws HeadlessException
143    */
144   public static int showConfirmDialog(Component parentComponent,
145           Object message, String title, int optionType, int messageType)
146           throws HeadlessException
147   {
148     // JalviewServicesChanged
149     // PromptUserConfig raiseDialog
150     return isInteractiveMode()
151             ? JOptionPane.showConfirmDialog(parentComponent, message, title,
152                     optionType, messageType)
153             : (int) getMockResponse();
154   }
155
156   /**
157    * Adds an icon
158    * 
159    * @param parentComponent
160    * @param message
161    * @param title
162    * @param optionType
163    * @param messageType
164    * @param icon
165    * @return
166    * @throws HeadlessException
167    */
168   public static int showConfirmDialog(Component parentComponent,
169           Object message, String title, int optionType, int messageType,
170           Icon icon) throws HeadlessException
171   {
172     // JvOptionPaneTest only
173     return isInteractiveMode()
174             ? JOptionPane.showConfirmDialog(parentComponent, message, title,
175                     optionType, messageType, icon)
176             : (int) getMockResponse();
177   }
178
179   /**
180    * Internal version "OK"
181    * 
182    * @param parentComponent
183    * @param message
184    * @return
185    */
186   public static int showInternalConfirmDialog(Component parentComponent,
187           Object message)
188   {
189     // JvOptionPaneTest only;
190     return isInteractiveMode()
191             ? JOptionPane.showInternalConfirmDialog(parentComponent,
192                     message)
193             : (int) getMockResponse();
194   }
195
196   /**
197    * Internal version -- changed to standard version for now
198    * 
199    * @param parentComponent
200    * @param message
201    * @param title
202    * @param optionType
203    * @return
204    */
205   public static int showInternalConfirmDialog(Component parentComponent,
206           String message, String title, int optionType)
207   {
208     if (!isInteractiveMode())
209     {
210       return (int) getMockResponse();
211     }
212     switch (optionType)
213     {
214     case JvOptionPane.YES_NO_CANCEL_OPTION:
215       // ColourMenuHelper.addMenuItmers.offerRemoval TODO
216     case JvOptionPane.YES_NO_OPTION:
217       // UserDefinedColoursSave -- relevant? TODO
218       // $FALL-THROUGH$
219     default:
220     case JvOptionPane.OK_CANCEL_OPTION:
221
222       // EditNameDialog --- uses panel for messsage TODO
223
224       // Desktop.inputURLMenuItem
225       // WsPreferenses
226       return JOptionPane.showConfirmDialog(parentComponent, message, title,
227               optionType);
228     }
229   }
230
231   /**
232    * 
233    * @param parentComponent
234    * @param message
235    * @param title
236    * @param optionType
237    * @param messageType
238    * @return
239    */
240   public static int showInternalConfirmDialog(Component parentComponent,
241           Object message, String title, int optionType, int messageType)
242   {
243     if (!isInteractiveMode())
244     {
245       return (int) getMockResponse();
246     }
247     switch (optionType)
248     {
249     case JvOptionPane.YES_NO_CANCEL_OPTION:
250     case JvOptionPane.YES_NO_OPTION:
251       // UserQuestionanaireCheck
252       // VamsasApplication
253       // $FALL-THROUGH$
254     default:
255     case JvOptionPane.OK_CANCEL_OPTION:
256       // will fall back to simple HTML
257       return JOptionPane.showConfirmDialog(parentComponent, message, title,
258               optionType, messageType);
259     }
260   }
261
262   /**
263    * adds icon; no longer internal
264    * 
265    * @param parentComponent
266    * @param message
267    * @param title
268    * @param optionType
269    * @param messageType
270    * @param icon
271    * @return
272    */
273   public static int showInternalConfirmDialog(Component parentComponent,
274           Object message, String title, int optionType, int messageType,
275           Icon icon)
276   {
277     if (!isInteractiveMode())
278     {
279       return (int) getMockResponse();
280     }
281     switch (optionType)
282     {
283     case JvOptionPane.YES_NO_CANCEL_OPTION:
284     case JvOptionPane.YES_NO_OPTION:
285       //$FALL-THROUGH$
286     default:
287     case JvOptionPane.OK_CANCEL_OPTION:
288       // Preferences editLink/newLink
289       return JOptionPane.showConfirmDialog(parentComponent, message, title,
290               optionType, messageType, icon);
291     }
292
293   }
294
295   /**
296    * custom options full-featured
297    * 
298    * @param parentComponent
299    * @param message
300    * @param title
301    * @param optionType
302    * @param messageType
303    * @param icon
304    * @param options
305    * @param initialValue
306    * @return
307    * @throws HeadlessException
308    */
309   public static int showOptionDialog(Component parentComponent,
310           String message, String title, int optionType, int messageType,
311           Icon icon, Object[] options, Object initialValue)
312           throws HeadlessException
313   {
314     if (!isInteractiveMode())
315     {
316       return (int) getMockResponse();
317     }
318     // two uses:
319     //
320     // TODO
321     //
322     // 1) AlignViewport for openLinkedAlignment
323     //
324     // Show a dialog with the option to open and link (cDNA <-> protein) as a
325     // new
326     // alignment, either as a standalone alignment or in a split frame. Returns
327     // true if the new alignment was opened, false if not, because the user
328     // declined the offer.
329     //
330     // 2) UserDefinedColors warning about saving over a name already defined
331     //
332     return JOptionPane.showOptionDialog(parentComponent, message, title,
333             optionType, messageType, icon, options, initialValue);
334   }
335
336   /**
337    * Just an OK message
338    * 
339    * @param message
340    * @throws HeadlessException
341    */
342   public static void showMessageDialog(Component parentComponent,
343           String message) throws HeadlessException
344   {
345     if (!isInteractiveMode())
346     {
347       outputMessage(message);
348       return;
349     }
350
351     // test class only
352
353     JOptionPane.showMessageDialog(parentComponent, message);
354   }
355
356   /**
357    * OK with message, title, and type
358    * 
359    * @param parentComponent
360    * @param message
361    * @param title
362    * @param messageType
363    * @throws HeadlessException
364    */
365   public static void showMessageDialog(Component parentComponent,
366           String message, String title, int messageType)
367           throws HeadlessException
368   {
369     // 30 implementations -- all just fine.
370
371     if (!isInteractiveMode())
372     {
373       outputMessage(message);
374       return;
375     }
376
377     JOptionPane.showMessageDialog(parentComponent,
378             getPrefix(messageType) + message, title, messageType);
379   }
380
381   /**
382    * adds title and icon
383    * 
384    * @param parentComponent
385    * @param message
386    * @param title
387    * @param messageType
388    * @param icon
389    * @throws HeadlessException
390    */
391   public static void showMessageDialog(Component parentComponent,
392           String message, String title, int messageType, Icon icon)
393           throws HeadlessException
394   {
395
396     // test only
397
398     if (!isInteractiveMode())
399     {
400       outputMessage(message);
401       return;
402     }
403
404     JOptionPane.showMessageDialog(parentComponent, message, title,
405             messageType, icon);
406   }
407
408   /**
409    * was internal
410    * 
411    */
412   public static void showInternalMessageDialog(Component parentComponent,
413           Object message)
414   {
415
416     // WsPreferences only
417
418     if (!isInteractiveMode())
419     {
420       outputMessage(message);
421       return;
422     }
423
424     JOptionPane.showMessageDialog(parentComponent, message);
425   }
426
427   /**
428    * Adds title and messageType
429    * 
430    * @param parentComponent
431    * @param message
432    * @param title
433    * @param messageType
434    */
435   public static void showInternalMessageDialog(Component parentComponent,
436           String message, String title, int messageType)
437   {
438
439     // 41 references
440
441     if (!isInteractiveMode())
442     {
443       outputMessage(message);
444       return;
445     }
446
447     JOptionPane.showMessageDialog(parentComponent,
448             getPrefix(messageType) + message, title, messageType);
449   }
450
451   /**
452    * 
453    * @param parentComponent
454    * @param message
455    * @param title
456    * @param messageType
457    * @param icon
458    */
459   public static void showInternalMessageDialog(Component parentComponent,
460           Object message, String title, int messageType, Icon icon)
461   {
462
463     // test only
464
465     if (!isInteractiveMode())
466     {
467       outputMessage(message);
468       return;
469     }
470
471     JOptionPane.showMessageDialog(parentComponent, message, title,
472             messageType, icon);
473   }
474
475   /**
476    * 
477    * @param message
478    * @return
479    * @throws HeadlessException
480    */
481   public static String showInputDialog(Object message)
482           throws HeadlessException
483   {
484     // test only
485
486     if (!isInteractiveMode())
487     {
488       return getMockResponse().toString();
489     }
490
491     return JOptionPane.showInputDialog(message);
492   }
493
494   /**
495    * adds inital selection value
496    * 
497    * @param message
498    * @param initialSelectionValue
499    * @return
500    */
501   public static String showInputDialog(String message,
502           String initialSelectionValue)
503   {
504     if (!isInteractiveMode())
505     {
506       return getMockResponse().toString();
507     }
508
509     // AnnotationPanel character option
510
511     return JOptionPane.showInputDialog(message, initialSelectionValue);
512   }
513
514   /**
515    * adds inital selection value
516    * 
517    * @param message
518    * @param initialSelectionValue
519    * @return
520    */
521   public static String showInputDialog(Object message,
522           Object initialSelectionValue)
523   {
524     if (!isInteractiveMode())
525     {
526       return getMockResponse().toString();
527     }
528
529     // AnnotationPanel character option
530
531     return JOptionPane.showInputDialog(message, initialSelectionValue);
532   }
533
534   /**
535    * centered on parent
536    * 
537    * @param parentComponent
538    * @param message
539    * @return
540    * @throws HeadlessException
541    */
542   public static String showInputDialog(Component parentComponent,
543           String message) throws HeadlessException
544   {
545     // test only
546
547     return isInteractiveMode()
548             ? JOptionPane.showInputDialog(parentComponent, message)
549             : getMockResponse().toString();
550   }
551
552   /**
553    * input with initial selection
554    * 
555    * @param parentComponent
556    * @param message
557    * @param initialSelectionValue
558    * @return
559    */
560   public static String showInputDialog(Component parentComponent,
561           String message, String initialSelectionValue)
562   {
563
564     // AnnotationPanel
565
566     return isInteractiveMode()
567             ? JOptionPane.showInputDialog(parentComponent, message,
568                     initialSelectionValue)
569             : getMockResponse().toString();
570   }
571
572   /**
573    * input with initial selection
574    * 
575    * @param parentComponent
576    * @param message
577    * @param initialSelectionValue
578    * @return
579    */
580   public static String showInputDialog(Component parentComponent,
581           Object message, Object initialSelectionValue)
582   {
583
584     // AnnotationPanel
585
586     return isInteractiveMode()
587             ? JOptionPane.showInputDialog(parentComponent, message,
588                     initialSelectionValue)
589             : getMockResponse().toString();
590   }
591
592   /**
593    * 
594    * @param parentComponent
595    * @param message
596    * @param title
597    * @param messageType
598    * @return
599    * @throws HeadlessException
600    */
601   public static String showInputDialog(Component parentComponent,
602           String message, String title, int messageType)
603           throws HeadlessException
604   {
605
606     // test only
607
608     return isInteractiveMode()
609             ? JOptionPane.showInputDialog(parentComponent, message, title,
610                     messageType)
611             : getMockResponse().toString();
612   }
613
614   /**
615    * Customized input option
616    * 
617    * @param parentComponent
618    * @param message
619    * @param title
620    * @param messageType
621    * @param icon
622    * @param selectionValues
623    * @param initialSelectionValue
624    * @return
625    * @throws HeadlessException
626    */
627   public static Object showInputDialog(Component parentComponent,
628           Object message, String title, int messageType, Icon icon,
629           Object[] selectionValues, Object initialSelectionValue)
630           throws HeadlessException
631   {
632
633     // test only
634
635     return isInteractiveMode()
636             ? JOptionPane.showInputDialog(parentComponent, message, title,
637                     messageType, icon, selectionValues,
638                     initialSelectionValue)
639             : getMockResponse().toString();
640   }
641
642   /**
643    * internal version
644    * 
645    * @param parentComponent
646    * @param message
647    * @return
648    */
649   public static String showInternalInputDialog(Component parentComponent,
650           String message)
651   {
652     // test only
653
654     return isInteractiveMode()
655             ? JOptionPane.showInternalInputDialog(parentComponent, message)
656             : getMockResponse().toString();
657   }
658
659   /**
660    * internal with title and messageType
661    * 
662    * @param parentComponent
663    * @param message
664    * @param title
665    * @param messageType
666    * @return
667    */
668   public static String showInternalInputDialog(Component parentComponent,
669           String message, String title, int messageType)
670   {
671
672     // AlignFrame tabbedPane_mousePressed
673
674     return isInteractiveMode()
675             ? JOptionPane.showInternalInputDialog(parentComponent,
676                     getPrefix(messageType) + message, title, messageType)
677             : getMockResponse().toString();
678   }
679
680   /**
681    * customized internal
682    * 
683    * @param parentComponent
684    * @param message
685    * @param title
686    * @param messageType
687    * @param icon
688    * @param selectionValues
689    * @param initialSelectionValue
690    * @return
691    */
692   public static Object showInternalInputDialog(Component parentComponent,
693           String message, String title, int messageType, Icon icon,
694           Object[] selectionValues, Object initialSelectionValue)
695   {
696     // test only
697
698     return isInteractiveMode()
699             ? JOptionPane.showInternalInputDialog(parentComponent, message,
700                     title, messageType, icon, selectionValues,
701                     initialSelectionValue)
702             : getMockResponse().toString();
703   }
704
705   ///////////// end of options ///////////////
706
707   private static void outputMessage(Object message)
708   {
709     System.out.println(">>> JOption Message : " + message.toString());
710   }
711
712   public static Object getMockResponse()
713   {
714     return mockResponse;
715   }
716
717   public static void setMockResponse(Object mockOption)
718   {
719     JvOptionPane.mockResponse = mockOption;
720   }
721
722   public static void resetMock()
723   {
724     setMockResponse(JvOptionPane.CANCEL_OPTION);
725     setInteractiveMode(true);
726   }
727
728   public static boolean isInteractiveMode()
729   {
730     return interactiveMode;
731   }
732
733   public static void setInteractiveMode(boolean interactive)
734   {
735     JvOptionPane.interactiveMode = interactive;
736   }
737
738   private static String getPrefix(int messageType)
739   {
740     String prefix = "";
741
742     // JavaScript only
743     if (Platform.isJS())
744     {
745       switch (messageType)
746       {
747       case JvOptionPane.WARNING_MESSAGE:
748         prefix = "WARNING! ";
749         break;
750       case JvOptionPane.ERROR_MESSAGE:
751         prefix = "ERROR! ";
752         break;
753       default:
754         prefix = "Note: ";
755       }
756     }
757     return prefix;
758   }
759
760   /**
761    * create a new option dialog that can be used to register responses - along
762    * lines of showOptionDialog
763    * 
764    * @param desktop
765    * @param question
766    * @param string
767    * @param defaultOption
768    * @param plainMessage
769    * @param object
770    * @param options
771    * @param string2
772    * @return
773    */
774   public static JvOptionPane newOptionDialog(Component parentComponent)
775   {
776     return new JvOptionPane(parentComponent);
777   }
778
779   public void showDialog(String message, String title, int optionType,
780           int messageType, Icon icon, Object[] options, Object initialValue)
781   {
782     showDialog(message, title, optionType, messageType, icon, options,
783             initialValue, true);
784   }
785
786   public void showDialog(String message, String title, int optionType,
787           int messageType, Icon icon, Object[] options, Object initialValue,
788           boolean modal)
789   {
790     if (!isInteractiveMode())
791     {
792       handleResponse(getMockResponse());
793     }
794     // two uses:
795     //
796     // TODO
797     //
798     // 1) AlignViewport for openLinkedAlignment
799     //
800     // Show a dialog with the option to open and link (cDNA <-> protein) as a
801     // new
802     // alignment, either as a standalone alignment or in a split frame. Returns
803     // true if the new alignment was opened, false if not, because the user
804     // declined the offer.
805     //
806     // 2) UserDefinedColors warning about saving over a name already defined
807     //
808
809     ourOptions = Arrays.asList(options);
810
811     if (modal)
812     {
813       // use a JOptionPane as usual
814       int response = JOptionPane.showOptionDialog(parentComponent, message,
815               title, optionType, messageType, icon, options, initialValue);
816
817       /*
818        * In Java, the response is returned to this thread and handled here;
819        * (for Javascript, see propertyChange)
820        */
821       if (!Platform.isJS())
822       /**
823        * Java only
824        * 
825        * @j2sIgnore
826        */
827       {
828         handleResponse(response);
829       }
830     }
831     else
832     {
833       /*
834        * This is java similar to the swingjs handling, with the callbacks
835        * attached to the button press of the dialog.  This means we can use
836        * a non-modal JDialog for the confirmation without blocking the GUI.
837        */
838       JOptionPane joptionpane = new JOptionPane();
839       // Make button options
840       int[] buttonActions = { JvOptionPane.YES_OPTION,
841           JvOptionPane.NO_OPTION, JvOptionPane.CANCEL_OPTION };
842
843       // we need the strings to make the buttons with actionEventListener
844       if (options == null)
845       {
846         ArrayList<String> options_default = new ArrayList<>();
847         options_default
848                 .add(UIManager.getString("OptionPane.yesButtonText"));
849         if (optionType == JvOptionPane.YES_NO_OPTION
850                 || optionType == JvOptionPane.YES_NO_CANCEL_OPTION)
851         {
852           options_default
853                   .add(UIManager.getString("OptionPane.noButtonText"));
854         }
855         if (optionType == JvOptionPane.YES_NO_CANCEL_OPTION)
856         {
857           options_default
858                   .add(UIManager.getString("OptionPane.cancelButtonText"));
859         }
860         options = options_default.toArray();
861       }
862
863       ArrayList<JButton> options_btns = new ArrayList<>();
864       Object initialValue_btn = null;
865       if (!Platform.isJS()) // JalviewJS already uses callback, don't need to
866                             // add them here
867       {
868         for (int i = 0; i < options.length && i < 3; i++)
869         {
870           Object o = options[i];
871           int buttonAction = buttonActions[i];
872           Runnable action = callbacks.get(buttonAction);
873           JButton jb = new JButton();
874           jb.setText((String) o);
875           jb.addActionListener(new ActionListener()
876           {
877             @Override
878             public void actionPerformed(ActionEvent e)
879             {
880               joptionpane.setValue(buttonAction);
881               if (action != null)
882                 Executors.defaultThreadFactory().newThread(action).start();
883               // joptionpane.transferFocusBackward();
884               joptionpane.transferFocusBackward();
885               joptionpane.setVisible(false);
886               // put focus and raise parent window if possible, unless cancel
887               // button pressed
888               boolean raiseParent = (parentComponent != null);
889               if (buttonAction == JvOptionPane.CANCEL_OPTION)
890                 raiseParent = false;
891               if (optionType == JvOptionPane.YES_NO_OPTION
892                       && buttonAction == JvOptionPane.NO_OPTION)
893                 raiseParent = false;
894               if (raiseParent)
895               {
896                 parentComponent.requestFocus();
897                 if (parentComponent instanceof JInternalFrame)
898                 {
899                   JInternalFrame jif = (JInternalFrame) parentComponent;
900                   jif.show();
901                   jif.moveToFront();
902                   jif.grabFocus();
903                 }
904                 else if (parentComponent instanceof Window)
905                 {
906                   Window w = (Window) parentComponent;
907                   w.toFront();
908                   w.requestFocus();
909                 }
910               }
911               joptionpane.setVisible(false);
912             }
913           });
914           options_btns.add(jb);
915           if (o.equals(initialValue))
916             initialValue_btn = jb;
917         }
918       }
919       joptionpane.setMessage(message);
920       joptionpane.setMessageType(messageType);
921       joptionpane.setOptionType(optionType);
922       joptionpane.setIcon(icon);
923       joptionpane.setOptions(
924               Platform.isJS() ? options : options_btns.toArray());
925       joptionpane.setInitialValue(
926               Platform.isJS() ? initialValue : initialValue_btn);
927
928       JDialog dialog = joptionpane.createDialog(parentComponent, title);
929       dialog.setIconImages(ChannelProperties.getIconList());
930       dialog.setModalityType(modal ? ModalityType.APPLICATION_MODAL
931               : ModalityType.MODELESS);
932       dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
933       dialog.setVisible(true);
934     }
935   }
936
937   public void showInternalDialog(JPanel mainPanel, String title,
938           int yesNoCancelOption, int questionMessage, Icon icon,
939           Object[] options, String initresponse)
940   {
941     if (!isInteractiveMode())
942     {
943       handleResponse(getMockResponse());
944     }
945
946     // need to set these separately so we can set the title bar icon later
947     this.setOptionType(yesNoCancelOption);
948     this.setMessageType(questionMessage);
949     this.setIcon(icon);
950     this.setInitialValue(initresponse);
951     this.setOptions(options);
952     this.setMessage(mainPanel);
953
954     ourOptions = Arrays.asList(options);
955     if (parentComponent != this)
956     {
957       JInternalFrame jif = this.createInternalFrame(parentComponent, title);
958       jif.setFrameIcon(null);
959       jif.addInternalFrameListener(new InternalFrameListener()
960       {
961         @Override
962         public void internalFrameActivated(InternalFrameEvent arg0)
963         {
964         }
965
966         @Override
967         public void internalFrameClosed(InternalFrameEvent arg0)
968         {
969           JvOptionPane.this.internalDialogHandleResponse();
970         }
971
972         @Override
973         public void internalFrameClosing(InternalFrameEvent arg0)
974         {
975         }
976
977         @Override
978         public void internalFrameDeactivated(InternalFrameEvent arg0)
979         {
980         }
981
982         @Override
983         public void internalFrameDeiconified(InternalFrameEvent arg0)
984         {
985         }
986
987         @Override
988         public void internalFrameIconified(InternalFrameEvent arg0)
989         {
990         }
991
992         @Override
993         public void internalFrameOpened(InternalFrameEvent arg0)
994         {
995         }
996       });
997       jif.setVisible(true);
998       startModal(jif);
999       return;
1000     }
1001     else
1002     {
1003       JDialog dialog = this.createDialog(parentComponent, title);
1004       dialog.setIconImages(ChannelProperties.getIconList());
1005       dialog.setVisible(true); // blocking
1006       this.internalDialogHandleResponse();
1007       return;
1008     }
1009   }
1010
1011   private void internalDialogHandleResponse()
1012   {
1013     String responseString = (String) this.getValue();
1014     int response = ourOptions.indexOf(responseString);
1015
1016     if (!Platform.isJS())
1017     /**
1018      * Java only
1019      * 
1020      * @j2sIgnore
1021      */
1022     {
1023       handleResponse(response);
1024     }
1025   }
1026
1027   @Override
1028   public JvOptionPane setResponseHandler(Object response, Runnable action)
1029   {
1030     callbacks.put(response, action);
1031     return this;
1032   }
1033
1034   /**
1035    * JalviewJS signals option selection by a property change event for the
1036    * option e.g. "OK". This methods responds to that by running the response
1037    * action that corresponds to that option.
1038    * 
1039    * @param evt
1040    */
1041   @Override
1042   public void propertyChange(PropertyChangeEvent evt)
1043   {
1044     Object newValue = evt.getNewValue();
1045     int ourOption = ourOptions.indexOf(newValue);
1046     if (ourOption >= 0)
1047     {
1048       handleResponse(ourOption);
1049     }
1050     else
1051     {
1052       // try our luck..
1053       handleResponse(newValue);
1054     }
1055   }
1056
1057   @Override
1058   public void handleResponse(Object response)
1059   {
1060     /*
1061     * this test is for NaN in Chrome
1062     */
1063     if (response != null && !response.equals(response))
1064     {
1065       return;
1066     }
1067     Runnable action = callbacks.get(response);
1068     if (action != null)
1069     {
1070       action.run();
1071       parentComponent.requestFocus();
1072     }
1073   }
1074
1075   /**
1076    * This helper method makes the JInternalFrame wait until it is notified by an
1077    * InternalFrameClosing event. This method also adds the given JOptionPane to
1078    * the JInternalFrame and sizes it according to the JInternalFrame's preferred
1079    * size.
1080    *
1081    * @param f
1082    *          The JInternalFrame to make modal.
1083    */
1084   private static void startModal(JInternalFrame f)
1085   {
1086     // We need to add an additional glasspane-like component directly
1087     // below the frame, which intercepts all mouse events that are not
1088     // directed at the frame itself.
1089     JPanel modalInterceptor = new JPanel();
1090     modalInterceptor.setOpaque(false);
1091     JLayeredPane lp = JLayeredPane.getLayeredPaneAbove(f);
1092     lp.setLayer(modalInterceptor, JLayeredPane.MODAL_LAYER.intValue());
1093     modalInterceptor.setBounds(0, 0, lp.getWidth(), lp.getHeight());
1094     modalInterceptor.addMouseListener(new MouseAdapter()
1095     {
1096     });
1097     modalInterceptor.addMouseMotionListener(new MouseMotionAdapter()
1098     {
1099     });
1100     lp.add(modalInterceptor);
1101     f.toFront();
1102
1103     // We need to explicitly dispatch events when we are blocking the event
1104     // dispatch thread.
1105     EventQueue queue = Toolkit.getDefaultToolkit().getSystemEventQueue();
1106     try
1107     {
1108       while (!f.isClosed())
1109       {
1110         if (EventQueue.isDispatchThread())
1111         {
1112           // The getNextEventMethod() issues wait() when no
1113           // event is available, so we don't need do explicitly wait().
1114           AWTEvent ev = queue.getNextEvent();
1115           // This mimics EventQueue.dispatchEvent(). We can't use
1116           // EventQueue.dispatchEvent() directly, because it is
1117           // protected, unfortunately.
1118           if (ev instanceof ActiveEvent)
1119             ((ActiveEvent) ev).dispatch();
1120           else if (ev.getSource() instanceof Component)
1121             ((Component) ev.getSource()).dispatchEvent(ev);
1122           else if (ev.getSource() instanceof MenuComponent)
1123             ((MenuComponent) ev.getSource()).dispatchEvent(ev);
1124           // Other events are ignored as per spec in
1125           // EventQueue.dispatchEvent
1126         }
1127         else
1128         {
1129           // Give other threads a chance to become active.
1130           Thread.yield();
1131         }
1132       }
1133     } catch (InterruptedException ex)
1134     {
1135       // If we get interrupted, then leave the modal state.
1136     } finally
1137     {
1138       // Clean up the modal interceptor.
1139       lp.remove(modalInterceptor);
1140
1141       // Remove the internal frame from its parent, so it is no longer
1142       // lurking around and clogging memory.
1143       Container parent = f.getParent();
1144       if (parent != null)
1145         parent.remove(f);
1146     }
1147   }
1148 }