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