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