b27518607159c03661be36f5cea17bbb4b8245d4
[jalview.git] / src / jalview / gui / AlignViewport.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 /*
22  * Jalview - A Sequence Alignment Editor and Viewer
23  * Copyright (C) 2007 AM Waterhouse, J Procter, G Barton, M Clamp, S Searle
24  *
25  * This program is free software; you can redistribute it and/or
26  * modify it under the terms of the GNU General Public License
27  * as published by the Free Software Foundation; either version 2
28  * of the License, or (at your option) any later version.
29  *
30  * This program is distributed in the hope that it will be useful,
31  * but WITHOUT ANY WARRANTY; without even the implied warranty of
32  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
33  * GNU General Public License for more details.
34  *
35  * You should have received a copy of the GNU General Public License
36  * along with this program; if not, write to the Free Software
37  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
38  */
39 package jalview.gui;
40
41 import jalview.analysis.AlignmentUtils;
42 import jalview.analysis.AnnotationSorter.SequenceAnnotationOrder;
43 import jalview.analysis.NJTree;
44 import jalview.api.AlignViewportI;
45 import jalview.api.ViewStyleI;
46 import jalview.bin.Cache;
47 import jalview.commands.CommandI;
48 import jalview.datamodel.AlignedCodonFrame;
49 import jalview.datamodel.Alignment;
50 import jalview.datamodel.AlignmentI;
51 import jalview.datamodel.ColumnSelection;
52 import jalview.datamodel.PDBEntry;
53 import jalview.datamodel.SearchResults;
54 import jalview.datamodel.Sequence;
55 import jalview.datamodel.SequenceGroup;
56 import jalview.datamodel.SequenceI;
57 import jalview.schemes.ColourSchemeProperty;
58 import jalview.schemes.UserColourScheme;
59 import jalview.structure.CommandListener;
60 import jalview.structure.SelectionSource;
61 import jalview.structure.StructureSelectionManager;
62 import jalview.structure.VamsasSource;
63 import jalview.util.MessageManager;
64 import jalview.viewmodel.AlignmentViewport;
65 import jalview.ws.params.AutoCalcSetting;
66
67 import java.awt.Container;
68 import java.awt.Dimension;
69 import java.awt.Font;
70 import java.awt.Rectangle;
71 import java.util.ArrayList;
72 import java.util.Hashtable;
73 import java.util.List;
74 import java.util.Set;
75 import java.util.Vector;
76
77 import javax.swing.JInternalFrame;
78 import javax.swing.JOptionPane;
79
80 /**
81  * DOCUMENT ME!
82  * 
83  * @author $author$
84  * @version $Revision: 1.141 $
85  */
86 public class AlignViewport extends AlignmentViewport implements
87         SelectionSource, CommandListener
88 {
89   Font font;
90
91   NJTree currentTree = null;
92
93   boolean cursorMode = false;
94
95   boolean antiAlias = false;
96
97   private Rectangle explodedGeometry;
98
99   String viewName;
100
101   /*
102    * Flag set true on the view that should 'gather' multiple views of the same
103    * sequence set id when a project is reloaded. Set false on all views when
104    * they are 'exploded' into separate windows. Set true on the current view
105    * when 'Gather' is performed, and also on the first tab when the first new
106    * view is created.
107    */
108   private boolean gatherViewsHere = false;
109
110   private AnnotationColumnChooser annotationColumnSelectionState;
111   /**
112    * Creates a new AlignViewport object.
113    * 
114    * @param al
115    *          alignment to view
116    */
117   public AlignViewport(AlignmentI al)
118   {
119     setAlignment(al);
120     init();
121   }
122
123   /**
124    * Create a new AlignViewport object with a specific sequence set ID
125    * 
126    * @param al
127    * @param seqsetid
128    *          (may be null - but potential for ambiguous constructor exception)
129    */
130   public AlignViewport(AlignmentI al, String seqsetid)
131   {
132     this(al, seqsetid, null);
133   }
134
135   public AlignViewport(AlignmentI al, String seqsetid, String viewid)
136   {
137     sequenceSetID = seqsetid;
138     viewId = viewid;
139     // TODO remove these once 2.4.VAMSAS release finished
140     if (Cache.log != null && Cache.log.isDebugEnabled() && seqsetid != null)
141     {
142       Cache.log.debug("Setting viewport's sequence set id : "
143               + sequenceSetID);
144     }
145     if (Cache.log != null && Cache.log.isDebugEnabled() && viewId != null)
146     {
147       Cache.log.debug("Setting viewport's view id : " + viewId);
148     }
149     setAlignment(al);
150     init();
151   }
152
153   /**
154    * Create a new AlignViewport with hidden regions
155    * 
156    * @param al
157    *          AlignmentI
158    * @param hiddenColumns
159    *          ColumnSelection
160    */
161   public AlignViewport(AlignmentI al, ColumnSelection hiddenColumns)
162   {
163     setAlignment(al);
164     if (hiddenColumns != null)
165     {
166       colSel = hiddenColumns;
167     }
168     init();
169   }
170
171   /**
172    * New viewport with hidden columns and an existing sequence set id
173    * 
174    * @param al
175    * @param hiddenColumns
176    * @param seqsetid
177    *          (may be null)
178    */
179   public AlignViewport(AlignmentI al, ColumnSelection hiddenColumns,
180           String seqsetid)
181   {
182     this(al, hiddenColumns, seqsetid, null);
183   }
184
185   /**
186    * New viewport with hidden columns and an existing sequence set id and viewid
187    * 
188    * @param al
189    * @param hiddenColumns
190    * @param seqsetid
191    *          (may be null)
192    * @param viewid
193    *          (may be null)
194    */
195   public AlignViewport(AlignmentI al, ColumnSelection hiddenColumns,
196           String seqsetid, String viewid)
197   {
198     sequenceSetID = seqsetid;
199     viewId = viewid;
200     // TODO remove these once 2.4.VAMSAS release finished
201     if (Cache.log != null && Cache.log.isDebugEnabled() && seqsetid != null)
202     {
203       Cache.log.debug("Setting viewport's sequence set id : "
204               + sequenceSetID);
205     }
206     if (Cache.log != null && Cache.log.isDebugEnabled() && viewId != null)
207     {
208       Cache.log.debug("Setting viewport's view id : " + viewId);
209     }
210     setAlignment(al);
211     if (hiddenColumns != null)
212     {
213       colSel = hiddenColumns;
214     }
215     init();
216   }
217
218   /**
219    * Apply any settings saved in user preferences
220    */
221   private void applyViewProperties()
222   {
223     antiAlias = Cache.getDefault("ANTI_ALIAS", false);
224
225     viewStyle.setShowJVSuffix(Cache.getDefault("SHOW_JVSUFFIX", true));
226     setShowAnnotation(Cache.getDefault("SHOW_ANNOTATIONS", true));
227
228     setRightAlignIds(Cache.getDefault("RIGHT_ALIGN_IDS", false));
229     setCentreColumnLabels(Cache.getDefault("CENTRE_COLUMN_LABELS", false));
230     autoCalculateConsensus = Cache.getDefault("AUTO_CALC_CONSENSUS", true);
231
232     setPadGaps(Cache.getDefault("PAD_GAPS", true));
233     setShowNPFeats(Cache.getDefault("SHOW_NPFEATS_TOOLTIP", true));
234     setShowDBRefs(Cache.getDefault("SHOW_DBREFS_TOOLTIP", true));
235     viewStyle.setSeqNameItalics(Cache.getDefault("ID_ITALICS", true));
236     viewStyle.setWrapAlignment(Cache.getDefault("WRAP_ALIGNMENT", false));
237     viewStyle.setShowUnconserved(Cache
238             .getDefault("SHOW_UNCONSERVED", false));
239     sortByTree = Cache.getDefault("SORT_BY_TREE", false);
240     followSelection = Cache.getDefault("FOLLOW_SELECTIONS", true);
241     sortAnnotationsBy = SequenceAnnotationOrder.valueOf(Cache.getDefault(
242             Preferences.SORT_ANNOTATIONS,
243             SequenceAnnotationOrder.NONE.name()));
244     showAutocalculatedAbove = Cache.getDefault(
245             Preferences.SHOW_AUTOCALC_ABOVE, false);
246     viewStyle.setScaleProteinAsCdna(Cache.getDefault(
247             Preferences.SCALE_PROTEIN_TO_CDNA, true));
248   }
249
250   void init()
251   {
252     this.startRes = 0;
253     this.endRes = alignment.getWidth() - 1;
254     this.startSeq = 0;
255     this.endSeq = alignment.getHeight() - 1;
256     applyViewProperties();
257
258     String fontName = Cache.getDefault("FONT_NAME", "SansSerif");
259     String fontStyle = Cache.getDefault("FONT_STYLE", Font.PLAIN + "");
260     String fontSize = Cache.getDefault("FONT_SIZE", "10");
261
262     int style = 0;
263
264     if (fontStyle.equals("bold"))
265     {
266       style = 1;
267     }
268     else if (fontStyle.equals("italic"))
269     {
270       style = 2;
271     }
272
273     setFont(new Font(fontName, style, Integer.parseInt(fontSize)), true);
274
275     alignment
276             .setGapCharacter(Cache.getDefault("GAP_SYMBOL", "-").charAt(0));
277
278     // We must set conservation and consensus before setting colour,
279     // as Blosum and Clustal require this to be done
280     if (hconsensus == null && !isDataset)
281     {
282       if (!alignment.isNucleotide())
283       {
284         showConservation = Cache.getDefault("SHOW_CONSERVATION", true);
285         showQuality = Cache.getDefault("SHOW_QUALITY", true);
286         showGroupConservation = Cache.getDefault("SHOW_GROUP_CONSERVATION",
287                 false);
288       }
289       showConsensusHistogram = Cache.getDefault("SHOW_CONSENSUS_HISTOGRAM",
290               true);
291       showSequenceLogo = Cache.getDefault("SHOW_CONSENSUS_LOGO", false);
292       normaliseSequenceLogo = Cache.getDefault("NORMALISE_CONSENSUS_LOGO",
293               false);
294       showGroupConsensus = Cache.getDefault("SHOW_GROUP_CONSENSUS", false);
295       showConsensus = Cache.getDefault("SHOW_IDENTITY", true);
296     }
297     initAutoAnnotation();
298     String colourProperty = alignment.isNucleotide() ? Preferences.DEFAULT_COLOUR_NUC
299             : Preferences.DEFAULT_COLOUR_PROT;
300     String propertyValue = Cache.getProperty(colourProperty);
301     if (propertyValue == null)
302     {
303       // fall back on this property for backwards compatibility
304       propertyValue = Cache.getProperty(Preferences.DEFAULT_COLOUR);
305     }
306     if (propertyValue != null)
307     {
308       globalColourScheme = ColourSchemeProperty.getColour(alignment,
309               propertyValue);
310
311       if (globalColourScheme instanceof UserColourScheme)
312       {
313         globalColourScheme = UserDefinedColours.loadDefaultColours();
314         ((UserColourScheme) globalColourScheme).setThreshold(0,
315                 isIgnoreGapsConsensus());
316       }
317
318       if (globalColourScheme != null)
319       {
320         globalColourScheme.setConsensus(hconsensus);
321       }
322     }
323   }
324
325   /**
326    * get the consensus sequence as displayed under the PID consensus annotation
327    * row.
328    * 
329    * @return consensus sequence as a new sequence object
330    */
331   public SequenceI getConsensusSeq()
332   {
333     if (consensus == null)
334     {
335       updateConsensus(null);
336     }
337     if (consensus == null)
338     {
339       return null;
340     }
341     StringBuffer seqs = new StringBuffer();
342     for (int i = 0; i < consensus.annotations.length; i++)
343     {
344       if (consensus.annotations[i] != null)
345       {
346         if (consensus.annotations[i].description.charAt(0) == '[')
347         {
348           seqs.append(consensus.annotations[i].description.charAt(1));
349         }
350         else
351         {
352           seqs.append(consensus.annotations[i].displayCharacter);
353         }
354       }
355     }
356
357     SequenceI sq = new Sequence("Consensus", seqs.toString());
358     sq.setDescription("Percentage Identity Consensus "
359             + ((ignoreGapsInConsensusCalculation) ? " without gaps" : ""));
360     return sq;
361   }
362
363   boolean validCharWidth;
364
365   /**
366    * update view settings with the given font. You may need to call
367    * alignPanel.fontChanged to update the layout geometry
368    * 
369    * @param setGrid
370    *          when true, charWidth/height is set according to font mentrics
371    */
372   public void setFont(Font f, boolean setGrid)
373   {
374     font = f;
375
376     Container c = new Container();
377
378     java.awt.FontMetrics fm = c.getFontMetrics(font);
379     int w = viewStyle.getCharWidth(), ww = fm.charWidth('M'), h = viewStyle
380             .getCharHeight();
381     if (setGrid)
382     {
383       setCharHeight(fm.getHeight());
384       setCharWidth(ww);
385     }
386     viewStyle.setFontName(font.getName());
387     viewStyle.setFontStyle(font.getStyle());
388     viewStyle.setFontSize(font.getSize());
389
390     validCharWidth = true;
391   }
392
393   @Override
394   public void setViewStyle(ViewStyleI settingsForView)
395   {
396     super.setViewStyle(settingsForView);
397     setFont(new Font(viewStyle.getFontName(), viewStyle.getFontStyle(),
398             viewStyle.getFontSize()), false);
399
400   }
401   /**
402    * DOCUMENT ME!
403    * 
404    * @return DOCUMENT ME!
405    */
406   public Font getFont()
407   {
408     return font;
409   }
410
411   /**
412    * DOCUMENT ME!
413    * 
414    * @param align
415    *          DOCUMENT ME!
416    */
417   public void setAlignment(AlignmentI align)
418   {
419     if (alignment != null && alignment.getCodonFrames() != null)
420     {
421       StructureSelectionManager.getStructureSelectionManager(
422               Desktop.instance).removeMappings(alignment.getCodonFrames());
423     }
424     this.alignment = align;
425     if (alignment != null && alignment.getCodonFrames() != null)
426     {
427       StructureSelectionManager.getStructureSelectionManager(
428               Desktop.instance).addMappings(alignment.getCodonFrames());
429     }
430   }
431
432   /**
433    * DOCUMENT ME!
434    * 
435    * @return DOCUMENT ME!
436    */
437   public char getGapCharacter()
438   {
439     return getAlignment().getGapCharacter();
440   }
441
442   /**
443    * DOCUMENT ME!
444    * 
445    * @param gap
446    *          DOCUMENT ME!
447    */
448   public void setGapCharacter(char gap)
449   {
450     if (getAlignment() != null)
451     {
452       getAlignment().setGapCharacter(gap);
453     }
454   }
455
456   /**
457    * DOCUMENT ME!
458    * 
459    * @return DOCUMENT ME!
460    */
461   public ColumnSelection getColumnSelection()
462   {
463     return colSel;
464   }
465
466   /**
467    * DOCUMENT ME!
468    * 
469    * @param tree
470    *          DOCUMENT ME!
471    */
472   public void setCurrentTree(NJTree tree)
473   {
474     currentTree = tree;
475   }
476
477   /**
478    * DOCUMENT ME!
479    * 
480    * @return DOCUMENT ME!
481    */
482   public NJTree getCurrentTree()
483   {
484     return currentTree;
485   }
486
487   /**
488    * returns the visible column regions of the alignment
489    * 
490    * @param selectedRegionOnly
491    *          true to just return the contigs intersecting with the selected
492    *          area
493    * @return
494    */
495   public int[] getViewAsVisibleContigs(boolean selectedRegionOnly)
496   {
497     int[] viscontigs = null;
498     int start = 0, end = 0;
499     if (selectedRegionOnly && selectionGroup != null)
500     {
501       start = selectionGroup.getStartRes();
502       end = selectionGroup.getEndRes() + 1;
503     }
504     else
505     {
506       end = alignment.getWidth();
507     }
508     viscontigs = colSel.getVisibleContigs(start, end);
509     return viscontigs;
510   }
511
512   /**
513    * get hash of undo and redo list for the alignment
514    * 
515    * @return long[] { historyList.hashCode, redoList.hashCode };
516    */
517   public long[] getUndoRedoHash()
518   {
519     // TODO: JAL-1126
520     if (historyList == null || redoList == null)
521     {
522       return new long[]
523       { -1, -1 };
524     }
525     return new long[]
526     { historyList.hashCode(), this.redoList.hashCode() };
527   }
528
529   /**
530    * test if a particular set of hashcodes are different to the hashcodes for
531    * the undo and redo list.
532    * 
533    * @param undoredo
534    *          the stored set of hashcodes as returned by getUndoRedoHash
535    * @return true if the hashcodes differ (ie the alignment has been edited) or
536    *         the stored hashcode array differs in size
537    */
538   public boolean isUndoRedoHashModified(long[] undoredo)
539   {
540     if (undoredo == null)
541     {
542       return true;
543     }
544     long[] cstate = getUndoRedoHash();
545     if (cstate.length != undoredo.length)
546     {
547       return true;
548     }
549
550     for (int i = 0; i < cstate.length; i++)
551     {
552       if (cstate[i] != undoredo[i])
553       {
554         return true;
555       }
556     }
557     return false;
558   }
559
560   public boolean followSelection = true;
561
562   /**
563    * @return true if view selection should always follow the selections
564    *         broadcast by other selection sources
565    */
566   public boolean getFollowSelection()
567   {
568     return followSelection;
569   }
570
571   /**
572    * Send the current selection to be broadcast to any selection listeners.
573    */
574   public void sendSelection()
575   {
576     jalview.structure.StructureSelectionManager
577             .getStructureSelectionManager(Desktop.instance).sendSelection(
578                     new SequenceGroup(getSelectionGroup()),
579                     new ColumnSelection(getColumnSelection()), this);
580   }
581
582   /**
583    * return the alignPanel containing the given viewport. Use this to get the
584    * components currently handling the given viewport.
585    * 
586    * @param av
587    * @return null or an alignPanel guaranteed to have non-null alignFrame
588    *         reference
589    */
590   public AlignmentPanel getAlignPanel()
591   {
592     AlignmentPanel[] aps = PaintRefresher.getAssociatedPanels(this
593             .getSequenceSetId());
594     for (int p = 0; aps != null && p < aps.length; p++)
595     {
596       if (aps[p].av == this)
597       {
598         return aps[p];
599       }
600     }
601     return null;
602   }
603
604   public boolean getSortByTree()
605   {
606     return sortByTree;
607   }
608
609   public void setSortByTree(boolean sort)
610   {
611     sortByTree = sort;
612   }
613
614   /**
615    * synthesize a column selection if none exists so it covers the given
616    * selection group. if wholewidth is false, no column selection is made if the
617    * selection group covers the whole alignment width.
618    * 
619    * @param sg
620    * @param wholewidth
621    */
622   public void expandColSelection(SequenceGroup sg, boolean wholewidth)
623   {
624     int sgs, sge;
625     if (sg != null
626             && (sgs = sg.getStartRes()) >= 0
627             && sg.getStartRes() <= (sge = sg.getEndRes())
628             && (colSel == null || colSel.getSelected() == null || colSel
629                     .getSelected().size() == 0))
630     {
631       if (!wholewidth && alignment.getWidth() == (1 + sge - sgs))
632       {
633         // do nothing
634         return;
635       }
636       if (colSel == null)
637       {
638         colSel = new ColumnSelection();
639       }
640       for (int cspos = sg.getStartRes(); cspos <= sg.getEndRes(); cspos++)
641       {
642         colSel.addElement(cspos);
643       }
644     }
645   }
646
647   /**
648    * Returns the (Desktop) instance of the StructureSelectionManager
649    */
650   @Override
651   public StructureSelectionManager getStructureSelectionManager()
652   {
653     return StructureSelectionManager
654             .getStructureSelectionManager(Desktop.instance);
655   }
656
657   /**
658    * 
659    * @param pdbEntries
660    * @return an array of SequenceI arrays, one for each PDBEntry, listing which
661    *         sequences in the alignment hold a reference to it
662    */
663   public SequenceI[][] collateForPDB(PDBEntry[] pdbEntries)
664   {
665     List<SequenceI[]> seqvectors = new ArrayList<SequenceI[]>();
666     for (PDBEntry pdb : pdbEntries)
667     {
668       List<SequenceI> seqs = new ArrayList<SequenceI>();
669       for (SequenceI sq : alignment.getSequences())
670       {
671         Vector<PDBEntry> pdbs = sq
672                 .getDatasetSequence().getPDBId();
673         if (pdbs == null)
674         {
675           continue;
676         }
677         for (PDBEntry p1 : pdbs)
678         {
679           if (p1.getId().equals(pdb.getId()))
680           {
681             if (!seqs.contains(sq))
682             {
683               seqs.add(sq);
684               continue;
685             }
686           }
687         }
688       }
689       seqvectors.add(seqs.toArray(new SequenceI[seqs.size()]));
690     }
691     return seqvectors.toArray(new SequenceI[seqvectors.size()][]);
692   }
693
694   public boolean isNormaliseSequenceLogo()
695   {
696     return normaliseSequenceLogo;
697   }
698
699   public void setNormaliseSequenceLogo(boolean state)
700   {
701     normaliseSequenceLogo = state;
702   }
703
704   /**
705    * 
706    * @return true if alignment characters should be displayed
707    */
708   public boolean isValidCharWidth()
709   {
710     return validCharWidth;
711   }
712
713   private Hashtable<String, AutoCalcSetting> calcIdParams = new Hashtable<String, AutoCalcSetting>();
714
715   public AutoCalcSetting getCalcIdSettingsFor(String calcId)
716   {
717     return calcIdParams.get(calcId);
718   }
719
720   public void setCalcIdSettingsFor(String calcId, AutoCalcSetting settings,
721           boolean needsUpdate)
722   {
723     calcIdParams.put(calcId, settings);
724     // TODO: create a restart list to trigger any calculations that need to be
725     // restarted after load
726     // calculator.getRegisteredWorkersOfClass(settings.getWorkerClass())
727     if (needsUpdate)
728     {
729       Cache.log.debug("trigger update for " + calcId);
730     }
731   }
732
733   /**
734    * Method called when another alignment's edit (or possibly other) command is
735    * broadcast to here.
736    *
737    * To allow for sequence mappings (e.g. protein to cDNA), we have to first
738    * 'unwind' the command on the source sequences (in simulation, not in fact),
739    * and then for each edit in turn:
740    * <ul>
741    * <li>compute the equivalent edit on the mapped sequences</li>
742    * <li>apply the mapped edit</li>
743    * <li>'apply' the source edit to the working copy of the source sequences</li>
744    * </ul>
745    * 
746    * @param command
747    * @param undo
748    * @param ssm
749    */
750   @Override
751   public void mirrorCommand(CommandI command, boolean undo,
752           StructureSelectionManager ssm, VamsasSource source)
753   {
754     /*
755      * Do nothing unless we are a 'complement' of the source. May replace this
756      * with direct calls not via SSM.
757      */
758     if (source instanceof AlignViewportI
759             && ((AlignViewportI) source).getCodingComplement() == this)
760     {
761       // ok to continue;
762     }
763     else
764     {
765       return;
766     }
767
768     CommandI mappedCommand = ssm.mapCommand(command, undo, getAlignment(),
769             getGapCharacter());
770     if (mappedCommand != null)
771     {
772       AlignmentI[] views = getAlignPanel().alignFrame.getViewAlignments();
773       mappedCommand.doCommand(views);
774       getAlignPanel().alignmentChanged();
775     }
776   }
777
778   /**
779    * Add the sequences from the given alignment to this viewport. Optionally,
780    * may give the user the option to open a new frame, or split panel, with cDNA
781    * and protein linked.
782    * 
783    * @param al
784    * @param title
785    */
786   public void addAlignment(AlignmentI al, String title)
787   {
788     // TODO: promote to AlignViewportI? applet CutAndPasteTransfer is different
789
790     // JBPComment: title is a largely redundant parameter at the moment
791     // JBPComment: this really should be an 'insert/pre/append' controller
792     // JBPComment: but the DNA/Protein check makes it a bit more complex
793
794     // refactored from FileLoader / CutAndPasteTransfer / SequenceFetcher with
795     // this comment:
796     // TODO: create undo object for this JAL-1101
797
798     /*
799      * If any cDNA/protein mappings can be made between the alignments, offer to
800      * open a linked alignment with split frame option.
801      */
802     if (Cache.getDefault(Preferences.ENABLE_SPLIT_FRAME, true))
803     {
804       if (AlignmentUtils.isMappable(al, getAlignment()))
805       {
806         if (openLinkedAlignment(al, title))
807         {
808           return;
809         }
810       }
811     }
812
813     /*
814      * No mappings, or offer declined - add sequences to this alignment
815      */
816     // TODO: JAL-407 regardless of above - identical sequences (based on ID and
817     // provenance) should share the same dataset sequence
818
819     for (int i = 0; i < al.getHeight(); i++)
820     {
821       getAlignment().addSequence(al.getSequenceAt(i));
822     }
823
824     setEndSeq(getAlignment().getHeight());
825     firePropertyChange("alignment", null, getAlignment().getSequences());
826   }
827
828   /**
829    * Show a dialog with the option to open and link (cDNA <-> protein) as a new
830    * alignment, either as a standalone alignment or in a split frame. Returns
831    * true if the new alignment was opened, false if not, because the user
832    * declined the offer.
833    * 
834    * @param al
835    * @param title
836    */
837   protected boolean openLinkedAlignment(AlignmentI al, String title)
838   {
839     String[] options = new String[]
840     { MessageManager.getString("action.no"),
841         MessageManager.getString("label.split_window"),
842         MessageManager.getString("label.new_window"), };
843     final String question = JvSwingUtils.wrapTooltip(true,
844             MessageManager.getString("label.open_split_window?"));
845     int response = JOptionPane.showOptionDialog(Desktop.desktop, question,
846             MessageManager.getString("label.open_split_window"),
847             JOptionPane.DEFAULT_OPTION, JOptionPane.PLAIN_MESSAGE, null,
848             options, options[0]);
849
850     if (response != 1 && response != 2)
851     {
852       return false;
853     }
854     final boolean openSplitPane = (response == 1);
855     final boolean openInNewWindow = (response == 2);
856
857     /*
858      * Identify protein and dna alignments. Make a copy of this one if opening
859      * in a new split pane.
860      */
861     AlignmentI thisAlignment = openSplitPane ? new Alignment(getAlignment())
862             : getAlignment();
863     AlignmentI protein = al.isNucleotide() ? thisAlignment : al;
864     final AlignmentI cdna = al.isNucleotide() ? al : thisAlignment;
865
866     /*
867      * Map sequences. At least one should get mapped as we have already passed
868      * the test for 'mappability'. Any mappings made will be added to the
869      * protein alignment. Note creating dataset sequences on the new alignment
870      * is a pre-requisite for building mappings.
871      */
872     al.setDataset(null);
873     AlignmentUtils.mapProteinToCdna(protein, cdna);
874
875     /*
876      * Create the AlignFrame for the added alignment. Note this will include the
877      * cDNA consensus annotation if it is protein (because the alignment holds
878      * mappings to nucleotide)
879      */
880     AlignFrame newAlignFrame = new AlignFrame(al, AlignFrame.DEFAULT_WIDTH,
881             AlignFrame.DEFAULT_HEIGHT);
882     newAlignFrame.setTitle(title);
883     newAlignFrame.statusBar.setText(MessageManager.formatMessage(
884             "label.successfully_loaded_file", new Object[]
885             { title }));
886
887     // TODO if we want this (e.g. to enable reload of the alignment from file),
888     // we will need to add parameters to the stack.
889     // if (!protocol.equals(AppletFormatAdapter.PASTE))
890     // {
891     // alignFrame.setFileName(file, format);
892     // }
893
894     if (openInNewWindow)
895     {
896       Desktop.addInternalFrame(newAlignFrame, title,
897               AlignFrame.DEFAULT_WIDTH,
898               AlignFrame.DEFAULT_HEIGHT);
899     }
900
901     try
902     {
903       newAlignFrame.setMaximum(jalview.bin.Cache.getDefault(
904               "SHOW_FULLSCREEN",
905               false));
906     } catch (java.beans.PropertyVetoException ex)
907     {
908     }
909
910     if (openSplitPane)
911     {
912       protein = openSplitFrame(newAlignFrame, thisAlignment,
913               protein.getCodonFrames());
914     }
915
916     /*
917      * Register the mappings (held on the protein alignment) with the
918      * StructureSelectionManager (for mouseover linking).
919      */
920     final StructureSelectionManager ssm = StructureSelectionManager
921             .getStructureSelectionManager(Desktop.instance);
922     ssm.addMappings(protein.getCodonFrames());
923
924     return true;
925   }
926
927   /**
928    * Helper method to open a new SplitFrame holding linked dna and protein
929    * alignments.
930    * 
931    * @param newAlignFrame
932    *          containing a new alignment to be shown
933    * @param complement
934    *          cdna/protein complement alignment to show in the other split half
935    * @param mappings
936    * @return the protein alignment in the split frame
937    */
938   protected AlignmentI openSplitFrame(AlignFrame newAlignFrame,
939           AlignmentI complement, Set<AlignedCodonFrame> mappings)
940   {
941     /*
942      * Make a new frame with a copy of the alignment we are adding to. If this
943      * is protein, the new frame will have a cDNA consensus annotation row
944      * added.
945      */
946     AlignFrame copyMe = new AlignFrame(complement,
947             AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
948     copyMe.setTitle(getAlignPanel().alignFrame.getTitle());
949
950     AlignmentI al = newAlignFrame.viewport.getAlignment();
951     final AlignFrame proteinFrame = al.isNucleotide() ? copyMe
952             : newAlignFrame;
953     final AlignFrame cdnaFrame = al.isNucleotide() ? newAlignFrame
954             : copyMe;
955     AlignmentI protein = proteinFrame.viewport.getAlignment();
956     protein.setCodonFrames(mappings);
957
958     cdnaFrame.setVisible(true);
959     proteinFrame.setVisible(true);
960     String linkedTitle = MessageManager
961             .getString("label.linked_view_title");
962
963     /*
964      * Open in split pane. DNA sequence above, protein below.
965      */
966     JInternalFrame splitFrame = new SplitFrame(cdnaFrame, proteinFrame);
967     Desktop.addInternalFrame(splitFrame, linkedTitle, -1, -1);
968
969     return protein;
970   }
971
972   public AnnotationColumnChooser getAnnotationColumnSelectionState()
973   {
974     return annotationColumnSelectionState;
975   }
976
977   public void setAnnotationColumnSelectionState(
978           AnnotationColumnChooser currentAnnotationColumnSelectionState)
979   {
980     this.annotationColumnSelectionState = currentAnnotationColumnSelectionState;
981   }
982
983   @Override
984   public void setIdWidth(int i)
985   {
986     super.setIdWidth(i);
987     AlignmentPanel ap = getAlignPanel();
988     if (ap != null)
989     {
990       // modify GUI elements to reflect geometry change
991       Dimension idw = getAlignPanel().getIdPanel().getIdCanvas()
992               .getPreferredSize();
993       idw.width = i;
994       getAlignPanel().getIdPanel().getIdCanvas().setPreferredSize(idw);
995     }
996   }
997
998   public Rectangle getExplodedGeometry()
999   {
1000     return explodedGeometry;
1001   }
1002
1003   public void setExplodedGeometry(Rectangle explodedPosition)
1004   {
1005     this.explodedGeometry = explodedPosition;
1006   }
1007
1008   public boolean isGatherViewsHere()
1009   {
1010     return gatherViewsHere;
1011   }
1012
1013   public void setGatherViewsHere(boolean gatherViewsHere)
1014   {
1015     this.gatherViewsHere = gatherViewsHere;
1016   }
1017
1018   /**
1019    * If this viewport has a (Protein/cDNA) complement, then scroll the
1020    * complementary alignment to match this one.
1021    */
1022   public void scrollComplementaryAlignment()
1023   {
1024     /*
1025      * Populate a SearchResults object with the mapped location to scroll to. If
1026      * there is no complement, or it is not following highlights, or no mapping
1027      * is found, the result will be empty.
1028      */
1029     SearchResults sr = new SearchResults();
1030     int seqOffset = findComplementScrollTarget(sr);
1031     if (!sr.isEmpty())
1032     {
1033       // TODO would like next line without cast but needs more refactoring...
1034       final AlignmentPanel complementPanel = ((AlignViewport) getCodingComplement()).getAlignPanel();
1035       complementPanel.setFollowingComplementScroll(true);
1036       complementPanel.scrollToCentre(sr, seqOffset);
1037     }
1038   }
1039
1040 }