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