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