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