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