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