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