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