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