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