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