Merge branch 'bug/JAL-2687concurrentModification' into features/JAL-2608closeOverview
[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.util.MessageManager;
24 import jalview.util.Platform;
25 import jalview.viewmodel.OverviewDimensions;
26 import jalview.viewmodel.OverviewDimensionsHideHidden;
27 import jalview.viewmodel.OverviewDimensionsShowHidden;
28 import jalview.viewmodel.ViewportListenerI;
29
30 import java.awt.BorderLayout;
31 import java.awt.Cursor;
32 import java.awt.Dimension;
33 import java.awt.event.ActionEvent;
34 import java.awt.event.ActionListener;
35 import java.awt.event.ComponentAdapter;
36 import java.awt.event.ComponentEvent;
37 import java.awt.event.MouseAdapter;
38 import java.awt.event.MouseEvent;
39 import java.awt.event.MouseMotionAdapter;
40 import java.beans.PropertyChangeEvent;
41 import java.beans.PropertyVetoException;
42
43 import javax.swing.JCheckBoxMenuItem;
44 import javax.swing.JInternalFrame;
45 import javax.swing.JPanel;
46 import javax.swing.JPopupMenu;
47 import javax.swing.SwingUtilities;
48
49 /**
50  * Panel displaying an overview of the full alignment, with an interactive box
51  * representing the viewport onto the alignment.
52  * 
53  * @author $author$
54  * @version $Revision$
55  */
56 public class OverviewPanel extends JPanel
57         implements Runnable, ViewportListenerI
58 {
59   private OverviewDimensions od;
60
61   private OverviewCanvas oviewCanvas;
62
63   private AlignViewport av;
64
65   private AlignmentPanel ap;
66
67   private JCheckBoxMenuItem displayToggle;
68
69   private boolean showHidden = true;
70
71   private boolean draggingBox = false;
72
73   /**
74    * Creates a new OverviewPanel object.
75    * 
76    * @param alPanel
77    *          The alignment panel which is shown in the overview panel
78    */
79   public OverviewPanel(AlignmentPanel alPanel)
80   {
81     this.av = alPanel.av;
82     this.ap = alPanel;
83
84     od = new OverviewDimensionsShowHidden(av.getRanges(),
85             (av.isShowAnnotation()
86                     && av.getAlignmentConservationAnnotation() != null));
87
88     setSize(od.getWidth(), od.getHeight());
89
90     oviewCanvas = new OverviewCanvas(od, av);
91     setLayout(new BorderLayout());
92     add(oviewCanvas, BorderLayout.CENTER);
93
94     av.getRanges().addPropertyChangeListener(this);
95
96     addComponentListener(new ComponentAdapter()
97     {
98       @Override
99       public void componentResized(ComponentEvent evt)
100       {
101         if ((getWidth() != od.getWidth())
102                 || (getHeight() != (od.getHeight())))
103         {
104           updateOverviewImage();
105           setBoxPosition();
106         }
107       }
108     });
109
110     addMouseMotionListener(new MouseMotionAdapter()
111     {
112       @Override
113       public void mouseDragged(MouseEvent evt)
114       {
115         if (!SwingUtilities.isRightMouseButton(evt))
116         {
117           if (draggingBox)
118           {
119             // set the mouse position as a fixed point in the box
120             // and drag relative to that position
121             od.adjustViewportFromMouse(evt.getX(), evt.getY(),
122                     av.getAlignment().getHiddenSequences(),
123                     av.getAlignment().getHiddenColumns());
124           }
125           else
126           {
127             od.updateViewportFromMouse(evt.getX(), evt.getY(),
128                     av.getAlignment().getHiddenSequences(),
129                     av.getAlignment().getHiddenColumns());
130           }
131         }
132       }
133
134       @Override
135       public void mouseMoved(MouseEvent evt)
136       {
137         if (od.isPositionInBox(evt.getX(), evt.getY()))
138         {
139           // display drag cursor at mouse position
140           setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
141         }
142         else
143         {
144           // reset cursor
145           setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
146         }
147       }
148     });
149
150     addMouseListener(new MouseAdapter()
151     {
152       @Override
153       public void mousePressed(MouseEvent evt)
154       {
155         if (SwingUtilities.isRightMouseButton(evt))
156         {
157           if (!Platform.isAMac())
158           {
159             showPopupMenu(evt);
160           }
161         }
162         else
163         // if (!av.getWrapAlignment())
164         {
165           if (!od.isPositionInBox(evt.getX(), evt.getY()))
166           {
167             // don't do anything if the mouse press is in the overview's box
168             // (wait to see if it's a drag instead)
169             // otherwise update the viewport
170             od.updateViewportFromMouse(evt.getX(), evt.getY(),
171                     av.getAlignment().getHiddenSequences(),
172                     av.getAlignment().getHiddenColumns());
173           }
174           else
175           {
176             draggingBox = true;
177             od.setDragPoint(evt.getX(), evt.getY(),
178                     av.getAlignment().getHiddenSequences(),
179                     av.getAlignment().getHiddenColumns());
180           }
181         }
182       }
183
184       @Override
185       public void mouseReleased(MouseEvent evt)
186       {
187         if (draggingBox)
188         {
189           draggingBox = false;
190         }
191       }
192
193       @Override
194       public void mouseClicked(MouseEvent evt)
195       {
196         if (SwingUtilities.isRightMouseButton(evt))
197         {
198           showPopupMenu(evt);
199         }
200       }
201     });
202
203     updateOverviewImage();
204   }
205
206   /*
207    * Displays the popup menu and acts on user input
208    */
209   private void showPopupMenu(MouseEvent e)
210   {
211     JPopupMenu popup = new JPopupMenu();
212     ActionListener menuListener = new ActionListener()
213     {
214       @Override
215       public void actionPerformed(ActionEvent event)
216       {
217         // switch on/off the hidden columns view
218         toggleHiddenColumns();
219         displayToggle.setSelected(showHidden);
220       }
221     };
222     displayToggle = new JCheckBoxMenuItem(
223             MessageManager.getString("label.togglehidden"));
224     displayToggle.setEnabled(true);
225     displayToggle.setSelected(showHidden);
226     popup.add(displayToggle);
227     displayToggle.addActionListener(menuListener);
228     popup.show(this, e.getX(), e.getY());
229   }
230
231   /*
232    * Toggle overview display between showing hidden columns and hiding hidden columns
233    */
234   private void toggleHiddenColumns()
235   {
236     if (showHidden)
237     {
238       showHidden = false;
239       od = new OverviewDimensionsHideHidden(av.getRanges(),
240               (av.isShowAnnotation()
241                       && av.getAlignmentConservationAnnotation() != null));
242     }
243     else
244     {
245       showHidden = true;
246       od = new OverviewDimensionsShowHidden(av.getRanges(),
247               (av.isShowAnnotation()
248                       && av.getAlignmentConservationAnnotation() != null));
249     }
250     oviewCanvas.resetOviewDims(od);
251     updateOverviewImage();
252     setBoxPosition();
253   }
254
255   /**
256    * Updates the overview image when the related alignment panel is updated
257    */
258   public void updateOverviewImage()
259   {
260     if (oviewCanvas == null)
261     {
262       /*
263        * panel has been disposed
264        */
265       return;
266     }
267
268     if ((getWidth() > 0) && (getHeight() > 0))
269     {
270       od.setWidth(getWidth());
271       od.setHeight(getHeight());
272     }
273
274     setPreferredSize(new Dimension(od.getWidth(), od.getHeight()));
275
276     if (oviewCanvas.restartDraw())
277     {
278       return;
279     }
280
281     Thread thread = new Thread(this);
282     thread.start();
283     repaint();
284   }
285
286   @Override
287   public void run()
288   {
289     oviewCanvas.draw(av.isShowSequenceFeatures(),
290             (av.isShowAnnotation()
291                     && av.getAlignmentConservationAnnotation() != null),
292             ap.getSeqPanel().seqCanvas.getFeatureRenderer());
293     setBoxPosition();
294   }
295
296   /**
297    * Update the overview panel box when the associated alignment panel is
298    * changed
299    * 
300    */
301   private void setBoxPosition()
302   {
303     od.setBoxPosition(av.getAlignment().getHiddenSequences(),
304             av.getAlignment().getHiddenColumns());
305     repaint();
306   }
307
308   @Override
309   public void propertyChange(PropertyChangeEvent evt)
310   {
311     setBoxPosition();
312   }
313
314   /**
315    * Removes this object as a property change listener, and nulls references
316    */
317   protected void dispose()
318   {
319     try
320     {
321       if (av != null)
322       {
323         av.getRanges().removePropertyChangeListener(this);
324       }
325
326       /*
327        * close the parent frame (which also removes it from the
328        * Desktop Windows menu)
329        */
330       ((JInternalFrame) SwingUtilities.getAncestorOfClass(
331               JInternalFrame.class, (this))).setClosed(true);
332     } catch (PropertyVetoException e)
333     {
334       // ignore
335     } finally
336     {
337       av = null;
338       oviewCanvas = null;
339       ap = null;
340       od = null;
341     }
342   }
343 }