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