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