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