JAL-2587 prevented double overview recalc on opening overview
[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.renderer.OverviewRenderer;
24 import jalview.util.MessageManager;
25 import jalview.util.Platform;
26 import jalview.viewmodel.OverviewDimensions;
27 import jalview.viewmodel.OverviewDimensionsHideHidden;
28 import jalview.viewmodel.OverviewDimensionsShowHidden;
29 import jalview.viewmodel.ViewportListenerI;
30
31 import java.awt.BorderLayout;
32 import java.awt.Cursor;
33 import java.awt.Dimension;
34 import java.awt.event.ActionEvent;
35 import java.awt.event.ActionListener;
36 import java.awt.event.ComponentAdapter;
37 import java.awt.event.ComponentEvent;
38 import java.awt.event.MouseAdapter;
39 import java.awt.event.MouseEvent;
40 import java.awt.event.MouseMotionAdapter;
41 import java.beans.PropertyChangeEvent;
42
43 import javax.swing.JCheckBoxMenuItem;
44 import javax.swing.JPanel;
45 import javax.swing.JPopupMenu;
46 import javax.swing.SwingUtilities;
47
48 /**
49  * Panel displaying an overview of the full alignment, with an interactive box
50  * representing the viewport onto the alignment.
51  * 
52  * @author $author$
53  * @version $Revision$
54  */
55 public class OverviewPanel extends JPanel implements Runnable,
56         ViewportListenerI
57 {
58   private OverviewDimensions od;
59
60   private OverviewCanvas oviewCanvas;
61
62   private AlignViewport av;
63
64   private AlignmentPanel ap;
65
66   private JCheckBoxMenuItem displayToggle;
67
68   private boolean showHidden = true;
69
70   private boolean draggingBox = false;
71
72   private ProgressPanel progressPanel;
73
74   boolean resizing = false;
75
76   /**
77    * Creates a new OverviewPanel object.
78    * 
79    * @param alPanel
80    *          The alignment panel which is shown in the overview panel
81    */
82   public OverviewPanel(AlignmentPanel alPanel)
83   {
84     this.av = alPanel.av;
85     this.ap = alPanel;
86
87     od = new OverviewDimensionsShowHidden(av.getRanges(),
88             (av.isShowAnnotation() && av
89                     .getAlignmentConservationAnnotation() != null));
90
91     setLayout(new BorderLayout());
92     progressPanel = new ProgressPanel(OverviewRenderer.UPDATE,
93             MessageManager.getString("label.oview_calc"));
94     this.add(progressPanel, BorderLayout.SOUTH);
95     oviewCanvas = new OverviewCanvas(od, av, progressPanel);
96
97     add(oviewCanvas, BorderLayout.CENTER);
98
99     av.getRanges().addPropertyChangeListener(this);
100
101     // without this the overview window does not size to fit the overview canvas
102     setPreferredSize(new Dimension(od.getWidth(), od.getHeight()));
103
104     addComponentListener(new ComponentAdapter()
105     {
106       @Override
107       public void componentResized(ComponentEvent evt)
108       {
109         // Resize is called on the initial display of the overview.
110         // This code adjusts sizes to account for the progress bar if it has not
111         // already been accounted for, which triggers another resize call for
112         // the correct sizing, at which point the overview image is updated.
113         // (This avoids a double recalculation of the image.)
114         if (getWidth() == od.getWidth() && getHeight() == od.getHeight()
115                 + progressPanel.getHeight())
116         {
117           updateOverviewImage();
118         }
119         else
120         {
121           if ((getWidth() > 0) && (getHeight() > 0))
122           {
123             od.setWidth(getWidth());
124             od.setHeight(getHeight() - progressPanel.getHeight());
125           }
126
127           setPreferredSize(new Dimension(od.getWidth(),
128                   od.getHeight() + progressPanel.getHeight()));
129         }
130       }
131
132     });
133
134     addMouseMotionListener(new MouseMotionAdapter()
135     {
136       @Override
137       public void mouseDragged(MouseEvent evt)
138       {
139         if (!SwingUtilities.isRightMouseButton(evt))
140         {
141           if (draggingBox)
142           {
143             // set the mouse position as a fixed point in the box
144             // and drag relative to that position
145             od.adjustViewportFromMouse(evt.getX(),
146                     evt.getY(), av.getAlignment().getHiddenSequences(),
147                     av.getAlignment().getHiddenColumns());
148           }
149           else
150           {
151             od.updateViewportFromMouse(evt.getX(), evt.getY(), av
152                   .getAlignment().getHiddenSequences(), av.getAlignment()
153                   .getHiddenColumns());
154           }
155         }
156       }
157
158       @Override
159       public void mouseMoved(MouseEvent evt)
160       {
161         if (od.isPositionInBox(evt.getX(), evt.getY()))
162         {
163           // display drag cursor at mouse position
164           setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
165         }
166         else
167         {
168           // reset cursor
169           setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
170         }
171       }
172     });
173
174     addMouseListener(new MouseAdapter()
175     {
176       @Override
177       public void mousePressed(MouseEvent evt)
178       {
179         if (SwingUtilities.isRightMouseButton(evt))
180         {
181           if (!Platform.isAMac())
182           {
183             showPopupMenu(evt);
184           }
185         }
186         else
187         {
188           if (!od.isPositionInBox(evt.getX(), evt.getY()))
189           {
190             // don't do anything if the mouse press is in the overview's box
191             // (wait to see if it's a drag instead)
192             // otherwise update the viewport
193             od.updateViewportFromMouse(evt.getX(), evt.getY(),
194                     av.getAlignment().getHiddenSequences(),
195                     av.getAlignment().getHiddenColumns());
196           }
197           else
198           {
199             draggingBox = true;
200             od.setDragPoint(evt.getX(), evt.getY(),
201                     av.getAlignment().getHiddenSequences(),
202                     av.getAlignment().getHiddenColumns());
203           }
204         }
205       }
206
207       @Override
208       public void mouseReleased(MouseEvent evt)
209       {
210         if (draggingBox)
211         {
212           draggingBox = false;
213         }
214       }
215
216       @Override
217       public void mouseClicked(MouseEvent evt)
218       {
219         if (SwingUtilities.isRightMouseButton(evt))
220         {
221           showPopupMenu(evt);
222         }
223       }
224     });
225   }
226
227   /*
228    * Displays the popup menu and acts on user input
229    */
230   private void showPopupMenu(MouseEvent e)
231   {
232     JPopupMenu popup = new JPopupMenu();
233     ActionListener menuListener = new ActionListener()
234     {
235       @Override
236       public void actionPerformed(ActionEvent event)
237       {
238         // switch on/off the hidden columns view
239         toggleHiddenColumns();
240         displayToggle.setSelected(showHidden);
241       }
242     };
243     displayToggle = new JCheckBoxMenuItem(
244             MessageManager.getString("label.togglehidden"));
245     displayToggle.setEnabled(true);
246     displayToggle.setSelected(showHidden);
247     popup.add(displayToggle);
248     displayToggle.addActionListener(menuListener);
249     popup.show(this, e.getX(), e.getY());
250   }
251
252   /*
253    * Toggle overview display between showing hidden columns and hiding hidden columns
254    */
255   private void toggleHiddenColumns()
256   {
257     if (showHidden)
258     {
259       showHidden = false;
260       od = new OverviewDimensionsHideHidden(av.getRanges(),
261               (av.isShowAnnotation() && av
262                       .getAlignmentConservationAnnotation() != null));
263     }
264     else
265     {
266       showHidden = true;
267       od = new OverviewDimensionsShowHidden(av.getRanges(),
268               (av.isShowAnnotation() && av
269                       .getAlignmentConservationAnnotation() != null));
270     }
271     oviewCanvas.resetOviewDims(od);
272     updateOverviewImage();
273     setBoxPosition();
274   }
275
276   /**
277    * Updates the overview image when the related alignment panel is updated
278    */
279   public void updateOverviewImage()
280   {
281     if (oviewCanvas == null)
282     {
283       /*
284        * panel has been disposed
285        */
286       return;
287     }
288
289     if ((getWidth() > 0) && (getHeight() > 0))
290     {
291       od.setWidth(getWidth());
292       od.setHeight(getHeight() - progressPanel.getHeight());
293     }
294     
295     setPreferredSize(new Dimension(od.getWidth(),
296             od.getHeight() + progressPanel.getHeight()));
297
298     if (oviewCanvas.restartDraw())
299     {
300       return;
301     }
302
303     Thread thread = new Thread(this);
304     thread.start();
305     repaint();
306
307     
308   }
309
310   @Override
311   public void run()
312   {
313     oviewCanvas.draw(av.isShowSequenceFeatures(),
314             (av.isShowAnnotation() && av
315                     .getAlignmentConservationAnnotation() != null), ap
316                     .getSeqPanel().seqCanvas.getFeatureRenderer());
317     setBoxPosition();
318   }
319
320   /**
321    * Update the overview panel box when the associated alignment panel is
322    * changed
323    * 
324    */
325   private void setBoxPosition()
326   {
327     od.setBoxPosition(av.getAlignment().getHiddenSequences(), av
328             .getAlignment().getHiddenColumns());
329     repaint();
330   }
331
332   @Override
333   public void propertyChange(PropertyChangeEvent evt)
334   {
335     setBoxPosition();
336   }
337
338   /**
339    * Removes this object as a property change listener, and nulls references
340    */
341   protected void dispose()
342   {
343     try
344     {
345       av.getRanges().removePropertyChangeListener(this);
346     } finally
347     {
348       progressPanel = null;
349       av = null;
350       oviewCanvas = null;
351       ap = null;
352       od = null;
353     }
354   }
355 }