733223d7792480caf2731cb51812d7748e988092
[jalview.git] / src / jalview / gui / JvOptionPane.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21
22 package jalview.gui;
23
24 import java.awt.Component;
25 import java.awt.HeadlessException;
26 import java.beans.PropertyChangeEvent;
27 import java.beans.PropertyChangeListener;
28 import java.util.Arrays;
29 import java.util.HashMap;
30 import java.util.List;
31 import java.util.Map;
32
33 import javax.swing.Icon;
34 import javax.swing.JOptionPane;
35 import javax.swing.JPanel;
36
37 import jalview.util.Platform;
38 import jalview.util.dialogrunner.DialogRunnerI;
39
40 public class JvOptionPane extends JOptionPane
41         implements DialogRunnerI, PropertyChangeListener
42 {
43   private static final long serialVersionUID = -3019167117756785229L;
44
45   private static Object mockResponse = JvOptionPane.CANCEL_OPTION;
46
47   private static boolean interactiveMode = true;
48
49   private Component parentComponent;
50
51   private Map<Object, Runnable> callbacks = new HashMap<>();
52
53   /*
54    * JalviewJS reports user choice in the dialog as the selected
55    * option (text); this list allows conversion to index (int)
56    */
57   List<Object> ourOptions;
58
59   public JvOptionPane(final Component parent)
60   {
61     this.parentComponent = Platform.isJS() ? this : parent;
62   }
63
64   public static int showConfirmDialog(Component parentComponent,
65           Object message) throws HeadlessException
66   {
67     // only called by test
68     return isInteractiveMode()
69             ? JOptionPane.showConfirmDialog(parentComponent, message)
70             : (int) getMockResponse();
71   }
72
73   /**
74    * Message, title, optionType
75    * 
76    * @param parentComponent
77    * @param message
78    * @param title
79    * @param optionType
80    * @return
81    * @throws HeadlessException
82    */
83   public static int showConfirmDialog(Component parentComponent,
84           Object message, String title, int optionType)
85           throws HeadlessException
86   {
87     if (!isInteractiveMode())
88     {
89       return (int) getMockResponse();
90     }
91     switch (optionType)
92     {
93     case JvOptionPane.YES_NO_CANCEL_OPTION:
94       // FeatureRenderer amendFeatures ?? TODO ??
95       // Chimera close
96       // PromptUserConfig
97       // $FALL-THROUGH$
98     default:
99     case JvOptionPane.YES_NO_OPTION:
100       // PromptUserConfig usage stats
101       // for now treated as "OK CANCEL"
102       // $FALL-THROUGH$
103     case JvOptionPane.OK_CANCEL_OPTION:
104       // will fall back to simple HTML
105       return JOptionPane.showConfirmDialog(parentComponent, message, title,
106               optionType);
107     }
108   }
109
110   /**
111    * Adds a message type. Fallback is to just add it in the beginning.
112    * 
113    * @param parentComponent
114    * @param message
115    * @param title
116    * @param optionType
117    * @param messageType
118    * @return
119    * @throws HeadlessException
120    */
121   public static int showConfirmDialog(Component parentComponent,
122           Object message, String title, int optionType, int messageType)
123           throws HeadlessException
124   {
125     // JalviewServicesChanged
126     // PromptUserConfig raiseDialog
127     return isInteractiveMode()
128             ? JOptionPane.showConfirmDialog(parentComponent, message, title,
129                     optionType, messageType)
130             : (int) getMockResponse();
131   }
132
133   /**
134    * Adds an icon
135    * 
136    * @param parentComponent
137    * @param message
138    * @param title
139    * @param optionType
140    * @param messageType
141    * @param icon
142    * @return
143    * @throws HeadlessException
144    */
145   public static int showConfirmDialog(Component parentComponent,
146           Object message, String title, int optionType, int messageType,
147           Icon icon) throws HeadlessException
148   {
149     // JvOptionPaneTest only
150     return isInteractiveMode()
151             ? JOptionPane.showConfirmDialog(parentComponent, message, title,
152                     optionType, messageType, icon)
153             : (int) getMockResponse();
154   }
155
156   /**
157    * Internal version "OK"
158    * 
159    * @param parentComponent
160    * @param message
161    * @return
162    */
163   public static int showInternalConfirmDialog(Component parentComponent,
164           Object message)
165   {
166     // JvOptionPaneTest only;
167     return isInteractiveMode()
168             ? JOptionPane.showInternalConfirmDialog(parentComponent,
169                     message)
170             : (int) getMockResponse();
171   }
172
173   /**
174    * Internal version -- changed to standard version for now
175    * 
176    * @param parentComponent
177    * @param message
178    * @param title
179    * @param optionType
180    * @return
181    */
182   public static int showInternalConfirmDialog(Component parentComponent,
183           String message, String title, int optionType)
184   {
185     if (!isInteractiveMode())
186     {
187       return (int) getMockResponse();
188     }
189     switch (optionType)
190     {
191     case JvOptionPane.YES_NO_CANCEL_OPTION:
192       // ColourMenuHelper.addMenuItmers.offerRemoval TODO
193     case JvOptionPane.YES_NO_OPTION:
194       // UserDefinedColoursSave -- relevant? TODO
195       // $FALL-THROUGH$
196     default:
197     case JvOptionPane.OK_CANCEL_OPTION:
198
199       // EditNameDialog --- uses panel for messsage TODO
200
201       // Desktop.inputURLMenuItem
202       // WsPreferenses
203       return JOptionPane.showConfirmDialog(parentComponent, message, title,
204               optionType);
205     }
206   }
207
208   /**
209    * 
210    * @param parentComponent
211    * @param message
212    * @param title
213    * @param optionType
214    * @param messageType
215    * @return
216    */
217   public static int showInternalConfirmDialog(Component parentComponent,
218           Object message, String title, int optionType, int messageType)
219   {
220     if (!isInteractiveMode())
221     {
222       return (int) getMockResponse();
223     }
224     switch (optionType)
225     {
226     case JvOptionPane.YES_NO_CANCEL_OPTION:
227     case JvOptionPane.YES_NO_OPTION:
228       // UserQuestionanaireCheck
229       // VamsasApplication
230       // $FALL-THROUGH$
231     default:
232     case JvOptionPane.OK_CANCEL_OPTION:
233       // will fall back to simple HTML
234       return JOptionPane.showConfirmDialog(parentComponent, message, title,
235               optionType, messageType);
236     }
237   }
238
239   /**
240    * adds icon; no longer internal
241    * 
242    * @param parentComponent
243    * @param message
244    * @param title
245    * @param optionType
246    * @param messageType
247    * @param icon
248    * @return
249    */
250   public static int showInternalConfirmDialog(Component parentComponent,
251           Object message, String title, int optionType, int messageType,
252           Icon icon)
253   {
254     if (!isInteractiveMode())
255     {
256       return (int) getMockResponse();
257     }
258     switch (optionType)
259     {
260     case JvOptionPane.YES_NO_CANCEL_OPTION:
261     case JvOptionPane.YES_NO_OPTION:
262       //$FALL-THROUGH$
263     default:
264     case JvOptionPane.OK_CANCEL_OPTION:
265       // Preferences editLink/newLink
266       return JOptionPane.showConfirmDialog(parentComponent, message, title,
267               optionType, messageType, icon);
268     }
269
270   }
271
272   /**
273    * custom options full-featured
274    * 
275    * @param parentComponent
276    * @param message
277    * @param title
278    * @param optionType
279    * @param messageType
280    * @param icon
281    * @param options
282    * @param initialValue
283    * @return
284    * @throws HeadlessException
285    */
286   public static int showOptionDialog(Component parentComponent,
287           String message, String title, int optionType, int messageType,
288           Icon icon, Object[] options, Object initialValue)
289           throws HeadlessException
290   {
291     if (!isInteractiveMode())
292     {
293       return (int) getMockResponse();
294     }
295     // two uses:
296     //
297     // TODO
298     //
299     // 1) AlignViewport for openLinkedAlignment
300     //
301     // Show a dialog with the option to open and link (cDNA <-> protein) as a
302     // new
303     // alignment, either as a standalone alignment or in a split frame. Returns
304     // true if the new alignment was opened, false if not, because the user
305     // declined the offer.
306     //
307     // 2) UserDefinedColors warning about saving over a name already defined
308     //
309     return JOptionPane.showOptionDialog(parentComponent, message, title,
310             optionType, messageType, icon, options, initialValue);
311   }
312
313   /**
314    * Just an OK message
315    * 
316    * @param message
317    * @throws HeadlessException
318    */
319   public static void showMessageDialog(Component parentComponent,
320           String message) throws HeadlessException
321   {
322     if (!isInteractiveMode())
323     {
324       outputMessage(message);
325       return;
326     }
327
328     // test class only
329
330     JOptionPane.showMessageDialog(parentComponent, message);
331   }
332
333   /**
334    * OK with message, title, and type
335    * 
336    * @param parentComponent
337    * @param message
338    * @param title
339    * @param messageType
340    * @throws HeadlessException
341    */
342   public static void showMessageDialog(Component parentComponent,
343           String message, String title, int messageType)
344           throws HeadlessException
345   {
346     // 30 implementations -- all just fine.
347
348     if (!isInteractiveMode())
349     {
350       outputMessage(message);
351       return;
352     }
353
354     JOptionPane.showMessageDialog(parentComponent,
355             getPrefix(messageType) + message, title, messageType);
356   }
357
358   /**
359    * adds title and icon
360    * 
361    * @param parentComponent
362    * @param message
363    * @param title
364    * @param messageType
365    * @param icon
366    * @throws HeadlessException
367    */
368   public static void showMessageDialog(Component parentComponent,
369           String message, String title, int messageType, Icon icon)
370           throws HeadlessException
371   {
372
373     // test only
374
375     if (!isInteractiveMode())
376     {
377       outputMessage(message);
378       return;
379     }
380
381     JOptionPane.showMessageDialog(parentComponent, message, title,
382             messageType, icon);
383   }
384
385   /**
386    * was internal
387    * 
388    */
389   public static void showInternalMessageDialog(Component parentComponent,
390           Object message)
391   {
392
393     // WsPreferences only
394
395     if (!isInteractiveMode())
396     {
397       outputMessage(message);
398       return;
399     }
400
401     JOptionPane.showMessageDialog(parentComponent, message);
402   }
403
404   /**
405    * Adds title and messageType
406    * 
407    * @param parentComponent
408    * @param message
409    * @param title
410    * @param messageType
411    */
412   public static void showInternalMessageDialog(Component parentComponent,
413           String message, String title, int messageType)
414   {
415
416     // 41 references
417
418     if (!isInteractiveMode())
419     {
420       outputMessage(message);
421       return;
422     }
423
424     JOptionPane.showMessageDialog(parentComponent,
425             getPrefix(messageType) + message, title, messageType);
426   }
427
428   /**
429    * 
430    * @param parentComponent
431    * @param message
432    * @param title
433    * @param messageType
434    * @param icon
435    */
436   public static void showInternalMessageDialog(Component parentComponent,
437           Object message, String title, int messageType, Icon icon)
438   {
439
440     // test only
441
442     if (!isInteractiveMode())
443     {
444       outputMessage(message);
445       return;
446     }
447
448     JOptionPane.showMessageDialog(parentComponent, message, title,
449             messageType, icon);
450   }
451
452   /**
453    * 
454    * @param message
455    * @return
456    * @throws HeadlessException
457    */
458   public static String showInputDialog(Object message)
459           throws HeadlessException
460   {
461     // test only
462
463     if (!isInteractiveMode())
464     {
465       return getMockResponse().toString();
466     }
467
468     return JOptionPane.showInputDialog(message);
469   }
470
471   /**
472    * adds inital selection value
473    * 
474    * @param message
475    * @param initialSelectionValue
476    * @return
477    */
478   public static String showInputDialog(String message,
479           String initialSelectionValue)
480   {
481     if (!isInteractiveMode())
482     {
483       return getMockResponse().toString();
484     }
485
486     // AnnotationPanel character option
487
488     return JOptionPane.showInputDialog(message, initialSelectionValue);
489   }
490
491   /**
492    * adds inital selection value
493    * 
494    * @param message
495    * @param initialSelectionValue
496    * @return
497    */
498   public static String showInputDialog(Object message,
499           Object initialSelectionValue)
500   {
501     if (!isInteractiveMode())
502     {
503       return getMockResponse().toString();
504     }
505
506     // AnnotationPanel character option
507
508     return JOptionPane.showInputDialog(message, initialSelectionValue);
509   }
510
511   /**
512    * centered on parent
513    * 
514    * @param parentComponent
515    * @param message
516    * @return
517    * @throws HeadlessException
518    */
519   public static String showInputDialog(Component parentComponent,
520           String message) throws HeadlessException
521   {
522     // test only
523
524     return isInteractiveMode()
525             ? JOptionPane.showInputDialog(parentComponent, message)
526             : getMockResponse().toString();
527   }
528
529   /**
530    * input with initial selection
531    * 
532    * @param parentComponent
533    * @param message
534    * @param initialSelectionValue
535    * @return
536    */
537   public static String showInputDialog(Component parentComponent,
538           String message, String initialSelectionValue)
539   {
540
541     // AnnotationPanel
542
543     return isInteractiveMode()
544             ? JOptionPane.showInputDialog(parentComponent, message,
545                     initialSelectionValue)
546             : getMockResponse().toString();
547   }
548
549   /**
550    * input with initial selection
551    * 
552    * @param parentComponent
553    * @param message
554    * @param initialSelectionValue
555    * @return
556    */
557   public static String showInputDialog(Component parentComponent,
558           Object message, Object initialSelectionValue)
559   {
560
561     // AnnotationPanel
562
563     return isInteractiveMode()
564             ? JOptionPane.showInputDialog(parentComponent, message,
565                     initialSelectionValue)
566             : getMockResponse().toString();
567   }
568
569   /**
570    * 
571    * @param parentComponent
572    * @param message
573    * @param title
574    * @param messageType
575    * @return
576    * @throws HeadlessException
577    */
578   public static String showInputDialog(Component parentComponent,
579           String message, String title, int messageType)
580           throws HeadlessException
581   {
582
583     // test only
584
585     return isInteractiveMode()
586             ? JOptionPane.showInputDialog(parentComponent, message, title,
587                     messageType)
588             : getMockResponse().toString();
589   }
590
591   /**
592    * Customized input option
593    * 
594    * @param parentComponent
595    * @param message
596    * @param title
597    * @param messageType
598    * @param icon
599    * @param selectionValues
600    * @param initialSelectionValue
601    * @return
602    * @throws HeadlessException
603    */
604   public static Object showInputDialog(Component parentComponent,
605           Object message, String title, int messageType, Icon icon,
606           Object[] selectionValues, Object initialSelectionValue)
607           throws HeadlessException
608   {
609
610     // test only
611
612     return isInteractiveMode()
613             ? JOptionPane.showInputDialog(parentComponent, message, title,
614                     messageType, icon, selectionValues,
615                     initialSelectionValue)
616             : getMockResponse().toString();
617   }
618
619   /**
620    * internal version
621    * 
622    * @param parentComponent
623    * @param message
624    * @return
625    */
626   public static String showInternalInputDialog(Component parentComponent,
627           String message)
628   {
629     // test only
630
631     return isInteractiveMode()
632             ? JOptionPane.showInternalInputDialog(parentComponent, message)
633             : getMockResponse().toString();
634   }
635
636   /**
637    * internal with title and messageType
638    * 
639    * @param parentComponent
640    * @param message
641    * @param title
642    * @param messageType
643    * @return
644    */
645   public static String showInternalInputDialog(Component parentComponent,
646           String message, String title, int messageType)
647   {
648
649     // AlignFrame tabbedPane_mousePressed
650
651     return isInteractiveMode()
652             ? JOptionPane.showInternalInputDialog(parentComponent,
653                     getPrefix(messageType) + message, title, messageType)
654             : getMockResponse().toString();
655   }
656
657   /**
658    * customized internal
659    * 
660    * @param parentComponent
661    * @param message
662    * @param title
663    * @param messageType
664    * @param icon
665    * @param selectionValues
666    * @param initialSelectionValue
667    * @return
668    */
669   public static Object showInternalInputDialog(Component parentComponent,
670           String message, String title, int messageType, Icon icon,
671           Object[] selectionValues, Object initialSelectionValue)
672   {
673     // test only
674
675     return isInteractiveMode()
676             ? JOptionPane.showInternalInputDialog(parentComponent, message,
677                     title, messageType, icon, selectionValues,
678                     initialSelectionValue)
679             : getMockResponse().toString();
680   }
681
682   ///////////// end of options ///////////////
683
684   private static void outputMessage(Object message)
685   {
686     System.out.println(">>> JOption Message : " + message.toString());
687   }
688
689   public static Object getMockResponse()
690   {
691     return mockResponse;
692   }
693
694   public static void setMockResponse(Object mockOption)
695   {
696     JvOptionPane.mockResponse = mockOption;
697   }
698
699   public static void resetMock()
700   {
701     setMockResponse(JvOptionPane.CANCEL_OPTION);
702     setInteractiveMode(true);
703   }
704
705   public static boolean isInteractiveMode()
706   {
707     return interactiveMode;
708   }
709
710   public static void setInteractiveMode(boolean interactive)
711   {
712     JvOptionPane.interactiveMode = interactive;
713   }
714
715   private static String getPrefix(int messageType)
716   {
717     String prefix = "";
718
719     // JavaScript only
720     if (Platform.isJS())
721     {
722       switch (messageType)
723       {
724       case JvOptionPane.WARNING_MESSAGE:
725         prefix = "WARNING! ";
726         break;
727       case JvOptionPane.ERROR_MESSAGE:
728         prefix = "ERROR! ";
729         break;
730       default:
731         prefix = "Note: ";
732       }
733     }
734     return prefix;
735   }
736
737   /**
738    * create a new option dialog that can be used to register responses - along
739    * lines of showOptionDialog
740    * 
741    * @param desktop
742    * @param question
743    * @param string
744    * @param defaultOption
745    * @param plainMessage
746    * @param object
747    * @param options
748    * @param string2
749    * @return
750    */
751   public static JvOptionPane newOptionDialog(Component parentComponent)
752   {
753     return new JvOptionPane(parentComponent);
754   }
755
756   public void showDialog(String message, String title, int optionType,
757           int messageType, Icon icon, Object[] options, Object initialValue)
758   {
759
760     if (!isInteractiveMode())
761     {
762       handleResponse(getMockResponse());
763     }
764     // two uses:
765     //
766     // TODO
767     //
768     // 1) AlignViewport for openLinkedAlignment
769     //
770     // Show a dialog with the option to open and link (cDNA <-> protein) as a
771     // new
772     // alignment, either as a standalone alignment or in a split frame. Returns
773     // true if the new alignment was opened, false if not, because the user
774     // declined the offer.
775     //
776     // 2) UserDefinedColors warning about saving over a name already defined
777     //
778
779     ourOptions = Arrays.asList(options);
780
781     int response = JOptionPane.showOptionDialog(parentComponent, message,
782             title, optionType, messageType, icon, options, initialValue);
783
784     /*
785      * In Java, the response is returned to this thread and handled here;
786      * (for Javascript, see propertyChange)
787      */
788     if (!Platform.isJS())
789     /**
790      * Java only
791      * 
792      * @j2sIgnore
793      */
794     {
795       handleResponse(response);
796     }
797   }
798
799   public void showInternalDialog(JPanel mainPanel, String title,
800           int yesNoCancelOption, int questionMessage, Icon icon,
801           Object[] options, String initresponse)
802   {
803     if (!isInteractiveMode())
804     {
805       handleResponse(getMockResponse());
806     }
807
808     ourOptions = Arrays.asList(options);
809     int response;
810     if (parentComponent != this)
811     {
812       response = JOptionPane.showInternalOptionDialog(parentComponent,
813               mainPanel, title, yesNoCancelOption, questionMessage, icon,
814               options, initresponse);
815     }
816     else
817     {
818       response = JOptionPane.showOptionDialog(parentComponent, mainPanel,
819               title, yesNoCancelOption, questionMessage, icon, options,
820               initresponse);
821     }
822     if (!Platform.isJS())
823     /**
824      * Java only
825      * 
826      * @j2sIgnore
827      */
828     {
829       handleResponse(response);
830     }
831   }
832
833   @Override
834   public JvOptionPane setResponseHandler(Object response, Runnable action)
835   {
836     callbacks.put(response, action);
837     return this;
838   }
839
840   /**
841    * JalviewJS signals option selection by a property change event for the
842    * option e.g. "OK". This methods responds to that by running the response
843    * action that corresponds to that option.
844    * 
845    * @param evt
846    */
847   @Override
848   public void propertyChange(PropertyChangeEvent evt)
849   {
850     Object newValue = evt.getNewValue();
851     int ourOption = ourOptions.indexOf(newValue);
852     if (ourOption >= 0)
853     {
854       handleResponse(ourOption);
855     }
856     else
857     {
858       // try our luck..
859       handleResponse(newValue);
860     }
861   }
862
863   @Override
864   public void handleResponse(Object response)
865   {
866     /*
867     * this test is for NaN in Chrome
868     */
869     if (response != null && !response.equals(response))
870     {
871       return;
872     }
873     Runnable action = callbacks.get(response);
874     if (action != null)
875     {
876       action.run();
877       parentComponent.requestFocus();
878     }
879   }
880 }