0a7c49ea2a1d758c7daa174d58499bc555cddc8d
[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 can set a final
863         // timeoutThreadF
864         Thread timeoutThread = null;
865         for (int i = 0; i < options.length; i++)
866         {
867           Object o = options[i];
868           JButton jb = buttons[i];
869           if (o.equals(initialValue))
870           {
871             initialValueButton = jb;
872             if (timeout > 0 && initialValueButton != null
873                     && initialValueButton instanceof JButton)
874             {
875               Runnable timeoutClose = () -> {
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               timeoutThread = new Thread(timeoutClose);
887             }
888           }
889         }
890         final Thread timeoutThreadF = timeoutThread;
891         timeoutThreadF.start();
892
893         int[] buttonActions = { JOptionPane.YES_OPTION,
894             JOptionPane.NO_OPTION, JOptionPane.CANCEL_OPTION };
895         for (int i = 0; i < options.length; i++)
896         {
897           Object o = options[i];
898           jalview.bin.Console.debug(
899                   "Setting button " + i + " to '" + o.toString() + "'");
900           JButton jb = buttons[i];
901
902           int buttonAction = buttonActions[i];
903           Runnable action = callbacks.get(buttonAction);
904           jb.setText((String) o);
905           jb.addActionListener(new ActionListener()
906           {
907             @Override
908             public void actionPerformed(ActionEvent e)
909             {
910               if (timeoutThreadF != null)
911               {
912                 timeoutThreadF.interrupt();
913               }
914
915               Object obj = e.getSource();
916               if (obj == null || !(obj instanceof Component))
917               {
918                 jalview.bin.Console.debug(
919                         "Could not find Component source of event object "
920                                 + obj);
921                 return;
922               }
923               Object joptionpaneObject = SwingUtilities.getAncestorOfClass(
924                       JOptionPane.class, (Component) obj);
925               if (joptionpaneObject == null
926                       || !(joptionpaneObject instanceof JOptionPane))
927               {
928                 jalview.bin.Console.debug(
929                         "Could not find JOptionPane ancestor of event object "
930                                 + obj);
931                 return;
932               }
933               JOptionPane joptionpane = (JOptionPane) joptionpaneObject;
934               joptionpane.setValue(buttonAction);
935               if (action != null)
936                 new Thread(action).start();
937               joptionpane.transferFocusBackward();
938               joptionpane.setVisible(false);
939               // put focus and raise parent window if possible, unless cancel or
940               // no button pressed
941               boolean raiseParent = (parentComponent != null);
942               if (buttonAction == JOptionPane.CANCEL_OPTION)
943                 raiseParent = false;
944               if (optionType == JOptionPane.YES_NO_OPTION
945                       && buttonAction == JOptionPane.NO_OPTION)
946                 raiseParent = false;
947               if (raiseParent)
948               {
949                 parentComponent.requestFocus();
950                 if (parentComponent instanceof JInternalFrame)
951                 {
952                   JInternalFrame jif = (JInternalFrame) parentComponent;
953                   jif.show();
954                   jif.moveToFront();
955                   jif.grabFocus();
956                 }
957                 else if (parentComponent instanceof Window)
958                 {
959                   Window w = (Window) parentComponent;
960                   w.toFront();
961                   w.requestFocus();
962                 }
963               }
964               joptionpane.setVisible(false);
965             }
966           });
967
968         }
969         useButtons = true;
970       }
971       // use a JOptionPane as usual
972       int response = JOptionPane.showOptionDialog(parentComponent, message,
973               title, optionType, messageType, icon,
974               useButtons ? buttons : options,
975               useButtons ? initialValueButton : initialValue);
976
977       /*
978        * In Java, the response is returned to this thread and handled here; (for
979        * Javascript, see propertyChange)
980        */
981       if (!Platform.isJS())
982       /**
983        * Java only
984        * 
985        * @j2sIgnore
986        */
987       {
988         handleResponse(response);
989       }
990     }
991     else
992     {
993       /*
994        * This is java similar to the swingjs handling, with the callbacks attached to
995        * the button press of the dialog. This means we can use a non-modal JDialog for
996        * the confirmation without blocking the GUI.
997        */
998       JOptionPane joptionpane = new JOptionPane();
999       // Make button options
1000       int[] buttonActions = { JvOptionPane.YES_OPTION,
1001           JvOptionPane.NO_OPTION, JvOptionPane.CANCEL_OPTION };
1002
1003       // we need the strings to make the buttons with actionEventListener
1004       if (options == null)
1005       {
1006         ArrayList<String> options_default = new ArrayList<>();
1007         options_default
1008                 .add(UIManager.getString("OptionPane.yesButtonText"));
1009         if (optionType == JvOptionPane.YES_NO_OPTION
1010                 || optionType == JvOptionPane.YES_NO_CANCEL_OPTION)
1011         {
1012           options_default
1013                   .add(UIManager.getString("OptionPane.noButtonText"));
1014         }
1015         if (optionType == JvOptionPane.YES_NO_CANCEL_OPTION)
1016         {
1017           options_default
1018                   .add(UIManager.getString("OptionPane.cancelButtonText"));
1019         }
1020         options = options_default.toArray();
1021       }
1022
1023       ArrayList<JButton> options_btns = new ArrayList<>();
1024       Object initialValue_btn = null;
1025       if (!Platform.isJS()) // JalviewJS already uses callback, don't need to
1026                             // add them here
1027       {
1028         for (int i = 0; i < options.length && i < 3; i++)
1029         {
1030           Object o = options[i];
1031           int buttonAction = buttonActions[i];
1032           Runnable action = callbacks.get(buttonAction);
1033           JButton jb = new JButton();
1034           jb.setText((String) o);
1035           jb.addActionListener(new ActionListener()
1036           {
1037
1038             @Override
1039             public void actionPerformed(ActionEvent e)
1040             {
1041               joptionpane.setValue(buttonAction);
1042               if (action != null)
1043                 new Thread(action).start();
1044               // joptionpane.transferFocusBackward();
1045               joptionpane.transferFocusBackward();
1046               joptionpane.setVisible(false);
1047               // put focus and raise parent window if possible, unless cancel
1048               // button pressed
1049               boolean raiseParent = (parentComponent != null);
1050               if (buttonAction == JvOptionPane.CANCEL_OPTION)
1051                 raiseParent = false;
1052               if (optionType == JvOptionPane.YES_NO_OPTION
1053                       && buttonAction == JvOptionPane.NO_OPTION)
1054                 raiseParent = false;
1055               if (raiseParent)
1056               {
1057                 parentComponent.requestFocus();
1058                 if (parentComponent instanceof JInternalFrame)
1059                 {
1060                   JInternalFrame jif = (JInternalFrame) parentComponent;
1061                   jif.show();
1062                   jif.moveToFront();
1063                   jif.grabFocus();
1064                 }
1065                 else if (parentComponent instanceof Window)
1066                 {
1067                   Window w = (Window) parentComponent;
1068                   w.toFront();
1069                   w.requestFocus();
1070                 }
1071               }
1072               joptionpane.setVisible(false);
1073             }
1074           });
1075           options_btns.add(jb);
1076           if (o.equals(initialValue))
1077             initialValue_btn = jb;
1078         }
1079       }
1080       joptionpane.setMessage(message);
1081       joptionpane.setMessageType(messageType);
1082       joptionpane.setOptionType(optionType);
1083       joptionpane.setIcon(icon);
1084       joptionpane.setOptions(
1085               Platform.isJS() ? options : options_btns.toArray());
1086       joptionpane.setInitialValue(
1087               Platform.isJS() ? initialValue : initialValue_btn);
1088
1089       JDialog dialog = joptionpane.createDialog(parentComponent, title);
1090       dialog.setIconImages(ChannelProperties.getIconList());
1091       dialog.setModalityType(modal ? ModalityType.APPLICATION_MODAL
1092               : ModalityType.MODELESS);
1093       dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
1094       dialog.setVisible(true);
1095       setDialog(dialog);
1096     }
1097   }
1098
1099   public void showInternalDialog(Object mainPanel, String title,
1100           int yesNoCancelOption, int questionMessage, Icon icon,
1101           Object[] options, String initresponse)
1102   {
1103     if (!isInteractiveMode())
1104     {
1105       handleResponse(getMockResponse());
1106     }
1107
1108     // need to set these separately so we can set the title bar icon later
1109     this.setOptionType(yesNoCancelOption);
1110     this.setMessageType(questionMessage);
1111     this.setIcon(icon);
1112     this.setInitialValue(initresponse);
1113     this.setOptions(options);
1114     this.setMessage(mainPanel);
1115
1116     ourOptions = Arrays.asList(options);
1117     if (parentComponent != this
1118             && !(parentComponent == null && Desktop.instance == null))
1119     {
1120       // note the parent goes back to a JRootPane so is probably
1121       // Desktop.getDesktop()
1122       JInternalFrame jif = this.createInternalFrame(
1123               parentComponent != null ? parentComponent : Desktop.instance,
1124               title);
1125       // connect to the alignFrame using a map in Desktop
1126       if (parentComponent instanceof AlignFrame)
1127       {
1128         Desktop.addModal((AlignFrame) parentComponent, jif);
1129       }
1130       jif.setFrameIcon(null);
1131       jif.addInternalFrameListener(new InternalFrameListener()
1132       {
1133         @Override
1134         public void internalFrameActivated(InternalFrameEvent arg0)
1135         {
1136         }
1137
1138         @Override
1139         public void internalFrameClosed(InternalFrameEvent arg0)
1140         {
1141           JvOptionPane.this.internalDialogHandleResponse();
1142         }
1143
1144         @Override
1145         public void internalFrameClosing(InternalFrameEvent arg0)
1146         {
1147         }
1148
1149         @Override
1150         public void internalFrameDeactivated(InternalFrameEvent arg0)
1151         {
1152         }
1153
1154         @Override
1155         public void internalFrameDeiconified(InternalFrameEvent arg0)
1156         {
1157         }
1158
1159         @Override
1160         public void internalFrameIconified(InternalFrameEvent arg0)
1161         {
1162         }
1163
1164         @Override
1165         public void internalFrameOpened(InternalFrameEvent arg0)
1166         {
1167         }
1168       });
1169       jif.setVisible(true);
1170       startModal(jif);
1171       return;
1172     }
1173     else
1174     {
1175       JDialog dialog = this.createDialog(parentComponent, title);
1176       dialog.setIconImages(ChannelProperties.getIconList());
1177       dialog.setVisible(true); // blocking
1178       this.internalDialogHandleResponse();
1179       return;
1180     }
1181   }
1182
1183   private void internalDialogHandleResponse()
1184   {
1185     Object value = this.getValue();
1186     if (value == null
1187             || (value instanceof Integer && (Integer) value == -1))
1188     {
1189       return;
1190     }
1191     String responseString = value.toString();
1192     int response = ourOptions.indexOf(responseString);
1193
1194     if (!Platform.isJS())
1195     /**
1196      * Java only
1197      * 
1198      * @j2sIgnore
1199      */
1200     {
1201       handleResponse(response);
1202     }
1203   }
1204
1205   /*
1206    * @Override public JvOptionPane setResponseHandler(Object response, Runnable
1207    * action) { callbacks.put(response, new Callable<Void>() {
1208    * 
1209    * @Override public Void call() { action.run(); return null; } }); return this;
1210    * }
1211    */
1212   @Override
1213   public JvOptionPane setResponseHandler(Object response, Runnable action)
1214   {
1215     if (action == null)
1216     {
1217       action = NULLCALLABLE;
1218     }
1219     callbacks.put(response, action);
1220     return this;
1221   }
1222
1223   public void setDialog(JDialog d)
1224   {
1225     dialog = d;
1226   }
1227
1228   public JDialog getDialog()
1229   {
1230     return dialog;
1231   }
1232
1233   /**
1234    * showDialogOnTop will create a dialog that (attempts to) come to top of OS
1235    * desktop windows
1236    */
1237   public static int showDialogOnTop(String label, String actionString,
1238           int JOPTIONPANE_OPTION, int JOPTIONPANE_MESSAGETYPE)
1239   {
1240     return showDialogOnTop(null, label, actionString, JOPTIONPANE_OPTION,
1241             JOPTIONPANE_MESSAGETYPE);
1242   }
1243
1244   public static int showDialogOnTop(Component dialogParentComponent,
1245           String label, String actionString, int JOPTIONPANE_OPTION,
1246           int JOPTIONPANE_MESSAGETYPE)
1247   {
1248     if (!isInteractiveMode())
1249     {
1250       return (int) getMockResponse();
1251     }
1252     // Ensure Jalview window is brought to front (primarily for Quit
1253     // confirmation window to be visible)
1254
1255     // This method of raising the Jalview window is broken in java
1256     // jalviewDesktop.setVisible(true);
1257     // jalviewDesktop.toFront();
1258
1259     // A better hack which works is to create a new JFrame parent with
1260     // setAlwaysOnTop(true)
1261     JFrame dialogParent = new JFrame();
1262     if (dialogParentComponent == null)
1263     {
1264       dialogParent.setIconImages(ChannelProperties.getIconList());
1265       dialogParent.setAlwaysOnTop(true);
1266     }
1267
1268     int answer = JOptionPane.showConfirmDialog(
1269             dialogParentComponent == null ? dialogParent
1270                     : dialogParentComponent,
1271             label, actionString, JOPTIONPANE_OPTION,
1272             JOPTIONPANE_MESSAGETYPE);
1273
1274     if (dialogParentComponent == null)
1275     {
1276       dialogParent.setAlwaysOnTop(false);
1277       dialogParent.dispose();
1278     }
1279
1280     return answer;
1281   }
1282
1283   public void showDialogOnTopAsync(String label, String actionString,
1284           int JOPTIONPANE_OPTION, int JOPTIONPANE_MESSAGETYPE, Icon icon,
1285           Object[] options, Object initialValue, boolean modal)
1286   {
1287     JFrame frame = new JFrame();
1288     frame.setIconImages(ChannelProperties.getIconList());
1289     showDialogOnTopAsync(frame, label, actionString, JOPTIONPANE_OPTION,
1290             JOPTIONPANE_MESSAGETYPE, icon, options, initialValue, modal);
1291   }
1292
1293   public void showDialogOnTopAsync(JFrame dialogParent, Object label,
1294           String actionString, int JOPTIONPANE_OPTION,
1295           int JOPTIONPANE_MESSAGETYPE, Icon icon, Object[] options,
1296           Object initialValue, boolean modal)
1297   {
1298     showDialogOnTopAsync(dialogParent, label, actionString,
1299             JOPTIONPANE_OPTION, JOPTIONPANE_MESSAGETYPE, icon, options,
1300             initialValue, modal, null);
1301   }
1302
1303   public void showDialogOnTopAsync(JFrame dialogParent, Object label,
1304           String actionString, int JOPTIONPANE_OPTION,
1305           int JOPTIONPANE_MESSAGETYPE, Icon icon, Object[] options,
1306           Object initialValue, boolean modal, JButton[] buttons)
1307   {
1308     showDialogOnTopAsync(dialogParent, label, actionString,
1309             JOPTIONPANE_OPTION, JOPTIONPANE_MESSAGETYPE, icon, options,
1310             initialValue, modal, buttons, true);
1311   }
1312
1313   public void showDialogOnTopAsync(JFrame dialogParent, Object label,
1314           String actionString, int JOPTIONPANE_OPTION,
1315           int JOPTIONPANE_MESSAGETYPE, Icon icon, Object[] options,
1316           Object initialValue, boolean modal, JButton[] buttons,
1317           boolean dispose)
1318   {
1319     if (!isInteractiveMode())
1320     {
1321       handleResponse(getMockResponse());
1322       return;
1323     }
1324     // Ensure Jalview window is brought to front (primarily for Quit
1325     // confirmation window to be visible)
1326
1327     // This method of raising the Jalview window is broken in java
1328     // jalviewDesktop.setVisible(true);
1329     // jalviewDesktop.toFront();
1330
1331     // A better hack which works is to create a new JFrame parent with
1332     // setAlwaysOnTop(true)
1333     dialogParent.setAlwaysOnTop(true);
1334     parentComponent = dialogParent;
1335
1336     showDialog(label, actionString, JOPTIONPANE_OPTION,
1337             JOPTIONPANE_MESSAGETYPE, icon, options, initialValue, modal,
1338             buttons);
1339
1340     if (dispose)
1341     {
1342       dialogParent.setAlwaysOnTop(false);
1343       dialogParent.dispose();
1344     }
1345   }
1346
1347   /**
1348    * JalviewJS signals option selection by a property change event for the
1349    * option e.g. "OK". This methods responds to that by running the response
1350    * action that corresponds to that option.
1351    * 
1352    * @param evt
1353    */
1354   @Override
1355   public void propertyChange(PropertyChangeEvent evt)
1356   {
1357     Object newValue = evt.getNewValue();
1358     int ourOption = ourOptions.indexOf(newValue);
1359     if (ourOption >= 0)
1360     {
1361       handleResponse(ourOption);
1362     }
1363     else
1364     {
1365       // try our luck..
1366       handleResponse(newValue);
1367     }
1368   }
1369
1370   @Override
1371   public void handleResponse(Object response)
1372   {
1373     /*
1374      * this test is for NaN in Chrome
1375      */
1376     if (response != null && !response.equals(response))
1377     {
1378       return;
1379     }
1380     Runnable action = callbacks.get(response);
1381     if (action != null)
1382     {
1383       try
1384       {
1385         new Thread(action).start();
1386         // action.call();
1387       } catch (Exception e)
1388       {
1389         e.printStackTrace();
1390       }
1391       if (parentComponent != null)
1392         parentComponent.requestFocus();
1393     }
1394   }
1395
1396   /**
1397    * Create a non-modal confirm dialog
1398    */
1399   public JDialog createDialog(Component parentComponent, Object message,
1400           String title, int optionType, int messageType, Icon icon,
1401           Object[] options, Object initialValue, boolean modal)
1402   {
1403     return createDialog(parentComponent, message, title, optionType,
1404             messageType, icon, options, initialValue, modal, null);
1405   }
1406
1407   public JDialog createDialog(Component parentComponent, Object message,
1408           String title, int optionType, int messageType, Icon icon,
1409           Object[] options, Object initialValue, boolean modal,
1410           JButton[] buttons)
1411   {
1412     if (!isInteractiveMode())
1413     {
1414       handleResponse(getMockResponse());
1415       return null;
1416     }
1417     JButton[] optionsButtons = null;
1418     Object initialValueButton = null;
1419     JOptionPane joptionpane = new JOptionPane();
1420     // Make button options
1421     int[] buttonActions = { JOptionPane.YES_OPTION, JOptionPane.NO_OPTION,
1422         JOptionPane.CANCEL_OPTION };
1423
1424     // we need the strings to make the buttons with actionEventListener
1425     if (options == null)
1426     {
1427       ArrayList<String> options_default = new ArrayList<>();
1428       options_default.add(UIManager.getString("OptionPane.yesButtonText"));
1429       if (optionType == JOptionPane.YES_NO_OPTION
1430               || optionType == JOptionPane.YES_NO_CANCEL_OPTION)
1431       {
1432         options_default.add(UIManager.getString("OptionPane.noButtonText"));
1433       }
1434       if (optionType == JOptionPane.YES_NO_CANCEL_OPTION)
1435       {
1436         options_default
1437                 .add(UIManager.getString("OptionPane.cancelButtonText"));
1438       }
1439       options = options_default.toArray();
1440     }
1441     if (!Platform.isJS()) // JalviewJS already uses callback, don't need to
1442                           // add them here
1443     {
1444       if (((optionType == JOptionPane.YES_OPTION
1445               || optionType == JOptionPane.NO_OPTION
1446               || optionType == JOptionPane.CANCEL_OPTION
1447               || optionType == JOptionPane.OK_OPTION
1448               || optionType == JOptionPane.DEFAULT_OPTION)
1449               && options.length < 1)
1450               || ((optionType == JOptionPane.YES_NO_OPTION
1451                       || optionType == JOptionPane.OK_CANCEL_OPTION)
1452                       && options.length < 2)
1453               || (optionType == JOptionPane.YES_NO_CANCEL_OPTION
1454                       && options.length < 3))
1455       {
1456         jalview.bin.Console
1457                 .debug("JvOptionPane: not enough options for dialog type");
1458       }
1459       optionsButtons = new JButton[options.length];
1460       for (int i = 0; i < options.length && i < 3; i++)
1461       {
1462         Object o = options[i];
1463         int buttonAction = buttonActions[i];
1464         Runnable action = callbacks.get(buttonAction);
1465         JButton jb;
1466         if (buttons != null && buttons.length > i && buttons[i] != null)
1467         {
1468           jb = buttons[i];
1469         }
1470         else
1471         {
1472           jb = new JButton();
1473         }
1474         jb.setText((String) o);
1475         jb.addActionListener(new ActionListener()
1476         {
1477           @Override
1478           public void actionPerformed(ActionEvent e)
1479           {
1480             joptionpane.setValue(buttonAction);
1481             if (action != null)
1482               new Thread(action).start();
1483             // joptionpane.transferFocusBackward();
1484             joptionpane.transferFocusBackward();
1485             joptionpane.setVisible(false);
1486             // put focus and raise parent window if possible, unless cancel
1487             // button pressed
1488             boolean raiseParent = (parentComponent != null);
1489             if (buttonAction == JOptionPane.CANCEL_OPTION)
1490               raiseParent = false;
1491             if (optionType == JOptionPane.YES_NO_OPTION
1492                     && buttonAction == JOptionPane.NO_OPTION)
1493               raiseParent = false;
1494             if (raiseParent)
1495             {
1496               parentComponent.requestFocus();
1497               if (parentComponent instanceof JInternalFrame)
1498               {
1499                 JInternalFrame jif = (JInternalFrame) parentComponent;
1500                 jif.show();
1501                 jif.moveToFront();
1502                 jif.grabFocus();
1503               }
1504               else if (parentComponent instanceof Window)
1505               {
1506                 Window w = (Window) parentComponent;
1507                 w.toFront();
1508                 w.requestFocus();
1509               }
1510             }
1511             joptionpane.setVisible(false);
1512           }
1513         });
1514         optionsButtons[i] = jb;
1515         if (o.equals(initialValue))
1516           initialValueButton = jb;
1517       }
1518     }
1519     joptionpane.setMessage(message);
1520     joptionpane.setMessageType(messageType);
1521     joptionpane.setOptionType(optionType);
1522     joptionpane.setIcon(icon);
1523     joptionpane.setOptions(Platform.isJS() ? options : optionsButtons);
1524     joptionpane.setInitialValue(
1525             Platform.isJS() ? initialValue : initialValueButton);
1526
1527     JDialog dialog = joptionpane.createDialog(parentComponent, title);
1528     dialog.setIconImages(ChannelProperties.getIconList());
1529     dialog.setModalityType(
1530             modal ? ModalityType.APPLICATION_MODAL : ModalityType.MODELESS);
1531     dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
1532     setDialog(dialog);
1533     return dialog;
1534   }
1535
1536   /**
1537    * Utility to programmatically click a button on a JOptionPane (as a JFrame)
1538    * 
1539    * returns true if button was found
1540    */
1541   public static boolean clickButton(JFrame frame, int buttonType)
1542   {
1543
1544     return false;
1545   }
1546
1547   /**
1548    * This helper method makes the JInternalFrame wait until it is notified by an
1549    * InternalFrameClosing event. This method also adds the given JOptionPane to
1550    * the JInternalFrame and sizes it according to the JInternalFrame's preferred
1551    * size.
1552    *
1553    * @param f
1554    *          The JInternalFrame to make modal.
1555    */
1556   private static void startModal(JInternalFrame f)
1557   {
1558     // We need to add an additional glasspane-like component directly
1559     // below the frame, which intercepts all mouse events that are not
1560     // directed at the frame itself.
1561     JPanel modalInterceptor = new JPanel();
1562     modalInterceptor.setOpaque(false);
1563     JLayeredPane lp = JLayeredPane.getLayeredPaneAbove(f);
1564     lp.setLayer(modalInterceptor, JLayeredPane.MODAL_LAYER.intValue());
1565     modalInterceptor.setBounds(0, 0, lp.getWidth(), lp.getHeight());
1566     modalInterceptor.addMouseListener(new MouseAdapter()
1567     {
1568     });
1569     modalInterceptor.addMouseMotionListener(new MouseMotionAdapter()
1570     {
1571     });
1572     lp.add(modalInterceptor);
1573     f.toFront();
1574
1575     // disable the main menu bar if in Linux
1576     JMenuBar menubar = null;
1577     if (Platform.isLinux())
1578     {
1579       JRootPane rootpane = Desktop.getDesktop().getRootPane();
1580       menubar = rootpane.getJMenuBar();
1581     }
1582
1583     // We need to explicitly dispatch events when we are blocking the event
1584     // dispatch thread.
1585     EventQueue queue = Toolkit.getDefaultToolkit().getSystemEventQueue();
1586     try
1587     {
1588       if (menubar != null)
1589       {
1590         // don't allow clicks on main menu on linux due to a hanging bug.
1591         // see JAL-4214.
1592         setMenusEnabled(menubar, false);
1593       }
1594
1595       while (!f.isClosed())
1596       {
1597         if (EventQueue.isDispatchThread())
1598         {
1599           // The getNextEventMethod() issues wait() when no
1600           // event is available, so we don't need do explicitly wait().
1601           AWTEvent ev = queue.getNextEvent();
1602           // This mimics EventQueue.dispatchEvent(). We can't use
1603           // EventQueue.dispatchEvent() directly, because it is
1604           // protected, unfortunately.
1605           if (ev instanceof ActiveEvent)
1606           {
1607             ((ActiveEvent) ev).dispatch();
1608           }
1609           else if (ev instanceof KeyEvent && ((KeyEvent) ev).isControlDown()
1610                   && menubar != null)
1611           {
1612             // temporarily enable menus to send Ctrl+? KeyEvents
1613             setMenusEnabled(menubar, true);
1614             ((Component) ev.getSource()).dispatchEvent(ev);
1615             setMenusEnabled(menubar, false);
1616           }
1617           else if (ev.getSource() instanceof MenuComponent)
1618           {
1619             ((MenuComponent) ev.getSource()).dispatchEvent(ev);
1620           }
1621           else if (ev.getSource() instanceof Component)
1622           {
1623             ((Component) ev.getSource()).dispatchEvent(ev);
1624           }
1625           // Other events are ignored as per spec in
1626           // EventQueue.dispatchEvent
1627         }
1628         else
1629         {
1630           // Give other threads a chance to become active.
1631           Thread.yield();
1632         }
1633       }
1634     } catch (InterruptedException ex)
1635     {
1636       // If we get interrupted, then leave the modal state.
1637     } finally
1638     {
1639       // re-enable the main menu bar
1640       if (menubar != null)
1641       {
1642         setMenusEnabled(menubar, true);
1643       }
1644
1645       // Clean up the modal interceptor.
1646       lp.remove(modalInterceptor);
1647
1648       // unpaint the frame
1649       f.setVisible(false);
1650
1651       // close the frame
1652       try
1653       {
1654         f.setClosed(true);
1655       } catch (PropertyVetoException e)
1656       {
1657         f.doDefaultCloseAction();
1658       }
1659
1660       // Remove the internal frame from its parent, so it is no longer
1661       // lurking around and clogging memory.
1662       Container parent = f.getParent();
1663       if (parent != null)
1664       {
1665         parent.remove(f);
1666       }
1667     }
1668   }
1669
1670   public static JvOptionPane frameDialog(Object message, String title,
1671           int messageType, String[] buttonsTextS, String defaultButtonS,
1672           List<Runnable> handlers, boolean modal)
1673   {
1674     JFrame parent = new JFrame();
1675     JvOptionPane jvop = JvOptionPane.newOptionDialog();
1676     final String[] buttonsText;
1677     final String defaultButton;
1678     if (buttonsTextS == null)
1679     {
1680       String ok = MessageManager.getString("action.ok");
1681       buttonsText = new String[] { ok };
1682       defaultButton = ok;
1683     }
1684     else
1685     {
1686       buttonsText = buttonsTextS;
1687       defaultButton = defaultButtonS;
1688     }
1689     JButton[] buttons = new JButton[buttonsText.length];
1690     for (int i = 0; i < buttonsText.length; i++)
1691     {
1692       buttons[i] = new JButton();
1693       buttons[i].setText(buttonsText[i]);
1694       Console.debug("DISABLING BUTTON " + buttons[i].getText());
1695       buttons[i].setEnabled(false);
1696       buttons[i].setVisible(false);
1697     }
1698
1699     int dialogType = -1;
1700     if (buttonsText.length == 1)
1701     {
1702       dialogType = JOptionPane.OK_OPTION;
1703     }
1704     else if (buttonsText.length == 2)
1705     {
1706       dialogType = JOptionPane.YES_NO_OPTION;
1707     }
1708     else
1709     {
1710       dialogType = JOptionPane.YES_NO_CANCEL_OPTION;
1711     }
1712     jvop.setResponseHandler(JOptionPane.YES_OPTION,
1713             (handlers != null && handlers.size() > 0) ? handlers.get(0)
1714                     : NULLCALLABLE);
1715     if (dialogType == JOptionPane.YES_NO_OPTION
1716             || dialogType == JOptionPane.YES_NO_CANCEL_OPTION)
1717     {
1718       jvop.setResponseHandler(JOptionPane.NO_OPTION,
1719               (handlers != null && handlers.size() > 1) ? handlers.get(1)
1720                       : NULLCALLABLE);
1721     }
1722     if (dialogType == JOptionPane.YES_NO_CANCEL_OPTION)
1723     {
1724       jvop.setResponseHandler(JOptionPane.CANCEL_OPTION,
1725               (handlers != null && handlers.size() > 2) ? handlers.get(2)
1726                       : NULLCALLABLE);
1727     }
1728
1729     final int dt = dialogType;
1730     new Thread(() -> {
1731       jvop.showDialog(message, title, dt, messageType, null, buttonsText,
1732               defaultButton, modal, buttons);
1733     }).start();
1734
1735     return jvop;
1736   }
1737
1738   private static void setMenusEnabled(JMenuBar menubar, boolean b)
1739   {
1740     for (int i = 0; i < menubar.getMenuCount(); i++)
1741     {
1742       JMenu menu = menubar.getMenu(i);
1743       menu.setEnabled(b);
1744     }
1745   }
1746
1747 }