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