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