JAL-4059 Tidy getting the namespace, and save the namespace in the Jalview instance...
[jalview.git] / src / jalview / gui / PCAPanel.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ 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 java.awt.BorderLayout;
24 import java.awt.Color;
25 import java.awt.Dimension;
26 import java.awt.Graphics;
27 import java.awt.event.ActionEvent;
28 import java.awt.event.ActionListener;
29 import java.awt.print.PageFormat;
30 import java.awt.print.Printable;
31 import java.awt.print.PrinterException;
32 import java.awt.print.PrinterJob;
33
34 import javax.swing.ButtonGroup;
35 import javax.swing.JMenuItem;
36 import javax.swing.JRadioButtonMenuItem;
37 import javax.swing.event.InternalFrameAdapter;
38 import javax.swing.event.InternalFrameEvent;
39
40 import jalview.analysis.scoremodels.ScoreModels;
41 import jalview.api.AlignViewportI;
42 import jalview.api.analysis.ScoreModelI;
43 import jalview.api.analysis.SimilarityParamsI;
44 import jalview.bin.Console;
45 import jalview.datamodel.Alignment;
46 import jalview.datamodel.AlignmentI;
47 import jalview.datamodel.AlignmentView;
48 import jalview.datamodel.HiddenColumns;
49 import jalview.datamodel.SequenceI;
50 import jalview.gui.ImageExporter.ImageWriterI;
51 import jalview.gui.JalviewColourChooser.ColourChooserListener;
52 import jalview.io.exceptions.ImageOutputException;
53 import jalview.jbgui.GPCAPanel;
54 import jalview.math.RotatableMatrix.Axis;
55 import jalview.util.IdUtils;
56 import jalview.util.IdUtils.IdType;
57 import jalview.util.ImageMaker;
58 import jalview.util.MessageManager;
59 import jalview.viewmodel.AlignmentViewport;
60 import jalview.viewmodel.PCAModel;
61
62 /**
63  * The panel holding the Principal Component Analysis 3-D visualisation
64  */
65 public class PCAPanel extends GPCAPanel
66         implements Runnable, IProgressIndicator
67 {
68   private static final int MIN_WIDTH = 470;
69
70   private static final int MIN_HEIGHT = 250;
71
72   private RotatableCanvas rc;
73
74   AlignmentPanel ap;
75
76   AlignmentViewport av;
77
78   private PCAModel pcaModel;
79
80   private int top = 0;
81
82   private IProgressIndicator progressBar;
83
84   private boolean working;
85
86   /**
87    * Constructor given sequence data, a similarity (or distance) score model
88    * name, and score calculation parameters
89    * 
90    * @param alignPanel
91    * @param modelName
92    * @param params
93    */
94   public PCAPanel(AlignmentPanel alignPanel, String modelName,
95           SimilarityParamsI params)
96   {
97     super();
98     this.setFrameIcon(null);
99     this.av = alignPanel.av;
100     this.ap = alignPanel;
101     boolean nucleotide = av.getAlignment().isNucleotide();
102
103     progressBar = new ProgressBar(statusPanel, statusBar);
104
105     addInternalFrameListener(new InternalFrameAdapter()
106     {
107       @Override
108       public void internalFrameClosed(InternalFrameEvent e)
109       {
110         close_actionPerformed();
111       }
112     });
113
114     boolean selected = av.getSelectionGroup() != null
115             && av.getSelectionGroup().getSize() > 0;
116     AlignmentView seqstrings = av.getAlignmentView(selected);
117     SequenceI[] seqs;
118     if (!selected)
119     {
120       seqs = av.getAlignment().getSequencesArray();
121     }
122     else
123     {
124       seqs = av.getSelectionGroup().getSequencesInOrder(av.getAlignment());
125     }
126
127     ScoreModelI scoreModel = ScoreModels.getInstance()
128             .getScoreModel(modelName, ap);
129     setPcaModel(
130             new PCAModel(seqstrings, seqs, nucleotide, scoreModel, params));
131     PaintRefresher.Register(this, av.getSequenceSetId());
132
133     setRotatableCanvas(new RotatableCanvas(alignPanel));
134     this.getContentPane().add(getRotatableCanvas(), BorderLayout.CENTER);
135
136     addKeyListener(getRotatableCanvas());
137     validate();
138   }
139
140   /**
141    * Ensure references to potentially very large objects (the PCA matrices) are
142    * nulled when the frame is closed
143    */
144   protected void close_actionPerformed()
145   {
146     setPcaModel(null);
147     if (this.rc != null)
148     {
149       this.rc.sequencePoints = null;
150       this.rc.setAxisEndPoints(null);
151       this.rc = null;
152     }
153   }
154
155   @Override
156   protected void bgcolour_actionPerformed()
157   {
158     String ttl = MessageManager.getString("label.select_background_colour");
159     ColourChooserListener listener = new ColourChooserListener()
160     {
161       @Override
162       public void colourSelected(Color c)
163       {
164         rc.setBgColour(c);
165         rc.repaint();
166       }
167     };
168     JalviewColourChooser.showColourChooser(this, ttl, rc.getBgColour(),
169             listener);
170   }
171
172   /**
173    * Calculates the PCA and displays the results
174    */
175   @Override
176   public void run()
177   {
178     working = true;
179     long progId = IdUtils.newId(IdType.PROGRESS);
180     IProgressIndicator progress = this;
181     String message = MessageManager.getString("label.pca_recalculating");
182     if (getParent() == null)
183     {
184       progress = ap.alignFrame;
185       message = MessageManager.getString("label.pca_calculating");
186     }
187     progress.setProgressBar(message, progId);
188     try
189     {
190       getPcaModel().calculate();
191
192       xCombobox.setSelectedIndex(0);
193       yCombobox.setSelectedIndex(1);
194       zCombobox.setSelectedIndex(2);
195
196       getPcaModel().updateRc(getRotatableCanvas());
197       // rc.invalidate();
198       setTop(getPcaModel().getTop());
199
200     } catch (OutOfMemoryError er)
201     {
202       new OOMWarning("calculating PCA", er);
203       working = false;
204       return;
205     } finally
206     {
207       progress.setProgressBar("", progId);
208     }
209
210     repaint();
211     if (getParent() == null)
212     {
213       Desktop.addInternalFrame(this,
214               MessageManager.formatMessage("label.calc_title", "PCA",
215                       getPcaModel().getScoreModelName()),
216               475, 450);
217       this.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
218     }
219     working = false;
220   }
221
222   /**
223    * Updates the PCA display after a change of component to use for x, y or z
224    * axis
225    */
226   @Override
227   protected void doDimensionChange()
228   {
229     if (getTop() == 0)
230     {
231       return;
232     }
233
234     int dim1 = getTop() - xCombobox.getSelectedIndex();
235     int dim2 = getTop() - yCombobox.getSelectedIndex();
236     int dim3 = getTop() - zCombobox.getSelectedIndex();
237     getPcaModel().updateRcView(dim1, dim2, dim3);
238     getRotatableCanvas().resetView();
239   }
240
241   /**
242    * Sets the selected checkbox item index for PCA dimension (1, 2, 3...) for
243    * the given axis (X/Y/Z)
244    * 
245    * @param index
246    * @param axis
247    */
248   public void setSelectedDimensionIndex(int index, Axis axis)
249   {
250     switch (axis)
251     {
252     case X:
253       xCombobox.setSelectedIndex(index);
254       break;
255     case Y:
256       yCombobox.setSelectedIndex(index);
257       break;
258     case Z:
259       zCombobox.setSelectedIndex(index);
260       break;
261     default:
262     }
263   }
264
265   @Override
266   protected void outputValues_actionPerformed()
267   {
268     CutAndPasteTransfer cap = new CutAndPasteTransfer();
269     try
270     {
271       cap.setText(getPcaModel().getDetails());
272       Desktop.addInternalFrame(cap,
273               MessageManager.getString("label.pca_details"), 500, 500);
274     } catch (OutOfMemoryError oom)
275     {
276       new OOMWarning("opening PCA details", oom);
277       cap.dispose();
278     }
279   }
280
281   @Override
282   protected void showLabels_actionPerformed()
283   {
284     getRotatableCanvas().showLabels(showLabels.getState());
285   }
286
287   @Override
288   protected void print_actionPerformed()
289   {
290     PCAPrinter printer = new PCAPrinter();
291     printer.start();
292   }
293
294   /**
295    * If available, shows the data which formed the inputs for the PCA as a new
296    * alignment
297    */
298   @Override
299   public void originalSeqData_actionPerformed()
300   {
301     // JAL-2647 disabled after load from project (until save to project done)
302     if (getPcaModel().getInputData() == null)
303     {
304       Console.info(
305               "Unexpected call to originalSeqData_actionPerformed - should have hidden this menu action.");
306       return;
307     }
308     // decide if av alignment is sufficiently different to original data to
309     // warrant a new window to be created
310     // create new alignment window with hidden regions (unhiding hidden regions
311     // yields unaligned seqs)
312     // or create a selection box around columns in alignment view
313     // test Alignment(SeqCigar[])
314     char gc = '-';
315     try
316     {
317       // we try to get the associated view's gap character
318       // but this may fail if the view was closed...
319       gc = av.getGapCharacter();
320     } catch (Exception ex)
321     {
322     }
323
324     Object[] alAndColsel = getPcaModel().getInputData()
325             .getAlignmentAndHiddenColumns(gc);
326
327     if (alAndColsel != null && alAndColsel[0] != null)
328     {
329       // AlignmentOrder origorder = new AlignmentOrder(alAndColsel[0]);
330
331       AlignmentI al = new Alignment((SequenceI[]) alAndColsel[0]);
332       AlignmentI dataset = (av != null && av.getAlignment() != null)
333               ? av.getAlignment().getDataset()
334               : null;
335       if (dataset != null)
336       {
337         al.setDataset(dataset);
338       }
339
340       if (true)
341       {
342         // make a new frame!
343         AlignFrame af = new AlignFrame(al, (HiddenColumns) alAndColsel[1],
344                 AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
345
346         // >>>This is a fix for the moment, until a better solution is
347         // found!!<<<
348         // af.getFeatureRenderer().transferSettings(alignFrame.getFeatureRenderer());
349
350         // af.addSortByOrderMenuItem(ServiceName + " Ordering",
351         // msaorder);
352
353         Desktop.addInternalFrame(af, MessageManager.formatMessage(
354                 "label.original_data_for_params", new String[]
355                 { this.title }), AlignFrame.DEFAULT_WIDTH,
356                 AlignFrame.DEFAULT_HEIGHT);
357       }
358     }
359     /*
360      * CutAndPasteTransfer cap = new CutAndPasteTransfer(); for (int i = 0; i <
361      * seqs.length; i++) { cap.appendText(new jalview.util.Format("%-" + 15 +
362      * "s").form( seqs[i].getName())); cap.appendText(" " + seqstrings[i] +
363      * "\n"); }
364      * 
365      * Desktop.addInternalFrame(cap, "Original Data", 400, 400);
366      */
367   }
368
369   class PCAPrinter extends Thread implements Printable
370   {
371     @Override
372     public void run()
373     {
374       PrinterJob printJob = PrinterJob.getPrinterJob();
375       PageFormat defaultPage = printJob.defaultPage();
376       PageFormat pf = printJob.pageDialog(defaultPage);
377
378       if (defaultPage == pf)
379       {
380         /*
381          * user cancelled
382          */
383         return;
384       }
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     @Override
401     public int print(Graphics pg, PageFormat pf, int pi)
402             throws PrinterException
403     {
404       pg.translate((int) pf.getImageableX(), (int) pf.getImageableY());
405
406       getRotatableCanvas().drawBackground(pg);
407       getRotatableCanvas().drawScene(pg);
408       if (getRotatableCanvas().drawAxes)
409       {
410         getRotatableCanvas().drawAxes(pg);
411       }
412
413       if (pi == 0)
414       {
415         return Printable.PAGE_EXISTS;
416       }
417       else
418       {
419         return Printable.NO_SUCH_PAGE;
420       }
421     }
422   }
423
424   @Override
425   public void makePCAImage(ImageMaker.TYPE type)
426   {
427     int width = getRotatableCanvas().getWidth();
428     int height = getRotatableCanvas().getHeight();
429     ImageWriterI writer = new ImageWriterI()
430     {
431       @Override
432       public void exportImage(Graphics g) throws Exception
433       {
434         RotatableCanvas canvas = getRotatableCanvas();
435         canvas.drawBackground(g);
436         canvas.drawScene(g);
437         if (canvas.drawAxes)
438         {
439           canvas.drawAxes(g);
440         }
441       }
442     };
443     String pca = MessageManager.getString("label.pca");
444     ImageExporter exporter = new ImageExporter(writer, null, type, pca);
445     try
446     {
447       exporter.doExport(null, this, width, height, pca);
448     } catch (ImageOutputException ioex)
449     {
450       Console.error("Unexpected error whilst writing " + type.toString(),
451               ioex);
452     }
453   }
454
455   @Override
456   protected void viewMenu_menuSelected()
457   {
458     buildAssociatedViewMenu();
459   }
460
461   /**
462    * Builds the menu showing the choice of possible views (for the associated
463    * sequence data) to which the PCA may be linked
464    */
465   void buildAssociatedViewMenu()
466   {
467     AlignmentPanel[] aps = PaintRefresher
468             .getAssociatedPanels(av.getSequenceSetId());
469     if (aps.length == 1 && getRotatableCanvas().av == aps[0].av)
470     {
471       associateViewsMenu.setVisible(false);
472       return;
473     }
474
475     associateViewsMenu.setVisible(true);
476
477     if ((viewMenu
478             .getItem(viewMenu.getItemCount() - 2) instanceof JMenuItem))
479     {
480       viewMenu.insertSeparator(viewMenu.getItemCount() - 1);
481     }
482
483     associateViewsMenu.removeAll();
484
485     JRadioButtonMenuItem item;
486     ButtonGroup buttonGroup = new ButtonGroup();
487     int iSize = aps.length;
488
489     for (int i = 0; i < iSize; i++)
490     {
491       final AlignmentPanel panel = aps[i];
492       item = new JRadioButtonMenuItem(panel.av.getViewName(),
493               panel.av == getRotatableCanvas().av);
494       buttonGroup.add(item);
495       item.addActionListener(new ActionListener()
496       {
497         @Override
498         public void actionPerformed(ActionEvent evt)
499         {
500           selectAssociatedView(panel);
501         }
502       });
503
504       associateViewsMenu.add(item);
505     }
506
507     final JRadioButtonMenuItem itemf = new JRadioButtonMenuItem(
508             "All Views");
509
510     buttonGroup.add(itemf);
511
512     itemf.setSelected(getRotatableCanvas().isApplyToAllViews());
513     itemf.addActionListener(new ActionListener()
514     {
515       @Override
516       public void actionPerformed(ActionEvent evt)
517       {
518         getRotatableCanvas().setApplyToAllViews(itemf.isSelected());
519       }
520     });
521     associateViewsMenu.add(itemf);
522
523   }
524
525   /*
526    * (non-Javadoc)
527    * 
528    * @see
529    * jalview.jbgui.GPCAPanel#outputPoints_actionPerformed(java.awt.event.ActionEvent
530    * )
531    */
532   @Override
533   protected void outputPoints_actionPerformed()
534   {
535     CutAndPasteTransfer cap = new CutAndPasteTransfer();
536     try
537     {
538       cap.setText(getPcaModel().getPointsasCsv(false,
539               xCombobox.getSelectedIndex(), yCombobox.getSelectedIndex(),
540               zCombobox.getSelectedIndex()));
541       Desktop.addInternalFrame(cap, MessageManager
542               .formatMessage("label.points_for_params", new String[]
543               { this.getTitle() }), 500, 500);
544     } catch (OutOfMemoryError oom)
545     {
546       new OOMWarning("exporting PCA points", oom);
547       cap.dispose();
548     }
549   }
550
551   /*
552    * (non-Javadoc)
553    * 
554    * @see
555    * jalview.jbgui.GPCAPanel#outputProjPoints_actionPerformed(java.awt.event
556    * .ActionEvent)
557    */
558   @Override
559   protected void outputProjPoints_actionPerformed()
560   {
561     CutAndPasteTransfer cap = new CutAndPasteTransfer();
562     try
563     {
564       cap.setText(getPcaModel().getPointsasCsv(true,
565               xCombobox.getSelectedIndex(), yCombobox.getSelectedIndex(),
566               zCombobox.getSelectedIndex()));
567       Desktop.addInternalFrame(cap, MessageManager.formatMessage(
568               "label.transformed_points_for_params", new String[]
569               { this.getTitle() }), 500, 500);
570     } catch (OutOfMemoryError oom)
571     {
572       new OOMWarning("exporting transformed PCA points", oom);
573       cap.dispose();
574     }
575   }
576
577   /*
578    * (non-Javadoc)
579    * 
580    * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
581    */
582   @Override
583   public void setProgressBar(String message, long id)
584   {
585     progressBar.setProgressBar(message, id);
586     // if (progressBars == null)
587     // {
588     // progressBars = new Hashtable();
589     // progressBarHandlers = new Hashtable();
590     // }
591     //
592     // JPanel progressPanel;
593     // Long lId = Long.valueOf(id);
594     // GridLayout layout = (GridLayout) statusPanel.getLayout();
595     // if (progressBars.get(lId) != null)
596     // {
597     // progressPanel = (JPanel) progressBars.get(Long.valueOf(id));
598     // statusPanel.remove(progressPanel);
599     // progressBars.remove(lId);
600     // progressPanel = null;
601     // if (message != null)
602     // {
603     // statusBar.setText(message);
604     // }
605     // if (progressBarHandlers.contains(lId))
606     // {
607     // progressBarHandlers.remove(lId);
608     // }
609     // layout.setRows(layout.getRows() - 1);
610     // }
611     // else
612     // {
613     // progressPanel = new JPanel(new BorderLayout(10, 5));
614     //
615     // JProgressBar progressBar = new JProgressBar();
616     // progressBar.setIndeterminate(true);
617     //
618     // progressPanel.add(new JLabel(message), BorderLayout.WEST);
619     // progressPanel.add(progressBar, BorderLayout.CENTER);
620     //
621     // layout.setRows(layout.getRows() + 1);
622     // statusPanel.add(progressPanel);
623     //
624     // progressBars.put(lId, progressPanel);
625     // }
626     // // update GUI
627     // // setMenusForViewport();
628     // validate();
629   }
630
631   @Override
632   public void registerHandler(final long id,
633           final IProgressIndicatorHandler handler)
634   {
635     progressBar.registerHandler(id, handler);
636     // if (progressBarHandlers == null ||
637     // !progressBars.contains(Long.valueOf(id)))
638     // {
639     // throw new
640     // Error(MessageManager.getString("error.call_setprogressbar_before_registering_handler"));
641     // }
642     // progressBarHandlers.put(Long.valueOf(id), handler);
643     // final JPanel progressPanel = (JPanel) progressBars.get(Long.valueOf(id));
644     // if (handler.canCancel())
645     // {
646     // JButton cancel = new JButton(
647     // MessageManager.getString("action.cancel"));
648     // final IProgressIndicator us = this;
649     // cancel.addActionListener(new ActionListener()
650     // {
651     //
652     // @Override
653     // public void actionPerformed(ActionEvent e)
654     // {
655     // handler.cancelActivity(id);
656     // us.setProgressBar(MessageManager.formatMessage("label.cancelled_params",
657     // new String[]{((JLabel) progressPanel.getComponent(0)).getText()}), id);
658     // }
659     // });
660     // progressPanel.add(cancel, BorderLayout.EAST);
661     // }
662   }
663
664   /**
665    * 
666    * @return true if any progress bars are still active
667    */
668   @Override
669   public boolean operationInProgress()
670   {
671     return progressBar.operationInProgress();
672   }
673
674   @Override
675   protected void resetButton_actionPerformed()
676   {
677     int t = getTop();
678     setTop(0); // ugly - prevents dimensionChanged events from being processed
679     xCombobox.setSelectedIndex(0);
680     yCombobox.setSelectedIndex(1);
681     setTop(t);
682     zCombobox.setSelectedIndex(2);
683   }
684
685   /**
686    * Answers true if PCA calculation is in progress, else false
687    * 
688    * @return
689    */
690   public boolean isWorking()
691   {
692     return working;
693   }
694
695   /**
696    * Answers the selected checkbox item index for PCA dimension for the X, Y or
697    * Z axis of the display
698    * 
699    * @param axis
700    * @return
701    */
702   public int getSelectedDimensionIndex(Axis axis)
703   {
704     switch (axis)
705     {
706     case X:
707       return xCombobox.getSelectedIndex();
708     case Y:
709       return yCombobox.getSelectedIndex();
710     default:
711       return zCombobox.getSelectedIndex();
712     }
713   }
714
715   public void setShowLabels(boolean show)
716   {
717     showLabels.setSelected(show);
718   }
719
720   /**
721    * Sets the input data used to calculate the PCA. This is provided for
722    * 'restore from project', which does not currently support this (AL-2647), so
723    * sets the value to null, and hides the menu option for "Input Data...". J
724    * 
725    * @param data
726    */
727   public void setInputData(AlignmentView data)
728   {
729     getPcaModel().setInputData(data);
730     originalSeqData.setVisible(data != null);
731   }
732
733   public AlignViewportI getAlignViewport()
734   {
735     return av;
736   }
737
738   public PCAModel getPcaModel()
739   {
740     return pcaModel;
741   }
742
743   public void setPcaModel(PCAModel pcaModel)
744   {
745     this.pcaModel = pcaModel;
746   }
747
748   public RotatableCanvas getRotatableCanvas()
749   {
750     return rc;
751   }
752
753   public void setRotatableCanvas(RotatableCanvas rc)
754   {
755     this.rc = rc;
756   }
757
758   public int getTop()
759   {
760     return top;
761   }
762
763   public void setTop(int top)
764   {
765     this.top = top;
766   }
767
768   /**
769    * set the associated view for this PCA.
770    * 
771    * @param panel
772    */
773   public void selectAssociatedView(AlignmentPanel panel)
774   {
775     getRotatableCanvas().setApplyToAllViews(false);
776
777     ap = panel;
778     av = panel.av;
779
780     getRotatableCanvas().av = panel.av;
781     getRotatableCanvas().ap = panel;
782     PaintRefresher.Register(PCAPanel.this, panel.av.getSequenceSetId());
783   }
784 }