JAL-2491 Renamed comp scrolling flag for consistency; small tweaks
[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           // TODO set via ViewportRanges in overview dimensions once JAL-2388 is
126           // merged
127           // ap.setScrollValues(od.getScrollCol(), od.getScrollRow());
128         }
129       }
130     });
131
132     addMouseListener(new MouseAdapter()
133     {
134       @Override
135       public void mousePressed(MouseEvent evt)
136       {
137         if (!av.getWrapAlignment())
138         {
139           od.updateViewportFromMouse(evt.getX(), evt.getY(), av
140                   .getAlignment().getHiddenSequences(), av
141                   .getColumnSelection(), av.getRanges());
142           // TODO set via ViewportRanges in overview dimensions once JAL-2388 is
143           // merged
144
145           // ap.setScrollValues(od.getScrollCol(), od.getScrollRow());
146         }
147       }
148     });
149
150     updateOverviewImage();
151   }
152
153   /**
154    * Updates the overview image when the related alignment panel is updated
155    */
156   public void updateOverviewImage()
157   {
158     if (resizing)
159     {
160       resizeAgain = true;
161       return;
162     }
163
164     resizing = true;
165
166     if ((getWidth() > 0) && (getHeight() > 0))
167     {
168       od.setWidth(getWidth());
169       od.setHeight(getHeight());
170     }
171
172     setPreferredSize(new Dimension(od.getWidth(), od.getHeight()));
173
174     Thread thread = new Thread(this);
175     thread.start();
176     repaint();
177   }
178
179   @Override
180   public void run()
181   {
182     miniMe = null;
183
184     if (av.isShowSequenceFeatures())
185     {
186       fr.transferSettings(ap.getSeqPanel().seqCanvas.getFeatureRenderer());
187     }
188
189     // why do we need to set preferred size again? was set in
190     // updateOverviewImage
191     setPreferredSize(new Dimension(od.getWidth(), od.getHeight()));
192
193     miniMe = new BufferedImage(od.getWidth(), od.getHeight(),
194             BufferedImage.TYPE_INT_RGB);
195
196     Graphics mg = miniMe.getGraphics();
197     mg.setColor(Color.orange);
198     mg.fillRect(0, 0, od.getWidth(), miniMe.getHeight());
199
200     // calculate sampleCol and sampleRow
201     // alignment width is max number of residues/bases
202     // alignment height is number of sequences
203     int alwidth = av.getAlignment().getWidth();
204     int alheight = av.getAlignment().getAbsoluteHeight();
205
206     // sampleCol or sampleRow is the width/height allocated to each residue
207     // in particular, sometimes we may need more than one row/col of the
208     // BufferedImage allocated
209     // sampleCol is how much of a residue to assign to each pixel
210     // sampleRow is how many sequences to assign to each pixel
211     float sampleCol = alwidth / (float) od.getWidth();
212     float sampleRow = alheight / (float) od.getSequencesHeight();
213
214     buildImage(sampleRow, sampleCol);
215
216     // check for conservation annotation to make sure overview works for DNA too
217     if (av.isShowAnnotation()
218             && (av.getAlignmentConservationAnnotation() != null))
219     {
220       renderer.updateFromAlignViewport(av);
221       for (int col = 0; col < od.getWidth() && !resizeAgain; col++)
222       {
223         mg.translate(col, od.getSequencesHeight());
224         renderer.drawGraph(mg, av.getAlignmentConservationAnnotation(),
225                 av.getAlignmentConservationAnnotation().annotations,
226                 (int) (sampleCol) + 1, od.getGraphHeight(),
227                 (int) (col * sampleCol), (int) (col * sampleCol) + 1);
228         mg.translate(-col, -od.getSequencesHeight());
229
230       }
231     }
232     System.gc();
233
234     resizing = false;
235
236     if (resizeAgain)
237     {
238       resizeAgain = false;
239       updateOverviewImage();
240     }
241     else
242     {
243       lastMiniMe = miniMe;
244     }
245
246     setBoxPosition();
247   }
248
249   /*
250    * Build the overview panel image
251    */
252   private void buildImage(float sampleRow, float sampleCol)
253   {
254     int lastcol = -1;
255     int lastrow = -1;
256     int rgbColour = Color.white.getRGB();
257
258     SequenceI seq = null;
259     FeatureColourFinder finder = new FeatureColourFinder(fr);
260
261     final boolean hasHiddenCols = av.hasHiddenColumns();
262     boolean hiddenRow = false;
263     // get hidden row and hidden column map once at beginning.
264     // clone featureRenderer settings to avoid race conditions... if state is
265     // updated just need to refresh again
266     for (int row = 0; row < od.getSequencesHeight() && !resizeAgain; row++)
267     {
268       boolean doCopy = true;
269       int currentrow = (int) (row * sampleRow);
270       if (currentrow != lastrow)
271       {
272         doCopy = false;
273
274         lastrow = currentrow;
275
276         // get the sequence which would be at alignment index 'lastrow' if no
277         // rows were hidden, and determine whether it is hidden or not
278         hiddenRow = av.getAlignment().isHidden(lastrow);
279         seq = av.getAlignment().getSequenceAtAbsoluteIndex(lastrow);
280       }
281
282       for (int col = 0; col < od.getWidth() && !resizeAgain; col++)
283       {
284         if (doCopy)
285         {
286           rgbColour = miniMe.getRGB(col, row - 1);
287         }
288         else if ((int) (col * sampleCol) != lastcol
289                 || (int) (row * sampleRow) != lastrow)
290         {
291           lastcol = (int) (col * sampleCol);
292           rgbColour = getColumnColourFromSequence(seq, hiddenRow,
293                   hasHiddenCols, lastcol, finder);
294         }
295         // else we just use the color we already have , so don't need to set it
296
297         miniMe.setRGB(col, row, rgbColour);
298       }
299     }
300   }
301
302   /*
303    * Find the colour of a sequence at a specified column position
304    */
305   private int getColumnColourFromSequence(
306           jalview.datamodel.SequenceI seq,
307           boolean hiddenRow, boolean hasHiddenCols, int lastcol,
308           FeatureColourFinder finder)
309   {
310     Color color = Color.white;
311
312     if ((seq != null) && (seq.getLength() > lastcol))
313     {
314         color = sr.getResidueColour(seq, lastcol, finder);
315     }
316
317     if (hiddenRow
318             || (hasHiddenCols && !av.getColumnSelection()
319                     .isVisible(lastcol)))
320     {
321       color = color.darker().darker();
322     }
323
324     return color.getRGB();
325   }
326
327   /**
328    * Update the overview panel box when the associated alignment panel is
329    * changed
330    * 
331    */
332   private void setBoxPosition()
333   {
334     od.setBoxPosition(av.getAlignment()
335             .getHiddenSequences(), av.getColumnSelection(), av.getRanges());
336     repaint();
337   }
338
339
340   @Override
341   public void paintComponent(Graphics g)
342   {
343     if (resizing || resizeAgain)
344     {
345       if (lastMiniMe == null)
346       {
347         g.setColor(Color.white);
348         g.fillRect(0, 0, getWidth(), getHeight());
349       }
350       else
351       {
352         g.drawImage(lastMiniMe, 0, 0, getWidth(), getHeight(), this);
353       }
354       g.setColor(TRANS_GREY);
355       g.fillRect(0, 0, getWidth(), getHeight());
356     }
357     else if (lastMiniMe != null)
358     {
359       g.drawImage(lastMiniMe, 0, 0, this);
360       if (lastMiniMe != miniMe)
361       {
362         g.setColor(TRANS_GREY);
363         g.fillRect(0, 0, getWidth(), getHeight());
364       }
365     }
366
367     g.setColor(Color.red);
368     od.drawBox(g);
369   }
370
371   @Override
372   public void propertyChange(PropertyChangeEvent evt)
373   {
374     setBoxPosition();
375   }
376 }