JAL-1482 refactor setTableData to more sensible name for feature settings api
[jalview.git] / src / jalview / appletgui / FeatureSettings.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.appletgui;
22
23 import jalview.api.FeatureSettingsControllerI;
24 import jalview.datamodel.AlignmentI;
25 import jalview.datamodel.SequenceFeature;
26 import jalview.schemes.AnnotationColourGradient;
27 import jalview.schemes.GraduatedColor;
28 import jalview.util.MessageManager;
29
30 import java.awt.BorderLayout;
31 import java.awt.Button;
32 import java.awt.Checkbox;
33 import java.awt.Color;
34 import java.awt.Component;
35 import java.awt.Dimension;
36 import java.awt.Font;
37 import java.awt.FontMetrics;
38 import java.awt.Frame;
39 import java.awt.Graphics;
40 import java.awt.GridLayout;
41 import java.awt.Image;
42 import java.awt.Label;
43 import java.awt.MenuItem;
44 import java.awt.Panel;
45 import java.awt.PopupMenu;
46 import java.awt.ScrollPane;
47 import java.awt.Scrollbar;
48 import java.awt.event.ActionEvent;
49 import java.awt.event.ActionListener;
50 import java.awt.event.AdjustmentEvent;
51 import java.awt.event.AdjustmentListener;
52 import java.awt.event.InputEvent;
53 import java.awt.event.ItemEvent;
54 import java.awt.event.ItemListener;
55 import java.awt.event.MouseEvent;
56 import java.awt.event.MouseListener;
57 import java.awt.event.MouseMotionListener;
58 import java.awt.event.WindowAdapter;
59 import java.awt.event.WindowEvent;
60 import java.util.Enumeration;
61 import java.util.Hashtable;
62 import java.util.List;
63 import java.util.Vector;
64
65 public class FeatureSettings extends Panel implements ItemListener,
66         MouseListener, MouseMotionListener, ActionListener,
67         AdjustmentListener, FeatureSettingsControllerI
68 {
69   FeatureRenderer fr;
70
71   AlignmentPanel ap;
72
73   AlignViewport av;
74
75   Frame frame;
76
77   Panel groupPanel;
78
79   Panel featurePanel = new Panel();
80
81   ScrollPane scrollPane;
82
83   Image linkImage;
84
85   Scrollbar transparency;
86
87   public FeatureSettings(final AlignmentPanel ap)
88   {
89     this.ap = ap;
90     this.av = ap.av;
91     ap.av.featureSettings = this;
92     fr = ap.seqPanel.seqCanvas.getFeatureRenderer();
93
94     transparency = new Scrollbar(Scrollbar.HORIZONTAL,
95             100 - (int) (fr.getTransparency() * 100), 1, 1, 100);
96
97     if (fr.isTransparencyAvailable())
98     {
99       transparency.addAdjustmentListener(this);
100     }
101     else
102     {
103       transparency.setEnabled(false);
104     }
105
106     java.net.URL url = getClass().getResource("/images/link.gif");
107     if (url != null)
108     {
109       linkImage = java.awt.Toolkit.getDefaultToolkit().getImage(url);
110     }
111
112     if (av.isShowSequenceFeatures() || !fr.hasRenderOrder())
113     {
114       fr.findAllFeatures(true); // was default - now true to make all visible
115     }
116
117     discoverAllFeatureData();
118
119     this.setLayout(new BorderLayout());
120     scrollPane = new ScrollPane();
121     scrollPane.add(featurePanel);
122     if (fr.getAllFeatureColours()!=null && fr.getAllFeatureColours().size()>0)
123     {
124       add(scrollPane, BorderLayout.CENTER);
125     }
126
127     Button invert = new Button("Invert Selection");
128     invert.addActionListener(this);
129
130     Panel lowerPanel = new Panel(new GridLayout(2, 1, 5, 10));
131     lowerPanel.add(invert);
132
133     Panel tPanel = new Panel(new BorderLayout());
134
135     if (fr.isTransparencyAvailable())
136     {
137       tPanel.add(transparency, BorderLayout.CENTER);
138       tPanel.add(new Label("Transparency"), BorderLayout.EAST);
139     }
140     else
141     {
142       tPanel.add(
143               new Label("Transparency not available in this web browser"),
144               BorderLayout.CENTER);
145     }
146
147     lowerPanel.add(tPanel, BorderLayout.SOUTH);
148
149     add(lowerPanel, BorderLayout.SOUTH);
150
151     if (groupPanel != null)
152     {
153       groupPanel
154               .setLayout(new GridLayout(
155                       (fr.getFeatureGroupsSize()) / 4 + 1,
156                       4)); // JBPNote - this was scaled on number of visible groups. seems broken
157       groupPanel.validate();
158
159       add(groupPanel, BorderLayout.NORTH);
160     }
161     frame = new Frame();
162     frame.add(this);
163     final FeatureSettings me = this;
164     frame.addWindowListener(new WindowAdapter()
165     {
166       public void windowClosing(WindowEvent e)
167       {
168         if (me.av.featureSettings == me)
169         {
170           me.av.featureSettings = null;
171           me.ap = null;
172           me.av = null;
173         }
174       }
175     });
176     int height = featurePanel.getComponentCount() * 50 + 60;
177
178     height = Math.max(200, height);
179     height = Math.min(400, height);
180     int width = 300;
181     jalview.bin.JalviewLite.addFrame(frame,
182             MessageManager.getString("label.feature_settings"), width,
183             height);
184   }
185
186   public void paint(Graphics g)
187   {
188     g.setColor(Color.black);
189     g.drawString(MessageManager
190             .getString("label.no_features_added_to_this_alignment"), 10, 20);
191     g.drawString(MessageManager
192             .getString("label.features_can_be_added_from_searches_1"), 10,
193             40);
194     g.drawString(MessageManager
195             .getString("label.features_can_be_added_from_searches_2"), 10,
196             60);
197   }
198
199   protected void popupSort(final MyCheckbox check, final Hashtable minmax,
200           int x, int y)
201   {
202     final String type = check.type;
203     final Object typeCol = fr.getFeatureStyle(type);
204     java.awt.PopupMenu men = new PopupMenu(MessageManager.formatMessage(
205             "label.settings_for_type", new String[]
206             { type }));
207     java.awt.MenuItem scr = new MenuItem(
208             MessageManager.getString("label.sort_by_score"));
209     men.add(scr);
210     final FeatureSettings me = this;
211     scr.addActionListener(new ActionListener()
212     {
213
214       public void actionPerformed(ActionEvent e)
215       {
216         me.ap.alignFrame.avc.sortAlignmentByFeatureScore(new String[]
217         { type });
218       }
219
220     });
221     MenuItem dens = new MenuItem(
222             MessageManager.getString("label.sort_by_density"));
223     dens.addActionListener(new ActionListener()
224     {
225
226       public void actionPerformed(ActionEvent e)
227       {
228         me.ap.alignFrame.avc.sortAlignmentByFeatureDensity(new String[]
229         { type });
230       }
231
232     });
233     men.add(dens);
234     if (minmax != null)
235     {
236       final Object typeMinMax = minmax.get(type);
237       /*
238        * final java.awt.CheckboxMenuItem chb = new
239        * java.awt.CheckboxMenuItem("Vary Height"); // this is broken at the
240        * moment chb.setState(minmax.get(type) != null);
241        * chb.addActionListener(new ActionListener() {
242        * 
243        * public void actionPerformed(ActionEvent e) {
244        * chb.setState(chb.getState()); if (chb.getState()) { minmax.put(type,
245        * null); } else { minmax.put(type, typeMinMax); } }
246        * 
247        * }); men.add(chb);
248        */
249       if (typeMinMax != null && ((float[][]) typeMinMax)[0] != null)
250       {
251         // graduated colourschemes for those where minmax exists for the
252         // positional features
253         MenuItem mxcol = new MenuItem(
254                 (typeCol instanceof Color) ? "Graduated Colour"
255                         : "Single Colour");
256         men.add(mxcol);
257         mxcol.addActionListener(new ActionListener()
258         {
259
260           public void actionPerformed(ActionEvent e)
261           {
262             if (typeCol instanceof Color)
263             {
264               new FeatureColourChooser(me, type);
265               // write back the current colour object to update the table
266               check.updateColor(fr.getFeatureStyle(type));
267             }
268             else
269             {
270               new UserDefinedColours(me, check.type,
271                       ((GraduatedColor) typeCol));
272             }
273           }
274
275         });
276       }
277     }
278     this.featurePanel.add(men);
279     men.show(this.featurePanel, x, y);
280   }
281
282   @Override
283   public void discoverAllFeatureData()
284   {
285     if (fr.getAllFeatureColours()!=null && fr.getAllFeatureColours().size()>0)
286     {
287       rebuildGroups();
288
289     }
290     resetTable(false);
291   }
292
293   /**
294    * rebuilds the group panel
295    */
296   public void rebuildGroups()
297   {
298     boolean rdrw = false;
299     if (groupPanel == null)
300     {
301       groupPanel = new Panel();
302     }
303     else
304     {
305       rdrw = true;
306       groupPanel.removeAll();
307     }
308     // TODO: JAL-964 - smoothly incorporate new group entries if panel already
309     // displayed and new groups present
310     for (String group:fr.getFeatureGroups())
311     {
312       boolean vis = fr.checkGroupVisibility(group, false);
313       Checkbox check = new MyCheckbox(group, vis,
314               (fr.featureLinks != null && fr.featureLinks
315                       .containsKey(group)));
316       check.addMouseListener(this);
317       check.setFont(new Font("Serif", Font.BOLD, 12));
318       check.addItemListener(groupItemListener);
319       // note - visibility seems to correlate with displayed. ???wtf ??
320       check.setVisible(vis);
321       groupPanel.add(check);
322     }
323     if (rdrw)
324     {
325       groupPanel.validate();
326     }
327   }
328   // This routine adds and removes checkboxes depending on
329   // Group selection states
330   void resetTable(boolean groupsChanged)
331   {
332     SequenceFeature[] tmpfeatures;
333     String group = null, type;
334     Vector visibleChecks = new Vector();
335     AlignmentI alignment = av.getAlignment();
336     for (int i = 0; i < alignment.getHeight(); i++)
337     {
338       if (alignment.getSequenceAt(i).getSequenceFeatures() == null)
339       {
340         continue;
341       }
342
343       tmpfeatures = alignment.getSequenceAt(i).getSequenceFeatures();
344       int index = 0;
345       while (index < tmpfeatures.length)
346       {
347         group = tmpfeatures[index].featureGroup;
348
349         if (group == null || fr.checkGroupVisibility(group, true))
350         {
351           type = tmpfeatures[index].getType();
352           if (!visibleChecks.contains(type))
353           {
354             visibleChecks.addElement(type);
355           }
356         }
357         index++;
358       }
359     }
360
361     Component[] comps;
362     int cSize = featurePanel.getComponentCount();
363     MyCheckbox check;
364     // This will remove any checkboxes which shouldn't be
365     // visible
366     for (int i = 0; i < cSize; i++)
367     {
368       comps = featurePanel.getComponents();
369       check = (MyCheckbox) comps[i];
370       if (!visibleChecks.contains(check.type))
371       {
372         featurePanel.remove(i);
373         cSize--;
374         i--;
375       }
376     }
377
378     if (fr.getRenderOrder() != null)
379     {
380       // First add the checks in the previous render order,
381       // in case the window has been closed and reopened
382       List<String> rol = fr.getRenderOrder();
383       for (int ro = rol.size() - 1; ro > -1; ro--)
384       {
385         String item = rol.get(ro);
386
387         if (!visibleChecks.contains(item))
388         {
389           continue;
390         }
391
392         visibleChecks.removeElement(item);
393
394         addCheck(false, item);
395       }
396     }
397
398     // now add checkboxes which should be visible,
399     // if they have not already been added
400     Enumeration en = visibleChecks.elements();
401
402     while (en.hasMoreElements())
403     {
404       addCheck(groupsChanged, en.nextElement().toString());
405     }
406
407     featurePanel.setLayout(new GridLayout(featurePanel.getComponentCount(),
408             1, 10, 5));
409     featurePanel.validate();
410
411     if (scrollPane != null)
412     {
413       scrollPane.validate();
414     }
415
416     itemStateChanged(null);
417   }
418
419   /**
420    * update the checklist of feature types with the given type
421    * 
422    * @param groupsChanged
423    *          true means if the type is not in the display list then it will be
424    *          added and displayed
425    * @param type
426    *          feature type to be checked for in the list.
427    */
428   void addCheck(boolean groupsChanged, String type)
429   {
430     boolean addCheck;
431     Component[] comps = featurePanel.getComponents();
432     MyCheckbox check;
433     addCheck = true;
434     for (int i = 0; i < featurePanel.getComponentCount(); i++)
435     {
436       check = (MyCheckbox) comps[i];
437       if (check.type.equals(type))
438       {
439         addCheck = false;
440         break;
441       }
442     }
443
444     if (addCheck)
445     {
446       boolean selected = false;
447       if (groupsChanged || av.getFeaturesDisplayed().isVisible(type))
448       {
449         selected = true;
450       }
451
452       check = new MyCheckbox(
453               type,
454               selected,
455               (fr.featureLinks != null && fr.featureLinks.containsKey(type)),
456               fr.getFeatureStyle(type));
457
458       check.addMouseListener(this);
459       check.addMouseMotionListener(this);
460       check.addItemListener(this);
461       if (groupsChanged)
462       {
463         // add at beginning of stack.
464         featurePanel.add(check, 0);
465       }
466       else
467       {
468         // add at end of stack.
469         featurePanel.add(check);
470       }
471     }
472   }
473
474   public void actionPerformed(ActionEvent evt)
475   {
476     for (int i = 0; i < featurePanel.getComponentCount(); i++)
477     {
478       Checkbox check = (Checkbox) featurePanel.getComponent(i);
479       check.setState(!check.getState());
480     }
481     selectionChanged();
482   }
483
484   private ItemListener groupItemListener = new ItemListener() {
485     public void itemStateChanged(ItemEvent evt) {
486       Checkbox source = (Checkbox) evt.getSource();
487       fr.setGroupVisibility(source.getLabel(),
488               source.getState());
489       ap.seqPanel.seqCanvas.repaint();
490       if (ap.overviewPanel != null)
491       {
492         ap.overviewPanel.updateOverviewImage();
493       }
494       resetTable(true);
495       return;
496     };
497   };
498   public void itemStateChanged(ItemEvent evt)
499   {
500     selectionChanged();
501   }
502
503   void selectionChanged()
504   {
505     Component[] comps = featurePanel.getComponents();
506     int cSize = comps.length;
507
508     Object[][] tmp = new Object[cSize][3];
509     int tmpSize = 0;
510     for (int i = 0; i < cSize; i++)
511     {
512       MyCheckbox check = (MyCheckbox) comps[i];
513       tmp[tmpSize][0] = check.type;
514       tmp[tmpSize][1] = fr.getFeatureStyle(check.type);
515       tmp[tmpSize][2] = new Boolean(check.getState());
516       tmpSize++;
517     }
518
519     Object[][] data = new Object[tmpSize][3];
520     System.arraycopy(tmp, 0, data, 0, tmpSize);
521
522     fr.setFeaturePriority(data);
523
524     ap.paintAlignment(true);
525   }
526
527   MyCheckbox selectedCheck;
528
529   boolean dragging = false;
530
531   public void mousePressed(MouseEvent evt)
532   {
533
534     selectedCheck = (MyCheckbox) evt.getSource();
535
536     if (fr.featureLinks != null
537             && fr.featureLinks.containsKey(selectedCheck.type))
538     {
539       if (evt.getX() > selectedCheck.stringWidth + 20)
540       {
541         evt.consume();
542       }
543     }
544
545   }
546
547   public void mouseDragged(MouseEvent evt)
548   {
549     if (((Component) evt.getSource()).getParent() != featurePanel)
550     {
551       return;
552     }
553     dragging = true;
554   }
555
556   public void mouseReleased(MouseEvent evt)
557   {
558     if (((Component) evt.getSource()).getParent() != featurePanel)
559     {
560       return;
561     }
562
563     Component comp = null;
564     Checkbox target = null;
565
566     int height = evt.getY() + evt.getComponent().getLocation().y;
567
568     if (height > featurePanel.getSize().height)
569     {
570
571       comp = featurePanel
572               .getComponent(featurePanel.getComponentCount() - 1);
573     }
574     else if (height < 0)
575     {
576       comp = featurePanel.getComponent(0);
577     }
578     else
579     {
580       comp = featurePanel.getComponentAt(evt.getX(), evt.getY()
581               + evt.getComponent().getLocation().y);
582     }
583
584     if (comp != null && comp instanceof Checkbox)
585     {
586       target = (Checkbox) comp;
587     }
588
589     if (selectedCheck != null && target != null && selectedCheck != target)
590     {
591       int targetIndex = -1;
592       for (int i = 0; i < featurePanel.getComponentCount(); i++)
593       {
594         if (target == featurePanel.getComponent(i))
595         {
596           targetIndex = i;
597           break;
598         }
599       }
600
601       featurePanel.remove(selectedCheck);
602       featurePanel.add(selectedCheck, targetIndex);
603       featurePanel.validate();
604       itemStateChanged(null);
605     }
606   }
607
608   public void setUserColour(String feature, Object originalColour)
609   {
610     if (originalColour instanceof Color
611             || originalColour instanceof GraduatedColor)
612     {
613       fr.setColour(feature, originalColour);
614     }
615     else
616     {
617       throw new Error(MessageManager.getString("error.implementation_error_unsupported_feature_colour_object"));
618     }
619     refreshTable();
620   }
621
622   public void refreshTable()
623   {
624     featurePanel.removeAll();
625     resetTable(false);
626     ap.paintAlignment(true);
627   }
628
629   public void mouseEntered(MouseEvent evt)
630   {
631   }
632
633   public void mouseExited(MouseEvent evt)
634   {
635   }
636
637   public void mouseClicked(MouseEvent evt)
638   {
639     MyCheckbox check = (MyCheckbox) evt.getSource();
640     if ((evt.getModifiers() & InputEvent.BUTTON3_MASK) != 0)
641     {
642       this.popupSort(check, fr.getMinMax(), evt.getX(), evt.getY());
643     }
644     if (fr.featureLinks != null && fr.featureLinks.containsKey(check.type))
645     {
646       if (evt.getX() > check.stringWidth + 20)
647       {
648         evt.consume();
649         String link = fr.featureLinks.get(check.type).toString();
650         ap.alignFrame.showURL(link.substring(link.indexOf("|") + 1),
651                 link.substring(0, link.indexOf("|")));
652       }
653     }
654
655     if (check.getParent() != featurePanel)
656     {
657       return;
658     }
659
660     if (evt.getClickCount() > 1)
661     {
662       Object fcol = fr.getFeatureStyle(check.type);
663       if (fcol instanceof Color)
664       {
665         new UserDefinedColours(this, check.type, (Color) fcol);
666       }
667       else
668       {
669         new FeatureColourChooser(this, check.type);
670         // write back the current colour object to update the table
671         check.updateColor(fr.getFeatureStyle(check.type));
672       }
673     }
674   }
675
676   public void mouseMoved(MouseEvent evt)
677   {
678   }
679
680   public void adjustmentValueChanged(AdjustmentEvent evt)
681   {
682     fr.setTransparency((100 - transparency.getValue()) / 100f);
683     ap.seqPanel.seqCanvas.repaint();
684
685   }
686
687   class MyCheckbox extends Checkbox
688   {
689     public String type;
690
691     public int stringWidth;
692
693     boolean hasLink;
694
695     GraduatedColor gcol;
696
697     Color col;
698
699     public void updateColor(Object newcol)
700     {
701       if (newcol instanceof Color)
702       {
703         col = (Color) newcol;
704         gcol = null;
705       }
706       else if (newcol instanceof GraduatedColor)
707       {
708         gcol = (GraduatedColor) newcol;
709         col = null;
710       }
711       else
712       {
713         throw new Error(MessageManager.getString("error.invalid_colour_for_mycheckbox"));
714       }
715       if (col != null)
716       {
717         setBackground(col);
718       }
719       else
720       {
721         String vlabel = type;
722         if (gcol.getThreshType() != AnnotationColourGradient.NO_THRESHOLD)
723         {
724           vlabel += " "
725                   + ((gcol.getThreshType() == AnnotationColourGradient.ABOVE_THRESHOLD) ? "(>)"
726                           : "(<)");
727         }
728         if (gcol.isColourByLabel())
729         {
730           setBackground(Color.white);
731           vlabel += " (by Label)";
732         }
733         else
734         {
735           setBackground(gcol.getMinColor());
736         }
737         this.setLabel(vlabel);
738       }
739       repaint();
740     }
741
742     public MyCheckbox(String label, boolean checked, boolean haslink)
743     {
744       super(label, checked);
745       type = label;
746       FontMetrics fm = av.nullFrame.getFontMetrics(av.nullFrame.getFont());
747       stringWidth = fm.stringWidth(label);
748       this.hasLink = haslink;
749     }
750
751     public MyCheckbox(String type, boolean selected, boolean b,
752             Object featureStyle)
753     {
754       this(type, selected, b);
755       updateColor(featureStyle);
756     }
757
758     public void paint(Graphics g)
759     {
760       Dimension d = getSize();
761       if (gcol != null)
762       {
763         if (gcol.isColourByLabel())
764         {
765           g.setColor(Color.white);
766           g.fillRect(d.width / 2, 0, d.width / 2, d.height);
767           /*
768            * g.setColor(Color.black); Font f=g.getFont().deriveFont(9);
769            * g.setFont(f);
770            * 
771            * // g.setFont(g.getFont().deriveFont( //
772            * AffineTransform.getScaleInstance( //
773            * width/g.getFontMetrics().stringWidth("Label"), //
774            * height/g.getFontMetrics().getHeight()))); g.drawString("Label",
775            * width/2, 0);
776            */
777
778         }
779         else
780         {
781           Color maxCol = gcol.getMaxColor();
782           g.setColor(maxCol);
783           g.fillRect(d.width / 2, 0, d.width / 2, d.height);
784
785         }
786       }
787
788       if (hasLink)
789       {
790         g.drawImage(linkImage, stringWidth + 25,
791                 (getSize().height - linkImage.getHeight(this)) / 2, this);
792       }
793     }
794   }
795
796 }