JAL-2388 Factored out applet overview canvas, threading adjustments
[jalview.git] / src / jalview / gui / OverviewCanvas.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.api.AlignViewportI;
24 import jalview.datamodel.SequenceI;
25 import jalview.renderer.AnnotationRenderer;
26 import jalview.renderer.seqfeatures.FeatureColourFinder;
27 import jalview.viewmodel.OverviewDimensions;
28
29 import java.awt.Color;
30 import java.awt.Dimension;
31 import java.awt.Graphics;
32 import java.awt.image.BufferedImage;
33
34 import javax.swing.JComponent;
35
36
37 public class OverviewCanvas extends JComponent
38 {
39   private static final Color TRANS_GREY = new Color(100, 100, 100, 25);
40
41   // This is set true if the alignment view changes whilst
42   // the overview is being calculated
43   private volatile boolean restart = false;
44
45   private volatile boolean updaterunning = false;
46
47   private BufferedImage miniMe;
48
49   private BufferedImage lastMiniMe = null;
50
51
52
53   // Can set different properties in this seqCanvas than
54   // main visible SeqCanvas
55   private SequenceRenderer sr;
56
57   private jalview.renderer.seqfeatures.FeatureRenderer fr;
58
59   private final AnnotationRenderer renderer = new AnnotationRenderer();
60
61   OverviewDimensions od;
62
63   AlignViewport av;
64
65   AlignmentPanel ap;
66
67   public OverviewCanvas(OverviewDimensions overviewDims,
68           AlignViewportI alignvp, AlignmentPanel alignp)
69   {
70     od = overviewDims;
71     av = alignp.av;
72     ap = alignp;
73
74     sr = new SequenceRenderer(av);
75     sr.renderGaps = false;
76     sr.forOverview = true;
77     fr = new FeatureRenderer(ap);
78   }
79
80   /*
81    * Signals to drawing code that the associated alignment viewport
82    * has changed and a redraw will be required
83    */
84   public boolean restartDraw()
85   {
86     synchronized (this)
87     {
88       if (updaterunning)
89       {
90         restart = true;
91       }
92       else
93       {
94         updaterunning = true;
95       }
96       return restart;
97     }
98   }
99
100   public void draw(boolean showSequenceFeatures, boolean showAnnotation)
101   {
102     miniMe = null;
103
104     if (showSequenceFeatures)
105     {
106       fr.transferSettings(ap.getSeqPanel().seqCanvas.getFeatureRenderer());
107     }
108
109     // why do we need to set preferred size again? was set in
110     // updateOverviewImage
111     setPreferredSize(new Dimension(od.getWidth(), od.getHeight()));
112
113     miniMe = new BufferedImage(od.getWidth(), od.getHeight(),
114             BufferedImage.TYPE_INT_RGB);
115
116     Graphics mg = miniMe.getGraphics();
117     mg.setColor(Color.orange);
118     mg.fillRect(0, 0, od.getWidth(), miniMe.getHeight());
119
120     // calculate sampleCol and sampleRow
121     // alignment width is max number of residues/bases
122     // alignment height is number of sequences
123     int alwidth = av.getAlignment().getWidth();
124     int alheight = av.getAlignment().getAbsoluteHeight();
125
126     // sampleCol or sampleRow is the width/height allocated to each residue
127     // in particular, sometimes we may need more than one row/col of the
128     // BufferedImage allocated
129     // sampleCol is how much of a residue to assign to each pixel
130     // sampleRow is how many sequences to assign to each pixel
131     float sampleCol = alwidth / (float) od.getWidth();
132     float sampleRow = alheight / (float) od.getSequencesHeight();
133
134     buildImage(sampleRow, sampleCol);
135
136     if (showAnnotation)
137     {
138       renderer.updateFromAlignViewport(av);
139       for (int col = 0; col < od.getWidth() && !restart; col++)
140       {
141         mg.translate(col, od.getSequencesHeight());
142         renderer.drawGraph(mg, av.getAlignmentConservationAnnotation(),
143                 av.getAlignmentConservationAnnotation().annotations,
144                 (int) (sampleCol) + 1, od.getGraphHeight(),
145                 (int) (col * sampleCol), (int) (col * sampleCol) + 1);
146         mg.translate(-col, -od.getSequencesHeight());
147
148       }
149     }
150     System.gc();
151
152     if (restart)
153     {
154       restart = false;
155       draw(showSequenceFeatures, showAnnotation);
156     }
157     else
158     {
159       updaterunning = false;
160       lastMiniMe = miniMe;
161     }
162   }
163
164   @Override
165   public void paintComponent(Graphics g)
166   {
167     if (restart)
168     {
169       if (lastMiniMe == null)
170       {
171         g.setColor(Color.white);
172         g.fillRect(0, 0, getWidth(), getHeight());
173       }
174       else
175       {
176         g.drawImage(lastMiniMe, 0, 0, getWidth(), getHeight(), this);
177       }
178       g.setColor(TRANS_GREY);
179       g.fillRect(0, 0, getWidth(), getHeight());
180     }
181     else if (lastMiniMe != null)
182     {
183       g.drawImage(lastMiniMe, 0, 0, this);
184       if (lastMiniMe != miniMe)
185       {
186         g.setColor(TRANS_GREY);
187         g.fillRect(0, 0, getWidth(), getHeight());
188       }
189     }
190
191     g.setColor(Color.red);
192     od.drawBox(g);
193   }
194
195   /*
196    * Build the overview panel image
197    */
198   private void buildImage(float sampleRow, float sampleCol)
199   {
200     int lastcol = -1;
201     int lastrow = -1;
202     int rgbcolor = Color.white.getRGB();
203
204     SequenceI seq = null;
205     FeatureColourFinder finder = new FeatureColourFinder(fr);
206
207     final boolean hasHiddenCols = av.hasHiddenColumns();
208     boolean hiddenRow = false;
209     // get hidden row and hidden column map once at beginning.
210     // clone featureRenderer settings to avoid race conditions... if state is
211     // updated just need to refresh again
212     for (int row = 0; row < od.getSequencesHeight() && !restart; row++)
213     {
214       boolean doCopy = true;
215       int currentrow = (int) (row * sampleRow);
216       if (currentrow != lastrow)
217       {
218         doCopy = false;
219
220         lastrow = currentrow;
221
222         // get the sequence which would be at alignment index 'lastrow' if no
223         // rows were hidden, and determine whether it is hidden or not
224         hiddenRow = av.getAlignment().isHidden(lastrow);
225         seq = av.getAlignment().getSequenceAtAbsoluteIndex(lastrow);
226       }
227
228       for (int col = 0; col < od.getWidth() && !restart; col++)
229       {
230         if (doCopy)
231         {
232           rgbcolor = miniMe.getRGB(col, row - 1);
233         }
234         else if ((int) (col * sampleCol) != lastcol
235                 || (int) (row * sampleRow) != lastrow)
236         {
237           lastcol = (int) (col * sampleCol);
238           rgbcolor = getColumnColourFromSequence(seq, hiddenRow,
239                   hasHiddenCols, lastcol, finder);
240         }
241         // else we just use the color we already have , so don't need to set it
242
243         miniMe.setRGB(col, row, rgbcolor);
244       }
245     }
246   }
247
248   /*
249    * Find the colour of a sequence at a specified column position
250    */
251   private int getColumnColourFromSequence(jalview.datamodel.SequenceI seq,
252           boolean hiddenRow, boolean hasHiddenCols, int lastcol,
253           FeatureColourFinder finder)
254   {
255     Color color = Color.white;
256
257     if ((seq != null) && (seq.getLength() > lastcol))
258     {
259       color = sr.getResidueColour(seq, lastcol, finder);
260     }
261
262     if (hiddenRow
263             || (hasHiddenCols && !av.getColumnSelection()
264                     .isVisible(lastcol)))
265     {
266       color = color.darker().darker();
267     }
268
269     return color.getRGB();
270   }
271
272 }