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