update author list in license for (JAL-826)
[jalview.git] / src / jalview / appletgui / RotatableCanvas.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.7)
3  * Copyright (C) 2011 J Procter, AM Waterhouse, J Engelhardt, LM Lui, 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.appletgui;
19
20 import java.util.*;
21
22 import java.awt.*;
23 import java.awt.event.*;
24
25 import jalview.datamodel.*;
26 import jalview.math.*;
27 import jalview.util.*;
28
29 public class RotatableCanvas extends Panel implements MouseListener,
30         MouseMotionListener, KeyListener
31 {
32   RotatableMatrix idmat = new RotatableMatrix(3, 3);
33
34   RotatableMatrix objmat = new RotatableMatrix(3, 3);
35
36   RotatableMatrix rotmat = new RotatableMatrix(3, 3);
37
38   String tooltip;
39
40   int toolx, tooly;
41
42   // RubberbandRectangle rubberband;
43
44   boolean drawAxes = true;
45
46   int omx = 0;
47
48   int mx = 0;
49
50   int omy = 0;
51
52   int my = 0;
53
54   Image img;
55
56   Graphics ig;
57
58   Dimension prefsize;
59
60   float centre[] = new float[3];
61
62   float width[] = new float[3];
63
64   float max[] = new float[3];
65
66   float min[] = new float[3];
67
68   float maxwidth;
69
70   float scale;
71
72   int npoint;
73
74   Vector points;
75
76   float[][] orig;
77
78   float[][] axes;
79
80   int startx;
81
82   int starty;
83
84   int lastx;
85
86   int lasty;
87
88   int rectx1;
89
90   int recty1;
91
92   int rectx2;
93
94   int recty2;
95
96   float scalefactor = 1;
97
98   AlignViewport av;
99
100   boolean showLabels = false;
101
102   public RotatableCanvas(AlignViewport av)
103   {
104     this.av = av;
105   }
106
107   public void showLabels(boolean b)
108   {
109     showLabels = b;
110     repaint();
111   }
112
113   public void setPoints(Vector points, int npoint)
114   {
115     this.points = points;
116     this.npoint = npoint;
117     PaintRefresher.Register(this, av.getSequenceSetId());
118
119     prefsize = getPreferredSize();
120     orig = new float[npoint][3];
121
122     for (int i = 0; i < npoint; i++)
123     {
124       SequencePoint sp = (SequencePoint) points.elementAt(i);
125       for (int j = 0; j < 3; j++)
126       {
127         orig[i][j] = sp.coord[j];
128       }
129     }
130     // Initialize the matrices to identity
131
132     for (int i = 0; i < 3; i++)
133     {
134       for (int j = 0; j < 3; j++)
135       {
136         if (i != j)
137         {
138           idmat.addElement(i, j, 0);
139           objmat.addElement(i, j, 0);
140           rotmat.addElement(i, j, 0);
141         }
142         else
143         {
144           idmat.addElement(i, j, 0);
145           objmat.addElement(i, j, 0);
146           rotmat.addElement(i, j, 0);
147         }
148       }
149     }
150
151     axes = new float[3][3];
152     initAxes();
153
154     findCentre();
155     findWidth();
156
157     scale = findScale();
158
159     // System.out.println("Scale factor = " + scale);
160
161     addMouseListener(this);
162     addKeyListener(this);
163     // if (getParent() != null) {
164     // getParent().addKeyListener(this);
165     // }
166     addMouseMotionListener(this);
167
168     // Add rubberband
169     // rubberband = new RubberbandRectangle(this);
170     // rubberband.setActive(true);
171     // rubberband.addListener(this);
172   }
173
174   /*
175    * public boolean handleSequenceSelectionEvent(SequenceSelectionEvent evt) {
176    * redrawneeded = true; repaint(); return true; }
177    * 
178    * public void removeNotify() { controller.removeListener(this);
179    * super.removeNotify(); }
180    */
181
182   public void initAxes()
183   {
184     for (int i = 0; i < 3; i++)
185     {
186       for (int j = 0; j < 3; j++)
187       {
188         if (i != j)
189         {
190           axes[i][j] = 0;
191         }
192         else
193         {
194           axes[i][j] = 1;
195         }
196       }
197     }
198   }
199
200   public void findWidth()
201   {
202     max = new float[3];
203     min = new float[3];
204
205     max[0] = (float) -1e30;
206     max[1] = (float) -1e30;
207     max[2] = (float) -1e30;
208
209     min[0] = (float) 1e30;
210     min[1] = (float) 1e30;
211     min[2] = (float) 1e30;
212
213     for (int i = 0; i < 3; i++)
214     {
215       for (int j = 0; j < npoint; j++)
216       {
217         SequencePoint sp = (SequencePoint) points.elementAt(j);
218         if (sp.coord[i] >= max[i])
219         {
220           max[i] = sp.coord[i];
221         }
222         if (sp.coord[i] <= min[i])
223         {
224           min[i] = sp.coord[i];
225         }
226       }
227     }
228
229     // System.out.println("xmax " + max[0] + " min " + min[0]);
230     // System.out.println("ymax " + max[1] + " min " + min[1]);
231     // System.out.println("zmax " + max[2] + " min " + min[2]);
232
233     width[0] = Math.abs(max[0] - min[0]);
234     width[1] = Math.abs(max[1] - min[1]);
235     width[2] = Math.abs(max[2] - min[2]);
236
237     maxwidth = width[0];
238
239     if (width[1] > width[0])
240     {
241       maxwidth = width[1];
242     }
243     if (width[2] > width[1])
244     {
245       maxwidth = width[2];
246     }
247
248     // System.out.println("Maxwidth = " + maxwidth);
249   }
250
251   public float findScale()
252   {
253     int dim, width, height;
254     if (getSize().width != 0)
255     {
256       width = getSize().width;
257       height = getSize().height;
258     }
259     else
260     {
261       width = prefsize.width;
262       height = prefsize.height;
263     }
264
265     if (width < height)
266     {
267       dim = width;
268     }
269     else
270     {
271       dim = height;
272     }
273
274     return (float) (dim * scalefactor / (2 * maxwidth));
275   }
276
277   public void findCentre()
278   {
279     // Find centre coordinate
280     findWidth();
281
282     centre[0] = (max[0] + min[0]) / 2;
283     centre[1] = (max[1] + min[1]) / 2;
284     centre[2] = (max[2] + min[2]) / 2;
285
286     // System.out.println("Centre x " + centre[0]);
287     // System.out.println("Centre y " + centre[1]);
288     // System.out.println("Centre z " + centre[2]);
289   }
290
291   public Dimension getPreferredSize()
292   {
293     if (prefsize != null)
294     {
295       return prefsize;
296     }
297     else
298     {
299       return new Dimension(400, 400);
300     }
301   }
302
303   public Dimension getMinimumSize()
304   {
305     return getPreferredSize();
306   }
307
308   public void update(Graphics g)
309   {
310     paint(g);
311   }
312
313   public void paint(Graphics g)
314   {
315     if (points == null)
316     {
317       g.setFont(new Font("Verdana", Font.PLAIN, 18));
318       g.drawString("Calculating PCA....", 20, getSize().height / 2);
319     }
320     else
321     {
322
323       // Only create the image at the beginning -
324       if ((img == null) || (prefsize.width != getSize().width)
325               || (prefsize.height != getSize().height))
326       {
327         prefsize.width = getSize().width;
328         prefsize.height = getSize().height;
329
330         scale = findScale();
331
332         // System.out.println("New scale = " + scale);
333         img = createImage(getSize().width, getSize().height);
334         ig = img.getGraphics();
335
336       }
337
338       drawBackground(ig, Color.black);
339       drawScene(ig);
340       if (drawAxes == true)
341       {
342         drawAxes(ig);
343       }
344
345       if (tooltip != null)
346       {
347         ig.setColor(Color.red);
348         ig.drawString(tooltip, toolx, tooly);
349       }
350
351       g.drawImage(img, 0, 0, this);
352     }
353   }
354
355   public void drawAxes(Graphics g)
356   {
357
358     g.setColor(Color.yellow);
359     for (int i = 0; i < 3; i++)
360     {
361       g.drawLine(getSize().width / 2, getSize().height / 2,
362               (int) (axes[i][0] * scale * max[0] + getSize().width / 2),
363               (int) (axes[i][1] * scale * max[1] + getSize().height / 2));
364     }
365   }
366
367   public void drawBackground(Graphics g, Color col)
368   {
369     g.setColor(col);
370     g.fillRect(0, 0, prefsize.width, prefsize.height);
371   }
372
373   public void drawScene(Graphics g)
374   {
375     // boolean darker = false;
376
377     int halfwidth = getSize().width / 2;
378     int halfheight = getSize().height / 2;
379
380     for (int i = 0; i < npoint; i++)
381     {
382       SequencePoint sp = (SequencePoint) points.elementAt(i);
383       int x = (int) ((float) (sp.coord[0] - centre[0]) * scale) + halfwidth;
384       int y = (int) ((float) (sp.coord[1] - centre[1]) * scale)
385               + halfheight;
386       float z = sp.coord[1] - centre[2];
387
388       if (av.getSequenceColour(sp.sequence) == Color.black)
389       {
390         g.setColor(Color.white);
391       }
392       else
393       {
394         g.setColor(av.getSequenceColour(sp.sequence));
395       }
396
397       if (av.getSelectionGroup() != null)
398       {
399         if (av.getSelectionGroup().getSequences(null)
400                 .contains(((SequencePoint) points.elementAt(i)).sequence))
401         {
402           g.setColor(Color.gray);
403         }
404       }
405       if (z < 0)
406       {
407         g.setColor(g.getColor().darker());
408       }
409
410       g.fillRect(x - 3, y - 3, 6, 6);
411       if (showLabels)
412       {
413         g.setColor(Color.red);
414         g.drawString(
415                 ((SequencePoint) points.elementAt(i)).sequence.getName(),
416                 x - 3, y - 4);
417       }
418     }
419   }
420
421   public Dimension minimumsize()
422   {
423     return prefsize;
424   }
425
426   public Dimension preferredsize()
427   {
428     return prefsize;
429   }
430
431   public void keyTyped(KeyEvent evt)
432   {
433   }
434
435   public void keyReleased(KeyEvent evt)
436   {
437   }
438
439   public void keyPressed(KeyEvent evt)
440   {
441     if (evt.getKeyCode() == KeyEvent.VK_UP)
442     {
443       scalefactor = (float) (scalefactor * 1.1);
444       scale = findScale();
445     }
446     else if (evt.getKeyCode() == KeyEvent.VK_DOWN)
447     {
448       scalefactor = (float) (scalefactor * 0.9);
449       scale = findScale();
450     }
451     else if (evt.getKeyChar() == 's')
452     {
453       System.err.println("DEBUG: Rectangle selection"); // log.debug
454       if (rectx2 != -1 && recty2 != -1)
455       {
456         rectSelect(rectx1, recty1, rectx2, recty2);
457
458       }
459     }
460     repaint();
461   }
462
463   public void printPoints()
464   {
465     for (int i = 0; i < npoint; i++)
466     {
467       SequencePoint sp = (SequencePoint) points.elementAt(i);
468       Format.print(System.out, "%5d ", i);
469       for (int j = 0; j < 3; j++)
470       {
471         Format.print(System.out, "%13.3f  ", sp.coord[j]);
472       }
473       System.out.println();
474     }
475   }
476
477   public void mouseClicked(MouseEvent evt)
478   {
479   }
480
481   public void mouseEntered(MouseEvent evt)
482   {
483   }
484
485   public void mouseExited(MouseEvent evt)
486   {
487   }
488
489   public void mouseReleased(MouseEvent evt)
490   {
491   }
492
493   public void mousePressed(MouseEvent evt)
494   {
495     int x = evt.getX();
496     int y = evt.getY();
497
498     mx = x;
499     my = y;
500
501     omx = mx;
502     omy = my;
503
504     startx = x;
505     starty = y;
506
507     rectx1 = x;
508     recty1 = y;
509
510     rectx2 = -1;
511     recty2 = -1;
512
513     SequenceI found = findPoint(x, y);
514
515     if (found != null)
516     {
517       // TODO: applet PCA is not associatable with multi-panels - only parent view 
518       if (av.getSelectionGroup() != null)
519       {
520         av.getSelectionGroup().addOrRemove(found, true);
521         av.getSelectionGroup().setEndRes(av.alignment.getWidth() - 1);
522       }
523       else
524       {
525         av.setSelectionGroup(new SequenceGroup());
526         av.getSelectionGroup().addOrRemove(found, true);
527         av.getSelectionGroup().setEndRes(av.alignment.getWidth() - 1);
528
529       }
530       PaintRefresher.Refresh(this, av.getSequenceSetId());
531       av.sendSelection();
532     }
533     repaint();
534   }
535
536   public void mouseMoved(MouseEvent evt)
537   {
538     SequenceI found = findPoint(evt.getX(), evt.getY());
539     if (found == null)
540     {
541       tooltip = null;
542     }
543     else
544     {
545       tooltip = found.getName();
546       toolx = evt.getX();
547       tooly = evt.getY();
548     }
549     repaint();
550   }
551
552   public void mouseDragged(MouseEvent evt)
553   {
554     mx = evt.getX();
555     my = evt.getY();
556
557     rotmat.setIdentity();
558
559     rotmat.rotate((float) (my - omy), 'x');
560     rotmat.rotate((float) (mx - omx), 'y');
561
562     for (int i = 0; i < npoint; i++)
563     {
564       SequencePoint sp = (SequencePoint) points.elementAt(i);
565       sp.coord[0] -= centre[0];
566       sp.coord[1] -= centre[1];
567       sp.coord[2] -= centre[2];
568
569       // Now apply the rotation matrix
570       sp.coord = rotmat.vectorMultiply(sp.coord);
571
572       // Now translate back again
573       sp.coord[0] += centre[0];
574       sp.coord[1] += centre[1];
575       sp.coord[2] += centre[2];
576     }
577
578     for (int i = 0; i < 3; i++)
579     {
580       axes[i] = rotmat.vectorMultiply(axes[i]);
581     }
582     omx = mx;
583     omy = my;
584
585     paint(this.getGraphics());
586   }
587
588   public void rectSelect(int x1, int y1, int x2, int y2)
589   {
590     // boolean changedSel = false;
591     for (int i = 0; i < npoint; i++)
592     {
593       SequencePoint sp = (SequencePoint) points.elementAt(i);
594       int tmp1 = (int) ((sp.coord[0] - centre[0]) * scale + (float) getSize().width / 2.0);
595       int tmp2 = (int) ((sp.coord[1] - centre[1]) * scale + (float) getSize().height / 2.0);
596
597       if (tmp1 > x1 && tmp1 < x2 && tmp2 > y1 && tmp2 < y2)
598       {
599         if (av != null)
600         {
601           if (!av.getSelectionGroup().getSequences(null)
602                   .contains(sp.sequence))
603           {
604             av.getSelectionGroup().addSequence(sp.sequence, true);
605           }
606         }
607       }
608     }
609   }
610
611   public SequenceI findPoint(int x, int y)
612   {
613
614     int halfwidth = getSize().width / 2;
615     int halfheight = getSize().height / 2;
616
617     int found = -1;
618
619     for (int i = 0; i < npoint; i++)
620     {
621
622       SequencePoint sp = (SequencePoint) points.elementAt(i);
623       int px = (int) ((float) (sp.coord[0] - centre[0]) * scale)
624               + halfwidth;
625       int py = (int) ((float) (sp.coord[1] - centre[1]) * scale)
626               + halfheight;
627
628       if (Math.abs(px - x) < 3 && Math.abs(py - y) < 3)
629       {
630         found = i;
631       }
632     }
633     if (found != -1)
634     {
635       return ((SequencePoint) points.elementAt(found)).sequence;
636     }
637     else
638     {
639       return null;
640     }
641   }
642
643 }