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