b84757ae1286939e8280ed9ce119210cf0869db6
[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 java.awt.BorderLayout;
24 import java.awt.Cursor;
25 import java.awt.Dimension;
26 import java.awt.Rectangle;
27 import java.awt.event.ActionEvent;
28 import java.awt.event.ActionListener;
29 import java.awt.event.ComponentAdapter;
30 import java.awt.event.ComponentEvent;
31 import java.awt.event.MouseAdapter;
32 import java.awt.event.MouseEvent;
33 import java.awt.event.MouseMotionAdapter;
34 import java.beans.PropertyChangeEvent;
35 import java.beans.PropertyVetoException;
36
37 import javax.swing.JCheckBoxMenuItem;
38 import javax.swing.JInternalFrame;
39 import javax.swing.JPanel;
40 import javax.swing.JPopupMenu;
41 import javax.swing.SwingUtilities;
42
43 import jalview.renderer.OverviewRenderer;
44 import jalview.util.MessageManager;
45 import jalview.util.Platform;
46 import jalview.viewmodel.OverviewDimensions;
47 import jalview.viewmodel.OverviewDimensionsHideHidden;
48 import jalview.viewmodel.OverviewDimensionsShowHidden;
49 import jalview.viewmodel.ViewportListenerI;
50
51 /**
52  * Panel displaying an overview of the full alignment, with an interactive box
53  * representing the viewport onto the alignment.
54  * 
55  * @author $author$
56  * @version $Revision$
57  */
58 @SuppressWarnings("serial")
59 public class OverviewPanel extends JPanel
60         implements Runnable, ViewportListenerI
61 {
62   protected OverviewDimensions od;
63
64   private OverviewCanvas oviewCanvas;
65
66   protected AlignViewport av;
67
68   private AlignmentPanel ap;
69
70   private JInternalFrame internalFrame;
71
72   protected JCheckBoxMenuItem displayToggle;
73
74   protected boolean showHidden = true;
75
76   protected boolean draggingBox = false;
77
78   protected ProgressPanel progressPanel;
79
80   /**
81    * Creates a new OverviewPanel object.
82    * 
83    * @param alPanel
84    *          The alignment panel which is shown in the overview panel
85    * @param frame
86    * @param isShowHidden
87    *          TODO
88    */
89   public OverviewPanel(AlignmentPanel alPanel, JInternalFrame frame,
90           boolean isShowHidden)
91   {
92     this.av = alPanel.av;
93     this.ap = alPanel;
94     this.internalFrame = frame;
95
96     showHidden = isShowHidden;
97     if (showHidden)
98     {
99       od = new OverviewDimensionsShowHidden(av.getRanges(),
100               (av.isShowAnnotation()
101                       && av.getAlignmentConservationAnnotation() != null));
102     }
103     else
104     {
105       od = new OverviewDimensionsHideHidden(av.getRanges(),
106               (av.isShowAnnotation()
107                       && av.getAlignmentConservationAnnotation() != null));
108     }
109
110     setLayout(new BorderLayout());
111     progressPanel = new ProgressPanel(OverviewRenderer.UPDATE,
112             MessageManager.getString("label.oview_calc"), getWidth());
113     this.add(progressPanel, BorderLayout.SOUTH);
114     oviewCanvas = new OverviewCanvas(od, av, progressPanel);
115
116     add(oviewCanvas, BorderLayout.CENTER);
117
118     av.getRanges().addPropertyChangeListener(this);
119
120     // without this the overview window does not size to fit the overview canvas
121     setPreferredSize(new Dimension(od.getWidth(), od.getHeight()));
122
123     addComponentListener(new ComponentAdapter()
124     {
125       @Override
126       public void componentResized(ComponentEvent evt)
127       {
128         // Resize is called on the initial display of the overview.
129         // This code adjusts sizes to account for the progress bar if it has not
130         // already been accounted for, which triggers another resize call for
131         // the correct sizing, at which point the overview image is updated.
132         // (This avoids a double recalculation of the image.)
133         if (getWidth() == od.getWidth() && getHeight() == od.getHeight()
134                 + progressPanel.getHeight())
135         {
136           updateOverviewImage();
137         }
138         else
139         {
140           if ((getWidth() > 0) && (getHeight() > 0))
141           {
142             od.setWidth(getWidth());
143             od.setHeight(getHeight() - progressPanel.getHeight());
144           }
145
146           setPreferredSize(new Dimension(od.getWidth(),
147                   od.getHeight() + progressPanel.getHeight()));
148         }
149       }
150
151     });
152
153     addMouseMotionListener(new MouseMotionAdapter()
154     {
155       @Override
156       public void mouseDragged(MouseEvent evt)
157       {
158         if (!SwingUtilities.isRightMouseButton(evt))
159         {
160           if (draggingBox)
161           {
162             // set the mouse position as a fixed point in the box
163             // and drag relative to that position
164             od.adjustViewportFromMouse(evt.getX(), evt.getY(),
165                     av.getAlignment().getHiddenSequences(),
166                     av.getAlignment().getHiddenColumns());
167           }
168           else
169           {
170             od.updateViewportFromMouse(evt.getX(), evt.getY(),
171                     av.getAlignment().getHiddenSequences(),
172                     av.getAlignment().getHiddenColumns());
173           }
174         }
175       }
176
177       @Override
178       public void mouseMoved(MouseEvent evt)
179       {
180         if (od.isPositionInBox(evt.getX(), evt.getY()))
181         {
182           /*
183            * using HAND_CURSOR rather than DRAG_CURSOR 
184            * as the latter is not supported on Mac
185            */
186           getParent().setCursor(
187                   Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
188         }
189         else
190         {
191           // reset cursor
192           getParent().setCursor(
193                   Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
194         }
195       }
196
197     });
198
199     addMouseListener(new MouseAdapter()
200     {
201       @Override
202       public void mousePressed(MouseEvent evt)
203       {
204
205         if (Platform.isWinRightButton(evt))
206         {
207           showPopupMenu(evt);
208           return;
209         }
210         if (SwingUtilities.isRightMouseButton(evt))
211         {
212           return;
213         }
214         // don't do anything if the mouse press is in the overview's box
215         // (wait to see if it's a drag instead)
216         // otherwise update the viewport
217         if (!od.isPositionInBox(evt.getX(), evt.getY()))
218         {
219           draggingBox = false;
220
221           // display drag cursor at mouse position
222           setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
223
224           od.updateViewportFromMouse(evt.getX(), evt.getY(),
225                   av.getAlignment().getHiddenSequences(),
226                   av.getAlignment().getHiddenColumns());
227           getParent().setCursor(
228                   Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
229         }
230         else
231         {
232           draggingBox = true;
233           od.setDragPoint(evt.getX(), evt.getY(),
234                   av.getAlignment().getHiddenSequences(),
235                   av.getAlignment().getHiddenColumns());
236         }
237       }
238
239       @Override
240       public void mouseClicked(MouseEvent evt)
241       {
242         if (SwingUtilities.isRightMouseButton(evt))
243         {
244           showPopupMenu(evt);
245         }
246       }
247
248       @Override
249       public void mouseReleased(MouseEvent evt)
250       {
251         draggingBox = false;
252       }
253
254     });
255
256     /*
257      * Javascript does not call componentResized on initial display,
258      * so do the update here
259      */
260     if (Platform.isJS())
261     {
262       updateOverviewImage();
263     }
264   }
265
266   /*
267    * Displays the popup menu and acts on user input
268    */
269   protected void showPopupMenu(MouseEvent e)
270   {
271     JPopupMenu popup = new JPopupMenu();
272     ActionListener menuListener = new ActionListener()
273     {
274       @Override
275       public void actionPerformed(ActionEvent event)
276       {
277         // switch on/off the hidden columns view
278         toggleHiddenColumns();
279         displayToggle.setSelected(showHidden);
280       }
281     };
282     displayToggle = new JCheckBoxMenuItem(
283             MessageManager.getString("label.togglehidden"));
284     displayToggle.setEnabled(true);
285     displayToggle.setSelected(showHidden);
286     popup.add(displayToggle);
287     displayToggle.addActionListener(menuListener);
288     popup.show(this, e.getX(), e.getY());
289   }
290
291   /*
292    * Toggle overview display between showing hidden columns and hiding hidden columns
293    */
294   protected void toggleHiddenColumns()
295   {
296     if (showHidden)
297     {
298       showHidden = false;
299       od = new OverviewDimensionsHideHidden(av.getRanges(),
300               (av.isShowAnnotation()
301                       && av.getAlignmentConservationAnnotation() != null));
302     }
303     else
304     {
305       showHidden = true;
306       od = new OverviewDimensionsShowHidden(av.getRanges(),
307               (av.isShowAnnotation()
308                       && av.getAlignmentConservationAnnotation() != null));
309     }
310     oviewCanvas.resetOviewDims(od);
311     updateOverviewImage();
312     setBoxPosition();
313   }
314
315   /**
316    * Updates the overview image when the related alignment panel is updated
317    */
318   public void updateOverviewImage()
319   {
320     if (oviewCanvas == null)
321     {
322       /*
323        * panel has been disposed
324        */
325       return;
326     }
327
328     if ((getWidth() > 0) && (getHeight() > 0))
329     {
330       od.setWidth(getWidth());
331       od.setHeight(getHeight() - progressPanel.getHeight());
332     }
333
334     setPreferredSize(new Dimension(od.getWidth(),
335             od.getHeight() + progressPanel.getHeight()));
336
337     if (oviewCanvas.restartDraw())
338     {
339       return;
340     }
341
342     Thread thread = new Thread(this);
343     thread.start();
344     repaint();
345
346   }
347
348   @Override
349   public void run()
350   {
351     if (oviewCanvas != null)
352     {
353       oviewCanvas.draw(av.isShowSequenceFeatures(),
354               (av.isShowAnnotation()
355                       && av.getAlignmentConservationAnnotation() != null),
356               ap.getSeqPanel().seqCanvas.getFeatureRenderer());
357       setBoxPosition();
358     }
359   }
360
361   /**
362    * Update the overview panel box when the associated alignment panel is
363    * changed
364    * 
365    */
366   private void setBoxPositionOnly()
367   {
368     if (od != null)
369     {
370       int oldX = od.getBoxX();
371       int oldY = od.getBoxY();
372       int oldWidth = od.getBoxWidth();
373       int oldHeight = od.getBoxHeight();
374       od.setBoxPosition(av.getAlignment().getHiddenSequences(),
375               av.getAlignment().getHiddenColumns());
376       repaint(oldX - 1, oldY - 1, oldWidth + 2, oldHeight + 2);
377       repaint(od.getBoxX(), od.getBoxY(), od.getBoxWidth(),
378               od.getBoxHeight());
379     }
380   }
381
382   private void setBoxPosition()
383   {
384     if (od != null)
385     {
386       od.setBoxPosition(av.getAlignment().getHiddenSequences(),
387               av.getAlignment().getHiddenColumns());
388       repaint();
389     }
390   }
391
392   @Override
393   public void propertyChange(PropertyChangeEvent evt)
394   {
395     setBoxPositionOnly();
396   }
397
398   /**
399    * Removes this object as a property change listener, and nulls references
400    */
401   protected void dispose()
402   {
403     try
404     {
405       if (av != null)
406       {
407         av.getRanges().removePropertyChangeListener(this);
408       }
409
410       oviewCanvas.dispose();
411
412       /*
413        * close the parent frame (which also removes it from the
414        * Desktop Windows menu)
415        */
416       internalFrame.setClosed(true);
417     } catch (PropertyVetoException e)
418     {
419       // ignore
420     } finally
421     {
422       progressPanel = null;
423       av = null;
424       oviewCanvas = null;
425       ap = null;
426       od = null;
427       internalFrame = null;
428     }
429   }
430
431   public boolean isShowHiddenRegions()
432   {
433     return showHidden;
434   }
435
436   public OverviewCanvas getCanvas()
437   {
438     return oviewCanvas;
439   }
440
441   /**
442    * Sets the title of the enclosing frame
443    * 
444    * @param title
445    */
446   public void setTitle(String title)
447   {
448     internalFrame.setTitle(title);
449   }
450
451   /**
452    * Returns the title of the enclosing frame
453    * 
454    * @return title
455    */
456   public String getTitle()
457   {
458     return internalFrame.getTitle();
459   }
460
461   /**
462    * Sets the bounds of the frame holding the Overview panel
463    * 
464    * @param xpos
465    * @param ypos
466    * @param width
467    * @param height
468    */
469   public void setFrameBounds(int xpos, int ypos, int width, int height)
470   {
471     internalFrame.setBounds(xpos, ypos, width, height);
472   }
473
474   /**
475    * Returns the bounds of the enclosing frame
476    * 
477    * @return
478    */
479   public Rectangle getFrameBounds()
480   {
481     return internalFrame.getBounds();
482   }
483 }