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