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