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