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