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