JAL-3690 List.copyOf() workaround
[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 jalview.util.Platform;
25 import jalview.util.dialogrunner.DialogRunnerI;
26
27 import java.awt.Component;
28 import java.awt.HeadlessException;
29 import java.beans.PropertyChangeEvent;
30 import java.beans.PropertyChangeListener;
31 import java.util.Arrays;
32 import java.util.HashMap;
33 import java.util.List;
34 import java.util.Map;
35
36 import javax.swing.Icon;
37 import javax.swing.JOptionPane;
38 import javax.swing.JPanel;
39
40 public class JvOptionPane extends JOptionPane implements DialogRunnerI,
41     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() ? JOptionPane.showInternalConfirmDialog(
168             parentComponent, message) : (int) getMockResponse();
169   }
170
171   /**
172    * Internal version -- changed to standard version for now
173    * 
174    * @param parentComponent
175    * @param message
176    * @param title
177    * @param optionType
178    * @return
179    */
180   public static int showInternalConfirmDialog(Component parentComponent,
181           String message, String title, int optionType)
182   {
183     if (!isInteractiveMode())
184     {
185       return (int) getMockResponse();
186     }
187     switch (optionType)
188     {
189     case JvOptionPane.YES_NO_CANCEL_OPTION:
190       // ColourMenuHelper.addMenuItmers.offerRemoval TODO
191     case JvOptionPane.YES_NO_OPTION:
192       // UserDefinedColoursSave -- relevant? TODO
193       // $FALL-THROUGH$
194     default:
195     case JvOptionPane.OK_CANCEL_OPTION:
196
197       // EditNameDialog --- uses panel for messsage TODO
198
199       // Desktop.inputURLMenuItem
200       // WsPreferenses
201       return JOptionPane.showConfirmDialog(parentComponent, message, title,
202               optionType);
203     }
204   }
205
206   /**
207    * 
208    * @param parentComponent
209    * @param message
210    * @param title
211    * @param optionType
212    * @param messageType
213    * @return
214    */
215   public static int showInternalConfirmDialog(Component parentComponent,
216           Object message, String title, int optionType, int messageType)
217   {
218     if (!isInteractiveMode())
219     {
220       return (int) getMockResponse();
221     }
222     switch (optionType)
223     {
224     case JvOptionPane.YES_NO_CANCEL_OPTION:
225     case JvOptionPane.YES_NO_OPTION:
226       // UserQuestionanaireCheck
227       // VamsasApplication
228       // $FALL-THROUGH$
229     default:
230     case JvOptionPane.OK_CANCEL_OPTION:
231       // will fall back to simple HTML
232       return JOptionPane.showConfirmDialog(parentComponent, message, title,
233               optionType, messageType);
234     }
235   }
236
237   /**
238    * adds icon; no longer internal
239    * 
240    * @param parentComponent
241    * @param message
242    * @param title
243    * @param optionType
244    * @param messageType
245    * @param icon
246    * @return
247    */
248   public static int showInternalConfirmDialog(Component parentComponent,
249           Object message, String title, int optionType, int messageType,
250           Icon icon)
251   {
252     if (!isInteractiveMode())
253     {
254       return (int) getMockResponse();
255     }
256     switch (optionType)
257     {
258     case JvOptionPane.YES_NO_CANCEL_OPTION:
259     case JvOptionPane.YES_NO_OPTION:
260       //$FALL-THROUGH$
261     default:
262     case JvOptionPane.OK_CANCEL_OPTION:
263       // Preferences editLink/newLink
264       return JOptionPane.showConfirmDialog(parentComponent, message, title,
265               optionType, messageType, icon);
266     }
267
268   }
269
270   /**
271    * custom options full-featured
272    * 
273    * @param parentComponent
274    * @param message
275    * @param title
276    * @param optionType
277    * @param messageType
278    * @param icon
279    * @param options
280    * @param initialValue
281    * @return
282    * @throws HeadlessException
283    */
284   public static int showOptionDialog(Component parentComponent,
285           String message, String title, int optionType, int messageType,
286           Icon icon, Object[] options, Object initialValue)
287           throws HeadlessException
288   {
289     if (!isInteractiveMode())
290     {
291       return (int) getMockResponse();
292     }
293     // two uses:
294     //
295     // TODO
296     //
297     // 1) AlignViewport for openLinkedAlignment
298     //
299     // Show a dialog with the option to open and link (cDNA <-> protein) as a
300     // new
301     // alignment, either as a standalone alignment or in a split frame. Returns
302     // true if the new alignment was opened, false if not, because the user
303     // declined the offer.
304     //
305     // 2) UserDefinedColors warning about saving over a name already defined
306     //
307     return JOptionPane.showOptionDialog(parentComponent, message, title,
308             optionType, messageType, icon, options, initialValue);
309   }
310
311   /**
312    * Just an OK message
313    * 
314    * @param message
315    * @throws HeadlessException
316    */
317   public static void showMessageDialog(Component parentComponent,
318           String message) throws HeadlessException
319   {
320     if (!isInteractiveMode())
321     {
322       outputMessage(message);
323       return;
324     }
325
326     // test class only
327
328     JOptionPane.showMessageDialog(parentComponent, message);
329   }
330
331   /**
332    * OK with message, title, and type
333    * 
334    * @param parentComponent
335    * @param message
336    * @param title
337    * @param messageType
338    * @throws HeadlessException
339    */
340   public static void showMessageDialog(Component parentComponent,
341           String message, String title, int messageType)
342           throws HeadlessException
343   {
344     // 30 implementations -- all just fine.
345
346     if (!isInteractiveMode())
347     {
348       outputMessage(message);
349       return;
350     }
351
352     JOptionPane.showMessageDialog(parentComponent,
353             getPrefix(messageType) + message, title, messageType);
354   }
355
356   /**
357    * adds title and icon
358    * 
359    * @param parentComponent
360    * @param message
361    * @param title
362    * @param messageType
363    * @param icon
364    * @throws HeadlessException
365    */
366   public static void showMessageDialog(Component parentComponent,
367           String message, String title, int messageType, Icon icon)
368           throws HeadlessException
369   {
370
371     // test only
372
373     if (!isInteractiveMode())
374     {
375       outputMessage(message);
376       return;
377     }
378
379     JOptionPane.showMessageDialog(parentComponent, message, title,
380             messageType, icon);
381   }
382
383   /**
384    * was internal
385    * 
386    */
387   public static void showInternalMessageDialog(Component parentComponent,
388           Object message)
389   {
390
391     // WsPreferences only
392
393     if (!isInteractiveMode())
394     {
395       outputMessage(message);
396       return;
397     }
398
399     JOptionPane.showMessageDialog(parentComponent, message);
400   }
401
402
403   /**
404    * Adds title and messageType
405    * 
406    * @param parentComponent
407    * @param message
408    * @param title
409    * @param messageType
410    */
411   public static void showInternalMessageDialog(Component parentComponent,
412           String message, String title, int messageType)
413   {
414
415     // 41 references
416
417     if (!isInteractiveMode())
418     {
419       outputMessage(message);
420       return;
421     }
422
423     JOptionPane.showMessageDialog(parentComponent,
424             getPrefix(messageType) + message, title, messageType);
425   }
426
427   /**
428    * 
429    * @param parentComponent
430    * @param message
431    * @param title
432    * @param messageType
433    * @param icon
434    */
435   public static void showInternalMessageDialog(Component parentComponent,
436           Object message, String title, int messageType, Icon icon)
437   {
438
439     // test only
440
441     if (!isInteractiveMode())
442     {
443       outputMessage(message);
444       return;
445     }
446
447     JOptionPane.showMessageDialog(parentComponent, message, title,
448             messageType, icon);
449   }
450
451   /**
452    * 
453    * @param message
454    * @return
455    * @throws HeadlessException
456    */
457   public static String showInputDialog(Object message)
458           throws HeadlessException
459   {
460     // test only
461
462     if (!isInteractiveMode())
463     {
464       return getMockResponse().toString();
465     }
466
467     return JOptionPane.showInputDialog(message);
468   }
469
470   /**
471    * adds inital selection value
472    * 
473    * @param message
474    * @param initialSelectionValue
475    * @return
476    */
477   public static String showInputDialog(String message,
478           String initialSelectionValue)
479   {
480     if (!isInteractiveMode())
481     {
482       return getMockResponse().toString();
483     }
484
485     // AnnotationPanel character option
486
487     return JOptionPane.showInputDialog(message, initialSelectionValue);
488   }
489
490   /**
491    * adds inital selection value
492    * 
493    * @param message
494    * @param initialSelectionValue
495    * @return
496    */
497   public static String showInputDialog(Object message,
498           Object initialSelectionValue)
499   {
500     if (!isInteractiveMode())
501     {
502       return getMockResponse().toString();
503     }
504
505     // AnnotationPanel character option
506
507     return JOptionPane.showInputDialog(message, initialSelectionValue);
508   }
509   /**
510    * centered on parent
511    * 
512    * @param parentComponent
513    * @param message
514    * @return
515    * @throws HeadlessException
516    */
517   public static String showInputDialog(Component parentComponent,
518           String message) throws HeadlessException
519   {
520     // test only
521
522     return isInteractiveMode()
523             ? JOptionPane.showInputDialog(parentComponent, message)
524             : getMockResponse().toString();
525   }
526
527   /**
528    * input with initial selection
529    * 
530    * @param parentComponent
531    * @param message
532    * @param initialSelectionValue
533    * @return
534    */
535   public static String showInputDialog(Component parentComponent,
536           String message, String initialSelectionValue)
537   {
538     
539     // AnnotationPanel
540     
541     return isInteractiveMode()
542             ? JOptionPane.showInputDialog(parentComponent, message,
543                     initialSelectionValue)
544             : getMockResponse().toString();
545   }
546   
547
548   /**
549    * input with initial selection
550    * 
551    * @param parentComponent
552    * @param message
553    * @param initialSelectionValue
554    * @return
555    */
556   public static String showInputDialog(Component parentComponent,
557           Object message, Object initialSelectionValue)
558   {
559     
560     // AnnotationPanel
561     
562     return isInteractiveMode()
563             ? JOptionPane.showInputDialog(parentComponent, message,
564                     initialSelectionValue)
565             : getMockResponse().toString();
566   }
567
568   /**
569    * 
570    * @param parentComponent
571    * @param message
572    * @param title
573    * @param messageType
574    * @return
575    * @throws HeadlessException
576    */
577   public static String showInputDialog(Component parentComponent,
578           String message, String title, int messageType)
579           throws HeadlessException
580   {
581
582     // test only
583
584     return isInteractiveMode() ? JOptionPane
585             .showInputDialog(parentComponent, message, title, messageType)
586             : getMockResponse().toString();
587   }
588
589   /**
590    * Customized input option 
591    * 
592    * @param parentComponent
593    * @param message
594    * @param title
595    * @param messageType
596    * @param icon
597    * @param selectionValues
598    * @param initialSelectionValue
599    * @return
600    * @throws HeadlessException
601    */
602   public static Object showInputDialog(Component parentComponent,
603           Object message, String title, int messageType, Icon icon,
604           Object[] selectionValues, Object initialSelectionValue)
605           throws HeadlessException
606   {
607     
608     // test only
609     
610     return isInteractiveMode()
611             ? JOptionPane.showInputDialog(parentComponent, message, title,
612                     messageType, icon, selectionValues,
613                     initialSelectionValue)
614             : getMockResponse().toString();
615   }
616
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   /**
638    * internal with title and messageType
639    * 
640    * @param parentComponent
641    * @param message
642    * @param title
643    * @param messageType
644    * @return
645    */
646   public static String showInternalInputDialog(Component parentComponent,
647           String message, String title, int messageType)
648   {
649     
650     // AlignFrame tabbedPane_mousePressed
651     
652     return isInteractiveMode()
653             ? JOptionPane.showInternalInputDialog(parentComponent,
654                     getPrefix(messageType) + message, title, messageType)
655             : getMockResponse().toString();
656   }
657
658   /**
659    * customized internal
660    * 
661    * @param parentComponent
662    * @param message
663    * @param title
664    * @param messageType
665    * @param icon
666    * @param selectionValues
667    * @param initialSelectionValue
668    * @return
669    */
670   public static Object showInternalInputDialog(Component parentComponent,
671           String message, String title, int messageType, Icon icon,
672           Object[] selectionValues, Object initialSelectionValue)
673   {
674     // test only
675     
676     return isInteractiveMode()
677             ? JOptionPane.showInternalInputDialog(parentComponent, message,
678                     title, messageType, icon, selectionValues,
679                     initialSelectionValue)
680             : getMockResponse().toString();
681   }
682
683   
684   ///////////// end of options ///////////////
685   
686   
687   private static void outputMessage(Object message)
688   {
689     System.out.println(">>> JOption Message : " + message.toString());
690   }
691
692   public static Object getMockResponse()
693   {
694     return mockResponse;
695   }
696
697   public static void setMockResponse(Object mockOption)
698   {
699     JvOptionPane.mockResponse = mockOption;
700   }
701
702   public static void resetMock()
703   {
704     setMockResponse(JvOptionPane.CANCEL_OPTION);
705     setInteractiveMode(true);
706   }
707
708   public static boolean isInteractiveMode()
709   {
710     return interactiveMode;
711   }
712
713   public static void setInteractiveMode(boolean interactive)
714   {
715     JvOptionPane.interactiveMode = interactive;
716   }
717
718   private static String getPrefix(int messageType)
719   {
720     String prefix = ""; 
721     
722     // JavaScript only
723     if (Platform.isJS())
724     {
725       switch (messageType)
726       {
727       case JvOptionPane.WARNING_MESSAGE:
728         prefix = "WARNING! ";
729         break;
730       case JvOptionPane.ERROR_MESSAGE:
731         prefix = "ERROR! ";
732         break;
733       default:
734         prefix = "Note: ";
735       }
736     }
737     return prefix;
738   }
739
740   /**
741    * create a new option dialog that can be used to register responses - along
742    * lines of showOptionDialog
743    * 
744    * @param desktop
745    * @param question
746    * @param string
747    * @param defaultOption
748    * @param plainMessage
749    * @param object
750    * @param options
751    * @param string2
752    * @return
753    */
754   public static JvOptionPane newOptionDialog(Component parentComponent)
755   {
756     return new JvOptionPane(parentComponent);
757   }
758
759   public void showDialog(
760           String message, String title, int optionType, int messageType,
761           Icon icon, Object[] options, Object initialValue)
762   {
763
764     if (!isInteractiveMode())
765     {
766       handleResponse(getMockResponse());
767     }
768     // two uses:
769     //
770     // TODO
771     //
772     // 1) AlignViewport for openLinkedAlignment
773     //
774     // Show a dialog with the option to open and link (cDNA <-> protein) as a
775     // new
776     // alignment, either as a standalone alignment or in a split frame. Returns
777     // true if the new alignment was opened, false if not, because the user
778     // declined the offer.
779     //
780     // 2) UserDefinedColors warning about saving over a name already defined
781     //
782     
783     ourOptions = Arrays.asList(options);
784     
785     int response = JOptionPane.showOptionDialog(parentComponent, message, title,
786             optionType, messageType, icon, options, initialValue);
787     
788     /*
789      * In Java, the response is returned to this thread and handled here;
790      * (for Javascript, see propertyChange)
791      */
792     if (!Platform.isJS())
793     /**
794      * Java only
795      * 
796      * @j2sIgnore
797      */
798     {
799       handleResponse(response);
800     }
801   }
802
803   public void showInternalDialog(JPanel mainPanel, String title,
804           int yesNoCancelOption, int questionMessage, Icon icon,
805           Object[] options, String initresponse)
806   {
807     if (!isInteractiveMode())
808     {
809       handleResponse(getMockResponse());
810     }
811     
812     ourOptions = Arrays.asList(options);
813     int response;
814     if (parentComponent != this) 
815     {
816       response = JOptionPane.showInternalOptionDialog(parentComponent, mainPanel,
817               title, yesNoCancelOption, questionMessage, icon, options,
818               initresponse);
819     }
820     else
821     {
822       response = JOptionPane.showOptionDialog(parentComponent, mainPanel, title,
823               yesNoCancelOption, questionMessage, icon, options,
824               initresponse);
825     }
826     if (!Platform.isJS())
827     /**
828      * Java only
829      * 
830      * @j2sIgnore
831      */
832     {
833       handleResponse(response);
834     }
835   }
836     
837   @Override
838   public JvOptionPane setResponseHandler(Object response, Runnable action)
839   {
840         callbacks.put(response,  action);
841     return this;
842   }
843
844   /**
845    * JalviewJS signals option selection by a property change event
846    * for the option e.g. "OK".  This methods responds to that by
847    * running the response action that corresponds to that option.
848    * 
849    * @param evt
850    */
851   @Override
852   public void propertyChange(PropertyChangeEvent evt)
853   {
854     Object newValue = evt.getNewValue();
855         int ourOption = ourOptions.indexOf(newValue);
856     if (ourOption >= 0)
857     {
858       handleResponse(ourOption);
859     }
860     else
861     {
862       // try our luck..
863       handleResponse(newValue);
864     }
865   }
866
867   @Override
868   public void handleResponse(Object response)
869   {
870     /*
871          * this test is for NaN in Chrome
872          */
873     if (response != null && !response.equals(response))
874     {
875       return;
876     }
877     Runnable action = callbacks.get(response);
878     if (action != null)
879     {
880       action.run();
881     }
882   }
883 }