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