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