JAL-2491 first addition of new event
[jalview.git] / src / jalview / gui / OverviewPanel.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.gui;
22
23 import jalview.datamodel.SequenceI;
24 import jalview.renderer.AnnotationRenderer;
25 import jalview.renderer.seqfeatures.FeatureColourFinder;
26 import jalview.viewmodel.OverviewDimensions;
27 import jalview.viewmodel.ViewportListenerI;
28
29 import java.awt.Color;
30 import java.awt.Dimension;
31 import java.awt.Graphics;
32 import java.awt.event.ComponentAdapter;
33 import java.awt.event.ComponentEvent;
34 import java.awt.event.MouseAdapter;
35 import java.awt.event.MouseEvent;
36 import java.awt.event.MouseMotionAdapter;
37 import java.awt.image.BufferedImage;
38 import java.beans.PropertyChangeEvent;
39
40 import javax.swing.JPanel;
41
42 /**
43  * Panel displaying an overview of the full alignment, with an interactive box
44  * representing the viewport onto the alignment.
45  * 
46  * @author $author$
47  * @version $Revision$
48  */
49 public class OverviewPanel extends JPanel implements Runnable,
50         ViewportListenerI
51 {
52   private static final Color TRANS_GREY = new Color(100, 100, 100, 25);
53
54   private final AnnotationRenderer renderer = new AnnotationRenderer();
55
56   private OverviewDimensions od;
57
58   private BufferedImage miniMe;
59
60   private BufferedImage lastMiniMe = null;
61
62   private AlignViewport av;
63
64   private AlignmentPanel ap;
65
66   //
67   private boolean resizing = false;
68
69   // This is set true if the user resizes whilst
70   // the overview is being calculated
71   private boolean resizeAgain = false;
72
73   // Can set different properties in this seqCanvas than
74   // main visible SeqCanvas
75   private SequenceRenderer sr;
76
77   jalview.renderer.seqfeatures.FeatureRenderer fr;
78
79   /**
80    * Creates a new OverviewPanel object.
81    * 
82    * @param alPanel
83    *          The alignment panel which is shown in the overview panel
84    */
85   public OverviewPanel(AlignmentPanel alPanel)
86   {
87     this.av = alPanel.av;
88     this.ap = alPanel;
89     setLayout(null);
90
91     sr = new SequenceRenderer(av);
92     sr.renderGaps = false;
93     sr.forOverview = true;
94     fr = new FeatureRenderer(ap);
95
96     od = new OverviewDimensions(av.getRanges(),
97             (av.isShowAnnotation() && av
98                     .getAlignmentConservationAnnotation() != null));
99
100     av.getRanges().addPropertyChangeListener(this);
101
102     addComponentListener(new ComponentAdapter()
103     {
104       @Override
105       public void componentResized(ComponentEvent evt)
106       {
107         if ((getWidth() != od.getWidth())
108                 || (getHeight() != (od.getHeight())))
109         {
110           updateOverviewImage();
111         }
112       }
113     });
114
115     addMouseMotionListener(new MouseMotionAdapter()
116     {
117       @Override
118       public void mouseDragged(MouseEvent evt)
119       {
120         if (!av.getWrapAlignment())
121         {
122           od.updateViewportFromMouse(evt.getX(), evt.getY(), av
123                   .getAlignment().getHiddenSequences(), av
124                   .getColumnSelection(), av.getRanges());
125           ap.setScrollValues(od.getScrollCol(), od.getScrollRow());
126         }
127       }
128     });
129
130     addMouseListener(new MouseAdapter()
131     {
132       @Override
133       public void mousePressed(MouseEvent evt)
134       {
135         if (!av.getWrapAlignment())
136         {
137           od.updateViewportFromMouse(evt.getX(), evt.getY(), av
138                   .getAlignment().getHiddenSequences(), av
139                   .getColumnSelection(), av.getRanges());
140           ap.setScrollValues(od.getScrollCol(), od.getScrollRow());
141         }
142       }
143     });
144
145     updateOverviewImage();
146   }
147
148   /**
149    * Updates the overview image when the related alignment panel is updated
150    */
151   public void updateOverviewImage()
152   {
153     if (resizing)
154     {
155       resizeAgain = true;
156       return;
157     }
158
159     resizing = true;
160
161     if ((getWidth() > 0) && (getHeight() > 0))
162     {
163       od.setWidth(getWidth());
164       od.setHeight(getHeight());
165     }
166
167     setPreferredSize(new Dimension(od.getWidth(), od.getHeight()));
168
169     Thread thread = new Thread(this);
170     thread.start();
171     repaint();
172   }
173
174   @Override
175   public void run()
176   {
177     miniMe = null;
178
179     if (av.isShowSequenceFeatures())
180     {
181       fr.transferSettings(ap.getSeqPanel().seqCanvas.getFeatureRenderer());
182     }
183
184     // why do we need to set preferred size again? was set in
185     // updateOverviewImage
186     setPreferredSize(new Dimension(od.getWidth(), od.getHeight()));
187
188     miniMe = new BufferedImage(od.getWidth(), od.getHeight(),
189             BufferedImage.TYPE_INT_RGB);
190
191     Graphics mg = miniMe.getGraphics();
192     mg.setColor(Color.orange);
193     mg.fillRect(0, 0, od.getWidth(), miniMe.getHeight());
194
195     // calculate sampleCol and sampleRow
196     // alignment width is max number of residues/bases
197     // alignment height is number of sequences
198     int alwidth = av.getAlignment().getWidth();
199     int alheight = av.getAlignment().getAbsoluteHeight();
200
201     // sampleCol or sampleRow is the width/height allocated to each residue
202     // in particular, sometimes we may need more than one row/col of the
203     // BufferedImage allocated
204     // sampleCol is how much of a residue to assign to each pixel
205     // sampleRow is how many sequences to assign to each pixel
206     float sampleCol = alwidth / (float) od.getWidth();
207     float sampleRow = alheight / (float) od.getSequencesHeight();
208
209     buildImage(sampleRow, sampleCol);
210
211     // check for conservation annotation to make sure overview works for DNA too
212     if (av.isShowAnnotation()
213             && (av.getAlignmentConservationAnnotation() != null))
214     {
215       renderer.updateFromAlignViewport(av);
216       for (int col = 0; col < od.getWidth() && !resizeAgain; col++)
217       {
218         mg.translate(col, od.getSequencesHeight());
219         renderer.drawGraph(mg, av.getAlignmentConservationAnnotation(),
220                 av.getAlignmentConservationAnnotation().annotations,
221                 (int) (sampleCol) + 1, od.getGraphHeight(),
222                 (int) (col * sampleCol), (int) (col * sampleCol) + 1);
223         mg.translate(-col, -od.getSequencesHeight());
224
225       }
226     }
227     System.gc();
228
229     resizing = false;
230
231     if (resizeAgain)
232     {
233       resizeAgain = false;
234       updateOverviewImage();
235     }
236     else
237     {
238       lastMiniMe = miniMe;
239     }
240
241     setBoxPosition();
242   }
243
244   /*
245    * Build the overview panel image
246    */
247   private void buildImage(float sampleRow, float sampleCol)
248   {
249     int lastcol = -1;
250     int lastrow = -1;
251     int rgbColour = Color.white.getRGB();
252
253     SequenceI seq = null;
254     FeatureColourFinder finder = new FeatureColourFinder(fr);
255
256     final boolean hasHiddenCols = av.hasHiddenColumns();
257     boolean hiddenRow = false;
258     // get hidden row and hidden column map once at beginning.
259     // clone featureRenderer settings to avoid race conditions... if state is
260     // updated just need to refresh again
261     for (int row = 0; row < od.getSequencesHeight() && !resizeAgain; row++)
262     {
263       boolean doCopy = true;
264       int currentrow = (int) (row * sampleRow);
265       if (currentrow != lastrow)
266       {
267         doCopy = false;
268
269         lastrow = currentrow;
270
271         // get the sequence which would be at alignment index 'lastrow' if no
272         // rows were hidden, and determine whether it is hidden or not
273         hiddenRow = av.getAlignment().isHidden(lastrow);
274         seq = av.getAlignment().getSequenceAtAbsoluteIndex(lastrow);
275       }
276
277       for (int col = 0; col < od.getWidth() && !resizeAgain; col++)
278       {
279         if (doCopy)
280         {
281           rgbColour = miniMe.getRGB(col, row - 1);
282         }
283         else if ((int) (col * sampleCol) != lastcol
284                 || (int) (row * sampleRow) != lastrow)
285         {
286           lastcol = (int) (col * sampleCol);
287           rgbColour = getColumnColourFromSequence(seq, hiddenRow,
288                   hasHiddenCols, lastcol, finder);
289         }
290         // else we just use the color we already have , so don't need to set it
291
292         miniMe.setRGB(col, row, rgbColour);
293       }
294     }
295   }
296
297   /*
298    * Find the colour of a sequence at a specified column position
299    */
300   private int getColumnColourFromSequence(
301           jalview.datamodel.SequenceI seq,
302           boolean hiddenRow, boolean hasHiddenCols, int lastcol,
303           FeatureColourFinder finder)
304   {
305     Color color = Color.white;
306
307     if ((seq != null) && (seq.getLength() > lastcol))
308     {
309         color = sr.getResidueColour(seq, lastcol, finder);
310     }
311
312     if (hiddenRow
313             || (hasHiddenCols && !av.getColumnSelection()
314                     .isVisible(lastcol)))
315     {
316       color = color.darker().darker();
317     }
318
319     return color.getRGB();
320   }
321
322   /**
323    * Update the overview panel box when the associated alignment panel is
324    * changed
325    * 
326    */
327   public void setBoxPosition()
328   {
329     od.setBoxPosition(av.getAlignment()
330             .getHiddenSequences(), av.getColumnSelection(), av.getRanges());
331     repaint();
332   }
333
334
335   @Override
336   public void paintComponent(Graphics g)
337   {
338     if (resizing || resizeAgain)
339     {
340       if (lastMiniMe == null)
341       {
342         g.setColor(Color.white);
343         g.fillRect(0, 0, getWidth(), getHeight());
344       }
345       else
346       {
347         g.drawImage(lastMiniMe, 0, 0, getWidth(), getHeight(), this);
348       }
349       g.setColor(TRANS_GREY);
350       g.fillRect(0, 0, getWidth(), getHeight());
351     }
352     else if (lastMiniMe != null)
353     {
354       g.drawImage(lastMiniMe, 0, 0, this);
355       if (lastMiniMe != miniMe)
356       {
357         g.setColor(TRANS_GREY);
358         g.fillRect(0, 0, getWidth(), getHeight());
359       }
360     }
361
362     g.setColor(Color.red);
363     od.drawBox(g);
364   }
365
366   @Override
367   public void propertyChange(PropertyChangeEvent evt)
368   {
369     setBoxPosition();
370   }
371 }