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