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