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