Jalview 2.6 source licence
[jalview.git] / src / jalview / gui / RotatableCanvas.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.6)
3  * Copyright (C) 2010 J Procter, AM Waterhouse, G Barton, M Clamp, S Searle
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 of the License, or (at your option) any later version.
10  * 
11  * Jalview is distributed in the hope that it will be useful, but 
12  * WITHOUT ANY WARRANTY; without even the implied warranty 
13  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
14  * PURPOSE.  See the GNU General Public License for more details.
15  * 
16  * You should have received a copy of the GNU General Public License along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 package jalview.gui;
19
20 import java.util.*;
21
22 import java.awt.*;
23 import java.awt.event.*;
24 import javax.swing.*;
25
26 import jalview.datamodel.*;
27 import jalview.math.*;
28
29 /**
30  * DOCUMENT ME!
31  * 
32  * @author $author$
33  * @version $Revision$
34  */
35 public class RotatableCanvas extends JPanel implements MouseListener,
36         MouseMotionListener, KeyListener
37 {
38   RotatableMatrix idmat = new RotatableMatrix(3, 3);
39
40   RotatableMatrix objmat = new RotatableMatrix(3, 3);
41
42   RotatableMatrix rotmat = new RotatableMatrix(3, 3);
43
44   // RubberbandRectangle rubberband;
45   boolean drawAxes = true;
46
47   int omx = 0;
48
49   int mx = 0;
50
51   int omy = 0;
52
53   int my = 0;
54
55   Image img;
56
57   Graphics ig;
58
59   Dimension prefsize;
60
61   float[] centre = new float[3];
62
63   float[] width = new float[3];
64
65   float[] max = new float[3];
66
67   float[] min = new float[3];
68
69   float maxwidth;
70
71   float scale;
72
73   int npoint;
74
75   Vector points;
76
77   float[][] orig;
78
79   float[][] axes;
80
81   int startx;
82
83   int starty;
84
85   int lastx;
86
87   int lasty;
88
89   int rectx1;
90
91   int recty1;
92
93   int rectx2;
94
95   int recty2;
96
97   float scalefactor = 1;
98
99   AlignViewport av;
100
101   AlignmentPanel ap;
102
103   boolean showLabels = false;
104
105   Color bgColour = Color.black;
106
107   boolean applyToAllViews = false;
108
109   // Controller controller;
110   public RotatableCanvas(AlignmentPanel ap)
111   {
112     this.av = ap.av;
113     this.ap = ap;
114
115     addMouseWheelListener(new MouseWheelListener()
116     {
117       public void mouseWheelMoved(MouseWheelEvent e)
118       {
119         if (e.getWheelRotation() > 0)
120         {
121           scale = (float) (scale * 1.1);
122           repaint();
123         }
124
125         else
126         {
127           scale = (float) (scale * 0.9);
128           repaint();
129         }
130       }
131     });
132
133   }
134
135   public void showLabels(boolean b)
136   {
137     showLabels = b;
138     repaint();
139   }
140
141   public void setPoints(Vector points, int npoint)
142   {
143     this.points = points;
144     this.npoint = npoint;
145     ToolTipManager.sharedInstance().registerComponent(this);
146     ToolTipManager.sharedInstance().setInitialDelay(0);
147     ToolTipManager.sharedInstance().setDismissDelay(10000);
148
149     prefsize = getPreferredSize();
150     orig = new float[npoint][3];
151
152     for (int i = 0; i < npoint; i++)
153     {
154       SequencePoint sp = (SequencePoint) points.elementAt(i);
155
156       for (int j = 0; j < 3; j++)
157       {
158         orig[i][j] = sp.coord[j];
159       }
160     }
161
162     // Initialize the matrices to identity
163     for (int i = 0; i < 3; i++)
164     {
165       for (int j = 0; j < 3; j++)
166       {
167         if (i != j)
168         {
169           idmat.addElement(i, j, 0);
170           objmat.addElement(i, j, 0);
171           rotmat.addElement(i, j, 0);
172         }
173         else
174         {
175           idmat.addElement(i, j, 0);
176           objmat.addElement(i, j, 0);
177           rotmat.addElement(i, j, 0);
178         }
179       }
180     }
181
182     axes = new float[3][3];
183     initAxes();
184
185     findCentre();
186     findWidth();
187
188     scale = findScale();
189
190     addMouseListener(this);
191
192     addMouseMotionListener(this);
193
194   }
195
196   public void initAxes()
197   {
198     for (int i = 0; i < 3; i++)
199     {
200       for (int j = 0; j < 3; j++)
201       {
202         if (i != j)
203         {
204           axes[i][j] = 0;
205         }
206         else
207         {
208           axes[i][j] = 1;
209         }
210       }
211     }
212   }
213
214   /**
215    * DOCUMENT ME!
216    */
217   public void findWidth()
218   {
219     max = new float[3];
220     min = new float[3];
221
222     max[0] = (float) -1e30;
223     max[1] = (float) -1e30;
224     max[2] = (float) -1e30;
225
226     min[0] = (float) 1e30;
227     min[1] = (float) 1e30;
228     min[2] = (float) 1e30;
229
230     for (int i = 0; i < 3; i++)
231     {
232       for (int j = 0; j < npoint; j++)
233       {
234         SequencePoint sp = (SequencePoint) points.elementAt(j);
235
236         if (sp.coord[i] >= max[i])
237         {
238           max[i] = sp.coord[i];
239         }
240
241         if (sp.coord[i] <= min[i])
242         {
243           min[i] = sp.coord[i];
244         }
245       }
246     }
247
248     // System.out.println("xmax " + max[0] + " min " + min[0]);
249     // System.out.println("ymax " + max[1] + " min " + min[1]);
250     // System.out.println("zmax " + max[2] + " min " + min[2]);
251     width[0] = Math.abs(max[0] - min[0]);
252     width[1] = Math.abs(max[1] - min[1]);
253     width[2] = Math.abs(max[2] - min[2]);
254
255     maxwidth = width[0];
256
257     if (width[1] > width[0])
258     {
259       maxwidth = width[1];
260     }
261
262     if (width[2] > width[1])
263     {
264       maxwidth = width[2];
265     }
266
267     // System.out.println("Maxwidth = " + maxwidth);
268   }
269
270   /**
271    * DOCUMENT ME!
272    * 
273    * @return DOCUMENT ME!
274    */
275   public float findScale()
276   {
277     int dim;
278     int width;
279     int height;
280
281     if (getWidth() != 0)
282     {
283       width = getWidth();
284       height = getHeight();
285     }
286     else
287     {
288       width = prefsize.width;
289       height = prefsize.height;
290     }
291
292     if (width < height)
293     {
294       dim = width;
295     }
296     else
297     {
298       dim = height;
299     }
300
301     return (float) ((dim * scalefactor) / (2 * maxwidth));
302   }
303
304   /**
305    * DOCUMENT ME!
306    */
307   public void findCentre()
308   {
309     // Find centre coordinate
310     findWidth();
311
312     centre[0] = (max[0] + min[0]) / 2;
313     centre[1] = (max[1] + min[1]) / 2;
314     centre[2] = (max[2] + min[2]) / 2;
315
316     // System.out.println("Centre x " + centre[0]);
317     // System.out.println("Centre y " + centre[1]);
318     // System.out.println("Centre z " + centre[2]);
319   }
320
321   /**
322    * DOCUMENT ME!
323    * 
324    * @return DOCUMENT ME!
325    */
326   public Dimension getPreferredSize()
327   {
328     if (prefsize != null)
329     {
330       return prefsize;
331     }
332     else
333     {
334       return new Dimension(400, 400);
335     }
336   }
337
338   /**
339    * DOCUMENT ME!
340    * 
341    * @return DOCUMENT ME!
342    */
343   public Dimension getMinimumSize()
344   {
345     return getPreferredSize();
346   }
347
348   /**
349    * DOCUMENT ME!
350    * 
351    * @param g
352    *          DOCUMENT ME!
353    */
354   public void paintComponent(Graphics g1)
355   {
356
357     Graphics2D g = (Graphics2D) g1;
358
359     g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
360             RenderingHints.VALUE_ANTIALIAS_ON);
361     if (points == null)
362     {
363       g.setFont(new Font("Verdana", Font.PLAIN, 18));
364       g.drawString("Calculating PCA....", 20, getHeight() / 2);
365     }
366     else
367     {
368       // Only create the image at the beginning -
369       if ((img == null) || (prefsize.width != getWidth())
370               || (prefsize.height != getHeight()))
371       {
372         prefsize.width = getWidth();
373         prefsize.height = getHeight();
374
375         scale = findScale();
376
377         // System.out.println("New scale = " + scale);
378         img = createImage(getWidth(), getHeight());
379         ig = img.getGraphics();
380       }
381
382       drawBackground(ig, bgColour);
383       drawScene(ig);
384
385       if (drawAxes == true)
386       {
387         drawAxes(ig);
388       }
389
390       g.drawImage(img, 0, 0, this);
391     }
392   }
393
394   /**
395    * DOCUMENT ME!
396    * 
397    * @param g
398    *          DOCUMENT ME!
399    */
400   public void drawAxes(Graphics g)
401   {
402
403     g.setColor(Color.yellow);
404
405     for (int i = 0; i < 3; i++)
406     {
407       g.drawLine(getWidth() / 2, getHeight() / 2, (int) ((axes[i][0]
408               * scale * max[0]) + (getWidth() / 2)), (int) ((axes[i][1]
409               * scale * max[1]) + (getHeight() / 2)));
410     }
411   }
412
413   /**
414    * DOCUMENT ME!
415    * 
416    * @param g
417    *          DOCUMENT ME!
418    * @param col
419    *          DOCUMENT ME!
420    */
421   public void drawBackground(Graphics g, Color col)
422   {
423     g.setColor(col);
424     g.fillRect(0, 0, prefsize.width, prefsize.height);
425   }
426
427   /**
428    * DOCUMENT ME!
429    * 
430    * @param g
431    *          DOCUMENT ME!
432    */
433   public void drawScene(Graphics g1)
434   {
435
436     Graphics2D g = (Graphics2D) g1;
437
438     g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
439             RenderingHints.VALUE_ANTIALIAS_ON);
440
441     int halfwidth = getWidth() / 2;
442     int halfheight = getHeight() / 2;
443
444     for (int i = 0; i < npoint; i++)
445     {
446       SequencePoint sp = (SequencePoint) points.elementAt(i);
447       int x = (int) ((float) (sp.coord[0] - centre[0]) * scale) + halfwidth;
448       int y = (int) ((float) (sp.coord[1] - centre[1]) * scale)
449               + halfheight;
450       float z = sp.coord[1] - centre[2];
451
452       if (av.getSequenceColour(sp.sequence) == Color.black)
453       {
454         g.setColor(Color.white);
455       }
456       else
457       {
458         g.setColor(av.getSequenceColour(sp.sequence));
459       }
460
461       if (av.getSelectionGroup() != null)
462       {
463         if (av.getSelectionGroup().getSequences(null).contains(
464                 ((SequencePoint) points.elementAt(i)).sequence))
465         {
466           g.setColor(Color.gray);
467         }
468       }
469
470       if (z < 0)
471       {
472         g.setColor(g.getColor().darker());
473       }
474
475       g.fillRect(x - 3, y - 3, 6, 6);
476       if (showLabels)
477       {
478         g.setColor(Color.red);
479         g.drawString(((SequencePoint) points.elementAt(i)).sequence
480                 .getName(), x - 3, y - 4);
481       }
482     }
483
484     // //Now the rectangle
485     // if (rectx2 != -1 && recty2 != -1) {
486     // g.setColor(Color.white);
487     //
488     // g.drawRect(rectx1,recty1,rectx2-rectx1,recty2-recty1);
489     // }
490   }
491
492   /**
493    * DOCUMENT ME!
494    * 
495    * @return DOCUMENT ME!
496    */
497   public Dimension minimumsize()
498   {
499     return prefsize;
500   }
501
502   /**
503    * DOCUMENT ME!
504    * 
505    * @return DOCUMENT ME!
506    */
507   public Dimension preferredsize()
508   {
509     return prefsize;
510   }
511
512   /**
513    * DOCUMENT ME!
514    * 
515    * @param evt
516    *          DOCUMENT ME!
517    */
518   public void keyTyped(KeyEvent evt)
519   {
520   }
521
522   /**
523    * DOCUMENT ME!
524    * 
525    * @param evt
526    *          DOCUMENT ME!
527    */
528   public void keyReleased(KeyEvent evt)
529   {
530   }
531
532   /**
533    * DOCUMENT ME!
534    * 
535    * @param evt
536    *          DOCUMENT ME!
537    */
538   public void keyPressed(KeyEvent evt)
539   {
540     if (evt.getKeyCode() == KeyEvent.VK_UP)
541     {
542       scalefactor = (float) (scalefactor * 1.1);
543       scale = findScale();
544     }
545     else if (evt.getKeyCode() == KeyEvent.VK_DOWN)
546     {
547       scalefactor = (float) (scalefactor * 0.9);
548       scale = findScale();
549     }
550     else if (evt.getKeyChar() == 's')
551     {
552       System.err.println("DEBUG: Rectangle selection"); // log.debug
553
554       if ((rectx2 != -1) && (recty2 != -1))
555       {
556         rectSelect(rectx1, recty1, rectx2, recty2);
557       }
558     }
559
560     repaint();
561   }
562
563   /**
564    * DOCUMENT ME!
565    * 
566    * @param evt
567    *          DOCUMENT ME!
568    */
569   public void mouseClicked(MouseEvent evt)
570   {
571   }
572
573   /**
574    * DOCUMENT ME!
575    * 
576    * @param evt
577    *          DOCUMENT ME!
578    */
579   public void mouseEntered(MouseEvent evt)
580   {
581   }
582
583   /**
584    * DOCUMENT ME!
585    * 
586    * @param evt
587    *          DOCUMENT ME!
588    */
589   public void mouseExited(MouseEvent evt)
590   {
591   }
592
593   /**
594    * DOCUMENT ME!
595    * 
596    * @param evt
597    *          DOCUMENT ME!
598    */
599   public void mouseReleased(MouseEvent evt)
600   {
601   }
602
603   /**
604    * DOCUMENT ME!
605    * 
606    * @param evt
607    *          DOCUMENT ME!
608    */
609   public void mousePressed(MouseEvent evt)
610   {
611     int x = evt.getX();
612     int y = evt.getY();
613
614     mx = x;
615     my = y;
616
617     omx = mx;
618     omy = my;
619
620     startx = x;
621     starty = y;
622
623     rectx1 = x;
624     recty1 = y;
625
626     rectx2 = -1;
627     recty2 = -1;
628
629     SequenceI found = findPoint(x, y);
630
631     if (found != null)
632     {
633       AlignmentPanel[] aps = getAssociatedPanels();
634
635       for (int a = 0; a < aps.length; a++)
636       {
637         if (aps[a].av.getSelectionGroup() != null)
638         {
639           aps[a].av.getSelectionGroup().addOrRemove(found, true);
640         }
641         else
642         {
643           aps[a].av.setSelectionGroup(new SequenceGroup());
644           aps[a].av.getSelectionGroup().addOrRemove(found, true);
645           aps[a].av.getSelectionGroup().setEndRes(
646                   aps[a].av.alignment.getWidth() - 1);
647         }
648       }
649       PaintRefresher.Refresh(this, av.getSequenceSetId());
650       // canonical selection is sent to other listeners
651       av.sendSelection();
652     }
653
654     repaint();
655   }
656
657   // private void fireSequenceSelectionEvent(Selection sel) {
658   // controller.handleSequenceSelectionEvent(new
659   // SequenceSelectionEvent(this,sel));
660   // }
661   public void mouseMoved(MouseEvent evt)
662   {
663     SequenceI found = findPoint(evt.getX(), evt.getY());
664
665     if (found != null)
666     {
667       this.setToolTipText(found.getName());
668     }
669     else
670     {
671       this.setToolTipText(null);
672     }
673   }
674
675   /**
676    * DOCUMENT ME!
677    * 
678    * @param evt
679    *          DOCUMENT ME!
680    */
681   public void mouseDragged(MouseEvent evt)
682   {
683     mx = evt.getX();
684     my = evt.getY();
685
686     // Check if this is a rectangle drawing drag
687     if ((evt.getModifiers() & InputEvent.BUTTON2_MASK) != 0)
688     {
689       // rectx2 = evt.getX();
690       // recty2 = evt.getY();
691     }
692     else
693     {
694       rotmat.setIdentity();
695
696       rotmat.rotate((float) (my - omy), 'x');
697       rotmat.rotate((float) (mx - omx), 'y');
698
699       for (int i = 0; i < npoint; i++)
700       {
701         SequencePoint sp = (SequencePoint) points.elementAt(i);
702         sp.coord[0] -= centre[0];
703         sp.coord[1] -= centre[1];
704         sp.coord[2] -= centre[2];
705
706         // Now apply the rotation matrix
707         sp.coord = rotmat.vectorMultiply(sp.coord);
708
709         // Now translate back again
710         sp.coord[0] += centre[0];
711         sp.coord[1] += centre[1];
712         sp.coord[2] += centre[2];
713       }
714
715       for (int i = 0; i < 3; i++)
716       {
717         axes[i] = rotmat.vectorMultiply(axes[i]);
718       }
719
720       omx = mx;
721       omy = my;
722
723       paint(this.getGraphics());
724     }
725   }
726
727   /**
728    * DOCUMENT ME!
729    * 
730    * @param x1
731    *          DOCUMENT ME!
732    * @param y1
733    *          DOCUMENT ME!
734    * @param x2
735    *          DOCUMENT ME!
736    * @param y2
737    *          DOCUMENT ME!
738    */
739   public void rectSelect(int x1, int y1, int x2, int y2)
740   {
741     for (int i = 0; i < npoint; i++)
742     {
743       SequencePoint sp = (SequencePoint) points.elementAt(i);
744       int tmp1 = (int) (((sp.coord[0] - centre[0]) * scale) + ((float) getWidth() / 2.0));
745       int tmp2 = (int) (((sp.coord[1] - centre[1]) * scale) + ((float) getHeight() / 2.0));
746
747       if ((tmp1 > x1) && (tmp1 < x2) && (tmp2 > y1) && (tmp2 < y2))
748       {
749         if (av != null)
750         {
751           if (!av.getSelectionGroup().getSequences(null).contains(
752                   sp.sequence))
753           {
754             av.getSelectionGroup().addSequence(sp.sequence, true);
755           }
756         }
757       }
758     }
759
760     // if (changedSel) {
761     // fireSequenceSelectionEvent(av.getSelection());
762     // }
763   }
764
765   /**
766    * DOCUMENT ME!
767    * 
768    * @param x
769    *          DOCUMENT ME!
770    * @param y
771    *          DOCUMENT ME!
772    * 
773    * @return DOCUMENT ME!
774    */
775   public SequenceI findPoint(int x, int y)
776   {
777     int halfwidth = getWidth() / 2;
778     int halfheight = getHeight() / 2;
779
780     int found = -1;
781
782     for (int i = 0; i < npoint; i++)
783     {
784       SequencePoint sp = (SequencePoint) points.elementAt(i);
785       int px = (int) ((float) (sp.coord[0] - centre[0]) * scale)
786               + halfwidth;
787       int py = (int) ((float) (sp.coord[1] - centre[1]) * scale)
788               + halfheight;
789
790       if ((Math.abs(px - x) < 3) && (Math.abs(py - y) < 3))
791       {
792         found = i;
793       }
794     }
795
796     if (found != -1)
797     {
798       return ((SequencePoint) points.elementAt(found)).sequence;
799     }
800     else
801     {
802       return null;
803     }
804   }
805
806   AlignmentPanel[] getAssociatedPanels()
807   {
808     if (applyToAllViews)
809     {
810       return PaintRefresher.getAssociatedPanels(av.getSequenceSetId());
811     }
812     else
813     {
814       return new AlignmentPanel[]
815       { ap };
816     }
817   }
818
819   /**
820    * 
821    * @return x,y,z positions of point s (index into points) under current transform.
822    */
823   public double[] getPointPosition(int s)
824   {
825     double pts[] = new double[3];
826     float[] p = ((SequencePoint)points.elementAt(s)).coord;
827     pts[0] = p[0];
828     pts[1] = p[1];
829     pts[2] = p[2];
830     return pts;
831   }
832
833 }