6670d2fea9632b7851f6acfacfa821f1faddf2ca
[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       if (groupsChanged)
322       {
323         // add at beginning of stack.
324         featurePanel.add(check, 0);
325       }
326       else
327       {
328         // add at end of stack.
329         featurePanel.add(check);
330       }
331     }
332   }
333
334   public void actionPerformed(ActionEvent evt)
335   {
336     for (int i = 0; i < featurePanel.getComponentCount(); i++)
337     {
338       Checkbox check = (Checkbox) featurePanel.getComponent(i);
339       check.setState(!check.getState());
340     }
341     selectionChanged();
342   }
343
344   public void itemStateChanged(ItemEvent evt)
345   {
346     if (evt != null)
347     {
348       //Is the source a top level featureGroup?
349       Checkbox source = (Checkbox) evt.getSource();
350       if (fr.featureGroups.containsKey(source.getLabel()))
351       {
352         fr.featureGroups.put(source.getLabel(), new Boolean(source.getState()));
353         ap.seqPanel.seqCanvas.repaint();
354         if (ap.overviewPanel != null)
355         {
356           ap.overviewPanel.updateOverviewImage();
357         }
358
359         resetTable(true);
360         return;
361       }
362     }
363     selectionChanged();
364   }
365
366   void selectionChanged()
367   {
368     Component[] comps = featurePanel.getComponents();
369     int cSize = comps.length;
370
371     Object[][] tmp = new Object[cSize][3];
372     int tmpSize = 0;
373     for (int i = 0; i < cSize; i++)
374     {
375       Checkbox check = (Checkbox) comps[i];
376       tmp[tmpSize][0] = check.getLabel();
377       tmp[tmpSize][1] = fr.getColour(check.getLabel());
378       tmp[tmpSize][2] = new Boolean(check.getState());
379       tmpSize++;
380     }
381
382     Object[][] data = new Object[tmpSize][3];
383     System.arraycopy(tmp, 0, data, 0, tmpSize);
384
385     fr.setFeaturePriority(data);
386
387     ap.paintAlignment(true);
388   }
389
390   MyCheckbox selectedCheck;
391   boolean dragging = false;
392
393   public void mousePressed(MouseEvent evt)
394   {
395
396     selectedCheck = (MyCheckbox) evt.getSource();
397
398     if (fr.featureLinks != null
399         && fr.featureLinks.containsKey(selectedCheck.getLabel())
400         )
401     {
402       if (evt.getX() > selectedCheck.stringWidth + 20)
403       {
404         evt.consume();
405       }
406     }
407
408   }
409
410   public void mouseDragged(MouseEvent evt)
411   {
412     if ( ( (Component) evt.getSource()).getParent() != featurePanel)
413     {
414       return;
415     }
416     dragging = true;
417   }
418
419   public void mouseReleased(MouseEvent evt)
420   {
421     if ( ( (Component) evt.getSource()).getParent() != featurePanel)
422     {
423       return;
424     }
425
426     Component comp = null;
427     Checkbox target = null;
428
429     int height = evt.getY() + evt.getComponent().getLocation().y;
430
431     if (height > featurePanel.getSize().height)
432     {
433
434       comp = featurePanel.getComponent(featurePanel.getComponentCount() - 1);
435     }
436     else if (height < 0)
437     {
438       comp = featurePanel.getComponent(0);
439     }
440     else
441     {
442       comp = featurePanel.getComponentAt(evt.getX(),
443                                          evt.getY() +
444                                          evt.getComponent().getLocation().y);
445     }
446
447     if (comp != null && comp instanceof Checkbox)
448     {
449       target = (Checkbox) comp;
450     }
451
452     if (selectedCheck != null
453         && target != null
454         && selectedCheck != target)
455     {
456       int targetIndex = -1;
457       for (int i = 0; i < featurePanel.getComponentCount(); i++)
458       {
459         if (target == featurePanel.getComponent(i))
460         {
461           targetIndex = i;
462           break;
463         }
464       }
465
466       featurePanel.remove(selectedCheck);
467       featurePanel.add(selectedCheck, targetIndex);
468       featurePanel.validate();
469       itemStateChanged(null);
470     }
471   }
472
473   public void setUserColour(String feature, Color col)
474   {
475     fr.setColour(feature, col);
476     featurePanel.removeAll();
477     resetTable(false);
478     ap.paintAlignment(true);
479   }
480
481   public void mouseEntered(MouseEvent evt)
482   {}
483
484   public void mouseExited(MouseEvent evt)
485   {}
486
487   public void mouseClicked(MouseEvent evt)
488   {
489     MyCheckbox check = (MyCheckbox) evt.getSource();
490
491     if (fr.featureLinks != null
492         && fr.featureLinks.containsKey(check.getLabel()))
493     {
494       if (evt.getX() > check.stringWidth + 20)
495       {
496         evt.consume();
497         String link = fr.featureLinks.get(check.getLabel()).toString();
498         ap.alignFrame.showURL(link.substring(link.indexOf("|") + 1),
499                               link.substring(0, link.indexOf("|")));
500       }
501     }
502
503     if (check.getParent() != featurePanel)
504     {
505       return;
506     }
507
508     if (evt.getClickCount() > 1)
509     {
510       new UserDefinedColours(this, check.getLabel(),
511                              fr.getColour(check.getLabel()));
512     }
513   }
514
515   public void mouseMoved(MouseEvent evt)
516   {}
517
518   public void adjustmentValueChanged(AdjustmentEvent evt)
519   {
520     fr.transparency = ( (float) (100 - transparency.getValue()) / 100f);
521     ap.seqPanel.seqCanvas.repaint();
522
523   }
524
525   class MyCheckbox
526       extends Checkbox
527   {
528     public int stringWidth;
529     boolean hasLink;
530     public MyCheckbox(String label, boolean checked, boolean haslink)
531     {
532       super(label, checked);
533
534       FontMetrics fm = av.nullFrame.getFontMetrics(av.nullFrame.getFont());
535       stringWidth = fm.stringWidth(label);
536       this.hasLink = haslink;
537     }
538
539     public void paint(Graphics g)
540     {
541       if (hasLink)
542       {
543         g.drawImage(linkImage, stringWidth + 25, (
544             getSize().height - linkImage.getHeight(this)) / 2,
545                     this);
546       }
547     }
548   }
549 }