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