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