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