JAL-2034 break out of redraw loop early if another update has already been requested
[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
25 import java.awt.Color;
26 import java.awt.Dimension;
27 import java.awt.Graphics;
28 import java.awt.event.ComponentAdapter;
29 import java.awt.event.ComponentEvent;
30 import java.awt.event.MouseAdapter;
31 import java.awt.event.MouseEvent;
32 import java.awt.event.MouseMotionAdapter;
33 import java.awt.image.BufferedImage;
34
35 import javax.swing.JPanel;
36
37 /**
38  * DOCUMENT ME!
39  * 
40  * @author $author$
41  * @version $Revision$
42  */
43 public class OverviewPanel extends JPanel implements Runnable
44 {
45   BufferedImage miniMe;
46
47   AlignViewport av;
48
49   AlignmentPanel ap;
50
51   final AnnotationRenderer renderer = new AnnotationRenderer();
52
53   float scalew = 1f;
54
55   float scaleh = 1f;
56
57   int width;
58
59   int sequencesHeight;
60
61   int graphHeight = 20;
62
63   int boxX = -1;
64
65   int boxY = -1;
66
67   int boxWidth = -1;
68
69   int boxHeight = -1;
70
71   boolean resizing = false;
72
73   // Can set different properties in this seqCanvas than
74   // main visible SeqCanvas
75   SequenceRenderer sr;
76
77   jalview.renderer.seqfeatures.FeatureRenderer fr;
78
79   /**
80    * Creates a new OverviewPanel object.
81    * 
82    * @param ap
83    *          DOCUMENT ME!
84    */
85   public OverviewPanel(AlignmentPanel ap)
86   {
87     this.av = ap.av;
88     this.ap = ap;
89     setLayout(null);
90
91     sr = new SequenceRenderer(av);
92     sr.renderGaps = false;
93     sr.forOverview = true;
94     fr = new FeatureRenderer(ap);
95
96     // scale the initial size of overviewpanel to shape of alignment
97     float initialScale = (float) av.getAlignment().getWidth()
98             / (float) av.getAlignment().getHeight();
99
100     if (av.getAlignmentConservationAnnotation() == null)
101     {
102       graphHeight = 0;
103     }
104
105     if (av.getAlignment().getWidth() > av.getAlignment().getHeight())
106     {
107       // wider
108       width = 400;
109       sequencesHeight = (int) (400f / initialScale);
110       if (sequencesHeight < 40)
111       {
112         sequencesHeight = 40;
113       }
114     }
115     else
116     {
117       // taller
118       width = (int) (400f * initialScale);
119       sequencesHeight = 300;
120
121       if (width < 120)
122       {
123         width = 120;
124       }
125     }
126
127     addComponentListener(new ComponentAdapter()
128     {
129       @Override
130       public void componentResized(ComponentEvent evt)
131       {
132         if ((getWidth() != width)
133                 || (getHeight() != (sequencesHeight + graphHeight)))
134         {
135           updateOverviewImage();
136         }
137       }
138     });
139
140     addMouseMotionListener(new MouseMotionAdapter()
141     {
142       @Override
143       public void mouseDragged(MouseEvent evt)
144       {
145         if (!av.getWrapAlignment())
146         {
147           // TODO: feature: jv2.5 detect shift drag and update selection from
148           // it.
149           boxX = evt.getX();
150           boxY = evt.getY();
151           checkValid();
152         }
153       }
154     });
155
156     addMouseListener(new MouseAdapter()
157     {
158       @Override
159       public void mousePressed(MouseEvent evt)
160       {
161         if (!av.getWrapAlignment())
162         {
163           boxX = evt.getX();
164           boxY = evt.getY();
165           checkValid();
166         }
167       }
168     });
169
170     updateOverviewImage();
171   }
172
173   /**
174    * DOCUMENT ME!
175    */
176   void checkValid()
177   {
178     if (boxY < 0)
179     {
180       boxY = 0;
181     }
182
183     if (boxY > (sequencesHeight - boxHeight))
184     {
185       boxY = sequencesHeight - boxHeight + 1;
186     }
187
188     if (boxX < 0)
189     {
190       boxX = 0;
191     }
192
193     if (boxX > (width - boxWidth))
194     {
195       if (av.hasHiddenColumns())
196       {
197         // Try smallest possible box
198         boxWidth = (int) ((av.endRes - av.startRes + 1) * av.getCharWidth() * scalew);
199       }
200       boxX = width - boxWidth;
201     }
202
203     int col = (int) (boxX / scalew / av.getCharWidth());
204     int row = (int) (boxY / scaleh / av.getCharHeight());
205
206     if (av.hasHiddenColumns())
207     {
208       if (!av.getColumnSelection().isVisible(col))
209       {
210         return;
211       }
212
213       col = av.getColumnSelection().findColumnPosition(col);
214     }
215
216     if (av.hasHiddenRows())
217     {
218       row = av.getAlignment().getHiddenSequences()
219               .findIndexWithoutHiddenSeqs(row);
220     }
221
222     ap.setScrollValues(col, row);
223
224   }
225
226   /**
227    * DOCUMENT ME!
228    */
229   public void updateOverviewImage()
230   {
231     if (resizing)
232     {
233       resizeAgain = true;
234       return;
235     }
236
237     resizing = true;
238
239     if ((getWidth() > 0) && (getHeight() > 0))
240     {
241       width = getWidth();
242       sequencesHeight = getHeight() - graphHeight;
243     }
244
245     setPreferredSize(new Dimension(width, sequencesHeight + graphHeight));
246
247     Thread thread = new Thread(this);
248     thread.start();
249     repaint();
250   }
251
252   // This is set true if the user resizes whilst
253   // the overview is being calculated
254   boolean resizeAgain = false;
255
256   /**
257    * DOCUMENT ME!
258    */
259   @Override
260   public void run()
261   {
262     miniMe = null;
263
264     if (av.isShowSequenceFeatures())
265     {
266       fr.transferSettings(ap.getSeqPanel().seqCanvas.getFeatureRenderer());
267     }
268
269     int alwidth = av.getAlignment().getWidth();
270     int alheight = av.getAlignment().getHeight()
271             + av.getAlignment().getHiddenSequences().getSize();
272
273     setPreferredSize(new Dimension(width, sequencesHeight + graphHeight));
274
275     int fullsizeWidth = alwidth * av.getCharWidth();
276     int fullsizeHeight = alheight * av.getCharHeight();
277
278     scalew = (float) width / (float) fullsizeWidth;
279     scaleh = (float) sequencesHeight / (float) fullsizeHeight;
280
281     miniMe = new BufferedImage(width, sequencesHeight + graphHeight,
282             BufferedImage.TYPE_INT_RGB);
283
284     Graphics mg = miniMe.getGraphics();
285     mg.setColor(Color.orange);
286     mg.fillRect(0, 0, width, miniMe.getHeight());
287
288     float sampleCol = (float) alwidth / (float) width;
289     float sampleRow = (float) alheight / (float) sequencesHeight;
290
291     int lastcol = -1, lastrow = -1;
292     int color = Color.white.getRGB();
293     int row, col;
294     jalview.datamodel.SequenceI seq;
295     final boolean hasHiddenRows = av.hasHiddenRows(), hasHiddenCols = av
296             .hasHiddenColumns();
297     boolean hiddenRow = false;
298     for (row = 0; row < sequencesHeight; row++)
299     {
300       if (resizeAgain)
301       {
302         break;
303       }
304       if ((int) (row * sampleRow) == lastrow)
305       {
306         // No need to recalculate the colours,
307         // Just copy from the row above
308         for (col = 0; col < width; col++)
309         {
310           if (resizeAgain)
311           {
312             break;
313           }
314           miniMe.setRGB(col, row, miniMe.getRGB(col, row - 1));
315         }
316         continue;
317       }
318
319       lastrow = (int) (row * sampleRow);
320
321       hiddenRow = false;
322       if (hasHiddenRows)
323       {
324         seq = av.getAlignment().getHiddenSequences()
325                 .getHiddenSequence(lastrow);
326         if (seq == null)
327         {
328           int index = av.getAlignment().getHiddenSequences()
329                   .findIndexWithoutHiddenSeqs(lastrow);
330
331           seq = av.getAlignment().getSequenceAt(index);
332         }
333         else
334         {
335           hiddenRow = true;
336         }
337       }
338       else
339       {
340         seq = av.getAlignment().getSequenceAt(lastrow);
341       }
342
343       if (seq == null)
344       {
345         System.out.println(lastrow + " null");
346         continue;
347       }
348
349       for (col = 0; col < width; col++)
350       {
351         if (resizeAgain)
352         {
353           break;
354         }
355         if ((int) (col * sampleCol) == lastcol
356                 && (int) (row * sampleRow) == lastrow)
357         {
358           miniMe.setRGB(col, row, color);
359           continue;
360         }
361
362         lastcol = (int) (col * sampleCol);
363
364         if (seq.getLength() > lastcol)
365         {
366           color = sr.getResidueBoxColour(seq, lastcol).getRGB();
367
368           if (av.isShowSequenceFeatures())
369           {
370             color = fr.findFeatureColour(color, seq, lastcol);
371           }
372         }
373         else
374         {
375           color = -1; // White
376         }
377
378         if (hiddenRow
379                 || (hasHiddenCols && !av.getColumnSelection().isVisible(
380                         lastcol)))
381         {
382           color = new Color(color).darker().darker().getRGB();
383         }
384
385         miniMe.setRGB(col, row, color);
386
387       }
388     }
389
390     if (av.getAlignmentConservationAnnotation() != null)
391     {
392       renderer.updateFromAlignViewport(av);
393       for (col = 0; col < width; col++)
394       {
395         if (resizeAgain)
396         {
397           break;
398         }
399         lastcol = (int) (col * sampleCol);
400         {
401           mg.translate(col, sequencesHeight);
402           renderer.drawGraph(mg, av.getAlignmentConservationAnnotation(),
403                   av.getAlignmentConservationAnnotation().annotations,
404                   (int) (sampleCol) + 1, graphHeight,
405                   (int) (col * sampleCol), (int) (col * sampleCol) + 1);
406           mg.translate(-col, -sequencesHeight);
407         }
408       }
409     }
410     System.gc();
411
412     resizing = false;
413
414     setBoxPosition();
415
416     if (resizeAgain)
417     {
418       resizeAgain = false;
419       updateOverviewImage();
420     }
421   }
422
423   /**
424    * DOCUMENT ME!
425    */
426   public void setBoxPosition()
427   {
428     int fullsizeWidth = av.getAlignment().getWidth() * av.getCharWidth();
429     int fullsizeHeight = (av.getAlignment().getHeight() + av.getAlignment()
430             .getHiddenSequences().getSize())
431             * av.getCharHeight();
432
433     int startRes = av.getStartRes();
434     int endRes = av.getEndRes();
435
436     if (av.hasHiddenColumns())
437     {
438       startRes = av.getColumnSelection().adjustForHiddenColumns(startRes);
439       endRes = av.getColumnSelection().adjustForHiddenColumns(endRes);
440     }
441
442     int startSeq = av.startSeq;
443     int endSeq = av.endSeq;
444
445     if (av.hasHiddenRows())
446     {
447       startSeq = av.getAlignment().getHiddenSequences()
448               .adjustForHiddenSeqs(startSeq);
449
450       endSeq = av.getAlignment().getHiddenSequences()
451               .adjustForHiddenSeqs(endSeq);
452
453     }
454
455     scalew = (float) width / (float) fullsizeWidth;
456     scaleh = (float) sequencesHeight / (float) fullsizeHeight;
457
458     boxX = (int) (startRes * av.getCharWidth() * scalew);
459     boxY = (int) (startSeq * av.getCharHeight() * scaleh);
460
461     if (av.hasHiddenColumns())
462     {
463       boxWidth = (int) ((endRes - startRes + 1) * av.getCharWidth() * scalew);
464     }
465     else
466     {
467       boxWidth = (int) ((endRes - startRes + 1) * av.getCharWidth() * scalew);
468     }
469
470     boxHeight = (int) ((endSeq - startSeq) * av.getCharHeight() * scaleh);
471
472     repaint();
473   }
474
475   /**
476    * DOCUMENT ME!
477    * 
478    * @param g
479    *          DOCUMENT ME!
480    */
481   @Override
482   public void paintComponent(Graphics g)
483   {
484     if (resizing)
485     {
486       g.setColor(Color.white);
487       g.fillRect(0, 0, getWidth(), getHeight());
488     }
489     else if (miniMe != null)
490     {
491       g.drawImage(miniMe, 0, 0, this);
492     }
493
494     g.setColor(Color.red);
495     g.drawRect(boxX, boxY, boxWidth, boxHeight);
496     g.drawRect(boxX + 1, boxY + 1, boxWidth - 2, boxHeight - 2);
497
498   }
499 }