JAL-2089 patch broken merge to master for Release 2.10.0b1
[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     // get hidden row and hidden column map once at beginning.
299     // clone featureRenderer settings to avoid race conditions... if state is
300     // updated just need to refresh again
301     for (row = 0; row < sequencesHeight; row++)
302     {
303       if (resizeAgain)
304       {
305         break;
306       }
307       if ((int) (row * sampleRow) == lastrow)
308       {
309         // No need to recalculate the colours,
310         // Just copy from the row above
311         for (col = 0; col < width; col++)
312         {
313           if (resizeAgain)
314           {
315             break;
316           }
317           miniMe.setRGB(col, row, miniMe.getRGB(col, row - 1));
318         }
319         continue;
320       }
321
322       lastrow = (int) (row * sampleRow);
323
324       hiddenRow = false;
325       if (hasHiddenRows)
326       {
327         seq = av.getAlignment().getHiddenSequences()
328                 .getHiddenSequence(lastrow);
329         if (seq == null)
330         {
331           int index = av.getAlignment().getHiddenSequences()
332                   .findIndexWithoutHiddenSeqs(lastrow);
333
334           seq = av.getAlignment().getSequenceAt(index);
335         }
336         else
337         {
338           hiddenRow = true;
339         }
340       }
341       else
342       {
343         seq = av.getAlignment().getSequenceAt(lastrow);
344       }
345
346       if (seq == null)
347       {
348         System.out.println(lastrow + " null");
349         continue;
350       }
351
352       for (col = 0; col < width; col++)
353       {
354         if (resizeAgain)
355         {
356           break;
357         }
358         if ((int) (col * sampleCol) == lastcol
359                 && (int) (row * sampleRow) == lastrow)
360         {
361           miniMe.setRGB(col, row, color);
362           continue;
363         }
364
365         lastcol = (int) (col * sampleCol);
366
367         if (seq.getLength() > lastcol)
368         {
369           color = sr.getResidueBoxColour(seq, lastcol).getRGB();
370
371           if (av.isShowSequenceFeatures())
372           {
373             color = fr.findFeatureColour(color, seq, lastcol);
374           }
375         }
376         else
377         {
378           color = -1; // White
379         }
380
381         if (hiddenRow
382                 || (hasHiddenCols && !av.getColumnSelection().isVisible(
383                         lastcol)))
384         {
385           color = new Color(color).darker().darker().getRGB();
386         }
387
388         miniMe.setRGB(col, row, color);
389
390       }
391     }
392
393     if (av.getAlignmentConservationAnnotation() != null)
394     {
395       renderer.updateFromAlignViewport(av);
396       for (col = 0; col < width; col++)
397       {
398         if (resizeAgain)
399         {
400           break;
401         }
402         lastcol = (int) (col * sampleCol);
403         {
404           mg.translate(col, sequencesHeight);
405           renderer.drawGraph(mg, av.getAlignmentConservationAnnotation(),
406                   av.getAlignmentConservationAnnotation().annotations,
407                   (int) (sampleCol) + 1, graphHeight,
408                   (int) (col * sampleCol), (int) (col * sampleCol) + 1);
409           mg.translate(-col, -sequencesHeight);
410         }
411       }
412     }
413     System.gc();
414
415     resizing = false;
416
417     if (resizeAgain)
418     {
419       resizeAgain = false;
420       updateOverviewImage();
421     }
422     else
423     {
424       lastMiniMe = miniMe;
425     }
426
427     setBoxPosition();
428   }
429
430   /**
431    * DOCUMENT ME!
432    */
433   public void setBoxPosition()
434   {
435     int fullsizeWidth = av.getAlignment().getWidth() * av.getCharWidth();
436     int fullsizeHeight = (av.getAlignment().getHeight() + av.getAlignment()
437             .getHiddenSequences().getSize())
438             * av.getCharHeight();
439
440     int startRes = av.getStartRes();
441     int endRes = av.getEndRes();
442
443     if (av.hasHiddenColumns())
444     {
445       startRes = av.getColumnSelection().adjustForHiddenColumns(startRes);
446       endRes = av.getColumnSelection().adjustForHiddenColumns(endRes);
447     }
448
449     int startSeq = av.startSeq;
450     int endSeq = av.endSeq;
451
452     if (av.hasHiddenRows())
453     {
454       startSeq = av.getAlignment().getHiddenSequences()
455               .adjustForHiddenSeqs(startSeq);
456
457       endSeq = av.getAlignment().getHiddenSequences()
458               .adjustForHiddenSeqs(endSeq);
459
460     }
461
462     scalew = (float) width / (float) fullsizeWidth;
463     scaleh = (float) sequencesHeight / (float) fullsizeHeight;
464
465     boxX = (int) (startRes * av.getCharWidth() * scalew);
466     boxY = (int) (startSeq * av.getCharHeight() * scaleh);
467
468     if (av.hasHiddenColumns())
469     {
470       boxWidth = (int) ((endRes - startRes + 1) * av.getCharWidth() * scalew);
471     }
472     else
473     {
474       boxWidth = (int) ((endRes - startRes + 1) * av.getCharWidth() * scalew);
475     }
476
477     boxHeight = (int) ((endSeq - startSeq) * av.getCharHeight() * scaleh);
478
479     repaint();
480   }
481
482   private BufferedImage lastMiniMe = null;
483
484   /**
485    * DOCUMENT ME!
486    * 
487    * @param g
488    *          DOCUMENT ME!
489    */
490   @Override
491   public void paintComponent(Graphics g)
492   {
493     if (resizing || resizeAgain)
494     {
495       if (lastMiniMe == null)
496       {
497         g.setColor(Color.white);
498         g.fillRect(0, 0, getWidth(), getHeight());
499       }
500       else
501       {
502         g.drawImage(lastMiniMe, 0, 0, getWidth(), getHeight(), this);
503       }
504       g.setColor(new Color(100, 100, 100, 25));
505       g.fillRect(0, 0, getWidth(), getHeight());
506     }
507     else if (lastMiniMe != null)
508     {
509       g.drawImage(lastMiniMe, 0, 0, this);
510       if (lastMiniMe != miniMe)
511       {
512         g.setColor(new Color(100, 100, 100, 25));
513         g.fillRect(0, 0, getWidth(), getHeight());
514       }
515     }
516     // TODO: render selected regions
517     g.setColor(Color.red);
518     g.drawRect(boxX, boxY, boxWidth, boxHeight);
519     g.drawRect(boxX + 1, boxY + 1, boxWidth - 2, boxHeight - 2);
520   }
521 }