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