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