Merge branch 'improvement/JAL-3416_default_to_LIVE_DRAG_MODE_for_flatlaf' into merge...
[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.MouseAdapter;
36 import java.awt.event.MouseMotionAdapter;
37 import java.beans.PropertyChangeEvent;
38 import java.beans.PropertyChangeListener;
39 import java.util.ArrayList;
40 import java.util.Arrays;
41 import java.util.HashMap;
42 import java.util.List;
43 import java.util.Map;
44 import java.util.concurrent.Callable;
45 import java.util.concurrent.Executors;
46
47 import javax.swing.Icon;
48 import javax.swing.JButton;
49 import javax.swing.JDialog;
50 import javax.swing.JFrame;
51 import javax.swing.JInternalFrame;
52 import javax.swing.JLayeredPane;
53 import javax.swing.JOptionPane;
54 import javax.swing.JPanel;
55 import javax.swing.SwingUtilities;
56 import javax.swing.UIManager;
57 import javax.swing.event.InternalFrameEvent;
58 import javax.swing.event.InternalFrameListener;
59
60 import jalview.util.Platform;
61 import jalview.util.dialogrunner.DialogRunnerI;
62
63 public class JvOptionPane extends JOptionPane
64         implements DialogRunnerI, PropertyChangeListener
65 {
66   private static final long serialVersionUID = -3019167117756785229L;
67
68   private static Object mockResponse = JvOptionPane.CANCEL_OPTION;
69
70   private static boolean interactiveMode = true;
71
72   private Component parentComponent;
73
74   private Map<Object, Callable<Void>> callbacks = new HashMap<>();
75
76   /*
77    * JalviewJS reports user choice in the dialog as the selected option (text);
78    * this list allows conversion to index (int)
79    */
80   List<Object> ourOptions;
81
82   public JvOptionPane(final Component parent)
83   {
84     this.parentComponent = Platform.isJS() ? this : parent;
85     this.setIcon(null);
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       return;
808     }
809     // two uses:
810     //
811     // TODO
812     //
813     // 1) AlignViewport for openLinkedAlignment
814     //
815     // Show a dialog with the option to open and link (cDNA <-> protein) as a
816     // new
817     // alignment, either as a standalone alignment or in a split frame. Returns
818     // true if the new alignment was opened, false if not, because the user
819     // declined the offer.
820     //
821     // 2) UserDefinedColors warning about saving over a name already defined
822     //
823
824     ourOptions = Arrays.asList(options);
825
826     if (modal)
827     {
828       boolean useButtons = false;
829       Object initialValueButton = null;
830       NOTNULL: if (buttons != null)
831       {
832         if (buttons.length != options.length)
833         {
834           jalview.bin.Console.error(
835                   "Supplied buttons array not the same length as supplied options array.");
836           break NOTNULL;
837         }
838         int[] buttonActions = { JOptionPane.YES_OPTION,
839             JOptionPane.NO_OPTION, JOptionPane.CANCEL_OPTION };
840         for (int i = 0; i < options.length; i++)
841         {
842           Object o = options[i];
843           jalview.bin.Console.debug(
844                   "Setting button " + i + " to '" + o.toString() + "'");
845           JButton jb = buttons[i];
846
847           if (o.equals(initialValue))
848             initialValueButton = jb;
849
850           int buttonAction = buttonActions[i];
851           Callable<Void> action = callbacks.get(buttonAction);
852           jb.setText((String) o);
853           jb.addActionListener(new ActionListener()
854           {
855             @Override
856             public void actionPerformed(ActionEvent e)
857             {
858
859               Object obj = e.getSource();
860               if (obj == null || !(obj instanceof Component))
861               {
862                 jalview.bin.Console.debug(
863                         "Could not find Component source of event object "
864                                 + obj);
865                 return;
866               }
867               Object joptionpaneObject = SwingUtilities.getAncestorOfClass(
868                       JOptionPane.class, (Component) obj);
869               if (joptionpaneObject == null
870                       || !(joptionpaneObject instanceof JOptionPane))
871               {
872                 jalview.bin.Console.debug(
873                         "Could not find JOptionPane ancestor of event object "
874                                 + obj);
875                 return;
876               }
877               JOptionPane joptionpane = (JOptionPane) joptionpaneObject;
878               joptionpane.setValue(buttonAction);
879               if (action != null)
880                 Executors.newSingleThreadExecutor().submit(action);
881               joptionpane.transferFocusBackward();
882               joptionpane.setVisible(false);
883               // put focus and raise parent window if possible, unless cancel or
884               // no button pressed
885               boolean raiseParent = (parentComponent != null);
886               if (buttonAction == JOptionPane.CANCEL_OPTION)
887                 raiseParent = false;
888               if (optionType == JOptionPane.YES_NO_OPTION
889                       && buttonAction == JOptionPane.NO_OPTION)
890                 raiseParent = false;
891               if (raiseParent)
892               {
893                 parentComponent.requestFocus();
894                 if (parentComponent instanceof JInternalFrame)
895                 {
896                   JInternalFrame jif = (JInternalFrame) parentComponent;
897                   jif.show();
898                   jif.moveToFront();
899                   jif.grabFocus();
900                 }
901                 else if (parentComponent instanceof Window)
902                 {
903                   Window w = (Window) parentComponent;
904                   w.toFront();
905                   w.requestFocus();
906                 }
907               }
908               joptionpane.setVisible(false);
909             }
910           });
911
912         }
913         useButtons = true;
914       }
915       // use a JOptionPane as usual
916       int response = JOptionPane.showOptionDialog(parentComponent, message,
917               title, optionType, messageType, icon,
918               useButtons ? buttons : options,
919               useButtons ? initialValueButton : initialValue);
920
921       /*
922        * In Java, the response is returned to this thread and handled here; (for
923        * Javascript, see propertyChange)
924        */
925       if (!Platform.isJS())
926       /**
927        * Java only
928        * 
929        * @j2sIgnore
930        */
931       {
932         handleResponse(response);
933       }
934     }
935     else
936     {
937       /*
938        * This is java similar to the swingjs handling, with the callbacks attached to
939        * the button press of the dialog. This means we can use a non-modal JDialog for
940        * the confirmation without blocking the GUI.
941        */
942       JOptionPane joptionpane = new JOptionPane();
943       // Make button options
944       int[] buttonActions = { JvOptionPane.YES_OPTION,
945           JvOptionPane.NO_OPTION, JvOptionPane.CANCEL_OPTION };
946
947       // we need the strings to make the buttons with actionEventListener
948       if (options == null)
949       {
950         ArrayList<String> options_default = new ArrayList<>();
951         options_default
952                 .add(UIManager.getString("OptionPane.yesButtonText"));
953         if (optionType == JvOptionPane.YES_NO_OPTION
954                 || optionType == JvOptionPane.YES_NO_CANCEL_OPTION)
955         {
956           options_default
957                   .add(UIManager.getString("OptionPane.noButtonText"));
958         }
959         if (optionType == JvOptionPane.YES_NO_CANCEL_OPTION)
960         {
961           options_default
962                   .add(UIManager.getString("OptionPane.cancelButtonText"));
963         }
964         options = options_default.toArray();
965       }
966
967       ArrayList<JButton> options_btns = new ArrayList<>();
968       Object initialValue_btn = null;
969       if (!Platform.isJS()) // JalviewJS already uses callback, don't need to
970                             // add them here
971       {
972         for (int i = 0; i < options.length && i < 3; i++)
973         {
974           Object o = options[i];
975           int buttonAction = buttonActions[i];
976           Callable<Void> action = callbacks.get(buttonAction);
977           JButton jb = new JButton();
978           jb.setText((String) o);
979           jb.addActionListener(new ActionListener()
980           {
981             @Override
982             public void actionPerformed(ActionEvent e)
983             {
984               joptionpane.setValue(buttonAction);
985               if (action != null)
986                 Executors.newSingleThreadExecutor().submit(action);
987               // joptionpane.transferFocusBackward();
988               joptionpane.transferFocusBackward();
989               joptionpane.setVisible(false);
990               // put focus and raise parent window if possible, unless cancel
991               // button pressed
992               boolean raiseParent = (parentComponent != null);
993               if (buttonAction == JvOptionPane.CANCEL_OPTION)
994                 raiseParent = false;
995               if (optionType == JvOptionPane.YES_NO_OPTION
996                       && buttonAction == JvOptionPane.NO_OPTION)
997                 raiseParent = false;
998               if (raiseParent)
999               {
1000                 parentComponent.requestFocus();
1001                 if (parentComponent instanceof JInternalFrame)
1002                 {
1003                   JInternalFrame jif = (JInternalFrame) parentComponent;
1004                   jif.show();
1005                   jif.moveToFront();
1006                   jif.grabFocus();
1007                 }
1008                 else if (parentComponent instanceof Window)
1009                 {
1010                   Window w = (Window) parentComponent;
1011                   w.toFront();
1012                   w.requestFocus();
1013                 }
1014               }
1015               joptionpane.setVisible(false);
1016             }
1017           });
1018           options_btns.add(jb);
1019           if (o.equals(initialValue))
1020             initialValue_btn = jb;
1021         }
1022       }
1023       joptionpane.setMessage(message);
1024       joptionpane.setMessageType(messageType);
1025       joptionpane.setOptionType(optionType);
1026       joptionpane.setIcon(icon);
1027       joptionpane.setOptions(
1028               Platform.isJS() ? options : options_btns.toArray());
1029       joptionpane.setInitialValue(
1030               Platform.isJS() ? initialValue : initialValue_btn);
1031
1032       JDialog dialog = joptionpane.createDialog(parentComponent, title);
1033       dialog.setIconImage(null);
1034       dialog.setModalityType(modal ? ModalityType.APPLICATION_MODAL
1035               : ModalityType.MODELESS);
1036       dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
1037       dialog.setVisible(true);
1038     }
1039   }
1040
1041   public void showInternalDialog(JPanel mainPanel, String title,
1042           int yesNoCancelOption, int questionMessage, Icon icon,
1043           Object[] options, String initresponse)
1044   {
1045     if (!isInteractiveMode())
1046     {
1047       handleResponse(getMockResponse());
1048     }
1049
1050     // need to set these separately so we can set the title bar icon later
1051     this.setOptionType(yesNoCancelOption);
1052     this.setMessageType(questionMessage);
1053     this.setIcon(icon);
1054     this.setInitialValue(initresponse);
1055     this.setOptions(options);
1056     this.setMessage(mainPanel);
1057
1058     ourOptions = Arrays.asList(options);
1059     int response;
1060     if (parentComponent != this)
1061     {
1062       JInternalFrame jif = this.createInternalFrame(parentComponent, title);
1063       jif.setFrameIcon(null);
1064       jif.addInternalFrameListener(new InternalFrameListener()
1065       {
1066         @Override
1067         public void internalFrameActivated(InternalFrameEvent arg0)
1068         {
1069         }
1070
1071         @Override
1072         public void internalFrameClosed(InternalFrameEvent arg0)
1073         {
1074           JvOptionPane.this.internalDialogHandleResponse();
1075         }
1076
1077         @Override
1078         public void internalFrameClosing(InternalFrameEvent arg0)
1079         {
1080         }
1081
1082         @Override
1083         public void internalFrameDeactivated(InternalFrameEvent arg0)
1084         {
1085         }
1086
1087         @Override
1088         public void internalFrameDeiconified(InternalFrameEvent arg0)
1089         {
1090         }
1091
1092         @Override
1093         public void internalFrameIconified(InternalFrameEvent arg0)
1094         {
1095         }
1096
1097         @Override
1098         public void internalFrameOpened(InternalFrameEvent arg0)
1099         {
1100         }
1101       });
1102       jif.setVisible(true);
1103       startModal(jif);
1104       return;
1105     }
1106     else
1107     {
1108       JDialog dialog = this.createDialog(parentComponent, title);
1109       dialog.setIconImage(null);
1110       dialog.setVisible(true); // blocking
1111       this.internalDialogHandleResponse();
1112       return;
1113     }
1114   }
1115
1116   private void internalDialogHandleResponse()
1117   {
1118     String responseString = (String) this.getValue();
1119     int response = ourOptions.indexOf(responseString);
1120
1121     if (!Platform.isJS())
1122     /**
1123      * Java only
1124      * 
1125      * @j2sIgnore
1126      */
1127     {
1128       handleResponse(response);
1129     }
1130   }
1131
1132   /*
1133    * @Override public JvOptionPane setResponseHandler(Object response, Runnable
1134    * action) { callbacks.put(response, new Callable<Void>() {
1135    * 
1136    * @Override public Void call() { action.run(); return null; } }); return this;
1137    * }
1138    */
1139   @Override
1140   public JvOptionPane setResponseHandler(Object response,
1141           Callable<Void> action)
1142   {
1143     callbacks.put(response, action);
1144     return this;
1145   }
1146
1147   /**
1148    * showDialogOnTop will create a dialog that (attempts to) come to top of OS
1149    * desktop windows
1150    */
1151   public static int showDialogOnTop(String label, String actionString,
1152           int JOPTIONPANE_OPTION, int JOPTIONPANE_MESSAGETYPE)
1153   {
1154     if (!isInteractiveMode())
1155     {
1156       return (int) getMockResponse();
1157     }
1158     // Ensure Jalview window is brought to front (primarily for Quit
1159     // confirmation window to be visible)
1160
1161     // This method of raising the Jalview window is broken in java
1162     // jalviewDesktop.setVisible(true);
1163     // jalviewDesktop.toFront();
1164
1165     // A better hack which works is to create a new JFrame parent with
1166     // setAlwaysOnTop(true)
1167     JFrame dialogParent = new JFrame();
1168     dialogParent.setIconImage(null);
1169     dialogParent.setAlwaysOnTop(true);
1170
1171     int answer = JOptionPane.showConfirmDialog(dialogParent, label,
1172             actionString, JOPTIONPANE_OPTION, JOPTIONPANE_MESSAGETYPE);
1173
1174     dialogParent.setAlwaysOnTop(false);
1175     dialogParent.dispose();
1176
1177     return answer;
1178   }
1179
1180   public void showDialogOnTopAsync(String label, String actionString,
1181           int JOPTIONPANE_OPTION, int JOPTIONPANE_MESSAGETYPE, Icon icon,
1182           Object[] options, Object initialValue, boolean modal)
1183   {
1184     JFrame frame = new JFrame();
1185     frame.setIconImage(null);
1186     showDialogOnTopAsync(frame, label, actionString, JOPTIONPANE_OPTION,
1187             JOPTIONPANE_MESSAGETYPE, icon, options, initialValue, modal);
1188   }
1189
1190   public void showDialogOnTopAsync(JFrame dialogParent, Object label,
1191           String actionString, int JOPTIONPANE_OPTION,
1192           int JOPTIONPANE_MESSAGETYPE, Icon icon, Object[] options,
1193           Object initialValue, boolean modal)
1194   {
1195     showDialogOnTopAsync(dialogParent, label, actionString,
1196             JOPTIONPANE_OPTION, JOPTIONPANE_MESSAGETYPE, icon, options,
1197             initialValue, modal, null);
1198   }
1199
1200   public void showDialogOnTopAsync(JFrame dialogParent, Object label,
1201           String actionString, int JOPTIONPANE_OPTION,
1202           int JOPTIONPANE_MESSAGETYPE, Icon icon, Object[] options,
1203           Object initialValue, boolean modal, JButton[] buttons)
1204   {
1205     if (!isInteractiveMode())
1206     {
1207       handleResponse(getMockResponse());
1208       return;
1209     }
1210     // Ensure Jalview window is brought to front (primarily for Quit
1211     // confirmation window to be visible)
1212
1213     // This method of raising the Jalview window is broken in java
1214     // jalviewDesktop.setVisible(true);
1215     // jalviewDesktop.toFront();
1216
1217     // A better hack which works is to create a new JFrame parent with
1218     // setAlwaysOnTop(true)
1219     dialogParent.setAlwaysOnTop(true);
1220     parentComponent = dialogParent;
1221
1222     showDialog(label, actionString, JOPTIONPANE_OPTION,
1223             JOPTIONPANE_MESSAGETYPE, icon, options, initialValue, modal,
1224             buttons);
1225
1226     dialogParent.setAlwaysOnTop(false);
1227     dialogParent.dispose();
1228   }
1229
1230   /**
1231    * JalviewJS signals option selection by a property change event for the
1232    * option e.g. "OK". This methods responds to that by running the response
1233    * action that corresponds to that option.
1234    * 
1235    * @param evt
1236    */
1237   @Override
1238   public void propertyChange(PropertyChangeEvent evt)
1239   {
1240     Object newValue = evt.getNewValue();
1241     int ourOption = ourOptions.indexOf(newValue);
1242     if (ourOption >= 0)
1243     {
1244       handleResponse(ourOption);
1245     }
1246     else
1247     {
1248       // try our luck..
1249       handleResponse(newValue);
1250     }
1251   }
1252
1253   @Override
1254   public void handleResponse(Object response)
1255   {
1256     /*
1257      * this test is for NaN in Chrome
1258      */
1259     if (response != null && !response.equals(response))
1260     {
1261       return;
1262     }
1263     Callable<Void> action = callbacks.get(response);
1264     if (action != null)
1265     {
1266       try
1267       {
1268         action.call();
1269       } catch (Exception e)
1270       {
1271         e.printStackTrace();
1272       }
1273       if (parentComponent != null)
1274         parentComponent.requestFocus();
1275     }
1276   }
1277
1278   /**
1279    * Create a non-modal confirm dialog
1280    */
1281   public JDialog createDialog(Component parentComponent, Object message,
1282           String title, int optionType, int messageType, Icon icon,
1283           Object[] options, Object initialValue, boolean modal)
1284   {
1285     return createDialog(parentComponent, message, title, optionType,
1286             messageType, icon, options, initialValue, modal, null);
1287   }
1288
1289   public JDialog createDialog(Component parentComponent, Object message,
1290           String title, int optionType, int messageType, Icon icon,
1291           Object[] options, Object initialValue, boolean modal,
1292           JButton[] buttons)
1293   {
1294     if (!isInteractiveMode())
1295     {
1296       handleResponse(getMockResponse());
1297       return null;
1298     }
1299     JButton[] optionsButtons = null;
1300     Object initialValueButton = null;
1301     JOptionPane joptionpane = new JOptionPane();
1302     // Make button options
1303     int[] buttonActions = { JOptionPane.YES_OPTION, JOptionPane.NO_OPTION,
1304         JOptionPane.CANCEL_OPTION };
1305
1306     // we need the strings to make the buttons with actionEventListener
1307     if (options == null)
1308     {
1309       ArrayList<String> options_default = new ArrayList<>();
1310       options_default.add(UIManager.getString("OptionPane.yesButtonText"));
1311       if (optionType == JOptionPane.YES_NO_OPTION
1312               || optionType == JOptionPane.YES_NO_CANCEL_OPTION)
1313       {
1314         options_default.add(UIManager.getString("OptionPane.noButtonText"));
1315       }
1316       if (optionType == JOptionPane.YES_NO_CANCEL_OPTION)
1317       {
1318         options_default
1319                 .add(UIManager.getString("OptionPane.cancelButtonText"));
1320       }
1321       options = options_default.toArray();
1322     }
1323     if (!Platform.isJS()) // JalviewJS already uses callback, don't need to
1324                           // add them here
1325     {
1326       if (((optionType == JOptionPane.YES_OPTION
1327               || optionType == JOptionPane.NO_OPTION
1328               || optionType == JOptionPane.CANCEL_OPTION
1329               || optionType == JOptionPane.OK_OPTION
1330               || optionType == JOptionPane.DEFAULT_OPTION)
1331               && options.length < 1)
1332               || ((optionType == JOptionPane.YES_NO_OPTION
1333                       || optionType == JOptionPane.OK_CANCEL_OPTION)
1334                       && options.length < 2)
1335               || (optionType == JOptionPane.YES_NO_CANCEL_OPTION
1336                       && options.length < 3))
1337       {
1338         jalview.bin.Console
1339                 .debug("JvOptionPane: not enough options for dialog type");
1340       }
1341       optionsButtons = new JButton[options.length];
1342       for (int i = 0; i < options.length && i < 3; i++)
1343       {
1344         Object o = options[i];
1345         int buttonAction = buttonActions[i];
1346         Callable<Void> action = callbacks.get(buttonAction);
1347         JButton jb;
1348         if (buttons != null && buttons.length > i && buttons[i] != null)
1349         {
1350           jb = buttons[i];
1351         }
1352         else
1353         {
1354           jb = new JButton();
1355         }
1356         jb.setText((String) o);
1357         jb.addActionListener(new ActionListener()
1358         {
1359           @Override
1360           public void actionPerformed(ActionEvent e)
1361           {
1362             joptionpane.setValue(buttonAction);
1363             if (action != null)
1364               Executors.newSingleThreadExecutor().submit(action);
1365             // joptionpane.transferFocusBackward();
1366             joptionpane.transferFocusBackward();
1367             joptionpane.setVisible(false);
1368             // put focus and raise parent window if possible, unless cancel
1369             // button pressed
1370             boolean raiseParent = (parentComponent != null);
1371             if (buttonAction == JOptionPane.CANCEL_OPTION)
1372               raiseParent = false;
1373             if (optionType == JOptionPane.YES_NO_OPTION
1374                     && buttonAction == JOptionPane.NO_OPTION)
1375               raiseParent = false;
1376             if (raiseParent)
1377             {
1378               parentComponent.requestFocus();
1379               if (parentComponent instanceof JInternalFrame)
1380               {
1381                 JInternalFrame jif = (JInternalFrame) parentComponent;
1382                 jif.show();
1383                 jif.moveToFront();
1384                 jif.grabFocus();
1385               }
1386               else if (parentComponent instanceof Window)
1387               {
1388                 Window w = (Window) parentComponent;
1389                 w.toFront();
1390                 w.requestFocus();
1391               }
1392             }
1393             joptionpane.setVisible(false);
1394           }
1395         });
1396         optionsButtons[i] = jb;
1397         if (o.equals(initialValue))
1398           initialValueButton = jb;
1399       }
1400     }
1401     joptionpane.setMessage(message);
1402     joptionpane.setMessageType(messageType);
1403     joptionpane.setOptionType(optionType);
1404     joptionpane.setIcon(icon);
1405     joptionpane.setOptions(Platform.isJS() ? options : optionsButtons);
1406     joptionpane.setInitialValue(
1407             Platform.isJS() ? initialValue : initialValueButton);
1408
1409     JDialog dialog = joptionpane.createDialog(parentComponent, title);
1410     dialog.setIconImage(null);
1411     dialog.setModalityType(
1412             modal ? ModalityType.APPLICATION_MODAL : ModalityType.MODELESS);
1413     dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
1414     return dialog;
1415   }
1416
1417   /**
1418    * Utility to programmatically click a button on a JOptionPane (as a JFrame)
1419    * 
1420    * returns true if button was found
1421    */
1422   public static boolean clickButton(JFrame frame, int buttonType)
1423   {
1424
1425     return false;
1426   }
1427
1428   /**
1429    * This helper method makes the JInternalFrame wait until it is notified by an
1430    * InternalFrameClosing event. This method also adds the given JOptionPane to
1431    * the JInternalFrame and sizes it according to the JInternalFrame's preferred
1432    * size.
1433    *
1434    * @param f
1435    *          The JInternalFrame to make modal.
1436    */
1437   private static void startModal(JInternalFrame f)
1438   {
1439     // We need to add an additional glasspane-like component directly
1440     // below the frame, which intercepts all mouse events that are not
1441     // directed at the frame itself.
1442     JPanel modalInterceptor = new JPanel();
1443     modalInterceptor.setOpaque(false);
1444     JLayeredPane lp = JLayeredPane.getLayeredPaneAbove(f);
1445     lp.setLayer(modalInterceptor, JLayeredPane.MODAL_LAYER.intValue());
1446     modalInterceptor.setBounds(0, 0, lp.getWidth(), lp.getHeight());
1447     modalInterceptor.addMouseListener(new MouseAdapter()
1448     {
1449     });
1450     modalInterceptor.addMouseMotionListener(new MouseMotionAdapter()
1451     {
1452     });
1453     lp.add(modalInterceptor);
1454     f.toFront();
1455
1456     // We need to explicitly dispatch events when we are blocking the event
1457     // dispatch thread.
1458     EventQueue queue = Toolkit.getDefaultToolkit().getSystemEventQueue();
1459     try
1460     {
1461       while (!f.isClosed())
1462       {
1463         if (EventQueue.isDispatchThread())
1464         {
1465           // The getNextEventMethod() issues wait() when no
1466           // event is available, so we don't need do explicitly wait().
1467           AWTEvent ev = queue.getNextEvent();
1468           // This mimics EventQueue.dispatchEvent(). We can't use
1469           // EventQueue.dispatchEvent() directly, because it is
1470           // protected, unfortunately.
1471           if (ev instanceof ActiveEvent)
1472             ((ActiveEvent) ev).dispatch();
1473           else if (ev.getSource() instanceof Component)
1474             ((Component) ev.getSource()).dispatchEvent(ev);
1475           else if (ev.getSource() instanceof MenuComponent)
1476             ((MenuComponent) ev.getSource()).dispatchEvent(ev);
1477           // Other events are ignored as per spec in
1478           // EventQueue.dispatchEvent
1479         }
1480         else
1481         {
1482           // Give other threads a chance to become active.
1483           Thread.yield();
1484         }
1485       }
1486     } catch (InterruptedException ex)
1487     {
1488       // If we get interrupted, then leave the modal state.
1489     } finally
1490     {
1491       // Clean up the modal interceptor.
1492       lp.remove(modalInterceptor);
1493
1494       // Remove the internal frame from its parent, so it is no longer
1495       // lurking around and clogging memory.
1496       Container parent = f.getParent();
1497       if (parent != null)
1498         parent.remove(f);
1499     }
1500   }
1501 }