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