cc647a87b931cf0865601972b3e755dbef6a63dc
[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           // BH: resizing is now exceptionally fast.
134           updateOverviewImage();
135         }
136         else
137         {
138           int w = getWidth();
139           int h = getHeight();
140           if ((w > 0) && (h > 0))
141           {
142             if (dim != null)
143             {
144               dim.setSize(w, h - ph);
145             }
146             od.setWidth(w);
147             od.setHeight(h - ph);
148             updateOverviewImage();
149             // repaint();
150           }
151           // BH 2019.07.29 this is unnecessary -- it is what layout managers are
152           // for:
153           // setPreferredSize(new Dimension(od.getWidth(), od.getHeight() +
154           // ph));
155         }
156       }
157
158     });
159
160     addMouseMotionListener(new MouseMotionAdapter()
161     {
162       @Override
163       public void mouseDragged(MouseEvent evt)
164       {
165         if (!SwingUtilities.isRightMouseButton(evt))
166         {
167           if (draggingBox)
168           {
169             // set the mouse position as a fixed point in the box
170             // and drag relative to that position
171             od.adjustViewportFromMouse(evt.getX(), evt.getY(),
172                     av.getAlignment().getHiddenSequences(),
173                     av.getAlignment().getHiddenColumns());
174           }
175           else
176           {
177             od.updateViewportFromMouse(evt.getX(), evt.getY(),
178                     av.getAlignment().getHiddenSequences(),
179                     av.getAlignment().getHiddenColumns());
180           }
181         }
182       }
183
184       @Override
185       public void mouseMoved(MouseEvent evt)
186       {
187         if (od.isPositionInBox(evt.getX(), evt.getY()))
188         {
189           /*
190            * using HAND_CURSOR rather than DRAG_CURSOR 
191            * as the latter is not supported on Mac
192            */
193           getParent().setCursor(
194                   Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
195         }
196         else
197         {
198           // reset cursor
199           getParent().setCursor(
200                   Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
201         }
202       }
203
204     });
205
206     addMouseListener(new MouseAdapter()
207     {
208       @Override
209       public void mousePressed(MouseEvent evt)
210       {
211
212         if (Platform.isWinRightButton(evt))
213         {
214           showPopupMenu(evt);
215           return;
216         }
217         if (SwingUtilities.isRightMouseButton(evt))
218         {
219           return;
220         }
221         // don't do anything if the mouse press is in the overview's box
222         // (wait to see if it's a drag instead)
223         // otherwise update the viewport
224         if (!od.isPositionInBox(evt.getX(), evt.getY()))
225         {
226           draggingBox = false;
227
228           // display drag cursor at mouse position
229           setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
230
231           od.updateViewportFromMouse(evt.getX(), evt.getY(),
232                   av.getAlignment().getHiddenSequences(),
233                   av.getAlignment().getHiddenColumns());
234           getParent().setCursor(
235                   Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
236         }
237         else
238         {
239           draggingBox = true;
240           od.setDragPoint(evt.getX(), evt.getY(),
241                   av.getAlignment().getHiddenSequences(),
242                   av.getAlignment().getHiddenColumns());
243         }
244       }
245
246       @Override
247       public void mouseClicked(MouseEvent evt)
248       {
249         if (SwingUtilities.isRightMouseButton(evt))
250         {
251           showPopupMenu(evt);
252         }
253       }
254
255       @Override
256       public void mouseReleased(MouseEvent evt)
257       {
258         draggingBox = false;
259       }
260
261     });
262
263     // /*
264     // * Javascript does not call componentResized on initial display,
265     // * so do the update here
266     // */
267     // if (Platform.isJS())
268     // {
269     // updateOverviewImage();
270     // }
271   }
272
273   /**
274    * Create the appropriate type of OverViewDimensions, with the desired size.
275    */
276   private void createOverviewDimensions()
277   {
278     boolean showAnnotation = (av.isShowAnnotation()
279             && av.getAlignmentConservationAnnotation() != null);
280     if (showHidden)
281     {
282       od = new OverviewDimensionsShowHidden(av.getRanges(), showAnnotation,
283               dim);
284     }
285     else
286     {
287       od = new OverviewDimensionsHideHidden(av.getRanges(), showAnnotation,
288               dim);
289     }
290
291   }
292
293   /*
294    * Displays the popup menu and acts on user input
295    */
296   protected void showPopupMenu(MouseEvent e)
297   {
298     JPopupMenu popup = new JPopupMenu();
299     ActionListener menuListener = new ActionListener()
300     {
301       @Override
302       public void actionPerformed(ActionEvent event)
303       {
304         // switch on/off the hidden columns view
305         toggleHiddenColumns();
306         displayToggle.setSelected(showHidden);
307       }
308     };
309     displayToggle = new JCheckBoxMenuItem(
310             MessageManager.getString("label.togglehidden"));
311     displayToggle.setEnabled(true);
312     displayToggle.setSelected(showHidden);
313     popup.add(displayToggle);
314     displayToggle.addActionListener(menuListener);
315     popup.show(this, e.getX(), e.getY());
316   }
317
318   /*
319    * Toggle overview display between showing hidden columns and hiding hidden columns
320    */
321   protected void toggleHiddenColumns()
322   {
323     showHidden = !showHidden;
324     createOverviewDimensions();
325     canvas.resetOviewDims(od);
326     updateOverviewImage();
327     setBoxPosition();
328   }
329
330   /**
331    * Updates the overview image when the related alignment panel is updated.
332    * 
333    * Cases:
334    * 
335    * AlignFrame.setFeatureGroupState
336    * 
337    * AlignmentPanel.paintAlignment(true,...) (117 references)
338    * 
339    * OverviewPanel..componentResized() OverviewPanel.toggleHiddenColumns()
340    * 
341    * PopupMenu for action.reveal_sequences, action.reveal_all
342    * 
343    * SliderPanel.mouseReleased()
344    * 
345    */
346   public void updateOverviewImage()
347   {
348     if (canvas == null)
349     {
350       /*
351        * panel has been disposed
352        */
353       return;
354     }
355
356     int ph = (progressPanel.getParent() == null ? 0
357             : progressPanel.getHeight());
358
359     if ((getWidth() > 0) && (getHeight() > 0))
360     {
361       od.setWidth(getWidth());
362       od.setHeight(getHeight() - ph);
363     }
364
365     setPreferredSize(new Dimension(od.getWidth(), od.getHeight() + ph));
366
367     if (canvas.restartDraw())
368     {
369       return;
370     }
371
372     Thread thread = new Thread(this);
373     thread.start();
374   }
375
376   @Override
377   public void run()
378   {
379     if (canvas != null)
380     {
381       setBoxPosition();
382       canvas.draw(av.isShowSequenceFeatures(),
383               (av.isShowAnnotation()
384                       && av.getAlignmentConservationAnnotation() != null),
385               ap.getFeatureRenderer());
386     }
387   }
388
389   /**
390    * Update the overview panel box when the associated alignment panel is
391    * changed
392    * 
393    */
394   private void setBoxPositionOnly()
395   {
396     if (od != null)
397     {
398       od.updateBox();
399       int oldX = od.getBoxX();
400       int oldY = od.getBoxY();
401       int oldWidth = od.getBoxWidth();
402       int oldHeight = od.getBoxHeight();
403       od.setBoxPosition(av.getAlignment().getHiddenSequences(),
404               av.getAlignment().getHiddenColumns());
405       repaint(oldX - 1, oldY - 1, oldWidth + 2, oldHeight + 2);
406       repaint(od.getBoxX(), od.getBoxY(), od.getBoxWidth(),
407               od.getBoxHeight());
408     }
409   }
410
411   private void setBoxPosition()
412   {
413     if (od != null)
414     {
415       od.setBoxPosition(av.getAlignment().getHiddenSequences(),
416               av.getAlignment().getHiddenColumns());
417       repaint();
418     }
419   }
420
421   @Override
422   public void propertyChange(PropertyChangeEvent evt)
423   {
424     setBoxPositionOnly();
425   }
426
427   /**
428    * Removes this object as a property change listener, and nulls references
429    */
430   protected void dispose()
431   {
432     try
433     {
434       if (av != null)
435       {
436         av.getRanges().removePropertyChangeListener(this);
437       }
438
439       canvas.dispose();
440
441       /*
442        * close the parent frame (which also removes it from the
443        * Desktop Windows menu)
444        */
445       ((JInternalFrame) SwingUtilities
446               .getAncestorOfClass(JInternalFrame.class, (this)))
447                       .setClosed(true);
448     } catch (PropertyVetoException e)
449     {
450       // ignore
451     } finally
452     {
453       progressPanel = null;
454       av = null;
455       canvas = null;
456       ap = null;
457       od = null;
458     }
459   }
460 }