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