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