JAL-192 need visible and actual sequence limits for scale
[jalview.git] / src / jalview / gui / 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.gui;
22
23 import jalview.datamodel.ColumnSelection;
24 import jalview.datamodel.SequenceGroup;
25 import jalview.datamodel.SequenceI;
26 import jalview.util.MessageManager;
27
28 import java.awt.Color;
29 import java.awt.FontMetrics;
30 import java.awt.Graphics;
31 import java.awt.Graphics2D;
32 import java.awt.RenderingHints;
33 import java.awt.event.ActionEvent;
34 import java.awt.event.ActionListener;
35 import java.awt.event.MouseEvent;
36 import java.awt.event.MouseListener;
37 import java.awt.event.MouseMotionListener;
38
39 import javax.swing.JMenuItem;
40 import javax.swing.JPanel;
41 import javax.swing.JPopupMenu;
42 import javax.swing.ToolTipManager;
43
44 /**
45  * DOCUMENT ME!
46  * 
47  * @author $author$
48  * @version $Revision$
49  */
50 public class ScalePanel extends JPanel implements MouseMotionListener,
51         MouseListener
52 {
53   protected int offy = 4;
54
55   /** DOCUMENT ME!! */
56   public int width;
57
58   protected AlignViewport av;
59
60   AlignmentPanel ap;
61
62   boolean stretchingGroup = false;
63
64   int min; // used by mouseDragged to see if user
65
66   int max; // used by mouseDragged to see if user
67
68   boolean mouseDragging = false;
69
70   // wants to delete columns
71   public ScalePanel(AlignViewport av, AlignmentPanel ap)
72   {
73     this.av = av;
74     this.ap = ap;
75
76     addMouseListener(this);
77     addMouseMotionListener(this);
78   }
79
80   /**
81    * DOCUMENT ME!
82    * 
83    * @param evt
84    *          DOCUMENT ME!
85    */
86   @Override
87   public void mousePressed(MouseEvent evt)
88   {
89     int x = (evt.getX() / av.getCharWidth()) + av.getStartRes();
90     final int res;
91
92     if (av.hasHiddenColumns())
93     {
94       x = av.getColumnSelection().adjustForHiddenColumns(x);
95     }
96
97     if (x >= av.getAlignment().getWidth())
98     {
99       res = av.getAlignment().getWidth() - 1;
100     }
101     else
102     {
103       res = x;
104     }
105
106     min = res;
107     max = res;
108
109     if (evt.isPopupTrigger())
110     {
111       rightMouseButtonPressed(evt, res);
112     }
113     else
114     {
115       leftMouseButtonPressed(evt, res);
116     }
117   }
118
119   /**
120    * Handles right mouse button press. If pressed in a selected column, opens
121    * context menu for 'Hide Columns'. If pressed on a hidden columns marker,
122    * opens context menu for 'Reveal / Reveal All'. Else does nothing.
123    * 
124    * @param evt
125    * @param res
126    */
127   protected void rightMouseButtonPressed(MouseEvent evt, final int res)
128   {
129     JPopupMenu pop = new JPopupMenu();
130     if (reveal != null)
131     {
132       JMenuItem item = new JMenuItem(
133               MessageManager.getString("label.reveal"));
134       item.addActionListener(new ActionListener()
135       {
136         @Override
137         public void actionPerformed(ActionEvent e)
138         {
139           av.showColumn(reveal[0]);
140           reveal = null;
141           ap.paintAlignment(true);
142           if (ap.overviewPanel != null)
143           {
144             ap.overviewPanel.updateOverviewImage();
145           }
146           av.sendSelection();
147         }
148       });
149       pop.add(item);
150
151       if (av.getColumnSelection().hasHiddenColumns())
152       {
153         item = new JMenuItem(MessageManager.getString("action.reveal_all"));
154         item.addActionListener(new ActionListener()
155         {
156           @Override
157           public void actionPerformed(ActionEvent e)
158           {
159             av.showAllHiddenColumns();
160             reveal = null;
161             ap.paintAlignment(true);
162             if (ap.overviewPanel != null)
163             {
164               ap.overviewPanel.updateOverviewImage();
165             }
166             av.sendSelection();
167           }
168         });
169         pop.add(item);
170       }
171       pop.show(this, evt.getX(), evt.getY());
172     }
173     else if (av.getColumnSelection().contains(res))
174     {
175       JMenuItem item = new JMenuItem(
176               MessageManager.getString("label.hide_columns"));
177       item.addActionListener(new ActionListener()
178       {
179         @Override
180         public void actionPerformed(ActionEvent e)
181         {
182           av.hideColumns(res, res);
183           if (av.getSelectionGroup() != null
184                   && av.getSelectionGroup().getSize() == av.getAlignment()
185                           .getHeight())
186           {
187             av.setSelectionGroup(null);
188           }
189
190           ap.paintAlignment(true);
191           if (ap.overviewPanel != null)
192           {
193             ap.overviewPanel.updateOverviewImage();
194           }
195           av.sendSelection();
196         }
197       });
198       pop.add(item);
199       pop.show(this, evt.getX(), evt.getY());
200     }
201   }
202
203   /**
204    * Handles left mouse button press
205    * 
206    * @param evt
207    * @param res
208    */
209   protected void leftMouseButtonPressed(MouseEvent evt, final int res)
210   {
211     if (!evt.isControlDown() && !evt.isShiftDown())
212     {
213       av.getColumnSelection().clear();
214     }
215
216     av.getColumnSelection().addElement(res);
217     SequenceGroup sg = new SequenceGroup();
218     // try to be as quick as possible
219     SequenceI[] iVec = av.getAlignment().getSequencesArray();
220     for (int i = 0; i < iVec.length; i++)
221     {
222       sg.addSequence(iVec[i], false);
223       iVec[i] = null;
224     }
225     iVec = null;
226     sg.setStartRes(res);
227     sg.setEndRes(res);
228
229     if (evt.isShiftDown())
230     {
231       int min = Math.min(av.getColumnSelection().getMin(), res);
232       int max = Math.max(av.getColumnSelection().getMax(), res);
233       for (int i = min; i < max; i++)
234       {
235         av.getColumnSelection().addElement(i);
236       }
237       sg.setStartRes(min);
238       sg.setEndRes(max);
239     }
240     av.setSelectionGroup(sg);
241     ap.paintAlignment(false);
242     av.sendSelection();
243   }
244
245   /**
246    * DOCUMENT ME!
247    * 
248    * @param evt
249    *          DOCUMENT ME!
250    */
251   @Override
252   public void mouseReleased(MouseEvent evt)
253   {
254     mouseDragging = false;
255
256     int res = (evt.getX() / av.getCharWidth()) + av.getStartRes();
257
258     if (av.hasHiddenColumns())
259     {
260       res = av.getColumnSelection().adjustForHiddenColumns(res);
261     }
262
263     if (res >= av.getAlignment().getWidth())
264     {
265       res = av.getAlignment().getWidth() - 1;
266     }
267
268     if (!stretchingGroup)
269     {
270       ap.paintAlignment(false);
271
272       return;
273     }
274
275     SequenceGroup sg = av.getSelectionGroup();
276
277     if (sg != null)
278     {
279       if (res > sg.getStartRes())
280       {
281         sg.setEndRes(res);
282       }
283       else if (res < sg.getStartRes())
284       {
285         sg.setStartRes(res);
286       }
287     }
288     stretchingGroup = false;
289     ap.paintAlignment(false);
290     av.sendSelection();
291   }
292
293   /**
294    * DOCUMENT ME!
295    * 
296    * @param evt
297    *          DOCUMENT ME!
298    */
299   @Override
300   public void mouseDragged(MouseEvent evt)
301   {
302     mouseDragging = true;
303
304     int res = (evt.getX() / av.getCharWidth()) + av.getStartRes();
305     if (res < 0)
306     {
307       res = 0;
308     }
309
310     if (av.hasHiddenColumns())
311     {
312       res = av.getColumnSelection().adjustForHiddenColumns(res);
313     }
314
315     if (res >= av.getAlignment().getWidth())
316     {
317       res = av.getAlignment().getWidth() - 1;
318     }
319
320     if (res < min)
321     {
322       min = res;
323     }
324
325     if (res > max)
326     {
327       max = res;
328     }
329
330     SequenceGroup sg = av.getSelectionGroup();
331
332     if (sg != null)
333     {
334       stretchingGroup = true;
335
336       if (!av.getColumnSelection().contains(res))
337       {
338         av.getColumnSelection().addElement(res);
339       }
340
341       if (res > sg.getStartRes())
342       {
343         sg.setEndRes(res);
344       }
345       if (res < sg.getStartRes())
346       {
347         sg.setStartRes(res);
348       }
349
350       int col;
351       for (int i = min; i <= max; i++)
352       {
353         col = i; // av.getColumnSelection().adjustForHiddenColumns(i);
354
355         if ((col < sg.getStartRes()) || (col > sg.getEndRes()))
356         {
357           av.getColumnSelection().removeElement(col);
358         }
359         else
360         {
361           av.getColumnSelection().addElement(col);
362         }
363       }
364
365       ap.paintAlignment(false);
366     }
367   }
368
369   @Override
370   public void mouseEntered(MouseEvent evt)
371   {
372     if (mouseDragging)
373     {
374       ap.getSeqPanel().scrollCanvas(null);
375     }
376   }
377
378   @Override
379   public void mouseExited(MouseEvent evt)
380   {
381     if (mouseDragging)
382     {
383       ap.getSeqPanel().scrollCanvas(evt);
384     }
385   }
386
387   @Override
388   public void mouseClicked(MouseEvent evt)
389   {
390   }
391
392   @Override
393   public void mouseMoved(MouseEvent evt)
394   {
395     if (!av.hasHiddenColumns())
396     {
397       return;
398     }
399
400     int res = (evt.getX() / av.getCharWidth()) + av.getStartRes();
401
402     res = av.getColumnSelection().adjustForHiddenColumns(res);
403
404     reveal = null;
405     if (av.getColumnSelection().getHiddenColumns() != null)
406     {
407       for (int[] region : av.getColumnSelection().getHiddenColumns())
408       {
409         if (res + 1 == region[0] || res - 1 == region[1])
410         {
411           reveal = region;
412           ToolTipManager.sharedInstance().registerComponent(this);
413           this.setToolTipText(MessageManager
414                   .getString("label.reveal_hidden_columns"));
415           break;
416         }
417         else
418         {
419           this.setToolTipText(null);
420         }
421       }
422     }
423     repaint();
424   }
425
426   int[] reveal;
427
428   /**
429    * DOCUMENT ME!
430    * 
431    * @param g
432    *          DOCUMENT ME!
433    */
434   @Override
435   public void paintComponent(Graphics g)
436   {
437     drawScale(g, av.getStartRes(), av.getEndRes(), getWidth(), getHeight());
438   }
439
440   // scalewidth will normally be screenwidth,
441   public void drawScale(Graphics g, int startx, int endx, int width,
442           int height)
443   {
444     Graphics2D gg = (Graphics2D) g;
445     gg.setFont(av.getFont());
446
447     if (av.antiAlias)
448     {
449       gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
450               RenderingHints.VALUE_ANTIALIAS_ON);
451     }
452
453     // Fill in the background
454     gg.setColor(Color.white);
455     gg.fillRect(0, 0, width, height);
456     gg.setColor(Color.black);
457
458     // Fill the selected columns
459     ColumnSelection cs = av.getColumnSelection();
460     int avCharWidth = av.getCharWidth(), avCharHeight = av.getCharHeight();
461
462     int s;
463     if (cs != null)
464     {
465       gg.setColor(new Color(220, 0, 0));
466
467       for (int sel : cs.getSelected())
468       {
469         // TODO: JAL-2001 - provide a fast method to list visible selected in a
470         // given range
471
472         if (av.hasHiddenColumns())
473         {
474           if (cs.isVisible(sel))
475           {
476             sel = cs.findColumnPosition(sel);
477           }
478           else
479           {
480             continue;
481           }
482         }
483
484         if ((sel >= startx) && (sel <= endx))
485         {
486           gg.fillRect((sel - startx) * avCharWidth, 0, avCharWidth,
487                   getHeight());
488         }
489       }
490     }
491     // Draw the scale numbers
492     gg.setColor(Color.black);
493
494     int scalestartx = (startx / 10) * 10;
495
496     SequenceI refSeq = av.getAlignment().getSeqrep();
497     int refSp = 0, refEp = -1, refStart = 0, refEnd = -1, refStartI = 0, refEndI = -1;
498     if (refSeq != null)
499     {
500       // find bounds and set origin appopriately
501       // locate first visible position for this sequence
502       int[] refbounds = av.getColumnSelection()
503               .locateVisibleBoundsOfSequence(refSeq);
504
505       refSp = refbounds[0];
506       refEp = refbounds[1];
507       refStart = refbounds[2];
508       refEnd = refbounds[3];
509       refStartI = refbounds[4];
510       refEndI = refbounds[5];
511       scalestartx = refSp + ((scalestartx - refSp) / 10) * 10;
512     }
513
514
515     int widthx = 1 + endx - startx;
516
517     FontMetrics fm = gg.getFontMetrics(av.getFont());
518     int y = avCharHeight - fm.getDescent();
519
520     if (refSeq == null && scalestartx % 10 == 0)
521     {
522       scalestartx += 5;
523     }
524
525     String string;
526     int maxX = 0, refN, iadj;
527     // todo: add a 'reference origin column' to set column number relative to
528     for (int i = scalestartx; i < endx; i += 5)
529     {
530       if (((i - refSp) % 10) == 0)
531       {
532         iadj = av.getColumnSelection().adjustForHiddenColumns(i) - 1;
533         if (refSeq == null)
534         {
535           string = String.valueOf(iadj + 1);
536         }
537         else
538         {
539           refN = refSeq.findPosition(iadj);
540           // TODO show bounds if position is a gap
541           // - ie L--R -> "1L|2R" for
542           // marker
543           if (iadj < refStartI)
544           {
545             string = String.valueOf(iadj - refStartI);
546           }
547           else if (iadj > refEndI)
548           {
549             string = "+" + String.valueOf(iadj - refEndI);
550           }
551           else
552           {
553             string = String.valueOf(refN) + refSeq.getCharAt(iadj);
554           }
555         }
556         if ((i - startx - 1) * avCharWidth > maxX)
557         {
558           gg.drawString(string, (i - startx - 1) * avCharWidth, y);
559           maxX = (i - startx + 1) * avCharWidth + fm.stringWidth(string);
560         }
561
562         gg.drawLine(((i - startx - 1) * avCharWidth) + (avCharWidth / 2),
563                 y + 2,
564                 ((i - startx - 1) * avCharWidth) + (avCharWidth / 2), y
565                         + (fm.getDescent() * 2));
566       }
567       else
568       {
569         gg.drawLine(((i - startx - 1) * avCharWidth) + (avCharWidth / 2), y
570                 + fm.getDescent(), ((i - startx - 1) * avCharWidth)
571                 + (avCharWidth / 2), y + (fm.getDescent() * 2));
572       }
573     }
574
575     if (av.hasHiddenColumns())
576     {
577       gg.setColor(Color.blue);
578       int res;
579       if (av.getShowHiddenMarkers()
580               && av.getColumnSelection().getHiddenColumns() != null)
581       {
582         for (int i = 0; i < av.getColumnSelection().getHiddenColumns()
583                 .size(); i++)
584         {
585           res = av.getColumnSelection().findHiddenRegionPosition(i)
586                   - startx;
587
588           if (res < 0 || res > widthx)
589           {
590             continue;
591           }
592
593           gg.fillPolygon(new int[] { res * avCharWidth - avCharHeight / 4,
594               res * avCharWidth + avCharHeight / 4, res * avCharWidth },
595                   new int[] { y - avCharHeight / 2, y - avCharHeight / 2,
596                       y + 8 }, 3);
597
598         }
599       }
600
601       if (reveal != null && reveal[0] > startx && reveal[0] < endx)
602       {
603         gg.drawString(MessageManager.getString("label.reveal_columns"),
604                 reveal[0] * avCharWidth, 0);
605       }
606     }
607
608   }
609 }