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