Merge branch 'features/JAL-2393customMatrices' into develop
[jalview.git] / src / jalview / gui / PCAPanel.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.analysis.scoremodels.ScoreModels;
24 import jalview.analysis.scoremodels.SimilarityParams;
25 import jalview.api.analysis.ScoreModelI;
26 import jalview.api.analysis.SimilarityParamsI;
27 import jalview.datamodel.Alignment;
28 import jalview.datamodel.AlignmentI;
29 import jalview.datamodel.AlignmentView;
30 import jalview.datamodel.ColumnSelection;
31 import jalview.datamodel.SequenceI;
32 import jalview.jbgui.GPCAPanel;
33 import jalview.util.MessageManager;
34 import jalview.viewmodel.AlignmentViewport;
35 import jalview.viewmodel.PCAModel;
36
37 import java.awt.BorderLayout;
38 import java.awt.Color;
39 import java.awt.Dimension;
40 import java.awt.Graphics;
41 import java.awt.event.ActionEvent;
42 import java.awt.event.ActionListener;
43 import java.awt.print.PageFormat;
44 import java.awt.print.Printable;
45 import java.awt.print.PrinterException;
46 import java.awt.print.PrinterJob;
47
48 import javax.swing.ButtonGroup;
49 import javax.swing.JCheckBoxMenuItem;
50 import javax.swing.JColorChooser;
51 import javax.swing.JMenuItem;
52 import javax.swing.JRadioButtonMenuItem;
53 import javax.swing.event.InternalFrameAdapter;
54 import javax.swing.event.InternalFrameEvent;
55
56 /**
57  * DOCUMENT ME!
58  * 
59  * @author $author$
60  * @version $Revision$
61  */
62 public class PCAPanel extends GPCAPanel implements Runnable,
63         IProgressIndicator
64 {
65
66   private IProgressIndicator progressBar;
67
68   RotatableCanvas rc;
69
70   AlignmentPanel ap;
71
72   AlignmentViewport av;
73
74   PCAModel pcaModel;
75
76   private static final int MIN_WIDTH = 470;
77
78   private static final int MIN_HEIGHT = 250;
79
80   int top = 0;
81
82   /**
83    * Creates a new PCAPanel object using default score model and parameters
84    * 
85    * @param alignPanel
86    */
87   public PCAPanel(AlignmentPanel alignPanel)
88   {
89     this(alignPanel, ScoreModels.getInstance()
90             .getDefaultModel(!alignPanel.av.getAlignment().isNucleotide())
91             .getName(), SimilarityParams.SeqSpace);
92   }
93
94   /**
95    * Constructor given sequence data, a similarity (or distance) score model
96    * name, and score calculation parameters
97    * 
98    * @param alignPanel
99    * @param modelName
100    * @param params
101    */
102   public PCAPanel(AlignmentPanel alignPanel, String modelName,
103           SimilarityParamsI params)
104   {
105     super();
106     this.av = alignPanel.av;
107     this.ap = alignPanel;
108     boolean nucleotide = av.getAlignment().isNucleotide();
109
110     progressBar = new ProgressBar(statusPanel, statusBar);
111
112     addInternalFrameListener(new InternalFrameAdapter()
113     {
114       @Override
115       public void internalFrameClosed(InternalFrameEvent e)
116       {
117         close_actionPerformed();
118       }
119     });
120
121     boolean selected = av.getSelectionGroup() != null
122             && av.getSelectionGroup().getSize() > 0;
123     AlignmentView seqstrings = av.getAlignmentView(selected);
124     SequenceI[] seqs;
125     if (!selected)
126     {
127       seqs = av.getAlignment().getSequencesArray();
128     }
129     else
130     {
131       seqs = av.getSelectionGroup().getSequencesInOrder(av.getAlignment());
132     }
133
134     ScoreModelI scoreModel = ScoreModels.getInstance().getScoreModel(
135             modelName, ap);
136     pcaModel = new PCAModel(seqstrings, seqs, nucleotide, scoreModel,
137             params);
138     PaintRefresher.Register(this, av.getSequenceSetId());
139
140     rc = new RotatableCanvas(alignPanel);
141     this.getContentPane().add(rc, BorderLayout.CENTER);
142     Thread worker = new Thread(this);
143     worker.start();
144   }
145
146   /**
147    * Ensure references to potentially very large objects (the PCA matrices) are
148    * nulled when the frame is closed
149    */
150   protected void close_actionPerformed()
151   {
152     pcaModel = null;
153   }
154
155   /**
156    * Repopulate the options and actions under the score model menu when it is
157    * selected. Options will depend on whether 'nucleotide' or 'peptide'
158    * modelling is selected (and also possibly on whether any additional score
159    * models have been added).
160    */
161   @Override
162   protected void scoreModel_menuSelected()
163   {
164     scoreModelMenu.removeAll();
165     for (final ScoreModelI sm : ScoreModels.getInstance().getModels())
166     {
167       final String name = sm.getName();
168       JCheckBoxMenuItem jm = new JCheckBoxMenuItem(name);
169
170       /*
171        * if the score model doesn't provide a description, try to look one
172        * up in the text bundle, falling back on its name
173        */
174       String tooltip = sm.getDescription();
175       if (tooltip == null)
176       {
177         tooltip = MessageManager.getStringOrReturn("label.score_model_",
178                 name);
179       }
180       jm.setToolTipText(tooltip);
181       jm.setSelected(pcaModel.getScoreModelName().equals(name));
182       if ((pcaModel.isNucleotide() && sm.isDNA())
183               || (!pcaModel.isNucleotide() && sm.isProtein()))
184       {
185         jm.addActionListener(new ActionListener()
186         {
187           @Override
188           public void actionPerformed(ActionEvent e)
189           {
190             if (!pcaModel.getScoreModelName().equals(name))
191             {
192               ScoreModelI sm2 = ScoreModels.getInstance().getScoreModel(
193                       name, ap);
194               pcaModel.setScoreModel(sm2);
195               Thread worker = new Thread(PCAPanel.this);
196               worker.start();
197             }
198           }
199         });
200         scoreModelMenu.add(jm);
201       }
202     }
203   }
204
205   @Override
206   public void bgcolour_actionPerformed(ActionEvent e)
207   {
208     Color col = JColorChooser.showDialog(this,
209             MessageManager.getString("label.select_background_colour"),
210             rc.bgColour);
211
212     if (col != null)
213     {
214       rc.bgColour = col;
215     }
216     rc.repaint();
217   }
218
219   /**
220    * DOCUMENT ME!
221    */
222   @Override
223   public void run()
224   {
225     long progId = System.currentTimeMillis();
226     IProgressIndicator progress = this;
227     String message = MessageManager.getString("label.pca_recalculating");
228     if (getParent() == null)
229     {
230       progress = ap.alignFrame;
231       message = MessageManager.getString("label.pca_calculating");
232     }
233     progress.setProgressBar(message, progId);
234     try
235     {
236       calcSettings.setEnabled(false);
237       pcaModel.run();
238       // ////////////////
239       xCombobox.setSelectedIndex(0);
240       yCombobox.setSelectedIndex(1);
241       zCombobox.setSelectedIndex(2);
242
243       pcaModel.updateRc(rc);
244       // rc.invalidate();
245       nuclSetting.setSelected(pcaModel.isNucleotide());
246       protSetting.setSelected(!pcaModel.isNucleotide());
247       top = pcaModel.getTop();
248
249     } catch (OutOfMemoryError er)
250     {
251       new OOMWarning("calculating PCA", er);
252       return;
253     } finally
254     {
255       progress.setProgressBar("", progId);
256     }
257     calcSettings.setEnabled(true);
258     repaint();
259     if (getParent() == null)
260     {
261       addKeyListener(rc);
262       Desktop.addInternalFrame(this, MessageManager
263               .getString("label.principal_component_analysis"), 475, 450);
264       this.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
265     }
266   }
267
268   @Override
269   protected void nuclSetting_actionPerfomed(ActionEvent arg0)
270   {
271     if (!pcaModel.isNucleotide())
272     {
273       pcaModel.setNucleotide(true);
274       pcaModel.setScoreModel(ScoreModels.getInstance().getDefaultModel(
275               false));
276       Thread worker = new Thread(this);
277       worker.start();
278     }
279
280   }
281
282   @Override
283   protected void protSetting_actionPerfomed(ActionEvent arg0)
284   {
285
286     if (pcaModel.isNucleotide())
287     {
288       pcaModel.setNucleotide(false);
289       pcaModel.setScoreModel(ScoreModels.getInstance()
290               .getDefaultModel(true));
291       Thread worker = new Thread(this);
292       worker.start();
293     }
294   }
295
296   /**
297    * DOCUMENT ME!
298    */
299   void doDimensionChange()
300   {
301     if (top == 0)
302     {
303       return;
304     }
305
306     int dim1 = top - xCombobox.getSelectedIndex();
307     int dim2 = top - yCombobox.getSelectedIndex();
308     int dim3 = top - zCombobox.getSelectedIndex();
309     pcaModel.updateRcView(dim1, dim2, dim3);
310     rc.img = null;
311     rc.rotmat.setIdentity();
312     rc.initAxes();
313     rc.paint(rc.getGraphics());
314   }
315
316   /**
317    * DOCUMENT ME!
318    * 
319    * @param e
320    *          DOCUMENT ME!
321    */
322   @Override
323   protected void xCombobox_actionPerformed(ActionEvent e)
324   {
325     doDimensionChange();
326   }
327
328   /**
329    * DOCUMENT ME!
330    * 
331    * @param e
332    *          DOCUMENT ME!
333    */
334   @Override
335   protected void yCombobox_actionPerformed(ActionEvent e)
336   {
337     doDimensionChange();
338   }
339
340   /**
341    * DOCUMENT ME!
342    * 
343    * @param e
344    *          DOCUMENT ME!
345    */
346   @Override
347   protected void zCombobox_actionPerformed(ActionEvent e)
348   {
349     doDimensionChange();
350   }
351
352   @Override
353   public void outputValues_actionPerformed(ActionEvent e)
354   {
355     CutAndPasteTransfer cap = new CutAndPasteTransfer();
356     try
357     {
358       cap.setText(pcaModel.getDetails());
359       Desktop.addInternalFrame(cap,
360               MessageManager.getString("label.pca_details"), 500, 500);
361     } catch (OutOfMemoryError oom)
362     {
363       new OOMWarning("opening PCA details", oom);
364       cap.dispose();
365     }
366   }
367
368   @Override
369   public void showLabels_actionPerformed(ActionEvent e)
370   {
371     rc.showLabels(showLabels.getState());
372   }
373
374   @Override
375   public void print_actionPerformed(ActionEvent e)
376   {
377     PCAPrinter printer = new PCAPrinter();
378     printer.start();
379   }
380
381   @Override
382   public void originalSeqData_actionPerformed(ActionEvent e)
383   {
384     // this was cut'n'pasted from the equivalent TreePanel method - we should
385     // make this an abstract function of all jalview analysis windows
386     if (pcaModel.getSeqtrings() == null)
387     {
388       jalview.bin.Cache.log
389               .info("Unexpected call to originalSeqData_actionPerformed - should have hidden this menu action.");
390       return;
391     }
392     // decide if av alignment is sufficiently different to original data to
393     // warrant a new window to be created
394     // create new alignmnt window with hidden regions (unhiding hidden regions
395     // yields unaligned seqs)
396     // or create a selection box around columns in alignment view
397     // test Alignment(SeqCigar[])
398     char gc = '-';
399     try
400     {
401       // we try to get the associated view's gap character
402       // but this may fail if the view was closed...
403       gc = av.getGapCharacter();
404     } catch (Exception ex)
405     {
406     }
407     ;
408     Object[] alAndColsel = pcaModel.getSeqtrings()
409             .getAlignmentAndColumnSelection(gc);
410
411     if (alAndColsel != null && alAndColsel[0] != null)
412     {
413       // AlignmentOrder origorder = new AlignmentOrder(alAndColsel[0]);
414
415       AlignmentI al = new Alignment((SequenceI[]) alAndColsel[0]);
416       AlignmentI dataset = (av != null && av.getAlignment() != null) ? av
417               .getAlignment().getDataset() : null;
418       if (dataset != null)
419       {
420         al.setDataset(dataset);
421       }
422
423       if (true)
424       {
425         // make a new frame!
426         AlignFrame af = new AlignFrame(al,
427                 (ColumnSelection) alAndColsel[1], AlignFrame.DEFAULT_WIDTH,
428                 AlignFrame.DEFAULT_HEIGHT);
429
430         // >>>This is a fix for the moment, until a better solution is
431         // found!!<<<
432         // af.getFeatureRenderer().transferSettings(alignFrame.getFeatureRenderer());
433
434         // af.addSortByOrderMenuItem(ServiceName + " Ordering",
435         // msaorder);
436
437         Desktop.addInternalFrame(af, MessageManager.formatMessage(
438                 "label.original_data_for_params",
439                 new String[] { this.title }), AlignFrame.DEFAULT_WIDTH,
440                 AlignFrame.DEFAULT_HEIGHT);
441       }
442     }
443     /*
444      * CutAndPasteTransfer cap = new CutAndPasteTransfer(); for (int i = 0; i <
445      * seqs.length; i++) { cap.appendText(new jalview.util.Format("%-" + 15 +
446      * "s").form( seqs[i].getName())); cap.appendText(" " + seqstrings[i] +
447      * "\n"); }
448      * 
449      * Desktop.addInternalFrame(cap, "Original Data", 400, 400);
450      */
451   }
452
453   class PCAPrinter extends Thread implements Printable
454   {
455     @Override
456     public void run()
457     {
458       PrinterJob printJob = PrinterJob.getPrinterJob();
459       PageFormat pf = printJob.pageDialog(printJob.defaultPage());
460
461       printJob.setPrintable(this, pf);
462
463       if (printJob.printDialog())
464       {
465         try
466         {
467           printJob.print();
468         } catch (Exception PrintException)
469         {
470           PrintException.printStackTrace();
471         }
472       }
473     }
474
475     @Override
476     public int print(Graphics pg, PageFormat pf, int pi)
477             throws PrinterException
478     {
479       pg.translate((int) pf.getImageableX(), (int) pf.getImageableY());
480
481       rc.drawBackground(pg, rc.bgColour);
482       rc.drawScene(pg);
483       if (rc.drawAxes == true)
484       {
485         rc.drawAxes(pg);
486       }
487
488       if (pi == 0)
489       {
490         return Printable.PAGE_EXISTS;
491       }
492       else
493       {
494         return Printable.NO_SUCH_PAGE;
495       }
496     }
497   }
498
499   /**
500    * DOCUMENT ME!
501    * 
502    * @param e
503    *          DOCUMENT ME!
504    */
505   @Override
506   public void eps_actionPerformed(ActionEvent e)
507   {
508     makePCAImage(jalview.util.ImageMaker.TYPE.EPS);
509   }
510
511   /**
512    * DOCUMENT ME!
513    * 
514    * @param e
515    *          DOCUMENT ME!
516    */
517   @Override
518   public void png_actionPerformed(ActionEvent e)
519   {
520     makePCAImage(jalview.util.ImageMaker.TYPE.PNG);
521   }
522
523   void makePCAImage(jalview.util.ImageMaker.TYPE type)
524   {
525     int width = rc.getWidth();
526     int height = rc.getHeight();
527
528     jalview.util.ImageMaker im;
529
530     if (type == jalview.util.ImageMaker.TYPE.PNG)
531     {
532       im = new jalview.util.ImageMaker(this,
533               jalview.util.ImageMaker.TYPE.PNG, "Make PNG image from PCA",
534               width, height, null, null, null, 0, false);
535     }
536     else if (type == jalview.util.ImageMaker.TYPE.EPS)
537     {
538       im = new jalview.util.ImageMaker(this,
539               jalview.util.ImageMaker.TYPE.EPS, "Make EPS file from PCA",
540               width, height, null, this.getTitle(), null, 0, false);
541     }
542     else
543     {
544       im = new jalview.util.ImageMaker(this,
545               jalview.util.ImageMaker.TYPE.SVG, "Make SVG file from PCA",
546               width, height, null, this.getTitle(), null, 0, false);
547
548     }
549
550     if (im.getGraphics() != null)
551     {
552       rc.drawBackground(im.getGraphics(), Color.black);
553       rc.drawScene(im.getGraphics());
554       if (rc.drawAxes == true)
555       {
556         rc.drawAxes(im.getGraphics());
557       }
558       im.writeImage();
559     }
560   }
561
562   @Override
563   public void viewMenu_menuSelected()
564   {
565     buildAssociatedViewMenu();
566   }
567
568   void buildAssociatedViewMenu()
569   {
570     AlignmentPanel[] aps = PaintRefresher.getAssociatedPanels(av
571             .getSequenceSetId());
572     if (aps.length == 1 && rc.av == aps[0].av)
573     {
574       associateViewsMenu.setVisible(false);
575       return;
576     }
577
578     associateViewsMenu.setVisible(true);
579
580     if ((viewMenu.getItem(viewMenu.getItemCount() - 2) instanceof JMenuItem))
581     {
582       viewMenu.insertSeparator(viewMenu.getItemCount() - 1);
583     }
584
585     associateViewsMenu.removeAll();
586
587     JRadioButtonMenuItem item;
588     ButtonGroup buttonGroup = new ButtonGroup();
589     int i, iSize = aps.length;
590     final PCAPanel thisPCAPanel = this;
591     for (i = 0; i < iSize; i++)
592     {
593       final AlignmentPanel ap = aps[i];
594       item = new JRadioButtonMenuItem(ap.av.viewName, ap.av == rc.av);
595       buttonGroup.add(item);
596       item.addActionListener(new ActionListener()
597       {
598         @Override
599         public void actionPerformed(ActionEvent evt)
600         {
601           rc.applyToAllViews = false;
602           rc.av = ap.av;
603           rc.ap = ap;
604           PaintRefresher.Register(thisPCAPanel, ap.av.getSequenceSetId());
605         }
606       });
607
608       associateViewsMenu.add(item);
609     }
610
611     final JRadioButtonMenuItem itemf = new JRadioButtonMenuItem("All Views");
612
613     buttonGroup.add(itemf);
614
615     itemf.setSelected(rc.applyToAllViews);
616     itemf.addActionListener(new ActionListener()
617     {
618       @Override
619       public void actionPerformed(ActionEvent evt)
620       {
621         rc.applyToAllViews = itemf.isSelected();
622       }
623     });
624     associateViewsMenu.add(itemf);
625
626   }
627
628   /*
629    * (non-Javadoc)
630    * 
631    * @see
632    * jalview.jbgui.GPCAPanel#outputPoints_actionPerformed(java.awt.event.ActionEvent
633    * )
634    */
635   @Override
636   protected void outputPoints_actionPerformed(ActionEvent e)
637   {
638     CutAndPasteTransfer cap = new CutAndPasteTransfer();
639     try
640     {
641       cap.setText(pcaModel.getPointsasCsv(false,
642               xCombobox.getSelectedIndex(), yCombobox.getSelectedIndex(),
643               zCombobox.getSelectedIndex()));
644       Desktop.addInternalFrame(cap, MessageManager.formatMessage(
645               "label.points_for_params", new String[] { this.getTitle() }),
646               500, 500);
647     } catch (OutOfMemoryError oom)
648     {
649       new OOMWarning("exporting PCA points", oom);
650       cap.dispose();
651     }
652   }
653
654   /*
655    * (non-Javadoc)
656    * 
657    * @see
658    * jalview.jbgui.GPCAPanel#outputProjPoints_actionPerformed(java.awt.event
659    * .ActionEvent)
660    */
661   @Override
662   protected void outputProjPoints_actionPerformed(ActionEvent e)
663   {
664     CutAndPasteTransfer cap = new CutAndPasteTransfer();
665     try
666     {
667       cap.setText(pcaModel.getPointsasCsv(true,
668               xCombobox.getSelectedIndex(), yCombobox.getSelectedIndex(),
669               zCombobox.getSelectedIndex()));
670       Desktop.addInternalFrame(cap, MessageManager.formatMessage(
671               "label.transformed_points_for_params",
672               new String[] { this.getTitle() }), 500, 500);
673     } catch (OutOfMemoryError oom)
674     {
675       new OOMWarning("exporting transformed PCA points", oom);
676       cap.dispose();
677     }
678   }
679
680   /*
681    * (non-Javadoc)
682    * 
683    * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
684    */
685   @Override
686   public void setProgressBar(String message, long id)
687   {
688     progressBar.setProgressBar(message, id);
689     // if (progressBars == null)
690     // {
691     // progressBars = new Hashtable();
692     // progressBarHandlers = new Hashtable();
693     // }
694     //
695     // JPanel progressPanel;
696     // Long lId = new Long(id);
697     // GridLayout layout = (GridLayout) statusPanel.getLayout();
698     // if (progressBars.get(lId) != null)
699     // {
700     // progressPanel = (JPanel) progressBars.get(new Long(id));
701     // statusPanel.remove(progressPanel);
702     // progressBars.remove(lId);
703     // progressPanel = null;
704     // if (message != null)
705     // {
706     // statusBar.setText(message);
707     // }
708     // if (progressBarHandlers.contains(lId))
709     // {
710     // progressBarHandlers.remove(lId);
711     // }
712     // layout.setRows(layout.getRows() - 1);
713     // }
714     // else
715     // {
716     // progressPanel = new JPanel(new BorderLayout(10, 5));
717     //
718     // JProgressBar progressBar = new JProgressBar();
719     // progressBar.setIndeterminate(true);
720     //
721     // progressPanel.add(new JLabel(message), BorderLayout.WEST);
722     // progressPanel.add(progressBar, BorderLayout.CENTER);
723     //
724     // layout.setRows(layout.getRows() + 1);
725     // statusPanel.add(progressPanel);
726     //
727     // progressBars.put(lId, progressPanel);
728     // }
729     // // update GUI
730     // // setMenusForViewport();
731     // validate();
732   }
733
734   @Override
735   public void registerHandler(final long id,
736           final IProgressIndicatorHandler handler)
737   {
738     progressBar.registerHandler(id, handler);
739     // if (progressBarHandlers == null || !progressBars.contains(new Long(id)))
740     // {
741     // throw new
742     // Error(MessageManager.getString("error.call_setprogressbar_before_registering_handler"));
743     // }
744     // progressBarHandlers.put(new Long(id), handler);
745     // final JPanel progressPanel = (JPanel) progressBars.get(new Long(id));
746     // if (handler.canCancel())
747     // {
748     // JButton cancel = new JButton(
749     // MessageManager.getString("action.cancel"));
750     // final IProgressIndicator us = this;
751     // cancel.addActionListener(new ActionListener()
752     // {
753     //
754     // @Override
755     // public void actionPerformed(ActionEvent e)
756     // {
757     // handler.cancelActivity(id);
758     // us.setProgressBar(MessageManager.formatMessage("label.cancelled_params",
759     // new String[]{((JLabel) progressPanel.getComponent(0)).getText()}), id);
760     // }
761     // });
762     // progressPanel.add(cancel, BorderLayout.EAST);
763     // }
764   }
765
766   /**
767    * 
768    * @return true if any progress bars are still active
769    */
770   @Override
771   public boolean operationInProgress()
772   {
773     return progressBar.operationInProgress();
774   }
775
776   @Override
777   protected void resetButton_actionPerformed(ActionEvent e)
778   {
779     int t = top;
780     top = 0; // ugly - prevents dimensionChanged events from being processed
781     xCombobox.setSelectedIndex(0);
782     yCombobox.setSelectedIndex(1);
783     top = t;
784     zCombobox.setSelectedIndex(2);
785   }
786 }