JAL-2388 Unit tests and tidies
[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.datamodel.SequenceI;
24 import jalview.renderer.AnnotationRenderer;
25 import jalview.viewmodel.OverviewDimensions;
26
27 import java.awt.Color;
28 import java.awt.Dimension;
29 import java.awt.Graphics;
30 import java.awt.event.ComponentAdapter;
31 import java.awt.event.ComponentEvent;
32 import java.awt.event.MouseAdapter;
33 import java.awt.event.MouseEvent;
34 import java.awt.event.MouseMotionAdapter;
35 import java.awt.image.BufferedImage;
36
37 import javax.swing.JPanel;
38
39 /**
40  * Panel displaying an overview of the full alignment, with an interactive box
41  * representing the viewport onto the alignment.
42  * 
43  * @author $author$
44  * @version $Revision$
45  */
46 public class OverviewPanel extends JPanel implements Runnable
47 {
48   private static final Color TRANS_GREY = new Color(100, 100, 100, 25);
49
50   private final AnnotationRenderer renderer = new AnnotationRenderer();
51
52   private OverviewDimensions od;
53
54   private BufferedImage miniMe;
55
56   private BufferedImage lastMiniMe = null;
57
58   private AlignViewport av;
59
60   private AlignmentPanel ap;
61
62   //
63   private boolean resizing = false;
64
65   // This is set true if the user resizes whilst
66   // the overview is being calculated
67   private boolean resizeAgain = false;
68
69   // Can set different properties in this seqCanvas than
70   // main visible SeqCanvas
71   private SequenceRenderer sr;
72
73   private jalview.renderer.seqfeatures.FeatureRenderer fr;
74
75   /**
76    * Creates a new OverviewPanel object.
77    * 
78    * @param alPanel
79    *          The alignment panel which is shown in the overview panel
80    */
81   public OverviewPanel(AlignmentPanel alPanel)
82   {
83     this.av = alPanel.av;
84     this.ap = alPanel;
85     setLayout(null);
86
87     sr = new SequenceRenderer(av);
88     sr.renderGaps = false;
89     sr.forOverview = true;
90     fr = new FeatureRenderer(alPanel);
91
92     od = new OverviewDimensions(av.getPosProps(), av.isShowAnnotation());
93
94     addComponentListener(new ComponentAdapter()
95     {
96       @Override
97       public void componentResized(ComponentEvent evt)
98       {
99         if ((getWidth() != od.getWidth())
100                 || (getHeight() != (od.getHeight())))
101         {
102           updateOverviewImage();
103         }
104       }
105     });
106
107     addMouseMotionListener(new MouseMotionAdapter()
108     {
109       @Override
110       public void mouseDragged(MouseEvent evt)
111       {
112         if (!av.getWrapAlignment())
113         {
114           od.updateViewportFromMouse(evt.getX(), evt.getY(), av
115                   .getAlignment().getHiddenSequences(), av
116                   .getColumnSelection(), av.getPosProps());
117           ap.setScrollValues(od.getScrollCol(), od.getScrollRow());
118         }
119       }
120     });
121
122     addMouseListener(new MouseAdapter()
123     {
124       @Override
125       public void mousePressed(MouseEvent evt)
126       {
127         if (!av.getWrapAlignment())
128         {
129           od.updateViewportFromMouse(evt.getX(), evt.getY(), av
130                   .getAlignment().getHiddenSequences(), av
131                   .getColumnSelection(), av.getPosProps());
132           ap.setScrollValues(od.getScrollCol(), od.getScrollRow());
133         }
134       }
135     });
136
137     updateOverviewImage();
138   }
139
140   /**
141    * Updates the overview image when the related alignment panel is updated
142    */
143   public void updateOverviewImage()
144   {
145     if (resizing)
146     {
147       resizeAgain = true;
148       return;
149     }
150
151     resizing = true;
152
153     if ((getWidth() > 0) && (getHeight() > 0))
154     {
155       od.setWidth(getWidth());
156       od.setHeight(getHeight());
157     }
158
159     setPreferredSize(new Dimension(od.getWidth(), od.getHeight()));
160
161     Thread thread = new Thread(this);
162     thread.start();
163     repaint();
164   }
165
166   @Override
167   public void run()
168   {
169     miniMe = null;
170
171     if (av.isShowSequenceFeatures())
172     {
173       fr.transferSettings(ap.getSeqPanel().seqCanvas.getFeatureRenderer());
174     }
175
176     // why do we need to set preferred size again? was set in
177     // updateOverviewImage
178     setPreferredSize(new Dimension(od.getWidth(), od.getHeight()));
179
180     miniMe = new BufferedImage(od.getWidth(), od.getHeight(),
181             BufferedImage.TYPE_INT_RGB);
182
183     Graphics mg = miniMe.getGraphics();
184     mg.setColor(Color.orange);
185     mg.fillRect(0, 0, od.getWidth(), miniMe.getHeight());
186
187     // calculate sampleCol and sampleRow
188     // alignment width is max number of residues/bases
189     // alignment height is number of sequences
190     int alwidth = av.getAlignment().getWidth();
191     int alheight = av.getAlignment().getAbsoluteHeight();
192
193     // sampleCol or sampleRow is the width/height allocated to each residue
194     // in particular, sometimes we may need more than one row/col of the
195     // BufferedImage allocated
196     // sampleCol is how much of a residue to assign to each pixel
197     // sampleRow is how many sequences to assign to each pixel
198     float sampleCol = alwidth / (float) od.getWidth();
199     float sampleRow = alheight / (float) od.getSequencesHeight();
200
201     buildImage(sampleRow, sampleCol);
202
203     if (av.isShowAnnotation())
204     {
205       renderer.updateFromAlignViewport(av);
206       for (int col = 0; col < od.getWidth() && !resizeAgain; col++)
207       {
208         mg.translate(col, od.getSequencesHeight());
209         renderer.drawGraph(mg, av.getAlignmentConservationAnnotation(),
210                 av.getAlignmentConservationAnnotation().annotations,
211                 (int) (sampleCol) + 1, od.getGraphHeight(),
212                 (int) (col * sampleCol), (int) (col * sampleCol) + 1);
213         mg.translate(-col, -od.getSequencesHeight());
214
215       }
216     }
217     System.gc();
218
219     resizing = false;
220
221     if (resizeAgain)
222     {
223       resizeAgain = false;
224       updateOverviewImage();
225     }
226     else
227     {
228       lastMiniMe = miniMe;
229     }
230
231     setBoxPosition();
232   }
233
234   /*
235    * Build the overview panel image
236    */
237   private void buildImage(float sampleRow, float sampleCol)
238   {
239     int lastcol = -1;
240     int lastrow = -1;
241     int color = Color.white.getRGB();
242
243     SequenceI seq = null;
244
245     final boolean hasHiddenCols = av.hasHiddenColumns();
246     boolean hiddenRow = false;
247     // get hidden row and hidden column map once at beginning.
248     // clone featureRenderer settings to avoid race conditions... if state is
249     // updated just need to refresh again
250     for (int row = 0; row < od.getSequencesHeight() && !resizeAgain; row++)
251     {
252       boolean doCopy = true;
253       int currentrow = (int) (row * sampleRow);
254       if (currentrow != lastrow)
255       {
256         doCopy = false;
257
258         lastrow = currentrow;
259
260         // get the sequence which would be at alignment index 'lastrow' if no
261         // columns were hidden, and determine whether it is hidden or not
262         hiddenRow = av.getAlignment().isHidden(lastrow);
263         seq = av.getAlignment().getSequenceAtAbsoluteIndex(lastrow);
264       }
265
266       for (int col = 0; col < od.getWidth() && !resizeAgain; col++)
267       {
268         if (doCopy)
269         {
270           color = miniMe.getRGB(col, row - 1);
271         }
272         else if ((int) (col * sampleCol) != lastcol
273                 || (int) (row * sampleRow) != lastrow)
274         {
275           lastcol = (int) (col * sampleCol);
276           color = getColumnColourFromSequence(seq, hiddenRow, hasHiddenCols,
277                   lastcol);
278         }
279         // else we just use the color we already have , so don't need to set it
280
281         miniMe.setRGB(col, row, color);
282       }
283     }
284   }
285
286   /*
287    * Find the colour of a sequence at a specified column position
288    */
289   private int getColumnColourFromSequence(jalview.datamodel.SequenceI seq,
290           boolean hiddenRow, boolean hasHiddenCols, int lastcol)
291   {
292     int color;
293
294     if (seq == null)
295     {
296       color = Color.white.getRGB();
297     }
298     else if (seq.getLength() > lastcol)
299     {
300       color = sr.getResidueBoxColour(seq, lastcol).getRGB();
301
302       if (av.isShowSequenceFeatures())
303       {
304         color = fr.findFeatureColour(color, seq, lastcol);
305       }
306     }
307     else
308     {
309       color = Color.white.getRGB();
310     }
311
312     if (hiddenRow
313             || (hasHiddenCols && !av.getColumnSelection()
314                     .isVisible(lastcol)))
315     {
316       color = new Color(color).darker().darker().getRGB();
317     }
318
319     return color;
320   }
321
322   /**
323    * Update the overview panel box when the associated alignment panel is
324    * changed
325    * 
326    */
327   public void setBoxPosition()
328   {
329     od.setBoxPosition(av.getAlignment()
330             .getHiddenSequences(), av.getColumnSelection(), av.getPosProps());
331     repaint();
332   }
333
334
335   @Override
336   public void paintComponent(Graphics g)
337   {
338     if (resizing || resizeAgain)
339     {
340       if (lastMiniMe == null)
341       {
342         g.setColor(Color.white);
343         g.fillRect(0, 0, getWidth(), getHeight());
344       }
345       else
346       {
347         g.drawImage(lastMiniMe, 0, 0, getWidth(), getHeight(), this);
348       }
349       g.setColor(TRANS_GREY);
350       g.fillRect(0, 0, getWidth(), getHeight());
351     }
352     else if (lastMiniMe != null)
353     {
354       g.drawImage(lastMiniMe, 0, 0, this);
355       if (lastMiniMe != miniMe)
356       {
357         g.setColor(TRANS_GREY);
358         g.fillRect(0, 0, getWidth(), getHeight());
359       }
360     }
361
362     g.setColor(Color.red);
363     od.drawBox(g);
364   }
365 }