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