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