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