Mouse wheel to zoom
[jalview.git] / src / jalview / gui / RotatableCanvas.java
1 /*\r
2  * Jalview - A Sequence Alignment Editor and Viewer\r
3  * Copyright (C) 2006 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 jalview.datamodel.*;\r
22 \r
23 import jalview.math.*;\r
24 \r
25 import java.awt.*;\r
26 import java.awt.event.*;\r
27 \r
28 import java.util.*;\r
29 \r
30 import javax.swing.*;\r
31 \r
32 \r
33 /**\r
34  * DOCUMENT ME!\r
35  *\r
36  * @author $author$\r
37  * @version $Revision$\r
38  */\r
39 public class RotatableCanvas extends JPanel implements MouseListener,\r
40     MouseMotionListener, KeyListener\r
41 {\r
42     RotatableMatrix idmat = new RotatableMatrix(3, 3);\r
43     RotatableMatrix objmat = new RotatableMatrix(3, 3);\r
44     RotatableMatrix rotmat = new RotatableMatrix(3, 3);\r
45 \r
46     //RubberbandRectangle rubberband;\r
47     boolean drawAxes = true;\r
48     int omx = 0;\r
49     int mx = 0;\r
50     int omy = 0;\r
51     int my = 0;\r
52     Image img;\r
53     Graphics ig;\r
54     Dimension prefsize;\r
55     float[] centre = new float[3];\r
56     float[] width = new float[3];\r
57     float[] max = new float[3];\r
58     float[] min = new float[3];\r
59     float maxwidth;\r
60     float scale;\r
61     int npoint;\r
62     Vector points;\r
63     float[][] orig;\r
64     float[][] axes;\r
65     int startx;\r
66     int starty;\r
67     int lastx;\r
68     int lasty;\r
69     int rectx1;\r
70     int recty1;\r
71     int rectx2;\r
72     int recty2;\r
73     float scalefactor = 1;\r
74     AlignViewport av;\r
75     boolean showLabels = false;\r
76     Color bgColour = Color.black;\r
77 \r
78     //  Controller    controller;\r
79     public RotatableCanvas(AlignViewport av)\r
80     {\r
81       this.av = av;\r
82 \r
83       addMouseWheelListener(new MouseWheelListener()\r
84       {\r
85         public void mouseWheelMoved(MouseWheelEvent e)\r
86         {\r
87           if (e.getWheelRotation() > 0)\r
88           {\r
89             scale = (float) (scale * 1.1);\r
90             repaint();\r
91           }\r
92 \r
93           else\r
94           {\r
95             scale = (float) (scale * 0.9);\r
96             repaint();\r
97           }\r
98         }\r
99       });\r
100 \r
101     }\r
102 \r
103     public void showLabels(boolean b)\r
104     {\r
105       showLabels = b;\r
106       repaint();\r
107     }\r
108 \r
109     public void setPoints(Vector points, int npoint)\r
110     {\r
111         this.points = points;\r
112         this.npoint = npoint;\r
113         ToolTipManager.sharedInstance().registerComponent(this);\r
114         ToolTipManager.sharedInstance().setInitialDelay(0);\r
115         ToolTipManager.sharedInstance().setDismissDelay(10000);\r
116         PaintRefresher.Register(this, av.getSequenceSetId());\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 \r
351         drawBackground(ig, bgColour);\r
352         drawScene(ig);\r
353 \r
354         if (drawAxes == true)\r
355         {\r
356           drawAxes(ig);\r
357         }\r
358 \r
359         g.drawImage(img, 0, 0, this);\r
360       }\r
361     }\r
362 \r
363     /**\r
364      * DOCUMENT ME!\r
365      *\r
366      * @param g DOCUMENT ME!\r
367      */\r
368     public void drawAxes(Graphics g)\r
369     {\r
370 \r
371         g.setColor(Color.yellow);\r
372 \r
373         for (int i = 0; i < 3; i++)\r
374         {\r
375             g.drawLine(getWidth() / 2, getHeight() / 2,\r
376                 (int) ((axes[i][0] * scale * max[0]) + (getWidth() / 2)),\r
377                 (int) ((axes[i][1] * scale * max[1]) + (getHeight() / 2)));\r
378         }\r
379     }\r
380 \r
381     /**\r
382      * DOCUMENT ME!\r
383      *\r
384      * @param g DOCUMENT ME!\r
385      * @param col DOCUMENT ME!\r
386      */\r
387     public void drawBackground(Graphics g, Color col)\r
388     {\r
389         g.setColor(col);\r
390         g.fillRect(0, 0, prefsize.width, prefsize.height);\r
391     }\r
392 \r
393     /**\r
394      * DOCUMENT ME!\r
395      *\r
396      * @param g DOCUMENT ME!\r
397      */\r
398     public void drawScene(Graphics g1)\r
399     {\r
400 \r
401       Graphics2D g = (Graphics2D) g1;\r
402 \r
403       g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,\r
404                          RenderingHints.VALUE_ANTIALIAS_ON);\r
405 \r
406 \r
407         int halfwidth = getWidth() / 2;\r
408         int halfheight = getHeight() / 2;\r
409 \r
410         for (int i = 0; i < npoint; i++)\r
411         {\r
412             SequencePoint sp = (SequencePoint) points.elementAt(i);\r
413             int x = (int) ((float) (sp.coord[0] - centre[0]) * scale) +\r
414                 halfwidth;\r
415             int y = (int) ((float) (sp.coord[1] - centre[1]) * scale) +\r
416                 halfheight;\r
417             float z = sp.coord[1] - centre[2];\r
418 \r
419             if (av.getSequenceColour(sp.sequence) == Color.black)\r
420             {\r
421                 g.setColor(Color.white);\r
422             }\r
423             else\r
424             {\r
425                 g.setColor(av.getSequenceColour(sp.sequence));\r
426             }\r
427 \r
428             if (av.getSelectionGroup() != null)\r
429             {\r
430                 if (av.getSelectionGroup().getSequences(false).contains(\r
431                             ((SequencePoint) points.elementAt(i)).sequence))\r
432                 {\r
433                     g.setColor(Color.gray);\r
434                 }\r
435             }\r
436 \r
437             if (z < 0)\r
438             {\r
439                 g.setColor(g.getColor().darker());\r
440             }\r
441 \r
442             g.fillRect(x - 3, y - 3, 6, 6);\r
443             if(showLabels)\r
444             {\r
445               g.setColor(Color.red);\r
446               g.drawString( ( (SequencePoint) points.elementAt(i)).sequence.\r
447                            getName(),\r
448                            x - 3, y - 4);\r
449             }\r
450         }\r
451 \r
452         //    //Now the rectangle\r
453         //    if (rectx2 != -1 && recty2 != -1) {\r
454         //      g.setColor(Color.white);\r
455         //\r
456         //      g.drawRect(rectx1,recty1,rectx2-rectx1,recty2-recty1);\r
457         //    }\r
458     }\r
459 \r
460     /**\r
461      * DOCUMENT ME!\r
462      *\r
463      * @return DOCUMENT ME!\r
464      */\r
465     public Dimension minimumsize()\r
466     {\r
467         return prefsize;\r
468     }\r
469 \r
470     /**\r
471      * DOCUMENT ME!\r
472      *\r
473      * @return DOCUMENT ME!\r
474      */\r
475     public Dimension preferredsize()\r
476     {\r
477         return prefsize;\r
478     }\r
479 \r
480     /**\r
481      * DOCUMENT ME!\r
482      *\r
483      * @param evt DOCUMENT ME!\r
484      */\r
485     public void keyTyped(KeyEvent evt)\r
486     {\r
487     }\r
488 \r
489     /**\r
490      * DOCUMENT ME!\r
491      *\r
492      * @param evt DOCUMENT ME!\r
493      */\r
494     public void keyReleased(KeyEvent evt)\r
495     {\r
496     }\r
497 \r
498     /**\r
499      * DOCUMENT ME!\r
500      *\r
501      * @param evt DOCUMENT ME!\r
502      */\r
503     public void keyPressed(KeyEvent evt)\r
504     {\r
505         if (evt.getKeyCode() == KeyEvent.VK_UP)\r
506         {\r
507             scalefactor = (float) (scalefactor * 1.1);\r
508             scale = findScale();\r
509         }\r
510         else if (evt.getKeyCode() == KeyEvent.VK_DOWN)\r
511         {\r
512             scalefactor = (float) (scalefactor * 0.9);\r
513             scale = findScale();\r
514         }\r
515         else if (evt.getKeyChar() == 's')\r
516         {\r
517             System.err.println("DEBUG: Rectangle selection"); // log.debug\r
518 \r
519             if ((rectx2 != -1) && (recty2 != -1))\r
520             {\r
521                 rectSelect(rectx1, recty1, rectx2, recty2);\r
522             }\r
523         }\r
524 \r
525         repaint();\r
526     }\r
527 \r
528 \r
529     /**\r
530      * DOCUMENT ME!\r
531      *\r
532      * @param evt DOCUMENT ME!\r
533      */\r
534     public void mouseClicked(MouseEvent evt)\r
535     {\r
536     }\r
537 \r
538     /**\r
539      * DOCUMENT ME!\r
540      *\r
541      * @param evt DOCUMENT ME!\r
542      */\r
543     public void mouseEntered(MouseEvent evt)\r
544     {\r
545     }\r
546 \r
547     /**\r
548      * DOCUMENT ME!\r
549      *\r
550      * @param evt DOCUMENT ME!\r
551      */\r
552     public void mouseExited(MouseEvent evt)\r
553     {\r
554     }\r
555 \r
556     /**\r
557      * DOCUMENT ME!\r
558      *\r
559      * @param evt DOCUMENT ME!\r
560      */\r
561     public void mouseReleased(MouseEvent evt)\r
562     {\r
563     }\r
564 \r
565     /**\r
566      * DOCUMENT ME!\r
567      *\r
568      * @param evt DOCUMENT ME!\r
569      */\r
570     public void mousePressed(MouseEvent evt)\r
571     {\r
572         int x = evt.getX();\r
573         int y = evt.getY();\r
574 \r
575         mx = x;\r
576         my = y;\r
577 \r
578         omx = mx;\r
579         omy = my;\r
580 \r
581         startx = x;\r
582         starty = y;\r
583 \r
584         rectx1 = x;\r
585         recty1 = y;\r
586 \r
587         rectx2 = -1;\r
588         recty2 = -1;\r
589 \r
590         SequenceI found = findPoint(x, y);\r
591 \r
592         if (found != null)\r
593         {\r
594             if (av.getSelectionGroup() != null)\r
595             {\r
596                 av.getSelectionGroup().addOrRemove(found, true);\r
597                 PaintRefresher.Refresh(this, av.getSequenceSetId());\r
598             }\r
599             else\r
600             {\r
601                 av.setSelectionGroup(new SequenceGroup());\r
602                 av.getSelectionGroup().addOrRemove(found, true);\r
603                 av.getSelectionGroup().setEndRes(av.alignment.getWidth()-1);\r
604             }\r
605         }\r
606 \r
607         repaint();\r
608     }\r
609 \r
610     // private void fireSequenceSelectionEvent(Selection sel) {\r
611     //   controller.handleSequenceSelectionEvent(new SequenceSelectionEvent(this,sel));\r
612     //}\r
613     public void mouseMoved(MouseEvent evt)\r
614     {\r
615         SequenceI found = findPoint(evt.getX(), evt.getY());\r
616 \r
617         if (found != null)\r
618         {\r
619             this.setToolTipText(found.getName());\r
620         }\r
621         else\r
622         {\r
623             this.setToolTipText(null);\r
624         }\r
625     }\r
626 \r
627     /**\r
628      * DOCUMENT ME!\r
629      *\r
630      * @param evt DOCUMENT ME!\r
631      */\r
632     public void mouseDragged(MouseEvent evt)\r
633     {\r
634         mx = evt.getX();\r
635         my = evt.getY();\r
636 \r
637         //Check if this is a rectangle drawing drag\r
638         if ((evt.getModifiers() & InputEvent.BUTTON2_MASK) != 0)\r
639         {\r
640             //      rectx2 = evt.getX();\r
641             //      recty2 = evt.getY();\r
642         }\r
643         else\r
644         {\r
645             rotmat.setIdentity();\r
646 \r
647             rotmat.rotate((float) (my - omy), 'x');\r
648             rotmat.rotate((float) (mx - omx), 'y');\r
649 \r
650             for (int i = 0; i < npoint; i++)\r
651             {\r
652                 SequencePoint sp = (SequencePoint) points.elementAt(i);\r
653                 sp.coord[0] -= centre[0];\r
654                 sp.coord[1] -= centre[1];\r
655                 sp.coord[2] -= centre[2];\r
656 \r
657                 //Now apply the rotation matrix\r
658                 sp.coord = rotmat.vectorMultiply(sp.coord);\r
659 \r
660                 //Now translate back again\r
661                 sp.coord[0] += centre[0];\r
662                 sp.coord[1] += centre[1];\r
663                 sp.coord[2] += centre[2];\r
664             }\r
665 \r
666             for (int i = 0; i < 3; i++)\r
667             {\r
668                 axes[i] = rotmat.vectorMultiply(axes[i]);\r
669             }\r
670 \r
671             omx = mx;\r
672             omy = my;\r
673 \r
674             paint(this.getGraphics());\r
675         }\r
676     }\r
677 \r
678     /**\r
679      * DOCUMENT ME!\r
680      *\r
681      * @param x1 DOCUMENT ME!\r
682      * @param y1 DOCUMENT ME!\r
683      * @param x2 DOCUMENT ME!\r
684      * @param y2 DOCUMENT ME!\r
685      */\r
686     public void rectSelect(int x1, int y1, int x2, int y2)\r
687     {\r
688         for (int i = 0; i < npoint; i++)\r
689         {\r
690             SequencePoint sp = (SequencePoint) points.elementAt(i);\r
691             int tmp1 = (int) (((sp.coord[0] - centre[0]) * scale) +\r
692                 ((float) getWidth() / 2.0));\r
693             int tmp2 = (int) (((sp.coord[1] - centre[1]) * scale) +\r
694                 ((float) getHeight() / 2.0));\r
695 \r
696             if ((tmp1 > x1) && (tmp1 < x2) && (tmp2 > y1) && (tmp2 < y2))\r
697             {\r
698                 if (av != null)\r
699                 {\r
700                     if (!av.getSelectionGroup().getSequences(false).contains(sp.sequence))\r
701                     {\r
702                         av.getSelectionGroup().addSequence(sp.sequence, true);\r
703                     }\r
704                 }\r
705             }\r
706         }\r
707 \r
708         // if (changedSel) {\r
709         //    fireSequenceSelectionEvent(av.getSelection());\r
710         // }\r
711     }\r
712 \r
713     /**\r
714      * DOCUMENT ME!\r
715      *\r
716      * @param x DOCUMENT ME!\r
717      * @param y DOCUMENT ME!\r
718      *\r
719      * @return DOCUMENT ME!\r
720      */\r
721     public SequenceI findPoint(int x, int y)\r
722     {\r
723         int halfwidth = getWidth() / 2;\r
724         int halfheight = getHeight() / 2;\r
725 \r
726         int found = -1;\r
727 \r
728         for (int i = 0; i < npoint; i++)\r
729         {\r
730             SequencePoint sp = (SequencePoint) points.elementAt(i);\r
731             int px = (int) ((float) (sp.coord[0] - centre[0]) * scale) +\r
732                 halfwidth;\r
733             int py = (int) ((float) (sp.coord[1] - centre[1]) * scale) +\r
734                 halfheight;\r
735 \r
736             if ((Math.abs(px - x) < 3) && (Math.abs(py - y) < 3))\r
737             {\r
738                 found = i;\r
739             }\r
740         }\r
741 \r
742         if (found != -1)\r
743         {\r
744             return ((SequencePoint) points.elementAt(found)).sequence;\r
745         }\r
746         else\r
747         {\r
748             return null;\r
749         }\r
750     }\r
751 \r
752     /*  public boolean handleRubberbandEvent(RubberbandEvent evt) {\r
753         System.out.println("Rubberband handler called in RotatableCanvas with " +\r
754                            evt.getBounds());\r
755 \r
756         Rubberband rb = (Rubberband)evt.getSource();\r
757 \r
758         // Clear the current selection (instance variable)\r
759         //if ((rb.getModifiers() & Event.SHIFT_MASK) == 0) {\r
760         //   clearSelection();\r
761         //}\r
762 \r
763         if (rb.getComponent() == this) {\r
764           Rectangle bounds = evt.getBounds();\r
765      rectSelect(bounds.x,bounds.y,bounds.x+bounds.width,bounds.y+bounds.height);\r
766         }\r
767 \r
768         redrawneeded = true;\r
769         paint(this.getGraphics());\r
770 \r
771         return true;\r
772       }*/\r
773 }\r