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