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