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