featureGroup show/hide and newView methods for applet's javascript API
[jalview.git] / src / jalview / appletgui / FeatureSettings.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer
3  * Copyright (C) 2007 AM Waterhouse, J Procter, G Barton, M Clamp, S Searle
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
18  */
19 package jalview.appletgui;
20
21 import java.util.*;
22
23 import java.awt.*;
24 import java.awt.event.*;
25
26 import jalview.datamodel.*;
27
28 public class FeatureSettings
29     extends Panel implements ItemListener,
30     MouseListener, MouseMotionListener, ActionListener, AdjustmentListener
31 {
32   FeatureRenderer fr;
33   AlignmentPanel ap;
34   AlignViewport av;
35   Frame frame;
36   Panel groupPanel;
37   Panel featurePanel = new Panel();
38   ScrollPane scrollPane;
39   boolean alignmentHasFeatures = false;
40   Image linkImage;
41   Scrollbar transparency;
42
43   public FeatureSettings(final AlignmentPanel ap)
44   {
45     this.ap = ap;
46     this.av = ap.av;
47     ap.av.featureSettings = this;
48     fr = ap.seqPanel.seqCanvas.getFeatureRenderer();
49
50     transparency = new Scrollbar(Scrollbar.HORIZONTAL,
51                                  100 - (int) (fr.transparency * 100), 1, 1, 100);
52
53     if (fr.transparencySetter != null)
54     {
55       transparency.addAdjustmentListener(this);
56     }
57     else
58     {
59       transparency.setEnabled(false);
60     }
61
62     java.net.URL url = getClass().getResource("/images/link.gif");
63     if (url != null)
64     {
65       linkImage = java.awt.Toolkit.getDefaultToolkit().getImage(url);
66     }
67
68     if (av.featuresDisplayed == null)
69     {
70       fr.findAllFeatures();
71     }
72
73     setTableData();
74
75     this.setLayout(new BorderLayout());
76     scrollPane = new ScrollPane();
77     scrollPane.add(featurePanel);
78     if (alignmentHasFeatures)
79     {
80       add(scrollPane, BorderLayout.CENTER);
81     }
82
83     Button invert = new Button("Invert Selection");
84     invert.addActionListener(this);
85
86     Panel lowerPanel = new Panel(new GridLayout(2, 1, 5, 10));
87     lowerPanel.add(invert);
88
89     Panel tPanel = new Panel(new BorderLayout());
90
91     if (fr.transparencySetter != null)
92     {
93       tPanel.add(transparency, BorderLayout.CENTER);
94       tPanel.add(new Label("Transparency"), BorderLayout.EAST);
95     }
96     else
97     {
98       tPanel.add(new Label("Transparency not available in this web browser"),
99                  BorderLayout.CENTER);
100     }
101
102     lowerPanel.add(tPanel, BorderLayout.SOUTH);
103
104     add(lowerPanel, BorderLayout.SOUTH);
105
106     if (groupPanel != null)
107     {
108       groupPanel.setLayout(
109           new GridLayout(fr.featureGroups.size() / 4 + 1, 4));
110       groupPanel.validate();
111
112       add(groupPanel, BorderLayout.NORTH);
113     }
114     frame = new Frame();
115     frame.add(this);
116     final FeatureSettings me = this;
117     frame.addWindowListener(new WindowAdapter()
118     {
119       public void windowClosing(WindowEvent e)
120       {
121         if (me.av.featureSettings==me)
122         {
123           me.av.featureSettings = null;
124           me.ap = null;
125           me.av = null;
126         }
127       }
128     }
129     );
130     int height = featurePanel.getComponentCount() * 50 + 60;
131
132     height = Math.max(200, height);
133     height = Math.min(400, height);
134     int width = 300;
135     jalview.bin.JalviewLite.addFrame(frame, "Feature Settings", width,
136                                      height);
137   }
138
139   public void paint(Graphics g)
140   {
141     g.setColor(Color.black);
142     g.drawString("No Features added to this alignment!!", 10, 20);
143     g.drawString("(Features can be added from searches or", 10, 40);
144     g.drawString("from Jalview / GFF features files)", 10, 60);
145   }
146
147   public void setTableData()
148   {
149     alignmentHasFeatures = fr.buildGroupHash();
150     if (alignmentHasFeatures)
151     {
152       rebuildGroups();
153       
154     }
155     resetTable(false);
156   }
157   /**
158    * rebuilds the group panel
159    */
160   public void rebuildGroups()
161   {
162     boolean rdrw = false;
163     if (groupPanel == null)
164     {
165       groupPanel = new Panel();
166     } else {
167       rdrw = true;
168       groupPanel.removeAll();
169     }
170     
171     Enumeration gps = fr.featureGroups.keys();
172     while (gps.hasMoreElements())
173     {
174       String group = (String) gps.nextElement();
175       Boolean vis = (Boolean) fr.featureGroups.get(group);
176       Checkbox check = new MyCheckbox(
177               group,
178               vis.booleanValue(),
179               (fr.featureLinks != null && fr.featureLinks.containsKey(group))
180         );    
181       check.addMouseListener(this);
182       check.setFont(new Font("Serif", Font.BOLD, 12));
183       check.addItemListener(this);
184       groupPanel.add(check);
185     }
186     if (rdrw)
187     {
188       groupPanel.validate();
189     }
190   }
191
192   //This routine adds and removes checkboxes depending on
193   //Group selection states
194   void resetTable(boolean groupsChanged)
195   {
196     SequenceFeature[] tmpfeatures;
197     String group = null, type;
198     Vector visibleChecks = new Vector();
199
200     for (int i = 0; i < av.alignment.getHeight(); i++)
201     {
202       if (av.alignment.getSequenceAt(i).getSequenceFeatures() == null)
203       {
204         continue;
205       }
206
207       tmpfeatures = av.alignment.getSequenceAt(i).getSequenceFeatures();
208       int index = 0;
209       while (index < tmpfeatures.length)
210       {
211         group = tmpfeatures[index].featureGroup;
212
213         if (group == null || fr.featureGroups.get(group) == null ||
214             ( (Boolean) fr.featureGroups.get(group)).booleanValue())
215         {
216           type = tmpfeatures[index].getType();
217           if (!visibleChecks.contains(type))
218           {
219             visibleChecks.addElement(type);
220           }
221         }
222         index++;
223       }
224     }
225
226     Component[] comps;
227     int cSize = featurePanel.getComponentCount();
228     Checkbox check;
229     //This will remove any checkboxes which shouldn't be
230     //visible
231     for (int i = 0; i < cSize; i++)
232     {
233       comps = featurePanel.getComponents();
234       check = (Checkbox) comps[i];
235       if (!visibleChecks.contains(check.getLabel()))
236       {
237         featurePanel.remove(i);
238         cSize--;
239         i--;
240       }
241     }
242
243     if (fr.renderOrder != null)
244     {
245       //First add the checks in the previous render order,
246       //in case the window has been closed and reopened
247       for (int ro = fr.renderOrder.length - 1; ro > -1; ro--)
248       {
249         String item = fr.renderOrder[ro];
250
251         if (!visibleChecks.contains(item))
252         {
253           continue;
254         }
255
256         visibleChecks.removeElement(item);
257
258         addCheck(false, item);
259       }
260     }
261
262     // now add checkboxes which should be visible,
263     // if they have not already been added
264     Enumeration en = visibleChecks.elements();
265
266     while (en.hasMoreElements())
267     {
268       addCheck(groupsChanged, en.nextElement().toString());
269     }
270
271     featurePanel.setLayout(new GridLayout(featurePanel.getComponentCount(), 1,
272                                           10, 5));
273     featurePanel.validate();
274
275     if (scrollPane != null)
276     {
277       scrollPane.validate();
278     }
279
280     itemStateChanged(null);
281   }
282   /**
283    * update the checklist of feature types with the given type
284    * @param groupsChanged true means if the type is not in the display list then it will be added and displayed
285    * @param type feature type to be checked for in the list.
286    */
287   void addCheck(boolean groupsChanged, String type)
288   {
289     boolean addCheck;
290     Component[] comps = featurePanel.getComponents();
291     Checkbox check;
292     addCheck = true;
293     for (int i = 0; i < featurePanel.getComponentCount(); i++)
294     {
295       check = (Checkbox) comps[i];
296       if (check.getLabel().equals(type))
297       {
298         addCheck = false;
299         break;
300       }
301     }
302
303     if (addCheck)
304     {
305       boolean selected = false;
306       if (groupsChanged || av.featuresDisplayed.containsKey(type))
307       {
308         selected = true;
309       }
310
311       check = new MyCheckbox(type,
312                              selected,
313                              (fr.featureLinks != null &&
314                               fr.featureLinks.containsKey(type))
315           );
316
317       check.addMouseListener(this);
318       check.addMouseMotionListener(this);
319       check.setBackground(fr.getColour(type));
320       check.addItemListener(this);
321       featurePanel.add(check);
322     }
323   }
324
325   public void actionPerformed(ActionEvent evt)
326   {
327     for (int i = 0; i < featurePanel.getComponentCount(); i++)
328     {
329       Checkbox check = (Checkbox) featurePanel.getComponent(i);
330       check.setState(!check.getState());
331     }
332     selectionChanged();
333   }
334
335   public void itemStateChanged(ItemEvent evt)
336   {
337     if (evt != null)
338     {
339       //Is the source a top level featureGroup?
340       Checkbox source = (Checkbox) evt.getSource();
341       if (fr.featureGroups.containsKey(source.getLabel()))
342       {
343         fr.featureGroups.put(source.getLabel(), new Boolean(source.getState()));
344         ap.seqPanel.seqCanvas.repaint();
345         if (ap.overviewPanel != null)
346         {
347           ap.overviewPanel.updateOverviewImage();
348         }
349
350         resetTable(true);
351         return;
352       }
353     }
354     selectionChanged();
355   }
356
357   void selectionChanged()
358   {
359     Component[] comps = featurePanel.getComponents();
360     int cSize = comps.length;
361
362     Object[][] tmp = new Object[cSize][3];
363     int tmpSize = 0;
364     for (int i = 0; i < cSize; i++)
365     {
366       Checkbox check = (Checkbox) comps[i];
367       tmp[tmpSize][0] = check.getLabel();
368       tmp[tmpSize][1] = fr.getColour(check.getLabel());
369       tmp[tmpSize][2] = new Boolean(check.getState());
370       tmpSize++;
371     }
372
373     Object[][] data = new Object[tmpSize][3];
374     System.arraycopy(tmp, 0, data, 0, tmpSize);
375
376     fr.setFeaturePriority(data);
377
378     ap.paintAlignment(true);
379   }
380
381   MyCheckbox selectedCheck;
382   boolean dragging = false;
383
384   public void mousePressed(MouseEvent evt)
385   {
386
387     selectedCheck = (MyCheckbox) evt.getSource();
388
389     if (fr.featureLinks != null
390         && fr.featureLinks.containsKey(selectedCheck.getLabel())
391         )
392     {
393       if (evt.getX() > selectedCheck.stringWidth + 20)
394       {
395         evt.consume();
396       }
397     }
398
399   }
400
401   public void mouseDragged(MouseEvent evt)
402   {
403     if ( ( (Component) evt.getSource()).getParent() != featurePanel)
404     {
405       return;
406     }
407     dragging = true;
408   }
409
410   public void mouseReleased(MouseEvent evt)
411   {
412     if ( ( (Component) evt.getSource()).getParent() != featurePanel)
413     {
414       return;
415     }
416
417     Component comp = null;
418     Checkbox target = null;
419
420     int height = evt.getY() + evt.getComponent().getLocation().y;
421
422     if (height > featurePanel.getSize().height)
423     {
424
425       comp = featurePanel.getComponent(featurePanel.getComponentCount() - 1);
426     }
427     else if (height < 0)
428     {
429       comp = featurePanel.getComponent(0);
430     }
431     else
432     {
433       comp = featurePanel.getComponentAt(evt.getX(),
434                                          evt.getY() +
435                                          evt.getComponent().getLocation().y);
436     }
437
438     if (comp != null && comp instanceof Checkbox)
439     {
440       target = (Checkbox) comp;
441     }
442
443     if (selectedCheck != null
444         && target != null
445         && selectedCheck != target)
446     {
447       int targetIndex = -1;
448       for (int i = 0; i < featurePanel.getComponentCount(); i++)
449       {
450         if (target == featurePanel.getComponent(i))
451         {
452           targetIndex = i;
453           break;
454         }
455       }
456
457       featurePanel.remove(selectedCheck);
458       featurePanel.add(selectedCheck, targetIndex);
459       featurePanel.validate();
460       itemStateChanged(null);
461     }
462   }
463
464   public void setUserColour(String feature, Color col)
465   {
466     fr.setColour(feature, col);
467     featurePanel.removeAll();
468     resetTable(false);
469     ap.paintAlignment(true);
470   }
471
472   public void mouseEntered(MouseEvent evt)
473   {}
474
475   public void mouseExited(MouseEvent evt)
476   {}
477
478   public void mouseClicked(MouseEvent evt)
479   {
480     MyCheckbox check = (MyCheckbox) evt.getSource();
481
482     if (fr.featureLinks != null
483         && fr.featureLinks.containsKey(check.getLabel()))
484     {
485       if (evt.getX() > check.stringWidth + 20)
486       {
487         evt.consume();
488         String link = fr.featureLinks.get(check.getLabel()).toString();
489         ap.alignFrame.showURL(link.substring(link.indexOf("|") + 1),
490                               link.substring(0, link.indexOf("|")));
491       }
492     }
493
494     if (check.getParent() != featurePanel)
495     {
496       return;
497     }
498
499     if (evt.getClickCount() > 1)
500     {
501       new UserDefinedColours(this, check.getLabel(),
502                              fr.getColour(check.getLabel()));
503     }
504   }
505
506   public void mouseMoved(MouseEvent evt)
507   {}
508
509   public void adjustmentValueChanged(AdjustmentEvent evt)
510   {
511     fr.transparency = ( (float) (100 - transparency.getValue()) / 100f);
512     ap.seqPanel.seqCanvas.repaint();
513
514   }
515
516   class MyCheckbox
517       extends Checkbox
518   {
519     public int stringWidth;
520     boolean hasLink;
521     public MyCheckbox(String label, boolean checked, boolean haslink)
522     {
523       super(label, checked);
524
525       FontMetrics fm = av.nullFrame.getFontMetrics(av.nullFrame.getFont());
526       stringWidth = fm.stringWidth(label);
527       this.hasLink = haslink;
528     }
529
530     public void paint(Graphics g)
531     {
532       if (hasLink)
533       {
534         g.drawImage(linkImage, stringWidth + 25, (
535             getSize().height - linkImage.getHeight(this)) / 2,
536                     this);
537       }
538     }
539   }
540 }