Adjustments to recent changes in LibrAlign.
[taxeditor.git] / eu.etaxonomy.taxeditor.editor / src / main / java / eu / etaxonomy / taxeditor / editor / molecular / AlignmentEditor.java
index 3d823745613c20b36ea4465a3caf12f7245809fb..057d71e18a17d17b12f19e05b94b7b284efb4b64 100644 (file)
 package eu.etaxonomy.taxeditor.editor.molecular;
 
 
-import info.bioinfweb.commons.bio.biojava3.alignment.SimpleAlignment;
-import info.bioinfweb.commons.bio.biojava3.alignment.template.Alignment;
-import info.bioinfweb.commons.bio.biojava3.core.sequence.compound.AlignmentAmbiguityNucleotideCompoundSet;
-import info.bioinfweb.libralign.AlignmentArea;
+import info.bioinfweb.jphyloio.events.TokenSetType;
+import info.bioinfweb.libralign.alignmentarea.AlignmentArea;
+import info.bioinfweb.libralign.alignmentarea.selection.SelectionModel;
 import info.bioinfweb.libralign.dataarea.implementations.ConsensusSequenceArea;
 import info.bioinfweb.libralign.dataarea.implementations.SequenceIndexArea;
-import info.bioinfweb.libralign.sequenceprovider.implementations.BioJavaSequenceDataProvider;
-import info.bioinfweb.libralign.sequenceprovider.tokenset.BioJavaTokenSet;
+import info.bioinfweb.libralign.dataarea.implementations.pherogram.PherogramAlignmentModel;
+import info.bioinfweb.libralign.dataarea.implementations.pherogram.PherogramArea;
+import info.bioinfweb.libralign.dataarea.implementations.pherogram.ShiftChange;
+import info.bioinfweb.libralign.editsettings.EditSettingsChangeEvent;
+import info.bioinfweb.libralign.editsettings.EditSettingsListener;
+import info.bioinfweb.libralign.model.AlignmentModel;
+import info.bioinfweb.libralign.model.AlignmentModelChangeListener;
+import info.bioinfweb.libralign.model.SequenceUtils;
+import info.bioinfweb.libralign.model.adapters.StringAdapter;
+import info.bioinfweb.libralign.model.events.SequenceChangeEvent;
+import info.bioinfweb.libralign.model.events.SequenceRenamedEvent;
+import info.bioinfweb.libralign.model.events.TokenChangeEvent;
+import info.bioinfweb.libralign.model.implementations.PackedAlignmentModel;
+import info.bioinfweb.libralign.model.tokenset.BioJavaTokenSet;
+import info.bioinfweb.libralign.model.tokenset.TokenSet;
+import info.bioinfweb.libralign.multiplealignments.AlignmentAreaList;
+import info.bioinfweb.libralign.multiplealignments.MultipleAlignmentsContainer;
+import info.bioinfweb.libralign.pherogram.model.BioJavaPherogramModel;
+import info.bioinfweb.libralign.pherogram.model.PherogramModel;
+import info.bioinfweb.libralign.pherogram.model.ReverseComplementPherogramModel;
 
-import org.biojava3.core.sequence.DNASequence;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.biojava.bio.chromatogram.ChromatogramFactory;
+import org.biojava.bio.chromatogram.UnsupportedChromatogramFormatException;
+import org.biojava3.core.sequence.compound.DNACompoundSet;
 import org.biojava3.core.sequence.compound.NucleotideCompound;
 import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.widgets.Composite;
+import org.eclipse.ui.IActionBars;
 import org.eclipse.ui.IEditorInput;
+import org.eclipse.ui.IEditorPart;
 import org.eclipse.ui.IEditorSite;
 import org.eclipse.ui.PartInitException;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.commands.ICommandService;
 import org.eclipse.ui.part.EditorPart;
 
+import eu.etaxonomy.cdm.api.conversation.ConversationHolder;
+import eu.etaxonomy.cdm.api.service.molecular.ISequenceService;
+import eu.etaxonomy.cdm.model.media.MediaUtils;
+import eu.etaxonomy.cdm.model.molecular.Sequence;
+import eu.etaxonomy.cdm.model.molecular.SequenceString;
+import eu.etaxonomy.cdm.model.molecular.SingleRead;
+import eu.etaxonomy.cdm.model.molecular.SingleReadAlignment;
+import eu.etaxonomy.taxeditor.editor.handler.alignmenteditor.ToggleInsertOverwriteHandler;
+import eu.etaxonomy.taxeditor.editor.handler.alignmenteditor.ToggleLeftRightInsertionHandler;
+import eu.etaxonomy.taxeditor.store.CdmStore;
+
 
 
 /**
@@ -36,70 +81,321 @@ import org.eclipse.ui.part.EditorPart;
  * a consensus sequence.
  * <p>
  * The contained GUI components used to edit the alignment come from <a href="http://bioinfweb.info/LibrAlign/">LibrAlign</a>.
- * 
+ *
+ * @author Ben Stöver
  * @author pplitzner
- * @author Ben Stöver
  * @date 04.08.2014
  */
 public class AlignmentEditor extends EditorPart {
     public static final String ID = "eu.etaxonomy.taxeditor.editor.molecular.AlignmentEditor";
-    
-    private AlignmentArea alignmentArea;
-
-
-       private AlignmentArea createAlignmentArea() {
-               Alignment<DNASequence, NucleotideCompound> alignment = 
-                               new SimpleAlignment<DNASequence, NucleotideCompound>();
-               alignment.add("Sequence 1", new DNASequence("ATCGTAGATCGTAGATCGTAGATCGTAGATCGTAGATCGTAGATCGTAG"));
-               alignment.add("Sequence 2", new DNASequence("AT-GTTG"));
-               alignment.add("Sequence 3", new DNASequence("AT-GTAG"));
-               
-               BioJavaSequenceDataProvider<DNASequence, NucleotideCompound> sequenceProvider = 
-                               new BioJavaSequenceDataProvider<DNASequence, NucleotideCompound>(
-                                               new BioJavaTokenSet<NucleotideCompound>(
-                                                               AlignmentAmbiguityNucleotideCompoundSet.getAlignmentAmbiguityNucleotideCompoundSet()),
-                                               alignment);
-               
-               AlignmentArea result = new AlignmentArea();
-               result.setSequenceProvider(sequenceProvider, false);
-               SequenceIndexArea sequenceIndexArea = new SequenceIndexArea(result);
-               //sequenceIndexArea.setFirstIndex(5);
-               //sequenceIndexArea.setHeight(25);
-               result.getDataAreas().getTopAreas().add(sequenceIndexArea);
-               result.getDataAreas().getBottomAreas().add(new ConsensusSequenceArea(result));
+
+       public static final int READS_AREA_INDEX = 1;
+       public static final int CONSENSUS_AREA_INDEX = READS_AREA_INDEX + 1;
+       public static final int PHEROGRAM_AREA_INDEX = 0;
+       public static final String DEFAULT_READ_NAME_PREFIX = "Read ";
+       public static final String CONSENSUS_NAME = "Consensus";
+
+
+    private final ConversationHolder conversationHolder;
+
+       private final AlignmentModelChangeListener DIRTY_LISTENER = new AlignmentModelChangeListener() {
+                               @Override
+                               public <T> void afterTokenChange(TokenChangeEvent<T> e) {
+                                       setDirty();
+                               }
+
+                               @Override
+                               public <T> void afterSequenceRenamed(SequenceRenamedEvent<T> e) {
+                                       setDirty();
+                               }
+
+                               @Override
+                               public <T> void afterSequenceChange(SequenceChangeEvent<T> e) {
+                                       setDirty();
+                               }
+
+                               @Override
+                               public <T, U> void afterProviderChanged(AlignmentModel<T> oldProvider,
+                                               AlignmentModel<U> newProvider) {  // Not expected.
+
+                                       setDirty();
+                               }
+                       };
+
+    private MultipleAlignmentsContainer alignmentsContainer = null;
+    private final Map<Integer, SingleReadAlignment> cdmMap = new TreeMap<Integer, SingleReadAlignment>();  //TODO Move this to ContigSequenceDataProvider
+    private boolean dirty = false;
+
+
+    public AlignmentEditor() {
+       super();
+       //conversationHolder = CdmStore.createConversation();
+       conversationHolder = null;
+    }
+
+
+    private void refreshToolbarElement(String id) {
+               ICommandService commandService =
+                               (ICommandService)PlatformUI.getWorkbench().getActiveWorkbenchWindow().getService(ICommandService.class);
+               if (commandService != null) {
+                       commandService.refreshElements(id, Collections.EMPTY_MAP);
+               }
+    }
+
+
+    private void registerEditSettingListener(MultipleAlignmentsContainer container) {
+       container.getEditSettings().addListener(new EditSettingsListener() {
+                                       @Override
+                                       public void workingModeChanged(EditSettingsChangeEvent e) {}  // Currently nothing to do
+
+                                       @Override
+                                       public void insertLeftInDataAreaChanged(EditSettingsChangeEvent e) {
+                                               updateStatusBar();
+                                       refreshToolbarElement(ToggleLeftRightInsertionHandler.COMMAND_ID);
+                                       }
+
+                                       @Override
+                                       public void insertChanged(EditSettingsChangeEvent e) {
+                                               updateStatusBar();
+                                       refreshToolbarElement(ToggleInsertOverwriteHandler.COMMAND_ID);
+                                       }
+                               });
+    }
+
+
+    private AlignmentArea createIndexArea(MultipleAlignmentsContainer container) {
+               AlignmentArea result = new AlignmentArea(container);
+               result.setAllowVerticalScrolling(false);
+               result.getDataAreas().getTopAreas().add(new SequenceIndexArea(result.getContentArea()));
+               return result;
+    }
+
+
+    private AlignmentArea createEditableAlignmentArea(MultipleAlignmentsContainer container, boolean allowVerticalScrolling) {
+               AlignmentArea result = new AlignmentArea(container);
+               result.setAllowVerticalScrolling(allowVerticalScrolling);
+
+               TokenSet<NucleotideCompound> tokenSet = new BioJavaTokenSet<NucleotideCompound>(
+                               TokenSetType.DNA, new DNACompoundSet(), true);  //TODO Should NUCLEOTIDE be used instead?
+               AlignmentModel<NucleotideCompound> provider = new PackedAlignmentModel<NucleotideCompound>(tokenSet);
+               result.setAlignmentModel(provider, false);
+               provider.getChangeListeners().add(DIRTY_LISTENER);
+
                return result;
        }
-       
-       
-    /* (non-Javadoc)
+
+
+    private AlignmentArea createConsensusHintArea(MultipleAlignmentsContainer container,
+               AlignmentArea labeledArea) {
+
+               AlignmentArea result = new AlignmentArea(container);
+               result.setAllowVerticalScrolling(false);
+               result.getDataAreas().getBottomAreas().add(
+                               new ConsensusSequenceArea(result.getContentArea(), labeledArea));
+               return result;
+    }
+
+
+    private MultipleAlignmentsContainer getAlignmentsContainer() {
+       if (alignmentsContainer == null) {
+               alignmentsContainer = new MultipleAlignmentsContainer();
+
+               AlignmentAreaList list = alignmentsContainer.getAlignmentAreas();
+               list.add(createIndexArea(alignmentsContainer));
+               AlignmentArea readsArea = createEditableAlignmentArea(alignmentsContainer, true);
+               list.add(readsArea);  // Make sure READS_AREA_INDEX is correct.
+               list.add(createEditableAlignmentArea(alignmentsContainer, false));  // Make sure COMSENSUS_AREA_INDEX is correct.
+               list.add(createConsensusHintArea(alignmentsContainer, readsArea));
+
+               registerEditSettingListener(alignmentsContainer);
+               }
+               return alignmentsContainer;
+       }
+
+
+    private AlignmentArea getReadsArea() {
+       return getAlignmentsContainer().getAlignmentAreas().get(READS_AREA_INDEX);
+    }
+
+
+    private AlignmentArea getConsensusArea() {
+       return getAlignmentsContainer().getAlignmentAreas().get(CONSENSUS_AREA_INDEX);
+    }
+
+
+    private PherogramArea getPherogramArea(int sequenceID) {
+       return (PherogramArea)getReadsArea().getDataAreas().getSequenceAreas(sequenceID).get(PHEROGRAM_AREA_INDEX);
+    }
+
+
+    private void createTestContents() {
+               // Just for testing:
+               try {
+                       addRead(new File("D:/Users/BenStoever/ownCloud/Dokumente/Projekte/EDITor/Quelltexte/LibrAlign branch/Repository/eu.etaxonomy.taxeditor.editor/src/main/resources/AlignmentTestData/JR430_JR-P01.ab1").toURI(), false);
+                       addRead(new File("D:/Users/BenStoever/ownCloud/Dokumente/Projekte/EDITor/Quelltexte/LibrAlign branch/Repository/eu.etaxonomy.taxeditor.editor/src/main/resources/AlignmentTestData/JR444_JR-P05.ab1").toURI(), false);
+
+                       // Add test consensus sequence:
+                       AlignmentModel consensusProvider = getConsensusArea().getAlignmentModel();
+                       int id = consensusProvider.addSequence(CONSENSUS_NAME);
+                       Collection<Object> tokens = new ArrayList<Object>();  // First save tokens in a collection to avoid GUI updated for each token.
+                       tokens.add(consensusProvider.getTokenSet().tokenByKeyChar('A'));
+                       tokens.add(consensusProvider.getTokenSet().tokenByKeyChar('C'));
+                       tokens.add(consensusProvider.getTokenSet().tokenByKeyChar('G'));
+                       tokens.add(consensusProvider.getTokenSet().tokenByKeyChar('T'));
+                       consensusProvider.insertTokensAt(id, 0, tokens);
+               }
+               catch (Exception e) {
+                       throw new RuntimeException(e);
+               }
+    }
+
+
+    private void readCDMData(Sequence sequenceNode) {
+       //TODO If called from somewhere else than createPartControl() the editorInput needs to be checked and previous contents need to be cleared (or updated).
+
+               // Add reads:
+               for (SingleReadAlignment singleReadAlignment : sequenceNode.getSingleReadAlignments()) {
+                       try {
+                               SingleRead pherogramInfo = singleReadAlignment.getSingleRead();
+                               int id = addRead(pherogramInfo.getPrimer().getLabel(),  //TODO Should the sequence name contain other/additional/alternative data? Can the same string as in the derivative tree be used here?
+                                               MediaUtils.getFirstMediaRepresentationPart(pherogramInfo.getPherogram()).getUri(),
+                                               singleReadAlignment.isReverseComplement(),
+                                               singleReadAlignment.getEditedSequence(),
+                                               singleReadAlignment.getShifts());
+                               cdmMap.put(id, singleReadAlignment);
+                       }
+                       catch (IOException e) {
+                               e.printStackTrace();  //TODO Output to user (Possibly collect for all pherograms and display in the end.)
+                       }
+                       catch (UnsupportedChromatogramFormatException e) {
+                               e.printStackTrace();  //TODO Output to user (Possibly collect for all pherograms and display in the end.)
+                       }
+               }
+
+               // Set consensus sequence:
+               AlignmentModel consensusProvider = getConsensusArea().getAlignmentModel();
+               int id = consensusProvider.addSequence(CONSENSUS_NAME);
+               consensusProvider.insertTokensAt(id, 0, SequenceUtils.stringToTokenList(
+                               sequenceNode.getConsensusSequence().getString(), consensusProvider.getTokenSet()));
+               //TODO Can the consensus sequence also be null? / Should it be created here, if nothing is in the DB?
+    }
+
+
+       /* (non-Javadoc)
      * @see org.eclipse.ui.part.WorkbenchPart#createPartControl(org.eclipse.swt.widgets.Composite)
      */
     @Override
     public void createPartControl(Composite parent) {
-       alignmentArea = createAlignmentArea();
-               Composite alignmentWidget = alignmentArea.createSWTWidget(parent, SWT.NONE);
+               getAlignmentsContainer().createSWTWidget(parent, SWT.NONE);
+               updateStatusBar();
+
+               if (getEditorInput() instanceof AlignmentEditorInput) {
+                       if (((AlignmentEditorInput)getEditorInput()).getSequenceNode() != null) {
+                           Sequence sequenceNode = ((AlignmentEditorInput)getEditorInput()).getSequenceNode();
+                       //re-load into the current session if it is already persisted in the DB
+                       if(sequenceNode!=null && sequenceNode.getId()!=0){
+                           sequenceNode = CdmStore.getService(ISequenceService.class).load(sequenceNode.getUuid());
+                       }
+                               readCDMData(sequenceNode);
+                       }
+                       else {
+                               createTestContents();  // This case will removed after the test phase and an exception should probably be thrown.
+                       }
+               }
+               else {
+                       throw new IllegalArgumentException("The editor input must have the type " +
+                                       AlignmentEditorInput.class.getCanonicalName());  //TODO What should be done here?
+               }
+    }
+
+
+    private void updateStatusBar() {
+        IActionBars bars = getEditorSite().getActionBars();
+        bars.getStatusLineManager().setMessage("Edit mode: " +
+                       (getReadsArea().getEditSettings().isInsert() ? "Insert" : "Overwrite") + "  " +
+                       "Insertion in pherogram: " +
+                       (getReadsArea().getEditSettings().isInsertLeftInDataArea() ? "Left" : "Right"));
     }
 
-    
+
+    private SingleReadAlignment.Shift[] convertToCDMShifts(PherogramAlignmentModel alignmentModel) {
+       SingleReadAlignment.Shift[] result = new SingleReadAlignment.Shift[alignmentModel.getShiftChangeCount()];
+       Iterator<ShiftChange> iterator = alignmentModel.shiftChangeIterator();
+       int pos = 0;
+       while (iterator.hasNext()) {
+               ShiftChange shiftChange = iterator.next();
+               result[pos] = new SingleReadAlignment.Shift(shiftChange.getBaseCallIndex(), shiftChange.getShiftChange());
+       }
+       return result;
+    }
+
+
     /* (non-Javadoc)
      * @see org.eclipse.ui.part.EditorPart#doSave(org.eclipse.core.runtime.IProgressMonitor)
      */
     @Override
     public void doSave(IProgressMonitor monitor) {
-        // TODO Auto-generated method stub
+       if (getEditorInput() instanceof AlignmentEditorInput) {
+               String taskName = "Saving alignment";
+            monitor.beginTask(taskName, 3);
 
+               Sequence sequenceNode = ((AlignmentEditorInput)getEditorInput()).getSequenceNode();
+               StringAdapter stringProvider = new StringAdapter(getConsensusArea().getAlignmentModel(), false);  // Throws an exception if a token has more than one character.
+
+               // Write consensus sequence:
+               SequenceString consensusSequenceObj = sequenceNode.getConsensusSequence();
+               String newConsensusSequence = stringProvider.getSequence(
+                               getConsensusArea().getAlignmentModel().sequenceIDByName(CONSENSUS_NAME));
+               if (consensusSequenceObj == null) {
+                       sequenceNode.setConsensusSequence(SequenceString.NewInstance(newConsensusSequence));
+               }
+               else {
+                       consensusSequenceObj.setString(newConsensusSequence);
+               }
+
+               // Write single reads:
+               stringProvider.setUnderlyingProvider(getReadsArea().getAlignmentModel());
+               sequenceNode.getSingleReadAlignments().retainAll(cdmMap.values());  // Remove all reads that are not in the alignment anymore.
+               Iterator<Integer> iterator = getReadsArea().getAlignmentModel().sequenceIDIterator();
+               while (iterator.hasNext()) {
+                       int id = iterator.next();
+                       SingleReadAlignment singleRead = cdmMap.get(id);
+                       if (singleRead == null) {
+                               //TODO Create new read object. => Shall it be allowed to add reads in the alignment editor which are not represented in the CDM tree before the alignment editor is saved?
+                               //singleRead = SingleReadAlignment.NewInstance(consensusSequence, singleRead, shifts, editedSequence);
+                       }
+
+                       singleRead.setEditedSequence(stringProvider.getSequence(id));
+                       singleRead.setReverseComplement(getPherogramArea(id).getProvider() instanceof ReverseComplementPherogramModel);  // Works only if ReverseComplementPherogramProvider instances are not nested.
+                       singleRead.setShifts(convertToCDMShifts(getPherogramArea(id).getAlignmentModel()));
+               }
+
+               if (!conversationHolder.isBound()) {
+                conversationHolder.bind();
+            }
+            monitor.worked(1);
+
+            // Commit the conversation and start a new transaction immediately:
+            conversationHolder.commit(true);
+            monitor.worked(1);
+
+            dirty = false;
+            monitor.worked(1);
+            monitor.done();
+            firePropertyChange(PROP_DIRTY);
+       }
+       else {
+               //TODO Throw exception as soon as testing period which allows unlinked AlignmentEditor is over.
+       }
     }
 
-    
+
     /* (non-Javadoc)
      * @see org.eclipse.ui.part.EditorPart#doSaveAs()
      */
     @Override
-    public void doSaveAs() {
-        // TODO Auto-generated method stub
+    public void doSaveAs() {}
 
-    }
-    
 
     /* (non-Javadoc)
      * @see org.eclipse.ui.part.EditorPart#init(org.eclipse.ui.IEditorSite, org.eclipse.ui.IEditorInput)
@@ -109,34 +405,167 @@ public class AlignmentEditor extends EditorPart {
         setSite(site);
         setInput(input);
     }
-    
+
 
     /* (non-Javadoc)
      * @see org.eclipse.ui.part.EditorPart#isDirty()
      */
     @Override
     public boolean isDirty() {
-        // TODO Auto-generated method stub
-        return false;
+        return dirty;
+    }
+
+
+    private void setDirty() {
+       dirty = true;
+       firePropertyChange(IEditorPart.PROP_DIRTY);
     }
 
-    
+
     /* (non-Javadoc)
      * @see org.eclipse.ui.part.EditorPart#isSaveAsAllowed()
      */
     @Override
     public boolean isSaveAsAllowed() {
-        // TODO Auto-generated method stub
-        return false;
+        return false;  // "Save as" not allowed.
     }
-    
 
-    /* (non-Javadoc)
-     * @see org.eclipse.ui.part.WorkbenchPart#setFocus()
-     */
+
     @Override
     public void setFocus() {
-        // TODO Auto-generated method stub
+        if(conversationHolder!=null){
+            conversationHolder.bind();
+        }
+    }
+
 
+    public boolean isInsertMode() {
+        return getAlignmentsContainer().getEditSettings().isInsert();
     }
-}
+
+
+    public boolean isInsertLeftInPherogram() {
+        return getAlignmentsContainer().getEditSettings().isInsertLeftInDataArea();
+    }
+
+
+    public void toggleLeftRightInsertionInPherogram() {
+       getAlignmentsContainer().getEditSettings().toggleInsertLeftInDataArea();
+    }
+
+
+    public void toggleInsertOverwrite() {
+       getAlignmentsContainer().getEditSettings().toggleInsert();
+    }
+
+
+    public void reverseComplementSelection() {
+       SelectionModel selection = getReadsArea().getSelection();
+       AlignmentModel<?> provider = getReadsArea().getAlignmentModel();
+       for (int row = selection.getStartRow(); row < selection.getStartRow() + selection.getCursorHeight(); row++) {
+                       int sequenceID = getReadsArea().getSequenceOrder().idByIndex(row);
+                       //TODO rc edited sequence
+
+                       if (getPherogramArea(sequenceID).getProvider() instanceof ReverseComplementPherogramModel) {
+                               //getPherogramArea(sequenceID).
+                               //TODO Allow to set new provider in PherogramArea or create new PherogramArea
+                               //TODO Reposition pherogram according to previous position in edited sequence and length
+                       }
+               }
+    }
+
+
+       public static PherogramModel readPherogram(URI uri) throws IOException, UnsupportedChromatogramFormatException {
+               PherogramModel result;
+               InputStream stream = uri.toURL().openStream();
+               try {
+                       result = new BioJavaPherogramModel(ChromatogramFactory.create(stream));
+               }
+               finally {
+                       stream.close();
+               }
+               return result;
+       }
+
+
+       private String newReadName() {
+               int index = 1;
+               while (getReadsArea().getAlignmentModel().sequenceIDByName(DEFAULT_READ_NAME_PREFIX + index)
+                               != AlignmentModel.NO_SEQUENCE_FOUND) {
+
+                       index++;
+               }
+               return DEFAULT_READ_NAME_PREFIX + index;
+       }
+
+
+    public void addRead(URI pherogramURI, boolean reverseComplemented) throws IOException, UnsupportedChromatogramFormatException {
+       addRead(newReadName(), pherogramURI, reverseComplemented, null, null);
+    }
+
+
+    /**
+     * Adds a new sequence with attached phergram data area to the reads alignment.
+     * <p>
+     * If {@code null} is specified as {@code editedSequence} the base call sequence from the pherogram will
+     * be set as the edited sequence. If {@code null} is specified as {@code shifts} no shifts between the edited
+     * and the base calls sequence are assumed.
+     *
+     * @param name the name of the new sequence
+     * @param pherogramURI the URI where the associated pherogram file is located
+     * @param reverseComplemented Specify {@code true} here, if the reverse complement of the pherogram data should
+     *        be added, {@code false} otherwise.
+     * @param editedSequence the edited version of the base call sequence (May be {@code null}.)
+     * @param shifts the alignment information that links the edited and the base call sequence (May be {@code null}.)
+     * @return the sequence ID of the added read
+     * @throws IOException if an error occurred when trying to read the pherogram file
+     * @throws UnsupportedChromatogramFormatException if the format of the pherogram file is not supported
+     */
+    public int addRead(String name, URI pherogramURI, boolean reverseComplemented, String editedSequence,
+               SingleReadAlignment.Shift[] shifts) throws IOException, UnsupportedChromatogramFormatException {
+
+               AlignmentModel provider = getReadsArea().getAlignmentModel();
+               PherogramModel pherogramProvider = readPherogram(pherogramURI);  // Must happen before a sequence is added, because it might throw an exception.
+               if (reverseComplemented) {
+                       pherogramProvider = new ReverseComplementPherogramModel(pherogramProvider);
+               }
+
+        // Create sequence:
+               provider.addSequence(name);
+               int id = provider.sequenceIDByName(name);
+
+               // Set edited sequence:
+               Collection<Object> tokens;  // First save tokens in a collection to avoid GUI updated for each token.
+               if (editedSequence != null) {
+                       tokens = SequenceUtils.stringToTokenList(editedSequence, provider.getTokenSet());
+               }
+               else {  // Copy base call sequence into alignment:
+                       tokens = new ArrayList<Object>();
+                       for (int i = 0; i < pherogramProvider.getSequenceLength(); i++) {
+                               tokens.add(provider.getTokenSet().tokenByKeyChar(
+                                       pherogramProvider.getBaseCall(i).getUpperedBase().charAt(0)));
+                       }
+                       setDirty();
+               }
+               provider.insertTokensAt(id, 0, tokens);
+
+               // Create pherogram area:
+               PherogramArea pherogramArea = new PherogramArea(getReadsArea().getContentArea(), pherogramProvider);
+
+               // Set shifts:
+               if ((shifts != null) && (shifts.length > 0)) {
+                       PherogramAlignmentModel alignmentModel = pherogramArea.getAlignmentModel();
+                       for (int i = 0; i < shifts.length; i++) {
+                               alignmentModel.addShiftChange(shifts[i].position, shifts[i].shift);
+                       }
+                       setDirty();
+               }
+
+               // Add pherogram area to GUI:
+               pherogramArea.addMouseListener(new PherogramMouseListener(pherogramURI));
+               getReadsArea().getDataAreas().getSequenceAreas(id).add(pherogramArea);
+
+               // Save source URI:
+               return id;
+       }
+}
\ No newline at end of file