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