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