patch to fix occasional arrayoutofbounds exception when working with hidden columns...
[jalview.git] / src / jalview / appletgui / RotatableCanvas.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.4)
3  * Copyright (C) 2008 AM Waterhouse, J Procter, G Barton, M Clamp, S Searle
4  * 
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  * 
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  * 
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
18  */
19 package jalview.appletgui;
20
21 import java.util.*;
22
23 import java.awt.*;
24 import java.awt.event.*;
25
26 import jalview.datamodel.*;
27 import jalview.math.*;
28 import jalview.util.*;
29
30 public class RotatableCanvas extends Panel implements MouseListener,
31         MouseMotionListener, KeyListener
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).contains(
401                 ((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(((SequencePoint) points.elementAt(i)).sequence
416                 .getName(), x - 3, y - 4);
417       }
418     }
419   }
420
421   public Dimension minimumsize()
422   {
423     return prefsize;
424   }
425
426   public Dimension preferredsize()
427   {
428     return prefsize;
429   }
430
431   public void keyTyped(KeyEvent evt)
432   {
433   }
434
435   public void keyReleased(KeyEvent evt)
436   {
437   }
438
439   public void keyPressed(KeyEvent evt)
440   {
441     if (evt.getKeyCode() == KeyEvent.VK_UP)
442     {
443       scalefactor = (float) (scalefactor * 1.1);
444       scale = findScale();
445     }
446     else if (evt.getKeyCode() == KeyEvent.VK_DOWN)
447     {
448       scalefactor = (float) (scalefactor * 0.9);
449       scale = findScale();
450     }
451     else if (evt.getKeyChar() == 's')
452     {
453       System.err.println("DEBUG: Rectangle selection"); // log.debug
454       if (rectx2 != -1 && recty2 != -1)
455       {
456         rectSelect(rectx1, recty1, rectx2, recty2);
457
458       }
459     }
460     repaint();
461   }
462
463   public void printPoints()
464   {
465     for (int i = 0; i < npoint; i++)
466     {
467       SequencePoint sp = (SequencePoint) points.elementAt(i);
468       Format.print(System.out, "%5d ", i);
469       for (int j = 0; j < 3; j++)
470       {
471         Format.print(System.out, "%13.3f  ", sp.coord[j]);
472       }
473       System.out.println();
474     }
475   }
476
477   public void mouseClicked(MouseEvent evt)
478   {
479   }
480
481   public void mouseEntered(MouseEvent evt)
482   {
483   }
484
485   public void mouseExited(MouseEvent evt)
486   {
487   }
488
489   public void mouseReleased(MouseEvent evt)
490   {
491   }
492
493   public void mousePressed(MouseEvent evt)
494   {
495     int x = evt.getX();
496     int y = evt.getY();
497
498     mx = x;
499     my = y;
500
501     omx = mx;
502     omy = my;
503
504     startx = x;
505     starty = y;
506
507     rectx1 = x;
508     recty1 = y;
509
510     rectx2 = -1;
511     recty2 = -1;
512
513     SequenceI found = findPoint(x, y);
514
515     if (found != null)
516     {
517       if (av.getSelectionGroup() != null)
518       {
519         av.getSelectionGroup().addOrRemove(found, true);
520         av.getSelectionGroup().setEndRes(av.alignment.getWidth() - 1);
521         PaintRefresher.Refresh(this, av.getSequenceSetId());
522       }
523       else
524       {
525         av.setSelectionGroup(new SequenceGroup());
526         av.getSelectionGroup().addOrRemove(found, true);
527         av.getSelectionGroup().setEndRes(av.alignment.getWidth() - 1);
528
529       }
530     }
531     repaint();
532   }
533
534   public void mouseMoved(MouseEvent evt)
535   {
536     SequenceI found = findPoint(evt.getX(), evt.getY());
537     if (found == null)
538     {
539       tooltip = null;
540     }
541     else
542     {
543       tooltip = found.getName();
544       toolx = evt.getX();
545       tooly = evt.getY();
546     }
547     repaint();
548   }
549
550   public void mouseDragged(MouseEvent evt)
551   {
552     mx = evt.getX();
553     my = evt.getY();
554
555     rotmat.setIdentity();
556
557     rotmat.rotate((float) (my - omy), 'x');
558     rotmat.rotate((float) (mx - omx), 'y');
559
560     for (int i = 0; i < npoint; i++)
561     {
562       SequencePoint sp = (SequencePoint) points.elementAt(i);
563       sp.coord[0] -= centre[0];
564       sp.coord[1] -= centre[1];
565       sp.coord[2] -= centre[2];
566
567       // Now apply the rotation matrix
568       sp.coord = rotmat.vectorMultiply(sp.coord);
569
570       // Now translate back again
571       sp.coord[0] += centre[0];
572       sp.coord[1] += centre[1];
573       sp.coord[2] += centre[2];
574     }
575
576     for (int i = 0; i < 3; i++)
577     {
578       axes[i] = rotmat.vectorMultiply(axes[i]);
579     }
580     omx = mx;
581     omy = my;
582
583     paint(this.getGraphics());
584   }
585
586   public void rectSelect(int x1, int y1, int x2, int y2)
587   {
588     // boolean changedSel = false;
589     for (int i = 0; i < npoint; i++)
590     {
591       SequencePoint sp = (SequencePoint) points.elementAt(i);
592       int tmp1 = (int) ((sp.coord[0] - centre[0]) * scale + (float) getSize().width / 2.0);
593       int tmp2 = (int) ((sp.coord[1] - centre[1]) * scale + (float) getSize().height / 2.0);
594
595       if (tmp1 > x1 && tmp1 < x2 && tmp2 > y1 && tmp2 < y2)
596       {
597         if (av != null)
598         {
599           if (!av.getSelectionGroup().getSequences(null).contains(
600                   sp.sequence))
601           {
602             av.getSelectionGroup().addSequence(sp.sequence, true);
603           }
604         }
605       }
606     }
607   }
608
609   public SequenceI findPoint(int x, int y)
610   {
611
612     int halfwidth = getSize().width / 2;
613     int halfheight = getSize().height / 2;
614
615     int found = -1;
616
617     for (int i = 0; i < npoint; i++)
618     {
619
620       SequencePoint sp = (SequencePoint) points.elementAt(i);
621       int px = (int) ((float) (sp.coord[0] - centre[0]) * scale)
622               + halfwidth;
623       int py = (int) ((float) (sp.coord[1] - centre[1]) * scale)
624               + halfheight;
625
626       if (Math.abs(px - x) < 3 && Math.abs(py - y) < 3)
627       {
628         found = i;
629       }
630     }
631     if (found != -1)
632     {
633       return ((SequencePoint) points.elementAt(found)).sequence;
634     }
635     else
636     {
637       return null;
638     }
639   }
640
641 }