JAL-3161 limit tooltip and status updates to visible columns
[jalview.git] / src / jalview / appletgui / RotatableCanvas.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.api.RotatableCanvasI;
24 import jalview.datamodel.SequenceGroup;
25 import jalview.datamodel.SequenceI;
26 import jalview.datamodel.SequencePoint;
27 import jalview.math.RotatableMatrix;
28 import jalview.util.Format;
29 import jalview.util.MessageManager;
30 import jalview.viewmodel.AlignmentViewport;
31
32 import java.awt.Color;
33 import java.awt.Dimension;
34 import java.awt.Font;
35 import java.awt.Graphics;
36 import java.awt.Image;
37 import java.awt.Panel;
38 import java.awt.event.KeyEvent;
39 import java.awt.event.KeyListener;
40 import java.awt.event.MouseEvent;
41 import java.awt.event.MouseListener;
42 import java.awt.event.MouseMotionListener;
43 import java.util.Vector;
44
45 public class RotatableCanvas extends Panel implements MouseListener,
46         MouseMotionListener, KeyListener, RotatableCanvasI
47 {
48   RotatableMatrix idmat = new RotatableMatrix(3, 3);
49
50   RotatableMatrix objmat = new RotatableMatrix(3, 3);
51
52   RotatableMatrix rotmat = new RotatableMatrix(3, 3);
53
54   String tooltip;
55
56   int toolx, tooly;
57
58   // RubberbandRectangle rubberband;
59
60   boolean drawAxes = true;
61
62   int omx = 0;
63
64   int mx = 0;
65
66   int omy = 0;
67
68   int my = 0;
69
70   Image img;
71
72   Graphics ig;
73
74   Dimension prefsize;
75
76   float centre[] = new float[3];
77
78   float width[] = new float[3];
79
80   float max[] = new float[3];
81
82   float min[] = new float[3];
83
84   float maxwidth;
85
86   float scale;
87
88   int npoint;
89
90   Vector points;
91
92   float[][] orig;
93
94   float[][] axes;
95
96   int startx;
97
98   int starty;
99
100   int lastx;
101
102   int lasty;
103
104   int rectx1;
105
106   int recty1;
107
108   int rectx2;
109
110   int recty2;
111
112   float scalefactor = 1;
113
114   AlignmentViewport av;
115
116   boolean showLabels = false;
117
118   public RotatableCanvas(AlignmentViewport av)
119   {
120     this.av = av;
121   }
122
123   public void showLabels(boolean b)
124   {
125     showLabels = b;
126     repaint();
127   }
128
129   public void setPoints(Vector points, int npoint)
130   {
131     this.points = points;
132     this.npoint = npoint;
133     PaintRefresher.Register(this, av.getSequenceSetId());
134
135     prefsize = getPreferredSize();
136     orig = new float[npoint][3];
137
138     for (int i = 0; i < npoint; i++)
139     {
140       SequencePoint sp = (SequencePoint) points.elementAt(i);
141       for (int j = 0; j < 3; j++)
142       {
143         orig[i][j] = sp.coord[j];
144       }
145     }
146     // Initialize the matrices to identity
147
148     for (int i = 0; i < 3; i++)
149     {
150       for (int j = 0; j < 3; j++)
151       {
152         if (i != j)
153         {
154           idmat.addElement(i, j, 0);
155           objmat.addElement(i, j, 0);
156           rotmat.addElement(i, j, 0);
157         }
158         else
159         {
160           idmat.addElement(i, j, 0);
161           objmat.addElement(i, j, 0);
162           rotmat.addElement(i, j, 0);
163         }
164       }
165     }
166
167     axes = new float[3][3];
168     initAxes();
169
170     findCentre();
171     findWidth();
172
173     scale = findScale();
174
175     // System.out.println("Scale factor = " + scale);
176
177     addMouseListener(this);
178     addKeyListener(this);
179     // if (getParent() != null) {
180     // getParent().addKeyListener(this);
181     // }
182     addMouseMotionListener(this);
183
184     // Add rubberband
185     // rubberband = new RubberbandRectangle(this);
186     // rubberband.setActive(true);
187     // rubberband.addListener(this);
188   }
189
190   /*
191    * public boolean handleSequenceSelectionEvent(SequenceSelectionEvent evt) {
192    * redrawneeded = true; repaint(); return true; }
193    * 
194    * public void removeNotify() { controller.removeListener(this);
195    * super.removeNotify(); }
196    */
197
198   public void initAxes()
199   {
200     for (int i = 0; i < 3; i++)
201     {
202       for (int j = 0; j < 3; j++)
203       {
204         if (i != j)
205         {
206           axes[i][j] = 0;
207         }
208         else
209         {
210           axes[i][j] = 1;
211         }
212       }
213     }
214   }
215
216   public void findWidth()
217   {
218     max = new float[3];
219     min = new float[3];
220
221     max[0] = (float) -1e30;
222     max[1] = (float) -1e30;
223     max[2] = (float) -1e30;
224
225     min[0] = (float) 1e30;
226     min[1] = (float) 1e30;
227     min[2] = (float) 1e30;
228
229     for (int i = 0; i < 3; i++)
230     {
231       for (int j = 0; j < npoint; j++)
232       {
233         SequencePoint sp = (SequencePoint) points.elementAt(j);
234         if (sp.coord[i] >= max[i])
235         {
236           max[i] = sp.coord[i];
237         }
238         if (sp.coord[i] <= min[i])
239         {
240           min[i] = sp.coord[i];
241         }
242       }
243     }
244
245     // System.out.println("xmax " + max[0] + " min " + min[0]);
246     // System.out.println("ymax " + max[1] + " min " + min[1]);
247     // System.out.println("zmax " + max[2] + " min " + min[2]);
248
249     width[0] = Math.abs(max[0] - min[0]);
250     width[1] = Math.abs(max[1] - min[1]);
251     width[2] = Math.abs(max[2] - min[2]);
252
253     maxwidth = width[0];
254
255     if (width[1] > width[0])
256     {
257       maxwidth = width[1];
258     }
259     if (width[2] > width[1])
260     {
261       maxwidth = width[2];
262     }
263
264     // System.out.println("Maxwidth = " + maxwidth);
265   }
266
267   public float findScale()
268   {
269     int dim, width, height;
270     if (getSize().width != 0)
271     {
272       width = getSize().width;
273       height = getSize().height;
274     }
275     else
276     {
277       width = prefsize.width;
278       height = prefsize.height;
279     }
280
281     if (width < height)
282     {
283       dim = width;
284     }
285     else
286     {
287       dim = height;
288     }
289
290     return (float) (dim * scalefactor / (2 * maxwidth));
291   }
292
293   public void findCentre()
294   {
295     // Find centre coordinate
296     findWidth();
297
298     centre[0] = (max[0] + min[0]) / 2;
299     centre[1] = (max[1] + min[1]) / 2;
300     centre[2] = (max[2] + min[2]) / 2;
301
302     // System.out.println("Centre x " + centre[0]);
303     // System.out.println("Centre y " + centre[1]);
304     // System.out.println("Centre z " + centre[2]);
305   }
306
307   public Dimension getPreferredSize()
308   {
309     if (prefsize != null)
310     {
311       return prefsize;
312     }
313     else
314     {
315       return new Dimension(400, 400);
316     }
317   }
318
319   public Dimension getMinimumSize()
320   {
321     return getPreferredSize();
322   }
323
324   public void update(Graphics g)
325   {
326     paint(g);
327   }
328
329   public void paint(Graphics g)
330   {
331     if (points == null)
332     {
333       g.setFont(new Font("Verdana", Font.PLAIN, 18));
334       g.drawString(
335               MessageManager.getString("label.calculating_pca") + "....",
336               20, getSize().height / 2);
337     }
338     else
339     {
340
341       // Only create the image at the beginning -
342       if ((img == null) || (prefsize.width != getSize().width)
343               || (prefsize.height != getSize().height))
344       {
345         prefsize.width = getSize().width;
346         prefsize.height = getSize().height;
347
348         scale = findScale();
349
350         // System.out.println("New scale = " + scale);
351         img = createImage(getSize().width, getSize().height);
352         ig = img.getGraphics();
353
354       }
355
356       drawBackground(ig, Color.black);
357       drawScene(ig);
358       if (drawAxes == true)
359       {
360         drawAxes(ig);
361       }
362
363       if (tooltip != null)
364       {
365         ig.setColor(Color.red);
366         ig.drawString(tooltip, toolx, tooly);
367       }
368
369       g.drawImage(img, 0, 0, this);
370     }
371   }
372
373   public void drawAxes(Graphics g)
374   {
375
376     g.setColor(Color.yellow);
377     for (int i = 0; i < 3; i++)
378     {
379       g.drawLine(getSize().width / 2, getSize().height / 2,
380               (int) (axes[i][0] * scale * max[0] + getSize().width / 2),
381               (int) (axes[i][1] * scale * max[1] + getSize().height / 2));
382     }
383   }
384
385   public void drawBackground(Graphics g, Color col)
386   {
387     g.setColor(col);
388     g.fillRect(0, 0, prefsize.width, prefsize.height);
389   }
390
391   public void drawScene(Graphics g)
392   {
393     // boolean darker = false;
394
395     int halfwidth = getSize().width / 2;
396     int halfheight = getSize().height / 2;
397
398     for (int i = 0; i < npoint; i++)
399     {
400       SequencePoint sp = (SequencePoint) points.elementAt(i);
401       int x = (int) ((float) (sp.coord[0] - centre[0]) * scale) + halfwidth;
402       int y = (int) ((float) (sp.coord[1] - centre[1]) * scale)
403               + halfheight;
404       float z = sp.coord[1] - centre[2];
405
406       if (av.getSequenceColour(sp.sequence) == Color.black)
407       {
408         g.setColor(Color.white);
409       }
410       else
411       {
412         g.setColor(av.getSequenceColour(sp.sequence));
413       }
414
415       if (av.getSelectionGroup() != null)
416       {
417         if (av.getSelectionGroup().getSequences(null)
418                 .contains(((SequencePoint) points.elementAt(i)).sequence))
419         {
420           g.setColor(Color.gray);
421         }
422       }
423       if (z < 0)
424       {
425         g.setColor(g.getColor().darker());
426       }
427
428       g.fillRect(x - 3, y - 3, 6, 6);
429       if (showLabels)
430       {
431         g.setColor(Color.red);
432         g.drawString(
433                 ((SequencePoint) points.elementAt(i)).sequence.getName(),
434                 x - 3, y - 4);
435       }
436     }
437   }
438
439   public Dimension minimumsize()
440   {
441     return prefsize;
442   }
443
444   public Dimension preferredsize()
445   {
446     return prefsize;
447   }
448
449   public void keyTyped(KeyEvent evt)
450   {
451   }
452
453   public void keyReleased(KeyEvent evt)
454   {
455   }
456
457   public void keyPressed(KeyEvent evt)
458   {
459     if (evt.getKeyCode() == KeyEvent.VK_UP)
460     {
461       scalefactor = (float) (scalefactor * 1.1);
462       scale = findScale();
463     }
464     else if (evt.getKeyCode() == KeyEvent.VK_DOWN)
465     {
466       scalefactor = (float) (scalefactor * 0.9);
467       scale = findScale();
468     }
469     else if (evt.getKeyChar() == 's')
470     {
471       System.err.println("DEBUG: Rectangle selection"); // log.debug
472       if (rectx2 != -1 && recty2 != -1)
473       {
474         rectSelect(rectx1, recty1, rectx2, recty2);
475
476       }
477     }
478     repaint();
479   }
480
481   public void printPoints()
482   {
483     for (int i = 0; i < npoint; i++)
484     {
485       SequencePoint sp = (SequencePoint) points.elementAt(i);
486       Format.print(System.out, "%5d ", i);
487       for (int j = 0; j < 3; j++)
488       {
489         Format.print(System.out, "%13.3f  ", sp.coord[j]);
490       }
491       System.out.println();
492     }
493   }
494
495   public void mouseClicked(MouseEvent evt)
496   {
497   }
498
499   public void mouseEntered(MouseEvent evt)
500   {
501   }
502
503   public void mouseExited(MouseEvent evt)
504   {
505   }
506
507   public void mouseReleased(MouseEvent evt)
508   {
509   }
510
511   public void mousePressed(MouseEvent evt)
512   {
513     int x = evt.getX();
514     int y = evt.getY();
515
516     mx = x;
517     my = y;
518
519     omx = mx;
520     omy = my;
521
522     startx = x;
523     starty = y;
524
525     rectx1 = x;
526     recty1 = y;
527
528     rectx2 = -1;
529     recty2 = -1;
530
531     SequenceI found = findPoint(x, y);
532
533     if (found != null)
534     {
535       // TODO: applet PCA is not associatable with multi-panels - only parent
536       // view
537       if (av.getSelectionGroup() != null)
538       {
539         av.getSelectionGroup().addOrRemove(found, true);
540         av.getSelectionGroup().setEndRes(av.getAlignment().getWidth() - 1);
541       }
542       else
543       {
544         av.setSelectionGroup(new SequenceGroup());
545         av.getSelectionGroup().addOrRemove(found, true);
546         av.getSelectionGroup().setEndRes(av.getAlignment().getWidth() - 1);
547
548       }
549       PaintRefresher.Refresh(this, av.getSequenceSetId());
550       av.sendSelection();
551     }
552     repaint();
553   }
554
555   public void mouseMoved(MouseEvent evt)
556   {
557     SequenceI found = findPoint(evt.getX(), evt.getY());
558     if (found == null)
559     {
560       tooltip = null;
561     }
562     else
563     {
564       tooltip = found.getName();
565       toolx = evt.getX();
566       tooly = evt.getY();
567     }
568     repaint();
569   }
570
571   public void mouseDragged(MouseEvent evt)
572   {
573     mx = evt.getX();
574     my = evt.getY();
575
576     rotmat.setIdentity();
577
578     rotmat.rotate((float) (my - omy), 'x');
579     rotmat.rotate((float) (mx - omx), 'y');
580
581     for (int i = 0; i < npoint; i++)
582     {
583       SequencePoint sp = (SequencePoint) points.elementAt(i);
584       sp.coord[0] -= centre[0];
585       sp.coord[1] -= centre[1];
586       sp.coord[2] -= centre[2];
587
588       // Now apply the rotation matrix
589       sp.coord = rotmat.vectorMultiply(sp.coord);
590
591       // Now translate back again
592       sp.coord[0] += centre[0];
593       sp.coord[1] += centre[1];
594       sp.coord[2] += centre[2];
595     }
596
597     for (int i = 0; i < 3; i++)
598     {
599       axes[i] = rotmat.vectorMultiply(axes[i]);
600     }
601     omx = mx;
602     omy = my;
603
604     paint(this.getGraphics());
605   }
606
607   public void rectSelect(int x1, int y1, int x2, int y2)
608   {
609     // boolean changedSel = false;
610     for (int i = 0; i < npoint; i++)
611     {
612       SequencePoint sp = (SequencePoint) points.elementAt(i);
613       int tmp1 = (int) ((sp.coord[0] - centre[0]) * scale
614               + (float) getSize().width / 2.0);
615       int tmp2 = (int) ((sp.coord[1] - centre[1]) * scale
616               + (float) getSize().height / 2.0);
617
618       if (tmp1 > x1 && tmp1 < x2 && tmp2 > y1 && tmp2 < y2)
619       {
620         if (av != null)
621         {
622           if (!av.getSelectionGroup().getSequences(null)
623                   .contains(sp.sequence))
624           {
625             av.getSelectionGroup().addSequence(sp.sequence, true);
626           }
627         }
628       }
629     }
630   }
631
632   public SequenceI findPoint(int x, int y)
633   {
634
635     int halfwidth = getSize().width / 2;
636     int halfheight = getSize().height / 2;
637
638     int found = -1;
639
640     for (int i = 0; i < npoint; i++)
641     {
642
643       SequencePoint sp = (SequencePoint) points.elementAt(i);
644       int px = (int) ((float) (sp.coord[0] - centre[0]) * scale)
645               + halfwidth;
646       int py = (int) ((float) (sp.coord[1] - centre[1]) * scale)
647               + halfheight;
648
649       if (Math.abs(px - x) < 3 && Math.abs(py - y) < 3)
650       {
651         found = i;
652       }
653     }
654     if (found != -1)
655     {
656       return ((SequencePoint) points.elementAt(found)).sequence;
657     }
658     else
659     {
660       return null;
661     }
662   }
663
664 }