JAL-2621 Overview uses hand cursor in drag box, crosshair outside box
[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         od.updateViewportFromMouse(evt.getX(), evt.getY(),
160                 av.getAlignment().getHiddenSequences(),
161                 av.getAlignment().getHiddenColumns());
162         getParent()
163                 .setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
164       }
165       else
166       {
167         draggingBox = true;
168         od.setDragPoint(evt.getX(), evt.getY(),
169                 av.getAlignment().getHiddenSequences(),
170                 av.getAlignment().getHiddenColumns());
171       }
172     }
173   }
174
175   @Override
176   public void mouseReleased(MouseEvent evt)
177   {
178   }
179
180   @Override
181   public void mouseDragged(MouseEvent evt)
182   {
183     if ((evt.getModifiers()
184             & InputEvent.BUTTON3_MASK) == InputEvent.BUTTON3_MASK)
185     {
186       if (!Platform.isAMac())
187       {
188         showPopupMenu(evt);
189       }
190     }
191     else
192     {
193       if (draggingBox)
194       {
195         // set the mouse position as a fixed point in the box
196         // and drag relative to that position
197         od.adjustViewportFromMouse(evt.getX(), evt.getY(),
198                 av.getAlignment().getHiddenSequences(),
199                 av.getAlignment().getHiddenColumns());
200       }
201       else
202       {
203         od.updateViewportFromMouse(evt.getX(), evt.getY(),
204                 av.getAlignment().getHiddenSequences(),
205                 av.getAlignment().getHiddenColumns());
206       }
207       ap.paintAlignment(false, false);
208     }
209   }
210
211   /**
212    * Updates the overview image when the related alignment panel is updated
213    */
214   public void updateOverviewImage()
215   {
216     if (oviewCanvas == null)
217     {
218       /*
219        * panel has been disposed
220        */
221       return;
222     }
223
224     if ((getSize().width > 0) && (getSize().height > 0))
225     {
226       od.setWidth(getSize().width);
227       od.setHeight(getSize().height);
228     }
229     setSize(new Dimension(od.getWidth(), od.getHeight()));
230
231     synchronized (this)
232     {
233       if (updateRunning)
234       {
235         oviewCanvas.restartDraw();
236         return;
237       }
238
239       updateRunning = true;
240     }
241     Thread thread = new Thread(this);
242     thread.start();
243     repaint();
244     updateRunning = false;
245   }
246
247   @Override
248   public void run()
249   {
250     oviewCanvas.draw(av.isShowSequenceFeatures(),
251             (av.isShowAnnotation()
252                     && av.getAlignmentConservationAnnotation() != null),
253             ap.seqPanel.seqCanvas.getFeatureRenderer());
254     setBoxPosition();
255   }
256
257   /**
258    * Update the overview panel box when the associated alignment panel is
259    * changed
260    * 
261    */
262   private void setBoxPosition()
263   {
264     od.setBoxPosition(av.getAlignment().getHiddenSequences(),
265             av.getAlignment().getHiddenColumns());
266     repaint();
267   }
268
269   /*
270    * Displays the popup menu and acts on user input
271    */
272   private void showPopupMenu(MouseEvent e)
273   {
274     PopupMenu popup = new PopupMenu();
275     ItemListener menuListener = new ItemListener()
276     {
277       @Override
278       public void itemStateChanged(ItemEvent e)
279       {
280         toggleHiddenColumns();
281       }
282     };
283     CheckboxMenuItem item = new CheckboxMenuItem(
284             MessageManager.getString("label.togglehidden"));
285     item.setState(showHidden);
286     popup.add(item);
287     item.addItemListener(menuListener);
288     this.add(popup);
289     popup.show(this, e.getX(), e.getY());
290   }
291
292   @Override
293   public void propertyChange(PropertyChangeEvent evt)
294   {
295     setBoxPosition();
296   }
297
298   /*
299    * Toggle overview display between showing hidden columns and hiding hidden columns
300    */
301   private void toggleHiddenColumns()
302   {
303     if (showHidden)
304     {
305       showHidden = false;
306       od = new OverviewDimensionsHideHidden(av.getRanges(),
307               (av.isShowAnnotation()
308                       && av.getAlignmentConservationAnnotation() != null));
309     }
310     else
311     {
312       showHidden = true;
313       od = new OverviewDimensionsShowHidden(av.getRanges(),
314               (av.isShowAnnotation()
315                       && av.getAlignmentConservationAnnotation() != null));
316     }
317     oviewCanvas.resetOviewDims(od);
318     updateOverviewImage();
319   }
320
321   /**
322    * Removes this object as a property change listener, and nulls references
323    */
324   protected void dispose()
325   {
326     try
327     {
328       av.getRanges().removePropertyChangeListener(this);
329       Frame parent = (Frame) getParent();
330       parent.dispose();
331       parent.setVisible(false);
332     } finally
333     {
334       av = null;
335       oviewCanvas = null;
336       ap = null;
337       od = null;
338     }
339   }
340 }