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