applied copyright 2008
[jalview.git] / src / jalview / gui / 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.gui;
20
21 import java.util.*;
22
23 import java.awt.*;
24 import java.awt.event.*;
25 import javax.swing.*;
26
27 import jalview.datamodel.*;
28 import jalview.math.*;
29
30 /**
31  * DOCUMENT ME!
32  *
33  * @author $author$
34  * @version $Revision$
35  */
36 public class RotatableCanvas
37     extends JPanel implements MouseListener,
38     MouseMotionListener, KeyListener
39 {
40   RotatableMatrix idmat = new RotatableMatrix(3, 3);
41   RotatableMatrix objmat = new RotatableMatrix(3, 3);
42   RotatableMatrix rotmat = new RotatableMatrix(3, 3);
43
44   //RubberbandRectangle rubberband;
45   boolean drawAxes = true;
46   int omx = 0;
47   int mx = 0;
48   int omy = 0;
49   int my = 0;
50   Image img;
51   Graphics ig;
52   Dimension prefsize;
53   float[] centre = new float[3];
54   float[] width = new float[3];
55   float[] max = new float[3];
56   float[] min = new float[3];
57   float maxwidth;
58   float scale;
59   int npoint;
60   Vector points;
61   float[][] orig;
62   float[][] axes;
63   int startx;
64   int starty;
65   int lastx;
66   int lasty;
67   int rectx1;
68   int recty1;
69   int rectx2;
70   int recty2;
71   float scalefactor = 1;
72   AlignViewport av;
73   AlignmentPanel ap;
74   boolean showLabels = false;
75   Color bgColour = Color.black;
76   boolean applyToAllViews = false;
77
78   //  Controller    controller;
79   public RotatableCanvas(AlignmentPanel ap)
80   {
81     this.av = ap.av;
82     this.ap = ap;
83
84     addMouseWheelListener(new MouseWheelListener()
85     {
86       public void mouseWheelMoved(MouseWheelEvent e)
87       {
88         if (e.getWheelRotation() > 0)
89         {
90           scale = (float) (scale * 1.1);
91           repaint();
92         }
93
94         else
95         {
96           scale = (float) (scale * 0.9);
97           repaint();
98         }
99       }
100     });
101
102   }
103
104   public void showLabels(boolean b)
105   {
106     showLabels = b;
107     repaint();
108   }
109
110   public void setPoints(Vector points, int npoint)
111   {
112     this.points = points;
113     this.npoint = npoint;
114     ToolTipManager.sharedInstance().registerComponent(this);
115     ToolTipManager.sharedInstance().setInitialDelay(0);
116     ToolTipManager.sharedInstance().setDismissDelay(10000);
117
118     prefsize = getPreferredSize();
119     orig = new float[npoint][3];
120
121     for (int i = 0; i < npoint; i++)
122     {
123       SequencePoint sp = (SequencePoint) points.elementAt(i);
124
125       for (int j = 0; j < 3; j++)
126       {
127         orig[i][j] = sp.coord[j];
128       }
129     }
130
131     //Initialize the matrices to identity
132     for (int i = 0; i < 3; i++)
133     {
134       for (int j = 0; j < 3; j++)
135       {
136         if (i != j)
137         {
138           idmat.addElement(i, j, 0);
139           objmat.addElement(i, j, 0);
140           rotmat.addElement(i, j, 0);
141         }
142         else
143         {
144           idmat.addElement(i, j, 0);
145           objmat.addElement(i, j, 0);
146           rotmat.addElement(i, j, 0);
147         }
148       }
149     }
150
151     axes = new float[3][3];
152     initAxes();
153
154     findCentre();
155     findWidth();
156
157     scale = findScale();
158
159     addMouseListener(this);
160
161     addMouseMotionListener(this);
162
163   }
164
165   public void initAxes()
166   {
167     for (int i = 0; i < 3; i++)
168     {
169       for (int j = 0; j < 3; j++)
170       {
171         if (i != j)
172         {
173           axes[i][j] = 0;
174         }
175         else
176         {
177           axes[i][j] = 1;
178         }
179       }
180     }
181   }
182
183   /**
184    * DOCUMENT ME!
185    */
186   public void findWidth()
187   {
188     max = new float[3];
189     min = new float[3];
190
191     max[0] = (float) - 1e30;
192     max[1] = (float) - 1e30;
193     max[2] = (float) - 1e30;
194
195     min[0] = (float) 1e30;
196     min[1] = (float) 1e30;
197     min[2] = (float) 1e30;
198
199     for (int i = 0; i < 3; i++)
200     {
201       for (int j = 0; j < npoint; j++)
202       {
203         SequencePoint sp = (SequencePoint) points.elementAt(j);
204
205         if (sp.coord[i] >= max[i])
206         {
207           max[i] = sp.coord[i];
208         }
209
210         if (sp.coord[i] <= min[i])
211         {
212           min[i] = sp.coord[i];
213         }
214       }
215     }
216
217     //    System.out.println("xmax " + max[0] + " min " + min[0]);
218     //System.out.println("ymax " + max[1] + " min " + min[1]);
219     //System.out.println("zmax " + max[2] + " min " + min[2]);
220     width[0] = Math.abs(max[0] - min[0]);
221     width[1] = Math.abs(max[1] - min[1]);
222     width[2] = Math.abs(max[2] - min[2]);
223
224     maxwidth = width[0];
225
226     if (width[1] > width[0])
227     {
228       maxwidth = width[1];
229     }
230
231     if (width[2] > width[1])
232     {
233       maxwidth = width[2];
234     }
235
236     //System.out.println("Maxwidth = " + maxwidth);
237   }
238
239   /**
240    * DOCUMENT ME!
241    *
242    * @return DOCUMENT ME!
243    */
244   public float findScale()
245   {
246     int dim;
247     int width;
248     int height;
249
250     if (getWidth() != 0)
251     {
252       width = getWidth();
253       height = getHeight();
254     }
255     else
256     {
257       width = prefsize.width;
258       height = prefsize.height;
259     }
260
261     if (width < height)
262     {
263       dim = width;
264     }
265     else
266     {
267       dim = height;
268     }
269
270     return (float) ( (dim * scalefactor) / (2 * maxwidth));
271   }
272
273   /**
274    * DOCUMENT ME!
275    */
276   public void findCentre()
277   {
278     //Find centre coordinate
279     findWidth();
280
281     centre[0] = (max[0] + min[0]) / 2;
282     centre[1] = (max[1] + min[1]) / 2;
283     centre[2] = (max[2] + min[2]) / 2;
284
285     //    System.out.println("Centre x " + centre[0]);
286     //System.out.println("Centre y " + centre[1]);
287     //System.out.println("Centre z " + centre[2]);
288   }
289
290   /**
291    * DOCUMENT ME!
292    *
293    * @return DOCUMENT ME!
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   /**
308    * DOCUMENT ME!
309    *
310    * @return DOCUMENT ME!
311    */
312   public Dimension getMinimumSize()
313   {
314     return getPreferredSize();
315   }
316
317   /**
318    * DOCUMENT ME!
319    *
320    * @param g DOCUMENT ME!
321    */
322   public void paintComponent(Graphics g1)
323   {
324
325     Graphics2D g = (Graphics2D) g1;
326
327     g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
328                        RenderingHints.VALUE_ANTIALIAS_ON);
329     if (points == null)
330     {
331       g.setFont(new Font("Verdana", Font.PLAIN, 18));
332       g.drawString("Calculating PCA....", 20, getHeight() / 2);
333     }
334     else
335     {
336       //Only create the image at the beginning -
337       if ( (img == null) || (prefsize.width != getWidth()) ||
338           (prefsize.height != getHeight()))
339       {
340         prefsize.width = getWidth();
341         prefsize.height = getHeight();
342
343         scale = findScale();
344
345         //      System.out.println("New scale = " + scale);
346         img = createImage(getWidth(), getHeight());
347         ig = img.getGraphics();
348       }
349
350       drawBackground(ig, bgColour);
351       drawScene(ig);
352
353       if (drawAxes == true)
354       {
355         drawAxes(ig);
356       }
357
358       g.drawImage(img, 0, 0, this);
359     }
360   }
361
362   /**
363    * DOCUMENT ME!
364    *
365    * @param g DOCUMENT ME!
366    */
367   public void drawAxes(Graphics g)
368   {
369
370     g.setColor(Color.yellow);
371
372     for (int i = 0; i < 3; i++)
373     {
374       g.drawLine(getWidth() / 2, getHeight() / 2,
375                  (int) ( (axes[i][0] * scale * max[0]) + (getWidth() / 2)),
376                  (int) ( (axes[i][1] * scale * max[1]) + (getHeight() / 2)));
377     }
378   }
379
380   /**
381    * DOCUMENT ME!
382    *
383    * @param g DOCUMENT ME!
384    * @param col DOCUMENT ME!
385    */
386   public void drawBackground(Graphics g, Color col)
387   {
388     g.setColor(col);
389     g.fillRect(0, 0, prefsize.width, prefsize.height);
390   }
391
392   /**
393    * DOCUMENT ME!
394    *
395    * @param g DOCUMENT ME!
396    */
397   public void drawScene(Graphics g1)
398   {
399
400     Graphics2D g = (Graphics2D) g1;
401
402     g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
403                        RenderingHints.VALUE_ANTIALIAS_ON);
404
405     int halfwidth = getWidth() / 2;
406     int halfheight = getHeight() / 2;
407
408     for (int i = 0; i < npoint; i++)
409     {
410       SequencePoint sp = (SequencePoint) points.elementAt(i);
411       int x = (int) ( (float) (sp.coord[0] - centre[0]) * scale) +
412           halfwidth;
413       int y = (int) ( (float) (sp.coord[1] - centre[1]) * scale) +
414           halfheight;
415       float z = sp.coord[1] - centre[2];
416
417       if (av.getSequenceColour(sp.sequence) == Color.black)
418       {
419         g.setColor(Color.white);
420       }
421       else
422       {
423         g.setColor(av.getSequenceColour(sp.sequence));
424       }
425
426       if (av.getSelectionGroup() != null)
427       {
428         if (av.getSelectionGroup().getSequences(null).contains(
429             ( (SequencePoint) points.elementAt(i)).sequence))
430         {
431           g.setColor(Color.gray);
432         }
433       }
434
435       if (z < 0)
436       {
437         g.setColor(g.getColor().darker());
438       }
439
440       g.fillRect(x - 3, y - 3, 6, 6);
441       if (showLabels)
442       {
443         g.setColor(Color.red);
444         g.drawString( ( (SequencePoint) points.elementAt(i)).sequence.
445                      getName(),
446                      x - 3, y - 4);
447       }
448     }
449
450     //    //Now the rectangle
451     //    if (rectx2 != -1 && recty2 != -1) {
452     //      g.setColor(Color.white);
453     //
454     //      g.drawRect(rectx1,recty1,rectx2-rectx1,recty2-recty1);
455     //    }
456   }
457
458   /**
459    * DOCUMENT ME!
460    *
461    * @return DOCUMENT ME!
462    */
463   public Dimension minimumsize()
464   {
465     return prefsize;
466   }
467
468   /**
469    * DOCUMENT ME!
470    *
471    * @return DOCUMENT ME!
472    */
473   public Dimension preferredsize()
474   {
475     return prefsize;
476   }
477
478   /**
479    * DOCUMENT ME!
480    *
481    * @param evt DOCUMENT ME!
482    */
483   public void keyTyped(KeyEvent evt)
484   {
485   }
486
487   /**
488    * DOCUMENT ME!
489    *
490    * @param evt DOCUMENT ME!
491    */
492   public void keyReleased(KeyEvent evt)
493   {
494   }
495
496   /**
497    * DOCUMENT ME!
498    *
499    * @param evt DOCUMENT ME!
500    */
501   public void keyPressed(KeyEvent evt)
502   {
503     if (evt.getKeyCode() == KeyEvent.VK_UP)
504     {
505       scalefactor = (float) (scalefactor * 1.1);
506       scale = findScale();
507     }
508     else if (evt.getKeyCode() == KeyEvent.VK_DOWN)
509     {
510       scalefactor = (float) (scalefactor * 0.9);
511       scale = findScale();
512     }
513     else if (evt.getKeyChar() == 's')
514     {
515       System.err.println("DEBUG: Rectangle selection"); // log.debug
516
517       if ( (rectx2 != -1) && (recty2 != -1))
518       {
519         rectSelect(rectx1, recty1, rectx2, recty2);
520       }
521     }
522
523     repaint();
524   }
525
526   /**
527    * DOCUMENT ME!
528    *
529    * @param evt DOCUMENT ME!
530    */
531   public void mouseClicked(MouseEvent evt)
532   {
533   }
534
535   /**
536    * DOCUMENT ME!
537    *
538    * @param evt DOCUMENT ME!
539    */
540   public void mouseEntered(MouseEvent evt)
541   {
542   }
543
544   /**
545    * DOCUMENT ME!
546    *
547    * @param evt DOCUMENT ME!
548    */
549   public void mouseExited(MouseEvent evt)
550   {
551   }
552
553   /**
554    * DOCUMENT ME!
555    *
556    * @param evt DOCUMENT ME!
557    */
558   public void mouseReleased(MouseEvent evt)
559   {
560   }
561
562   /**
563    * DOCUMENT ME!
564    *
565    * @param evt DOCUMENT ME!
566    */
567   public void mousePressed(MouseEvent evt)
568   {
569     int x = evt.getX();
570     int y = evt.getY();
571
572     mx = x;
573     my = y;
574
575     omx = mx;
576     omy = my;
577
578     startx = x;
579     starty = y;
580
581     rectx1 = x;
582     recty1 = y;
583
584     rectx2 = -1;
585     recty2 = -1;
586
587     SequenceI found = findPoint(x, y);
588
589     if (found != null)
590     {
591       AlignmentPanel[] aps = getAssociatedPanels();
592
593       for (int a = 0; a < aps.length; a++)
594       {
595         if (aps[a].av.getSelectionGroup() != null)
596         {
597           aps[a].av.getSelectionGroup().addOrRemove(found, true);
598         }
599         else
600         {
601           aps[a].av.setSelectionGroup(new SequenceGroup());
602           aps[a].av.getSelectionGroup().addOrRemove(found, true);
603           aps[a].av.getSelectionGroup().setEndRes(
604               aps[a].av.alignment.getWidth() - 1);
605         }
606       }
607
608       PaintRefresher.Refresh(this, av.getSequenceSetId());
609     }
610
611     repaint();
612   }
613
614   // private void fireSequenceSelectionEvent(Selection sel) {
615   //   controller.handleSequenceSelectionEvent(new SequenceSelectionEvent(this,sel));
616   //}
617   public void mouseMoved(MouseEvent evt)
618   {
619     SequenceI found = findPoint(evt.getX(), evt.getY());
620
621     if (found != null)
622     {
623       this.setToolTipText(found.getName());
624     }
625     else
626     {
627       this.setToolTipText(null);
628     }
629   }
630
631   /**
632    * DOCUMENT ME!
633    *
634    * @param evt DOCUMENT ME!
635    */
636   public void mouseDragged(MouseEvent evt)
637   {
638     mx = evt.getX();
639     my = evt.getY();
640
641     //Check if this is a rectangle drawing drag
642     if ( (evt.getModifiers() & InputEvent.BUTTON2_MASK) != 0)
643     {
644       //      rectx2 = evt.getX();
645       //      recty2 = evt.getY();
646     }
647     else
648     {
649       rotmat.setIdentity();
650
651       rotmat.rotate( (float) (my - omy), 'x');
652       rotmat.rotate( (float) (mx - omx), 'y');
653
654       for (int i = 0; i < npoint; i++)
655       {
656         SequencePoint sp = (SequencePoint) points.elementAt(i);
657         sp.coord[0] -= centre[0];
658         sp.coord[1] -= centre[1];
659         sp.coord[2] -= centre[2];
660
661         //Now apply the rotation matrix
662         sp.coord = rotmat.vectorMultiply(sp.coord);
663
664         //Now translate back again
665         sp.coord[0] += centre[0];
666         sp.coord[1] += centre[1];
667         sp.coord[2] += centre[2];
668       }
669
670       for (int i = 0; i < 3; i++)
671       {
672         axes[i] = rotmat.vectorMultiply(axes[i]);
673       }
674
675       omx = mx;
676       omy = my;
677
678       paint(this.getGraphics());
679     }
680   }
681
682   /**
683    * DOCUMENT ME!
684    *
685    * @param x1 DOCUMENT ME!
686    * @param y1 DOCUMENT ME!
687    * @param x2 DOCUMENT ME!
688    * @param y2 DOCUMENT ME!
689    */
690   public void rectSelect(int x1, int y1, int x2, int y2)
691   {
692     for (int i = 0; i < npoint; i++)
693     {
694       SequencePoint sp = (SequencePoint) points.elementAt(i);
695       int tmp1 = (int) ( ( (sp.coord[0] - centre[0]) * scale) +
696                         ( (float) getWidth() / 2.0));
697       int tmp2 = (int) ( ( (sp.coord[1] - centre[1]) * scale) +
698                         ( (float) getHeight() / 2.0));
699
700       if ( (tmp1 > x1) && (tmp1 < x2) && (tmp2 > y1) && (tmp2 < y2))
701       {
702         if (av != null)
703         {
704           if (!av.getSelectionGroup().getSequences(null).contains(sp.sequence))
705           {
706             av.getSelectionGroup().addSequence(sp.sequence, true);
707           }
708         }
709       }
710     }
711
712     // if (changedSel) {
713     //    fireSequenceSelectionEvent(av.getSelection());
714     // }
715   }
716
717   /**
718    * DOCUMENT ME!
719    *
720    * @param x DOCUMENT ME!
721    * @param y DOCUMENT ME!
722    *
723    * @return DOCUMENT ME!
724    */
725   public SequenceI findPoint(int x, int y)
726   {
727     int halfwidth = getWidth() / 2;
728     int halfheight = getHeight() / 2;
729
730     int found = -1;
731
732     for (int i = 0; i < npoint; i++)
733     {
734       SequencePoint sp = (SequencePoint) points.elementAt(i);
735       int px = (int) ( (float) (sp.coord[0] - centre[0]) * scale) +
736           halfwidth;
737       int py = (int) ( (float) (sp.coord[1] - centre[1]) * scale) +
738           halfheight;
739
740       if ( (Math.abs(px - x) < 3) && (Math.abs(py - y) < 3))
741       {
742         found = i;
743       }
744     }
745
746     if (found != -1)
747     {
748       return ( (SequencePoint) points.elementAt(found)).sequence;
749     }
750     else
751     {
752       return null;
753     }
754   }
755
756   AlignmentPanel[] getAssociatedPanels()
757   {
758     if (applyToAllViews)
759     {
760       return PaintRefresher.getAssociatedPanels(av.getSequenceSetId());
761     }
762     else
763     {
764       return new AlignmentPanel[]
765           {
766           ap};
767     }
768   }
769 }