update author list in license for (JAL-826)
[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.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)
464                 .contains(((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(
480                 ((SequencePoint) points.elementAt(i)).sequence.getName(),
481                 x - 3, y - 4);
482       }
483     }
484
485     // //Now the rectangle
486     // if (rectx2 != -1 && recty2 != -1) {
487     // g.setColor(Color.white);
488     //
489     // g.drawRect(rectx1,recty1,rectx2-rectx1,recty2-recty1);
490     // }
491   }
492
493   /**
494    * DOCUMENT ME!
495    * 
496    * @return DOCUMENT ME!
497    */
498   public Dimension minimumsize()
499   {
500     return prefsize;
501   }
502
503   /**
504    * DOCUMENT ME!
505    * 
506    * @return DOCUMENT ME!
507    */
508   public Dimension preferredsize()
509   {
510     return prefsize;
511   }
512
513   /**
514    * DOCUMENT ME!
515    * 
516    * @param evt
517    *          DOCUMENT ME!
518    */
519   public void keyTyped(KeyEvent evt)
520   {
521   }
522
523   /**
524    * DOCUMENT ME!
525    * 
526    * @param evt
527    *          DOCUMENT ME!
528    */
529   public void keyReleased(KeyEvent evt)
530   {
531   }
532
533   /**
534    * DOCUMENT ME!
535    * 
536    * @param evt
537    *          DOCUMENT ME!
538    */
539   public void keyPressed(KeyEvent evt)
540   {
541     if (evt.getKeyCode() == KeyEvent.VK_UP)
542     {
543       scalefactor = (float) (scalefactor * 1.1);
544       scale = findScale();
545     }
546     else if (evt.getKeyCode() == KeyEvent.VK_DOWN)
547     {
548       scalefactor = (float) (scalefactor * 0.9);
549       scale = findScale();
550     }
551     else if (evt.getKeyChar() == 's')
552     {
553       System.err.println("DEBUG: Rectangle selection"); // log.debug
554
555       if ((rectx2 != -1) && (recty2 != -1))
556       {
557         rectSelect(rectx1, recty1, rectx2, recty2);
558       }
559     }
560
561     repaint();
562   }
563
564   /**
565    * DOCUMENT ME!
566    * 
567    * @param evt
568    *          DOCUMENT ME!
569    */
570   public void mouseClicked(MouseEvent evt)
571   {
572   }
573
574   /**
575    * DOCUMENT ME!
576    * 
577    * @param evt
578    *          DOCUMENT ME!
579    */
580   public void mouseEntered(MouseEvent evt)
581   {
582   }
583
584   /**
585    * DOCUMENT ME!
586    * 
587    * @param evt
588    *          DOCUMENT ME!
589    */
590   public void mouseExited(MouseEvent evt)
591   {
592   }
593
594   /**
595    * DOCUMENT ME!
596    * 
597    * @param evt
598    *          DOCUMENT ME!
599    */
600   public void mouseReleased(MouseEvent evt)
601   {
602   }
603
604   /**
605    * DOCUMENT ME!
606    * 
607    * @param evt
608    *          DOCUMENT ME!
609    */
610   public void mousePressed(MouseEvent evt)
611   {
612     int x = evt.getX();
613     int y = evt.getY();
614
615     mx = x;
616     my = y;
617
618     omx = mx;
619     omy = my;
620
621     startx = x;
622     starty = y;
623
624     rectx1 = x;
625     recty1 = y;
626
627     rectx2 = -1;
628     recty2 = -1;
629
630     SequenceI found = findPoint(x, y);
631
632     if (found != null)
633     {
634       AlignmentPanel[] aps = getAssociatedPanels();
635
636       for (int a = 0; a < aps.length; a++)
637       {
638         if (aps[a].av.getSelectionGroup() != null)
639         {
640           aps[a].av.getSelectionGroup().addOrRemove(found, true);
641         }
642         else
643         {
644           aps[a].av.setSelectionGroup(new SequenceGroup());
645           aps[a].av.getSelectionGroup().addOrRemove(found, true);
646           aps[a].av.getSelectionGroup().setEndRes(
647                   aps[a].av.alignment.getWidth() - 1);
648         }
649       }
650       PaintRefresher.Refresh(this, av.getSequenceSetId());
651       // canonical selection is sent to other listeners
652       av.sendSelection();
653     }
654
655     repaint();
656   }
657
658   // private void fireSequenceSelectionEvent(Selection sel) {
659   // controller.handleSequenceSelectionEvent(new
660   // SequenceSelectionEvent(this,sel));
661   // }
662   public void mouseMoved(MouseEvent evt)
663   {
664     SequenceI found = findPoint(evt.getX(), evt.getY());
665
666     if (found != null)
667     {
668       this.setToolTipText(found.getName());
669     }
670     else
671     {
672       this.setToolTipText(null);
673     }
674   }
675
676   /**
677    * DOCUMENT ME!
678    * 
679    * @param evt
680    *          DOCUMENT ME!
681    */
682   public void mouseDragged(MouseEvent evt)
683   {
684     mx = evt.getX();
685     my = evt.getY();
686
687     // Check if this is a rectangle drawing drag
688     if ((evt.getModifiers() & InputEvent.BUTTON2_MASK) != 0)
689     {
690       // rectx2 = evt.getX();
691       // recty2 = evt.getY();
692     }
693     else
694     {
695       rotmat.setIdentity();
696
697       rotmat.rotate((float) (my - omy), 'x');
698       rotmat.rotate((float) (mx - omx), 'y');
699
700       for (int i = 0; i < npoint; i++)
701       {
702         SequencePoint sp = (SequencePoint) points.elementAt(i);
703         sp.coord[0] -= centre[0];
704         sp.coord[1] -= centre[1];
705         sp.coord[2] -= centre[2];
706
707         // Now apply the rotation matrix
708         sp.coord = rotmat.vectorMultiply(sp.coord);
709
710         // Now translate back again
711         sp.coord[0] += centre[0];
712         sp.coord[1] += centre[1];
713         sp.coord[2] += centre[2];
714       }
715
716       for (int i = 0; i < 3; i++)
717       {
718         axes[i] = rotmat.vectorMultiply(axes[i]);
719       }
720
721       omx = mx;
722       omy = my;
723
724       paint(this.getGraphics());
725     }
726   }
727
728   /**
729    * DOCUMENT ME!
730    * 
731    * @param x1
732    *          DOCUMENT ME!
733    * @param y1
734    *          DOCUMENT ME!
735    * @param x2
736    *          DOCUMENT ME!
737    * @param y2
738    *          DOCUMENT ME!
739    */
740   public void rectSelect(int x1, int y1, int x2, int y2)
741   {
742     for (int i = 0; i < npoint; i++)
743     {
744       SequencePoint sp = (SequencePoint) points.elementAt(i);
745       int tmp1 = (int) (((sp.coord[0] - centre[0]) * scale) + ((float) getWidth() / 2.0));
746       int tmp2 = (int) (((sp.coord[1] - centre[1]) * scale) + ((float) getHeight() / 2.0));
747
748       if ((tmp1 > x1) && (tmp1 < x2) && (tmp2 > y1) && (tmp2 < y2))
749       {
750         if (av != null)
751         {
752           if (!av.getSelectionGroup().getSequences(null)
753                   .contains(sp.sequence))
754           {
755             av.getSelectionGroup().addSequence(sp.sequence, true);
756           }
757         }
758       }
759     }
760
761     // if (changedSel) {
762     // fireSequenceSelectionEvent(av.getSelection());
763     // }
764   }
765
766   /**
767    * DOCUMENT ME!
768    * 
769    * @param x
770    *          DOCUMENT ME!
771    * @param y
772    *          DOCUMENT ME!
773    * 
774    * @return DOCUMENT ME!
775    */
776   public SequenceI findPoint(int x, int y)
777   {
778     int halfwidth = getWidth() / 2;
779     int halfheight = getHeight() / 2;
780
781     int found = -1;
782
783     for (int i = 0; i < npoint; i++)
784     {
785       SequencePoint sp = (SequencePoint) points.elementAt(i);
786       int px = (int) ((float) (sp.coord[0] - centre[0]) * scale)
787               + halfwidth;
788       int py = (int) ((float) (sp.coord[1] - centre[1]) * scale)
789               + halfheight;
790
791       if ((Math.abs(px - x) < 3) && (Math.abs(py - y) < 3))
792       {
793         found = i;
794       }
795     }
796
797     if (found != -1)
798     {
799       return ((SequencePoint) points.elementAt(found)).sequence;
800     }
801     else
802     {
803       return null;
804     }
805   }
806
807   AlignmentPanel[] getAssociatedPanels()
808   {
809     if (applyToAllViews)
810     {
811       return PaintRefresher.getAssociatedPanels(av.getSequenceSetId());
812     }
813     else
814     {
815       return new AlignmentPanel[]
816       { ap };
817     }
818   }
819
820   /**
821    * 
822    * @return x,y,z positions of point s (index into points) under current
823    *         transform.
824    */
825   public double[] getPointPosition(int s)
826   {
827     double pts[] = new double[3];
828     float[] p = ((SequencePoint) points.elementAt(s)).coord;
829     pts[0] = p[0];
830     pts[1] = p[1];
831     pts[2] = p[2];
832     return pts;
833   }
834
835 }