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