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