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