64bf15c8fb348102ae53f77effafbd0d6b7bb391
[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.bin.Cache;
25 import jalview.renderer.OverviewRenderer;
26 import jalview.renderer.OverviewResColourFinder;
27 import jalview.viewmodel.OverviewDimensions;
28
29 import java.awt.AlphaComposite;
30 import java.awt.Color;
31 import java.awt.Dimension;
32 import java.awt.Graphics;
33 import java.awt.Graphics2D;
34 import java.awt.event.ActionEvent;
35 import java.awt.event.ActionListener;
36 import java.awt.image.BufferedImage;
37
38 import javax.swing.JComponent;
39 import javax.swing.Timer;
40
41 public class OverviewCanvas extends JComponent
42 {
43   private static final long RUNNING_TIME = 1000;
44
45   private static final int SPEED = 40;
46
47   private static final Color TRANS_GREY = new Color(100, 100, 100, 25);
48
49   // This is set true if the alignment view changes whilst
50   // the overview is being calculated
51   private volatile boolean restart = false;
52
53   private volatile boolean updaterunning = false;
54
55   private boolean dispose = false;
56
57   private BufferedImage miniMe;
58
59   private BufferedImage lastMiniMe = null;
60
61   private BufferedImage veryLastMiniMe = null;
62
63   // Can set different properties in this seqCanvas than
64   // main visible SeqCanvas
65   private SequenceRenderer sr;
66
67   private jalview.renderer.seqfeatures.FeatureRenderer fr;
68
69   private OverviewDimensions od;
70
71   private OverviewRenderer or = null;
72
73   private AlignViewportI av;
74
75
76   private OverviewResColourFinder cf;
77
78   private float alpha = 0f;
79
80   private long startTime = -1;
81
82   private final Timer timer;
83
84   private ProgressPanel progressPanel;
85
86   public OverviewCanvas(OverviewDimensions overviewDims,
87           AlignViewportI alignvp, ProgressPanel pp)
88   {
89     od = overviewDims;
90     av = alignvp;
91     progressPanel = pp;
92
93     sr = new SequenceRenderer(av);
94     sr.renderGaps = false;
95     fr = new jalview.renderer.seqfeatures.FeatureRenderer(av);
96
97     boolean useLegacy = Cache.getDefault(Preferences.USE_LEGACY_GAP, false);
98     Color gapCol = Cache.getDefaultColour(Preferences.GAP_COLOUR,
99             jalview.renderer.OverviewResColourFinder.OVERVIEW_DEFAULT_GAP);
100     Color hiddenCol = Cache.getDefaultColour(Preferences.HIDDEN_COLOUR,
101             jalview.renderer.OverviewResColourFinder.OVERVIEW_DEFAULT_HIDDEN);
102     cf = new OverviewResColourFinder(useLegacy, gapCol, hiddenCol);
103
104     setSize(od.getWidth(), od.getHeight());
105
106     timer = new Timer(SPEED, new ActionListener()
107     {
108
109       @Override
110       public void actionPerformed(ActionEvent e)
111       {
112         if (startTime < 0)
113         {
114           startTime = System.currentTimeMillis();
115         }
116         else
117         {
118
119           long time = System.currentTimeMillis();
120           long duration = time - startTime;
121           if (duration >= RUNNING_TIME)
122           {
123             startTime = -1;
124             ((Timer) e.getSource()).stop();
125             alpha = 0f;
126           }
127           else
128           {
129             alpha = 1f - ((float) duration / (float) RUNNING_TIME);
130           }
131           repaint();
132         }
133       }
134     });
135
136   }
137
138   /**
139    * Update the overview dimensions object used by the canvas (e.g. if we change
140    * from showing hidden columns to hiding them or vice versa)
141    * 
142    * @param overviewDims
143    */
144   public void resetOviewDims(OverviewDimensions overviewDims)
145   {
146     od = overviewDims;
147   }
148
149   /**
150    * Signals to drawing code that the associated alignment viewport has changed
151    * and a redraw will be required
152    */
153   public boolean restartDraw()
154   {
155     synchronized (this)
156     {
157       if (updaterunning)
158       {
159         restart = true;
160         if (or != null)
161         {
162           or.setRedraw(true);
163         }
164       }
165       else
166       {
167         updaterunning = true;
168       }
169       return restart;
170     }
171   }
172
173   /**
174    * Draw the overview sequences
175    * 
176    * @param showSequenceFeatures
177    *          true if sequence features are to be shown
178    * @param showAnnotation
179    *          true if the annotation is to be shown
180    * @param transferRenderer
181    *          the renderer to transfer feature colouring from
182    */
183   public void draw(boolean showSequenceFeatures, boolean showAnnotation,
184           FeatureRenderer transferRenderer)
185   {
186     miniMe = null;
187     veryLastMiniMe = lastMiniMe;
188
189     if (showSequenceFeatures)
190     {
191       fr.transferSettings(transferRenderer);
192     }
193
194     setPreferredSize(new Dimension(od.getWidth(), od.getHeight()));
195
196     or = new OverviewRenderer(fr, od, av.getAlignment(),
197             av.getResidueShading(), cf);
198
199     or.addPropertyChangeListener(progressPanel);
200
201     miniMe = or.draw(od.getRows(av.getAlignment()),
202             od.getColumns(av.getAlignment()));
203
204     Graphics mg = miniMe.getGraphics();
205
206     if (showAnnotation)
207     {
208       mg.translate(0, od.getSequencesHeight());
209       or.drawGraph(mg, av.getAlignmentConservationAnnotation(),
210               av.getCharWidth(), od.getGraphHeight(),
211               od.getColumns(av.getAlignment()));
212       mg.translate(0, -od.getSequencesHeight());
213     }
214     System.gc();
215
216     or.removePropertyChangeListener(progressPanel);
217     or = null;
218     if (restart)
219     {
220       restart = false;
221       if (!dispose)
222       {
223         draw(showSequenceFeatures, showAnnotation, transferRenderer);
224       }
225     }
226     else
227     {
228       updaterunning = false;
229       lastMiniMe = miniMe;
230       alpha = 1f;
231       timer.start();
232     }
233   }
234
235   @Override
236   public void paintComponent(Graphics g)
237   {
238     if (restart)
239     {
240       if (lastMiniMe == null)
241       {
242         g.setColor(Color.white);
243         g.fillRect(0, 0, getWidth(), getHeight());
244       }
245       else
246       {
247         g.drawImage(lastMiniMe, 0, 0, getWidth(), getHeight(), this);
248       }
249       g.setColor(TRANS_GREY);
250       g.fillRect(0, 0, getWidth(), getHeight());
251     }
252     else if (lastMiniMe != null)
253     {
254       // is this a resize?
255       if ((getWidth() > 0) && (getHeight() > 0)
256               && ((getWidth() != od.getWidth())
257                       || (getHeight() != od.getHeight())))
258       {
259         // if there is annotation, scale the alignment and annotation separately
260         if (od.getGraphHeight() > 0)
261         {
262           BufferedImage topImage = lastMiniMe.getSubimage(0, 0,
263                   od.getWidth(), od.getSequencesHeight());
264           BufferedImage bottomImage = lastMiniMe.getSubimage(0,
265                   od.getSequencesHeight(), od.getWidth(),
266                   od.getGraphHeight());
267
268           // must be done at this point as we rely on using old width/height
269           // above, and new width/height below
270           od.setWidth(getWidth());
271           od.setHeight(getHeight());
272
273           // stick the images back together so lastMiniMe is consistent in the
274           // event of a repaint - BUT probably not thread safe
275           lastMiniMe = new BufferedImage(od.getWidth(), od.getHeight(),
276                   BufferedImage.TYPE_INT_RGB);
277           Graphics lg = lastMiniMe.getGraphics();
278           lg.drawImage(topImage, 0, 0, od.getWidth(),
279                   od.getSequencesHeight(), null);
280           lg.drawImage(bottomImage, 0, od.getSequencesHeight(),
281                   od.getWidth(), od.getGraphHeight(), this);
282           lg.dispose();
283         }
284         else
285         {
286           od.setWidth(getWidth());
287           od.setHeight(getHeight());
288         }
289
290         // scale lastMiniMe to the new size
291         g.drawImage(lastMiniMe, 0, 0, getWidth(), getHeight(), this);
292
293         // make sure the box is in the right place
294         od.setBoxPosition(av.getAlignment().getHiddenSequences(),
295                 av.getAlignment().getHiddenColumns());
296       }
297       else // not a resize
298       {
299         if (alpha != 0) // this is a timer triggered dissolve
300         {
301           Graphics2D g2d = (Graphics2D) g.create();
302           
303           // draw the original image
304           g2d.drawImage(veryLastMiniMe, 0, 0, getWidth(), getHeight(),
305                   this);
306
307           // draw the new image on top with varying degrees of transparency
308           g2d.setComposite(AlphaComposite.SrcOver.derive(1f - alpha));
309           g2d.drawImage(lastMiniMe, 0, 0, getWidth(), getHeight(), this);
310
311           g2d.dispose();
312         }
313         else
314         {
315           // fall back to normal behaviour
316           g.drawImage(lastMiniMe, 0, 0, getWidth(), getHeight(), this);
317         }
318       }
319     }
320
321     // draw the box
322     g.setColor(Color.red);
323     od.drawBox(g);
324   }
325
326   public void dispose()
327   {
328     dispose = true;
329     synchronized (this)
330     {
331       restart = true;
332       if (or != null)
333       {
334         or.setRedraw(true);
335       }
336     }
337   }
338 }