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