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