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