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