JAL-2069 spike updated with latest (FeatureTypeSettings)
[jalview.git] / src / jalview / appletgui / ScalePanel.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.datamodel.ColumnSelection;
24 import jalview.datamodel.HiddenColumns;
25 import jalview.datamodel.SequenceGroup;
26 import jalview.renderer.ScaleRenderer;
27 import jalview.renderer.ScaleRenderer.ScaleMark;
28 import jalview.util.MessageManager;
29 import jalview.viewmodel.ViewportListenerI;
30 import jalview.viewmodel.ViewportRanges;
31
32 import java.awt.Color;
33 import java.awt.FontMetrics;
34 import java.awt.Graphics;
35 import java.awt.MenuItem;
36 import java.awt.Panel;
37 import java.awt.PopupMenu;
38 import java.awt.event.ActionEvent;
39 import java.awt.event.ActionListener;
40 import java.awt.event.InputEvent;
41 import java.awt.event.MouseEvent;
42 import java.awt.event.MouseListener;
43 import java.awt.event.MouseMotionListener;
44 import java.beans.PropertyChangeEvent;
45 import java.util.List;
46
47 public class ScalePanel extends Panel
48         implements MouseMotionListener, MouseListener, ViewportListenerI
49 {
50
51   protected int offy = 4;
52
53   public int width;
54
55   protected AlignViewport av;
56
57   AlignmentPanel ap;
58
59   boolean stretchingGroup = false;
60
61   int min; // used by mouseDragged to see if user
62
63   int max; // used by mouseDragged to see if user
64
65   boolean mouseDragging = false;
66
67   int[] reveal;
68
69   public ScalePanel(AlignViewport av, AlignmentPanel ap)
70   {
71     setLayout(null);
72     this.av = av;
73     this.ap = ap;
74
75     addMouseListener(this);
76     addMouseMotionListener(this);
77
78     av.getRanges().addPropertyChangeListener(this);
79   }
80
81   @Override
82   public void mousePressed(MouseEvent evt)
83   {
84     int x = (evt.getX() / av.getCharWidth()) + av.getRanges().getStartRes();
85     final int res;
86
87     if (av.hasHiddenColumns())
88     {
89       res = av.getAlignment().getHiddenColumns().adjustForHiddenColumns(x);
90     }
91     else
92     {
93       res = x;
94     }
95
96     min = res;
97     max = res;
98     if ((evt.getModifiers()
99             & InputEvent.BUTTON3_MASK) == InputEvent.BUTTON3_MASK)
100     {
101       rightMouseButtonPressed(evt, res);
102     }
103     else
104     {
105       leftMouseButtonPressed(evt, res);
106     }
107   }
108
109   /**
110    * Handles left mouse button pressed (selection / clear selections)
111    * 
112    * @param evt
113    * @param res
114    */
115   protected void leftMouseButtonPressed(MouseEvent evt, final int res)
116   {
117     if (!evt.isControlDown() && !evt.isShiftDown())
118     {
119       av.getColumnSelection().clear();
120     }
121
122     av.getColumnSelection().addElement(res);
123     SequenceGroup sg = new SequenceGroup();
124     for (int i = 0; i < av.getAlignment().getSequences().size(); i++)
125     {
126       sg.addSequence(av.getAlignment().getSequenceAt(i), false);
127     }
128
129     sg.setStartRes(res);
130     sg.setEndRes(res);
131     av.setSelectionGroup(sg);
132
133     if (evt.isShiftDown())
134     {
135       int min = Math.min(av.getColumnSelection().getMin(), res);
136       int max = Math.max(av.getColumnSelection().getMax(), res);
137       for (int i = min; i < max; i++)
138       {
139         av.getColumnSelection().addElement(i);
140       }
141       sg.setStartRes(min);
142       sg.setEndRes(max);
143     }
144     ap.paintAlignment(false, false);
145     av.sendSelection();
146   }
147
148   /**
149    * Handles right mouse button press. If pressed in a selected column, opens
150    * context menu for 'Hide Columns'. If pressed on a hidden columns marker,
151    * opens context menu for 'Reveal / Reveal All'. Else does nothing.
152    * 
153    * @param evt
154    * @param res
155    */
156   protected void rightMouseButtonPressed(MouseEvent evt, final int res)
157   {
158     PopupMenu pop = new PopupMenu();
159     if (reveal != null)
160     {
161       MenuItem item = new MenuItem(
162               MessageManager.getString("label.reveal"));
163       item.addActionListener(new ActionListener()
164       {
165         @Override
166         public void actionPerformed(ActionEvent e)
167         {
168           av.showColumn(reveal[0]);
169           reveal = null;
170           ap.paintAlignment(true, true);
171           av.sendSelection();
172         }
173       });
174       pop.add(item);
175
176       if (av.getAlignment().getHiddenColumns().hasManyHiddenColumns())
177       {
178         item = new MenuItem(MessageManager.getString("action.reveal_all"));
179         item.addActionListener(new ActionListener()
180         {
181           @Override
182           public void actionPerformed(ActionEvent e)
183           {
184             av.showAllHiddenColumns();
185             reveal = null;
186             ap.paintAlignment(true, true);
187             av.sendSelection();
188           }
189         });
190         pop.add(item);
191       }
192       this.add(pop);
193       pop.show(this, evt.getX(), evt.getY());
194     }
195     else if (av.getColumnSelection().contains(res))
196     {
197       MenuItem item = new MenuItem(
198               MessageManager.getString("label.hide_columns"));
199       item.addActionListener(new ActionListener()
200       {
201         @Override
202         public void actionPerformed(ActionEvent e)
203         {
204           av.hideColumns(res, res);
205           if (av.getSelectionGroup() != null && av.getSelectionGroup()
206                   .getSize() == av.getAlignment().getHeight())
207           {
208             av.setSelectionGroup(null);
209           }
210
211           ap.paintAlignment(true, true);
212           av.sendSelection();
213         }
214       });
215       pop.add(item);
216       this.add(pop);
217       pop.show(this, evt.getX(), evt.getY());
218     }
219   }
220
221   @Override
222   public void mouseReleased(MouseEvent evt)
223   {
224     mouseDragging = false;
225
226     int res = (evt.getX() / av.getCharWidth())
227             + av.getRanges().getStartRes();
228
229     if (res > av.getAlignment().getWidth())
230     {
231       res = av.getAlignment().getWidth() - 1;
232     }
233
234     if (av.hasHiddenColumns())
235     {
236       res = av.getAlignment().getHiddenColumns()
237               .adjustForHiddenColumns(res);
238     }
239
240     if (!stretchingGroup)
241     {
242       ap.paintAlignment(false, false);
243
244       return;
245     }
246
247     SequenceGroup sg = av.getSelectionGroup();
248
249     if (res > sg.getStartRes())
250     {
251       sg.setEndRes(res);
252     }
253     else if (res < sg.getStartRes())
254     {
255       sg.setStartRes(res);
256     }
257
258     stretchingGroup = false;
259     ap.paintAlignment(false, false);
260     av.sendSelection();
261   }
262
263   /**
264    * Action on dragging the mouse in the scale panel is to expand or shrink the
265    * selection group range (including any hidden columns that it spans)
266    * 
267    * @param evt
268    */
269   @Override
270   public void mouseDragged(MouseEvent evt)
271   {
272     mouseDragging = true;
273     ColumnSelection cs = av.getColumnSelection();
274
275     int res = (evt.getX() / av.getCharWidth())
276             + av.getRanges().getStartRes();
277     res = Math.max(0, res);
278     res = av.getAlignment().getHiddenColumns().adjustForHiddenColumns(res);
279     res = Math.min(res, av.getAlignment().getWidth() - 1);
280     min = Math.min(res, min);
281     max = Math.max(res, max);
282
283     SequenceGroup sg = av.getSelectionGroup();
284     if (sg != null)
285     {
286       stretchingGroup = true;
287       cs.stretchGroup(res, sg, min, max);
288       ap.paintAlignment(false, false);
289     }
290   }
291
292   @Override
293   public void mouseEntered(MouseEvent evt)
294   {
295     if (mouseDragging)
296     {
297       ap.seqPanel.scrollCanvas(null);
298     }
299   }
300
301   @Override
302   public void mouseExited(MouseEvent evt)
303   {
304     if (mouseDragging)
305     {
306       ap.seqPanel.scrollCanvas(evt);
307     }
308   }
309
310   @Override
311   public void mouseClicked(MouseEvent evt)
312   {
313
314   }
315
316   @Override
317   public void mouseMoved(MouseEvent evt)
318   {
319     if (!av.hasHiddenColumns())
320     {
321       return;
322     }
323
324     int res = (evt.getX() / av.getCharWidth())
325             + av.getRanges().getStartRes();
326
327     reveal = av.getAlignment().getHiddenColumns()
328             .getRegionWithEdgeAtRes(res);
329
330     repaint();
331   }
332
333   @Override
334   public void update(Graphics g)
335   {
336     paint(g);
337   }
338
339   @Override
340   public void paint(Graphics g)
341   {
342     /*
343      * shouldn't get called in wrapped mode as the scale above is
344      * drawn instead by SeqCanvas.drawNorthScale
345      */
346     if (!av.getWrapAlignment())
347     {
348       drawScale(g, av.getRanges().getStartRes(), av.getRanges().getEndRes(),
349               getSize().width, getSize().height);
350     }
351   }
352
353   // scalewidth will normally be screenwidth,
354   public void drawScale(Graphics gg, int startx, int endx, int width,
355           int height)
356   {
357     gg.setFont(av.getFont());
358     // Fill in the background
359     gg.setColor(Color.white);
360     gg.fillRect(0, 0, width, height);
361     gg.setColor(Color.black);
362
363     // Fill the selected columns
364     ColumnSelection cs = av.getColumnSelection();
365     HiddenColumns hidden = av.getAlignment().getHiddenColumns();
366     int avCharWidth = av.getCharWidth();
367     int avcharHeight = av.getCharHeight();
368     if (cs != null)
369     {
370       gg.setColor(new Color(220, 0, 0));
371       boolean hasHiddenColumns = hidden.hasHiddenColumns();
372       for (int sel : cs.getSelected())
373       {
374         // TODO: JAL-2001 - provide a fast method to list visible selected in a
375         // given range
376         if (hasHiddenColumns)
377         {
378           if (hidden.isVisible(sel))
379           {
380             sel = hidden.findColumnPosition(sel);
381           }
382           else
383           {
384             continue;
385           }
386         }
387
388         if ((sel >= startx) && (sel <= endx))
389         {
390           gg.fillRect((sel - startx) * avCharWidth, 0, avCharWidth,
391                   getSize().height);
392         }
393       }
394     }
395
396     // Draw the scale numbers
397     gg.setColor(Color.black);
398
399     int maxX = 0;
400     List<ScaleMark> marks = new ScaleRenderer().calculateMarks(av, startx,
401             endx);
402
403     FontMetrics fm = gg.getFontMetrics(av.getFont());
404     int y = avcharHeight;
405     int yOf = fm.getDescent();
406     y -= yOf;
407     for (ScaleMark mark : marks)
408     {
409       boolean major = mark.major;
410       int mpos = mark.column; // (i - startx - 1)
411       String mstring = mark.text;
412       if (mstring != null)
413       {
414         if (mpos * avCharWidth > maxX)
415         {
416           gg.drawString(mstring, mpos * avCharWidth, y);
417           maxX = (mpos + 2) * avCharWidth + fm.stringWidth(mstring);
418         }
419       }
420       if (major)
421       {
422         gg.drawLine((mpos * avCharWidth) + (avCharWidth / 2), y + 2,
423                 (mpos * avCharWidth) + (avCharWidth / 2), y + (yOf * 2));
424       }
425       else
426       {
427         gg.drawLine((mpos * avCharWidth) + (avCharWidth / 2), y + yOf,
428                 (mpos * avCharWidth) + (avCharWidth / 2), y + (yOf * 2));
429       }
430     }
431
432     if (av.hasHiddenColumns())
433     {
434       gg.setColor(Color.blue);
435       int res;
436       if (av.getShowHiddenMarkers())
437       {
438         int widthx = 1 + endx - startx;
439         List<Integer> positions = hidden.findHiddenRegionPositions();
440         for (int pos : positions)
441         {
442
443           res = pos - startx;
444
445           if (res < 0 || res > widthx)
446           {
447             continue;
448           }
449
450           gg.fillPolygon(
451                   new int[]
452                   { -1 + res * avCharWidth - avcharHeight / 4,
453                       -1 + res * avCharWidth + avcharHeight / 4,
454                       -1 + res * avCharWidth },
455                   new int[]
456                   { y, y, y + 2 * yOf }, 3);
457         }
458       }
459     }
460   }
461
462   @Override
463   public void propertyChange(PropertyChangeEvent evt)
464   {
465     // Respond to viewport change events (e.g. alignment panel was scrolled)
466     // Both scrolling and resizing change viewport ranges: scrolling changes
467     // both start and end points, but resize only changes end values.
468     // Here we only want to fastpaint on a scroll, with resize using a normal
469     // paint, so scroll events are identified as changes to the horizontal or
470     // vertical start value.
471     if (evt.getPropertyName().equals(ViewportRanges.STARTRES)
472             || evt.getPropertyName().equals(ViewportRanges.STARTRESANDSEQ)
473             || evt.getPropertyName().equals(ViewportRanges.MOVE_VIEWPORT))
474     {
475       // scroll event, repaint panel
476       repaint();
477     }
478   }
479
480 }