fc3cc4b3e5287d5548db28c89e5572807422b05d
[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.renderer.AnnotationRenderer;
24 import jalview.viewmodel.OverviewDimensions;
25
26 import java.awt.Color;
27 import java.awt.Dimension;
28 import java.awt.Graphics;
29 import java.awt.event.ComponentAdapter;
30 import java.awt.event.ComponentEvent;
31 import java.awt.event.MouseAdapter;
32 import java.awt.event.MouseEvent;
33 import java.awt.event.MouseMotionAdapter;
34 import java.awt.image.BufferedImage;
35
36 import javax.swing.JPanel;
37
38 /**
39  * DOCUMENT ME!
40  * 
41  * @author $author$
42  * @version $Revision$
43  */
44 public class OverviewPanel extends JPanel implements Runnable
45 {
46   private static final Color TRANS_GREY = new Color(100, 100, 100, 25);
47
48   private final AnnotationRenderer renderer = new AnnotationRenderer();
49
50   private OverviewDimensions od;
51
52   private BufferedImage miniMe;
53
54   private BufferedImage lastMiniMe = null;
55
56   private AlignViewport av;
57
58   private AlignmentPanel ap;
59
60   //
61   private boolean resizing = false;
62
63   // This is set true if the user resizes whilst
64   // the overview is being calculated
65   private boolean resizeAgain = false;
66
67   // Can set different properties in this seqCanvas than
68   // main visible SeqCanvas
69   private SequenceRenderer sr;
70
71   private jalview.renderer.seqfeatures.FeatureRenderer fr;
72
73   /**
74    * Creates a new OverviewPanel object.
75    * 
76    * @param ap
77    *          The alignment panel which is shown in the overview panel
78    */
79   public OverviewPanel(AlignmentPanel ap)
80   {
81     this.av = ap.av;
82     this.ap = ap;
83     setLayout(null);
84
85     sr = new SequenceRenderer(av);
86     sr.renderGaps = false;
87     sr.forOverview = true;
88     fr = new FeatureRenderer(ap);
89
90     od = new OverviewDimensions(av);
91
92     addComponentListener(new ComponentAdapter()
93     {
94       @Override
95       public void componentResized(ComponentEvent evt)
96       {
97         if ((getWidth() != od.getWidth())
98                 || (getHeight() != (od.getHeight())))
99         {
100           updateOverviewImage();
101         }
102       }
103     });
104
105     addMouseMotionListener(new MouseMotionAdapter()
106     {
107       @Override
108       public void mouseDragged(MouseEvent evt)
109       {
110         if (!av.getWrapAlignment())
111         {
112           // TODO: feature: jv2.5 detect shift drag and update selection from
113           // it.
114           od.setBoxX(evt.getX());
115           od.setBoxY(evt.getY());
116           checkValid();
117         }
118       }
119     });
120
121     addMouseListener(new MouseAdapter()
122     {
123       @Override
124       public void mousePressed(MouseEvent evt)
125       {
126         if (!av.getWrapAlignment())
127         {
128           od.setBoxX(evt.getX());
129           od.setBoxY(evt.getY());
130           checkValid();
131         }
132       }
133     });
134
135     updateOverviewImage();
136   }
137
138   /**
139    * Check box dimensions and scroll positions and correct if necessary
140    */
141   private void checkValid()
142   {
143     od.checkValid();
144     ap.setScrollValues(od.getScrollCol(), od.getScrollRow());
145
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()); // width = getWidth();
164       od.setHeight(getHeight()); // sequencesHeight = getHeight() - graphHeight;
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 scale based on current alignment width and height
196     od.updateScales();
197
198     // calculate sampleCol and sampleRow
199     // alignment width is max number of residues/bases
200     // alignment height is number of sequences
201     int alwidth = av.getAlignment().getWidth();
202     int alheight = av.getAlignment().getHeight()
203             + av.getAlignment().getHiddenSequences().getSize();
204
205     // sampleCol or sampleRow is the width/height allocated to each residue
206     // in particular, sometimes we may need more than one row/col of the
207     // BufferedImage allocated
208     // sampleCol is how much of a residue to assign to each pixel
209     // sampleRow is how many sequences to assign to each pixel
210     float sampleCol = alwidth / (float) od.getWidth();
211     float sampleRow = alheight / (float) od.getSequencesHeight();
212
213     buildImage(sampleRow, sampleCol);
214
215     if (av.getAlignmentConservationAnnotation() != null)
216     {
217       renderer.updateFromAlignViewport(av);
218       for (int col = 0; col < od.getWidth() && !resizeAgain; col++)
219       {
220         mg.translate(col, od.getSequencesHeight());
221         renderer.drawGraph(mg, av.getAlignmentConservationAnnotation(),
222                 av.getAlignmentConservationAnnotation().annotations,
223                 (int) (sampleCol) + 1, od.getGraphHeight(),
224                 (int) (col * sampleCol), (int) (col * sampleCol) + 1);
225         mg.translate(-col, -od.getSequencesHeight());
226
227       }
228     }
229     System.gc();
230
231     resizing = false;
232
233     if (resizeAgain)
234     {
235       resizeAgain = false;
236       updateOverviewImage();
237     }
238     else
239     {
240       lastMiniMe = miniMe;
241     }
242
243     setBoxPosition();
244   }
245
246   private void buildImage(float sampleRow, float sampleCol)
247   {
248     int lastcol = -1;
249     int lastrow = -1;
250     int color = Color.white.getRGB();
251
252     jalview.datamodel.SequenceI seq = null;
253     final boolean hasHiddenRows = av.hasHiddenRows();
254     final boolean hasHiddenCols = av.hasHiddenColumns();
255     boolean hiddenRow = false;
256     // get hidden row and hidden column map once at beginning.
257     // clone featureRenderer settings to avoid race conditions... if state is
258     // updated just need to refresh again
259     for (int row = 0; row < od.getSequencesHeight() && !resizeAgain; row++)
260     {
261       boolean doCopy = true;
262       int currentrow = (int) (row * sampleRow);
263       if (currentrow != lastrow)
264       {
265         doCopy = false;
266
267         lastrow = currentrow;
268
269         hiddenRow = false;
270         // should be method(s) in Alignment
271         if (hasHiddenRows)
272         {
273           seq = av.getAlignment().getHiddenSequences()
274                   .getHiddenSequence(lastrow);
275           if (seq == null)
276           {
277             int index = av.getAlignment().getHiddenSequences()
278                     .findIndexWithoutHiddenSeqs(lastrow);
279
280             seq = av.getAlignment().getSequenceAt(index);
281           }
282           else
283           {
284             hiddenRow = true;
285           }
286         }
287         else
288         {
289           seq = av.getAlignment().getSequenceAt(lastrow);
290         }
291         // end of Alignment method(s)
292       }
293
294       if (seq == null)
295       {
296         System.out.println(lastrow + " null");
297         continue;
298       }
299
300       for (int col = 0; col < od.getWidth() && !resizeAgain; col++)
301       {
302         if (doCopy)
303         {
304           color = miniMe.getRGB(col, row - 1);
305         }
306         else if ((int) (col * sampleCol) != lastcol
307                 || (int) (row * sampleRow) != lastrow)
308         {
309           lastcol = (int) (col * sampleCol);
310           color = getColumnColourFromSequence(seq, hiddenRow, hasHiddenCols,
311                   lastcol);
312         }
313         // else if ((int) (col * sampleCol) == lastcol && (int) (row *
314         // sampleRow) == lastrow))
315         // we just use the color we already have , so don't need to set it
316
317         miniMe.setRGB(col, row, color);
318       }
319     }
320   }
321
322   /*
323    * Find the colour of a sequence at a specified column position
324    */
325   private int getColumnColourFromSequence(jalview.datamodel.SequenceI seq,
326           boolean hiddenRow, boolean hasHiddenCols, int lastcol)
327   {
328     int color;
329
330     if (seq.getLength() > lastcol)
331     {
332       color = sr.getResidueBoxColour(seq, lastcol).getRGB();
333
334       if (av.isShowSequenceFeatures())
335       {
336         color = fr.findFeatureColour(color, seq, lastcol);
337       }
338     }
339     else
340     {
341       color = Color.white.getRGB(); // White
342     }
343
344     if (hiddenRow
345             || (hasHiddenCols && !av.getColumnSelection()
346                     .isVisible(lastcol)))
347     {
348       color = new Color(color).darker().darker().getRGB();
349     }
350
351     return color;
352   }
353
354   /**
355    * Update the overview panel box when the associated alignment panel is
356    * changed
357    * 
358    */
359   public void setBoxPosition()
360   {
361     od.setBoxPosition();
362     repaint();
363   }
364
365
366   @Override
367   public void paintComponent(Graphics g)
368   {
369     if (resizing || resizeAgain)
370     {
371       if (lastMiniMe == null)
372       {
373         g.setColor(Color.white);
374         g.fillRect(0, 0, getWidth(), getHeight());
375       }
376       else
377       {
378         g.drawImage(lastMiniMe, 0, 0, getWidth(), getHeight(), this);
379       }
380       g.setColor(TRANS_GREY);
381       g.fillRect(0, 0, getWidth(), getHeight());
382     }
383     else if (lastMiniMe != null)
384     {
385       g.drawImage(lastMiniMe, 0, 0, this);
386       if (lastMiniMe != miniMe)
387       {
388         g.setColor(TRANS_GREY);
389         g.fillRect(0, 0, getWidth(), getHeight());
390       }
391     }
392     // TODO: render selected regions
393     g.setColor(Color.red);
394     g.drawRect(od.getBoxX(), od.getBoxY(), od.getBoxWidth(),
395             od.getBoxHeight());
396     g.drawRect(od.getBoxX() + 1, od.getBoxY() + 1, od.getBoxWidth() - 2,
397             od.getBoxHeight() - 2);
398   }
399 }