update author list in license for (JAL-826)
[jalview.git] / src / jalview / gui / OptsAndParamsPage.java
1 /*******************************************************************************
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.7)
3  * Copyright (C) 2011 J Procter, AM Waterhouse, J Engelhardt, LM Lui, G Barton, M Clamp, S Searle
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 of the License, or (at your option) any later version.
10  *
11  * Jalview is distributed in the hope that it will be useful, but 
12  * WITHOUT ANY WARRANTY; without even the implied warranty 
13  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
14  * PURPOSE.  See the GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
17  *******************************************************************************/
18 package jalview.gui;
19
20 import jalview.ws.params.ArgumentI;
21 import jalview.ws.params.OptionI;
22 import jalview.ws.params.ParameterI;
23 import jalview.ws.params.ValueConstrainI;
24 import jalview.ws.params.ValueConstrainI.ValueType;
25
26 import java.awt.BorderLayout;
27 import java.awt.Component;
28 import java.awt.Dimension;
29 import java.awt.Font;
30 import java.awt.GridLayout;
31 import java.awt.Rectangle;
32 import java.awt.event.ActionEvent;
33 import java.awt.event.ActionListener;
34 import java.awt.event.KeyEvent;
35 import java.awt.event.KeyListener;
36 import java.awt.event.MouseEvent;
37 import java.awt.event.MouseListener;
38 import java.net.URL;
39 import java.util.ArrayList;
40 import java.util.Hashtable;
41 import java.util.List;
42 import java.util.Map;
43
44 import javax.swing.JButton;
45 import javax.swing.JCheckBox;
46 import javax.swing.JComboBox;
47 import javax.swing.JComponent;
48 import javax.swing.JLabel;
49 import javax.swing.JMenuItem;
50 import javax.swing.JPanel;
51 import javax.swing.JPopupMenu;
52 import javax.swing.JScrollPane;
53 import javax.swing.JSlider;
54 import javax.swing.JTextArea;
55 import javax.swing.JTextField;
56 import javax.swing.border.TitledBorder;
57 import javax.swing.event.ChangeEvent;
58 import javax.swing.event.ChangeListener;
59
60 import net.miginfocom.swing.MigLayout;
61
62 /**
63  * GUI generator/manager for options and parameters. Originally abstracted from
64  * the WsJobParameters dialog box.
65  * 
66  * @author jprocter
67  * 
68  */
69 public class OptsAndParamsPage
70 {
71   /**
72    * compact or verbose style parameters
73    */
74   boolean compact = false;
75
76   public class OptionBox extends JPanel implements MouseListener,
77           ActionListener
78   {
79     JCheckBox enabled = new JCheckBox();
80
81     final URL finfo;
82
83     boolean hasLink = false;
84
85     boolean initEnabled = false;
86
87     String initVal = null;
88
89     OptionI option;
90
91     JLabel optlabel = new JLabel();
92
93     JComboBox val = new JComboBox();
94
95     public OptionBox(OptionI opt)
96     {
97       option = opt;
98       setLayout(new BorderLayout());
99       enabled.setSelected(opt.isRequired()); // TODO: lock required options
100       enabled.setFont(new Font("Verdana", Font.PLAIN, 11));
101       enabled.setText("");
102       enabled.setText(opt.getName());
103       enabled.addActionListener(this);
104       finfo = option.getFurtherDetails();
105       String desc = opt.getDescription();
106       if (finfo != null)
107       {
108         hasLink = true;
109
110         enabled.setToolTipText("<html>"
111                 + JvSwingUtils
112                         .wrapTooltip(((desc == null) ? "see further details by right-clicking"
113                                 : desc)
114                                 + "<br><img src=\"" + linkImageURL + "\"/>")
115                 + "</html>");
116         enabled.addMouseListener(this);
117       }
118       else
119       {
120         if (desc != null)
121         {
122           enabled.setToolTipText("<html>"
123                   + JvSwingUtils.wrapTooltip(opt.getDescription())
124                   + "</html>");
125         }
126       }
127       add(enabled, BorderLayout.NORTH);
128       if (opt.getPossibleValues().size() > 1)
129       {
130         setLayout(new GridLayout(1, 2));
131         for (Object str : opt.getPossibleValues())
132         {
133           val.addItem((String) str);
134         }
135         val.setSelectedItem((String) opt.getValue());
136         val.addActionListener(this);
137         add(val, BorderLayout.SOUTH);
138       }
139       // TODO: add actionListeners for popup (to open further info),
140       // and to update list of parameters if an option is enabled
141       // that takes a value. JBPNote: is this TODO still valid ?
142       setInitialValue();
143     }
144
145     public void actionPerformed(ActionEvent e)
146     {
147       if (e.getSource() != enabled)
148       {
149         enabled.setSelected(true);
150       }
151       checkIfModified();
152     }
153
154     private void checkIfModified()
155     {
156       boolean notmod = (initEnabled == enabled.isSelected());
157       if (enabled.isSelected())
158       {
159         if (initVal != null)
160         {
161           notmod &= initVal.equals(val.getSelectedItem());
162         }
163         else
164         {
165           // compare against default service setting
166           notmod &= option.getValue() == null
167                   || option.getValue().equals(val.getSelectedItem());
168         }
169       }
170       else
171       {
172         notmod &= initVal == null;
173       }
174       poparent.argSetModified(this, !notmod);
175     }
176
177     public OptionI getOptionIfEnabled()
178     {
179       if (!enabled.isSelected())
180       {
181         return null;
182       }
183       OptionI opt = option.copy();
184
185       if (val.getSelectedItem() != null)
186       {
187         opt.setValue((String) val.getSelectedItem());
188       }
189       return opt;
190     }
191
192     public void mouseClicked(MouseEvent e)
193     {
194       if (javax.swing.SwingUtilities.isRightMouseButton(e))
195       {
196         showUrlPopUp(this, finfo.toString(), e.getX(), e.getY());
197       }
198     }
199
200     public void mouseEntered(MouseEvent e)
201     {
202       // TODO Auto-generated method stub
203
204     }
205
206     public void mouseExited(MouseEvent e)
207     {
208       // TODO Auto-generated method stub
209
210     }
211
212     public void mousePressed(MouseEvent e)
213     {
214       // TODO Auto-generated method stub
215
216     }
217
218     public void mouseReleased(MouseEvent e)
219     {
220       // TODO Auto-generated method stub
221
222     }
223
224     public void resetToDefault()
225     {
226       enabled.setSelected(false);
227       if (option.isRequired())
228       {
229         // Apply default value
230         selectOption(option, option.getValue());
231       }
232     }
233
234     public void setInitialValue()
235     {
236       initEnabled = enabled.isSelected();
237       if (option.getPossibleValues() != null
238               && option.getPossibleValues().size() > 1)
239       {
240         initVal = (String) val.getSelectedItem();
241       }
242       else
243       {
244         initVal = (initEnabled) ? option.getValue() : null;
245       }
246     }
247
248   }
249
250   public class ParamBox extends JPanel implements ChangeListener,
251           ActionListener, MouseListener
252   {
253     boolean adjusting = false;
254
255     boolean choice = false;
256
257     JComboBox choicebox;
258
259     JPanel controlPanel = new JPanel();
260
261     boolean descisvisible = false;
262
263     JScrollPane descPanel = new JScrollPane();
264
265     final URL finfo;
266
267     boolean integ = false;
268
269     Object lastVal;
270
271     ParameterI parameter;
272
273     final OptsParametersContainerI pmdialogbox;
274
275     JPanel settingPanel = new JPanel();
276
277     JButton showDesc = new JButton();
278
279     JSlider slider = null;
280
281     JTextArea string = new JTextArea();
282
283     ValueConstrainI validator = null;
284
285     JTextField valueField = null;
286
287     public ParamBox(final OptsParametersContainerI pmlayout, ParameterI parm)
288     {
289       pmdialogbox = pmlayout;
290       finfo = parm.getFurtherDetails();
291       validator = parm.getValidValue();
292       parameter = parm;
293       if (validator != null)
294       {
295         integ = validator.getType() == ValueType.Integer;
296       }
297       else
298       {
299         if (parameter.getPossibleValues() != null)
300         {
301           choice = true;
302         }
303       }
304       
305       if (!compact)
306       {
307         makeExpanderParam(parm);
308       }
309       else
310       {
311         makeCompactParam(parm);
312
313       }
314     }
315
316     private void makeCompactParam(ParameterI parm)
317     {
318       setLayout(new MigLayout("","[][grow]"));
319
320       String ttipText=null;
321
322       controlPanel.setLayout(new BorderLayout());
323
324       if (parm.getDescription() != null
325               && parm.getDescription().trim().length() > 0)
326       {
327         // Only create description boxes if there actually is a description.
328         ttipText = ("<html>"
329                   + JvSwingUtils
330                           .wrapTooltip(parm.getDescription()+(finfo!=null ?"<br><img src=\""
331                                   + linkImageURL
332                                   + "\"/> Right click for further information.":""))
333                   + "</html>");
334       }
335       
336       JvSwingUtils.mgAddtoLayout(this, ttipText, new JLabel(parm.getName()),controlPanel, "");
337       updateControls(parm);
338       validate();
339     }
340
341     private void makeExpanderParam(ParameterI parm)
342     {
343       setPreferredSize(new Dimension(PARAM_WIDTH, PARAM_CLOSEDHEIGHT));
344       setBorder(new TitledBorder(parm.getName()));
345       setLayout(null);
346       showDesc.setFont(new Font("Verdana", Font.PLAIN, 6));
347       showDesc.setText("+");
348       string.setFont(new Font("Verdana", Font.PLAIN, 11));
349       string.setBackground(getBackground());
350
351       string.setEditable(false);
352       descPanel.getViewport().setView(string);
353
354       descPanel.setVisible(false);
355
356       JPanel firstrow = new JPanel();
357       firstrow.setLayout(null);
358       controlPanel.setLayout(new BorderLayout());
359       controlPanel.setBounds(new Rectangle(39, 10, PARAM_WIDTH - 70,
360               PARAM_CLOSEDHEIGHT - 50));
361       firstrow.add(controlPanel);
362       firstrow.setBounds(new Rectangle(10, 20, PARAM_WIDTH - 30,
363               PARAM_CLOSEDHEIGHT - 30));
364
365       final ParamBox me = this;
366
367       if (parm.getDescription() != null
368               && parm.getDescription().trim().length() > 0)
369       {
370         // Only create description boxes if there actually is a description.
371         if (finfo != null)
372         {
373           showDesc.setToolTipText("<html>"
374                   + JvSwingUtils
375                           .wrapTooltip("Click to show brief description<br><img src=\""
376                                   + linkImageURL
377                                   + "\"/> Right click for further information.")
378                   + "</html>");
379           showDesc.addMouseListener(this);
380         }
381         else
382         {
383           showDesc.setToolTipText("<html>"
384                   + JvSwingUtils
385                           .wrapTooltip("Click to show brief description.")
386                   + "</html>");
387         }
388         showDesc.addActionListener(new ActionListener()
389         {
390
391           public void actionPerformed(ActionEvent e)
392           {
393             descisvisible = !descisvisible;
394             descPanel.setVisible(descisvisible);
395             descPanel.getVerticalScrollBar().setValue(0);
396             me.setPreferredSize(new Dimension(PARAM_WIDTH,
397                     (descisvisible) ? PARAM_HEIGHT : PARAM_CLOSEDHEIGHT));
398             me.validate();
399             pmdialogbox.refreshParamLayout();
400           }
401         });
402         string.setWrapStyleWord(true);
403         string.setLineWrap(true);
404         string.setColumns(32);
405         string.setText(parm.getDescription());
406         showDesc.setBounds(new Rectangle(10, 10, 16, 16));
407         firstrow.add(showDesc);
408       }
409       add(firstrow);
410       validator = parm.getValidValue();
411       parameter = parm;
412       if (validator != null)
413       {
414         integ = validator.getType() == ValueType.Integer;
415       }
416       else
417       {
418         if (parameter.getPossibleValues() != null)
419         {
420           choice = true;
421         }
422       }
423       updateControls(parm);
424       descPanel.setBounds(new Rectangle(10, PARAM_CLOSEDHEIGHT,
425               PARAM_WIDTH - 20, PARAM_HEIGHT - PARAM_CLOSEDHEIGHT - 5));
426       add(descPanel);
427       validate();
428     }
429
430     public void actionPerformed(ActionEvent e)
431     {
432       if (adjusting)
433       {
434         return;
435       }
436       if (!choice)
437       {
438         updateSliderFromValueField();
439       }
440       checkIfModified();
441     }
442
443     private void checkIfModified()
444     {
445       Object cstate = updateSliderFromValueField();
446       boolean notmod = false;
447       if (cstate.getClass() == lastVal.getClass())
448       {
449         if (cstate instanceof int[])
450         {
451           notmod = (((int[]) cstate)[0] == ((int[]) lastVal)[0]);
452         }
453         else if (cstate instanceof float[])
454         {
455           notmod = (((float[]) cstate)[0] == ((float[]) lastVal)[0]);
456         }
457         else if (cstate instanceof String[])
458         {
459           notmod = (((String[]) cstate)[0].equals(((String[]) lastVal)[0]));
460         }
461       }
462       pmdialogbox.argSetModified(this, !notmod);
463     }
464
465     @Override
466     public int getBaseline(int width, int height)
467     {
468       return 0;
469     }
470
471     // from
472     // http://stackoverflow.com/questions/2743177/top-alignment-for-flowlayout
473     // helpful hint of using the Java 1.6 alignBaseLine property of FlowLayout
474     @Override
475     public Component.BaselineResizeBehavior getBaselineResizeBehavior()
476     {
477       return Component.BaselineResizeBehavior.CONSTANT_ASCENT;
478     }
479
480     public int getBoxHeight()
481     {
482       return (descisvisible ? PARAM_HEIGHT : PARAM_CLOSEDHEIGHT);
483     }
484
485     public ParameterI getParameter()
486     {
487       ParameterI prm = parameter.copy();
488       if (choice)
489       {
490         prm.setValue((String) choicebox.getSelectedItem());
491       }
492       else
493       {
494         prm.setValue(valueField.getText());
495       }
496       return prm;
497     }
498
499     public void init()
500     {
501       // reset the widget's initial value.
502       lastVal = null;
503     }
504
505     public void mouseClicked(MouseEvent e)
506     {
507       if (javax.swing.SwingUtilities.isRightMouseButton(e))
508       {
509         showUrlPopUp(this, finfo.toString(), e.getX(), e.getY());
510       }
511     }
512
513     public void mouseEntered(MouseEvent e)
514     {
515       // TODO Auto-generated method stub
516
517     }
518
519     public void mouseExited(MouseEvent e)
520     {
521       // TODO Auto-generated method stub
522
523     }
524
525     public void mousePressed(MouseEvent e)
526     {
527       // TODO Auto-generated method stub
528
529     }
530
531     public void mouseReleased(MouseEvent e)
532     {
533       // TODO Auto-generated method stub
534
535     }
536
537     public void stateChanged(ChangeEvent e)
538     {
539       if (!adjusting)
540       {
541         valueField.setText(""
542                 + ((integ) ? ("" + (int) slider.getValue())
543                         : ("" + (float) (slider.getValue() / 1000f))));
544         checkIfModified();
545       }
546
547     }
548
549     public void updateControls(ParameterI parm)
550     {
551       adjusting = true;
552       boolean init = (choicebox == null && valueField == null);
553       if (init)
554       {
555         if (choice)
556         {
557           choicebox = new JComboBox();
558           choicebox.addActionListener(this);
559           controlPanel.add(choicebox, BorderLayout.CENTER);
560         }
561         else
562         {
563           slider = new JSlider();
564           slider.addChangeListener(this);
565           valueField = new JTextField();
566           valueField.addActionListener(this);
567           valueField.addKeyListener(new KeyListener()
568           {
569             
570             @Override
571             public void keyTyped(KeyEvent e)
572             {
573             }
574             
575             @Override
576             public void keyReleased(KeyEvent e)
577             {
578               if (valueField.getText().trim().length()>0)
579                 {
580                 actionPerformed(null);
581                 }
582             }
583             
584             @Override
585             public void keyPressed(KeyEvent e)
586             {
587             }
588           });
589           valueField.setPreferredSize(new Dimension(60, 25));
590           controlPanel.add(slider, BorderLayout.WEST);
591           controlPanel.add(valueField, BorderLayout.EAST);
592
593         }
594       }
595
596       if (parm != null)
597       {
598         if (choice)
599         {
600           if (init)
601           {
602             List vals = parm.getPossibleValues();
603             for (Object val : vals)
604             {
605               choicebox.addItem(val);
606             }
607           }
608
609           if (parm.getValue() != null)
610           {
611             choicebox.setSelectedItem(parm.getValue());
612           }
613         }
614         else
615         {
616           valueField.setText(parm.getValue());
617         }
618       }
619       lastVal = updateSliderFromValueField();
620       adjusting = false;
621     }
622
623     public Object updateSliderFromValueField()
624     {
625       int iVal;
626       float fVal;
627       if (validator != null)
628       {
629         if (integ)
630         {
631           iVal = 0;
632           try
633           {
634             valueField.setText(valueField.getText().trim());
635             iVal = Integer.valueOf(valueField.getText());
636             if (validator.getMin() != null
637                     && validator.getMin().intValue() > iVal)
638             {
639               iVal = validator.getMin().intValue();
640               // TODO: provide visual indication that hard limit was reached for
641               // this parameter
642             }
643             if (validator.getMax() != null
644                     && validator.getMax().intValue() < iVal)
645             {
646               iVal = validator.getMax().intValue();
647               // TODO: provide visual indication that hard limit was reached for
648               // this parameter
649             }
650           } catch (Exception e)
651           {
652           }
653           ;
654           if (validator.getMin() != null && validator.getMax() != null)
655           {
656             slider.getModel().setRangeProperties(iVal, 1,
657                     validator.getMin().intValue(),
658                     validator.getMax().intValue(), true);
659           }
660           else
661           {
662             slider.setVisible(false);
663           }
664           return new int[]
665           { iVal };
666         }
667         else
668         {
669           fVal = 0f;
670           try
671           {
672             fVal = Float.valueOf(valueField.getText());
673             if (validator.getMin() != null
674                     && validator.getMin().floatValue() > fVal)
675             {
676               fVal = validator.getMin().floatValue();
677               // TODO: provide visual indication that hard limit was reached for
678               // this parameter
679             }
680             if (validator.getMax() != null
681                     && validator.getMax().floatValue() < fVal)
682             {
683               fVal = validator.getMax().floatValue();
684               // TODO: provide visual indication that hard limit was reached for
685               // this parameter
686             }
687           } catch (Exception e)
688           {
689           }
690           ;
691           if (validator.getMin() != null && validator.getMax() != null)
692           {
693             slider.getModel().setRangeProperties((int) fVal * 1000, 1,
694                     (int) validator.getMin().floatValue() * 1000,
695                     (int) validator.getMax().floatValue() * 1000, true);
696           }
697           else
698           {
699             slider.setVisible(false);
700           }
701           return new float[]
702           { fVal };
703         }
704       }
705       else
706       {
707         if (!choice)
708         {
709           slider.setVisible(false);
710           return new String[]
711           { valueField.getText().trim() };
712         }
713         else
714         {
715           return new String[]
716           { (String) choicebox.getSelectedItem() };
717         }
718       }
719
720     }
721   }
722
723   public static final int PARAM_WIDTH = 340;
724
725   public static final int PARAM_HEIGHT = 150;
726
727   public static final int PARAM_CLOSEDHEIGHT = 80;
728
729   public OptsAndParamsPage(OptsParametersContainerI paramContainer)
730   {
731     this(paramContainer,false);
732   }
733   public OptsAndParamsPage(OptsParametersContainerI paramContainer, boolean compact)
734   {
735     poparent = paramContainer;
736     this.compact=compact;
737   }
738
739   public static void showUrlPopUp(JComponent invoker, final String finfo,
740           int x, int y)
741   {
742
743     JPopupMenu mnu = new JPopupMenu();
744     JMenuItem mitem = new JMenuItem("View " + finfo);
745     mitem.addActionListener(new ActionListener()
746     {
747
748       @Override
749       public void actionPerformed(ActionEvent e)
750       {
751         Desktop.showUrl(finfo);
752
753       }
754     });
755     mnu.add(mitem);
756     mnu.show(invoker, x, y);
757   }
758
759   URL linkImageURL = getClass().getResource("/images/link.gif");
760
761   Map<String, OptionBox> optSet = new Hashtable<String, OptionBox>();
762
763   Map<String, ParamBox> paramSet = new Hashtable<String, ParamBox>();
764
765   public Map<String, OptionBox> getOptSet()
766   {
767     return optSet;
768   }
769
770   public void setOptSet(Map<String, OptionBox> optSet)
771   {
772     this.optSet = optSet;
773   }
774
775   public Map<String, ParamBox> getParamSet()
776   {
777     return paramSet;
778   }
779
780   public void setParamSet(Map<String, ParamBox> paramSet)
781   {
782     this.paramSet = paramSet;
783   }
784
785   OptsParametersContainerI poparent;
786
787   OptionBox addOption(OptionI opt)
788   {
789     OptionBox cb = optSet.get(opt.getName());
790     if (cb == null)
791     {
792       cb = new OptionBox(opt);
793       optSet.put(opt.getName(), cb);
794       // jobOptions.add(cb, FlowLayout.LEFT);
795     }
796     return cb;
797   }
798
799   ParamBox addParameter(ParameterI arg)
800   {
801     ParamBox pb = paramSet.get(arg.getName());
802     if (pb == null)
803     {
804       pb = new ParamBox(poparent, arg);
805       paramSet.put(arg.getName(), pb);
806       // paramList.add(pb);
807     }
808     pb.init();
809     // take the defaults from the parameter
810     pb.updateControls(arg);
811     return pb;
812   }
813
814   void selectOption(OptionI option, String string)
815   {
816     OptionBox cb = optSet.get(option.getName());
817     if (cb == null)
818     {
819       cb = addOption(option);
820     }
821     cb.enabled.setSelected(string != null); // initial state for an option.
822     if (string != null)
823     {
824       if (option.getPossibleValues().contains(string))
825       {
826         cb.val.setSelectedItem(string);
827       }
828       else
829       {
830         throw new Error("Invalid value " + string + " for option " + option);
831       }
832
833     }
834     if (option.isRequired() && !cb.enabled.isSelected())
835     {
836       // TODO: indicate paramset is not valid.. option needs to be selected!
837     }
838     cb.setInitialValue();
839   }
840
841   void setParameter(ParameterI arg)
842   {
843     ParamBox pb = paramSet.get(arg.getName());
844     if (pb == null)
845     {
846       addParameter(arg);
847     }
848     else
849     {
850       pb.updateControls(arg);
851     }
852
853   }
854
855   /**
856    * recover options and parameters from GUI
857    * 
858    * @return
859    */
860   public List<ArgumentI> getCurrentSettings()
861   {
862     List<ArgumentI> argSet = new ArrayList<ArgumentI>();
863     for (OptionBox opts : getOptSet().values())
864     {
865       OptionI opt = opts.getOptionIfEnabled();
866       if (opt != null)
867       {
868         argSet.add(opt);
869       }
870     }
871     for (ParamBox parambox : getParamSet().values())
872     {
873       ParameterI parm = parambox.getParameter();
874       if (parm != null)
875       {
876         argSet.add(parm);
877       }
878     }
879
880     return argSet;
881   }
882
883 }