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