108bf2409bf767441f705ee8288a7bbdb4652a90
[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
492     int widthx = 1 + endx - startx;
493
494     FontMetrics fm = gg.getFontMetrics(av.getFont());
495     int y = avCharHeight, yOf = fm.getDescent();
496     y -= yOf;
497     if (av.hasHiddenColumns())
498     {
499       // draw any hidden column markers
500       gg.setColor(Color.blue);
501       int res;
502       if (av.getShowHiddenMarkers()
503               && av.getColumnSelection().getHiddenColumns() != null)
504       {
505         for (int i = 0; i < av.getColumnSelection().getHiddenColumns()
506                 .size(); i++)
507         {
508           res = av.getColumnSelection().findHiddenRegionPosition(i)
509                   - startx;
510
511           if (res < 0 || res > widthx)
512           {
513             continue;
514           }
515
516           gg.fillPolygon(new int[] {
517               -1 + res * avCharWidth - avCharHeight / 4,
518               -1 + res * avCharWidth + avCharHeight / 4,
519               -1 + res * avCharWidth }, new int[] { y, y, y + 2 * yOf }, 3);
520
521         }
522       }
523     }
524     // Draw the scale numbers
525     gg.setColor(Color.black);
526
527     int scalestartx = (startx / 10) * 10;
528
529     SequenceI refSeq = av.getAlignment().getSeqrep();
530     int refSp = 0, refEp = -1, refStart = 0, refEnd = -1, refStartI = 0, refEndI = -1;
531     if (refSeq != null)
532     {
533       // find bounds and set origin appopriately
534       // locate first visible position for this sequence
535       int[] refbounds = av.getColumnSelection()
536               .locateVisibleBoundsOfSequence(refSeq);
537
538       refSp = refbounds[0];
539       refEp = refbounds[1];
540       refStart = refbounds[2];
541       refEnd = refbounds[3];
542       refStartI = refbounds[4];
543       refEndI = refbounds[5];
544       scalestartx = refSp + ((scalestartx - refSp) / 10) * 10;
545     }
546
547
548     if (refSeq == null && scalestartx % 10 == 0)
549     {
550       scalestartx += 5;
551     }
552
553     String string;
554     int maxX = 0, refN, iadj;
555     // todo: add a 'reference origin column' to set column number relative to
556     for (int i = scalestartx; i < endx; i += 5)
557     {
558       if (((i - refSp) % 10) == 0)
559       {
560         if (refSeq == null)
561         {
562           iadj = av.getColumnSelection().adjustForHiddenColumns(i - 1) + 1;
563           string = String.valueOf(iadj);
564         }
565         else
566         {
567           iadj = av.getColumnSelection().adjustForHiddenColumns(i - 1);
568           refN = refSeq.findPosition(iadj);
569           // TODO show bounds if position is a gap
570           // - ie L--R -> "1L|2R" for
571           // marker
572           if (iadj < refStartI)
573           {
574             string = String.valueOf(iadj - refStartI);
575           }
576           else if (iadj > refEndI)
577           {
578             string = "+" + String.valueOf(iadj - refEndI);
579           }
580           else
581           {
582             string = String.valueOf(refN) + refSeq.getCharAt(iadj);
583           }
584         }
585         if ((i - startx - 1) * avCharWidth > maxX)
586         {
587           gg.drawString(string, (i - startx - 1) * avCharWidth, y);
588           maxX = (i - startx + 1) * avCharWidth + fm.stringWidth(string);
589         }
590
591         gg.drawLine(((i - startx - 1) * avCharWidth) + (avCharWidth / 2),
592                 y + 2,
593                 ((i - startx - 1) * avCharWidth) + (avCharWidth / 2), y
594                         + (yOf * 2));
595       }
596       else
597       {
598         gg.drawLine(((i - startx - 1) * avCharWidth) + (avCharWidth / 2), y
599                 + yOf,
600                 ((i - startx - 1) * avCharWidth) + (avCharWidth / 2), y
601                         + (yOf * 2));
602       }
603     }
604
605     if (av.hasHiddenColumns())
606     {
607       if (reveal != null && reveal[0] > startx && reveal[0] < endx)
608       {
609         gg.drawString(MessageManager.getString("label.reveal_columns"),
610                 reveal[0] * avCharWidth, 0);
611       }
612     }
613
614   }
615 }