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