JAL-2446 FeatureStore with NCList - work in progress
[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       // long start = System.currentTimeMillis();
353       for (col = 0; col < width; col++)
354       {
355         if (resizeAgain)
356         {
357           break;
358         }
359         if ((int) (col * sampleCol) == lastcol
360                 && (int) (row * sampleRow) == lastrow)
361         {
362           miniMe.setRGB(col, row, color);
363           continue;
364         }
365
366         lastcol = (int) (col * sampleCol);
367
368         if (seq.getLength() > lastcol)
369         {
370           color = sr.getResidueBoxColour(seq, lastcol).getRGB();
371
372           if (av.isShowSequenceFeatures())
373           {
374             color = fr.findFeatureColour(color, seq, lastcol);
375           }
376         }
377         else
378         {
379           color = -1; // White
380         }
381
382         if (hiddenRow
383                 || (hasHiddenCols && !av.getColumnSelection().isVisible(
384                         lastcol)))
385         {
386           color = new Color(color).darker().darker().getRGB();
387         }
388
389         miniMe.setRGB(col, row, color);
390
391       }
392       // System.out.println(String.format("Row %d with width %d took %dms",
393       // row, width, System.currentTimeMillis() - start));
394     }
395
396     if (av.getAlignmentConservationAnnotation() != null)
397     {
398       renderer.updateFromAlignViewport(av);
399       for (col = 0; col < width; col++)
400       {
401         if (resizeAgain)
402         {
403           break;
404         }
405         lastcol = (int) (col * sampleCol);
406         {
407           mg.translate(col, sequencesHeight);
408           renderer.drawGraph(mg, av.getAlignmentConservationAnnotation(),
409                   av.getAlignmentConservationAnnotation().annotations,
410                   (int) (sampleCol) + 1, graphHeight,
411                   (int) (col * sampleCol), (int) (col * sampleCol) + 1);
412           mg.translate(-col, -sequencesHeight);
413         }
414       }
415     }
416     System.gc();
417
418     resizing = false;
419
420     if (resizeAgain)
421     {
422       resizeAgain = false;
423       updateOverviewImage();
424     }
425     else
426     {
427       lastMiniMe = miniMe;
428     }
429
430     setBoxPosition();
431   }
432
433   /**
434    * DOCUMENT ME!
435    */
436   public void setBoxPosition()
437   {
438     int fullsizeWidth = av.getAlignment().getWidth() * av.getCharWidth();
439     int fullsizeHeight = (av.getAlignment().getHeight() + av.getAlignment()
440             .getHiddenSequences().getSize())
441             * av.getCharHeight();
442
443     int startRes = av.getStartRes();
444     int endRes = av.getEndRes();
445
446     if (av.hasHiddenColumns())
447     {
448       startRes = av.getColumnSelection().adjustForHiddenColumns(startRes);
449       endRes = av.getColumnSelection().adjustForHiddenColumns(endRes);
450     }
451
452     int startSeq = av.startSeq;
453     int endSeq = av.endSeq;
454
455     if (av.hasHiddenRows())
456     {
457       startSeq = av.getAlignment().getHiddenSequences()
458               .adjustForHiddenSeqs(startSeq);
459
460       endSeq = av.getAlignment().getHiddenSequences()
461               .adjustForHiddenSeqs(endSeq);
462
463     }
464
465     scalew = (float) width / (float) fullsizeWidth;
466     scaleh = (float) sequencesHeight / (float) fullsizeHeight;
467
468     boxX = (int) (startRes * av.getCharWidth() * scalew);
469     boxY = (int) (startSeq * av.getCharHeight() * scaleh);
470
471     if (av.hasHiddenColumns())
472     {
473       boxWidth = (int) ((endRes - startRes + 1) * av.getCharWidth() * scalew);
474     }
475     else
476     {
477       boxWidth = (int) ((endRes - startRes + 1) * av.getCharWidth() * scalew);
478     }
479
480     boxHeight = (int) ((endSeq - startSeq) * av.getCharHeight() * scaleh);
481
482     repaint();
483   }
484
485   private BufferedImage lastMiniMe = null;
486
487   /**
488    * DOCUMENT ME!
489    * 
490    * @param g
491    *          DOCUMENT ME!
492    */
493   @Override
494   public void paintComponent(Graphics g)
495   {
496     if (resizing || resizeAgain)
497     {
498       if (lastMiniMe == null)
499       {
500         g.setColor(Color.white);
501         g.fillRect(0, 0, getWidth(), getHeight());
502       }
503       else
504       {
505         g.drawImage(lastMiniMe, 0, 0, getWidth(), getHeight(), this);
506       }
507       g.setColor(new Color(100, 100, 100, 25));
508       g.fillRect(0, 0, getWidth(), getHeight());
509     }
510     else if (lastMiniMe != null)
511     {
512       g.drawImage(lastMiniMe, 0, 0, this);
513       if (lastMiniMe != miniMe)
514       {
515         g.setColor(new Color(100, 100, 100, 25));
516         g.fillRect(0, 0, getWidth(), getHeight());
517       }
518     }
519     // TODO: render selected regions
520     g.setColor(Color.red);
521     g.drawRect(boxX, boxY, boxWidth, boxHeight);
522     g.drawRect(boxX + 1, boxY + 1, boxWidth - 2, boxHeight - 2);
523   }
524 }