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