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