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