79d0ca257670f992155653132b24b17f9f549535
[taxeditor.git] / eu.etaxonomy.taxeditor.editor / src / main / java / eu / etaxonomy / taxeditor / editor / molecular / AlignmentEditor.java
1 // $Id$
2 /**
3 * Copyright (C) 2014 EDIT
4 * European Distributed Institute of Taxonomy
5 * http://www.e-taxonomy.eu
6 *
7 * The contents of this file are subject to the Mozilla Public License Version 1.1
8 * See LICENSE.TXT at the top of this package for the full license terms.
9 */
10 package eu.etaxonomy.taxeditor.editor.molecular;
11
12
13 import info.bioinfweb.jphyloio.events.TokenSetType;
14 import info.bioinfweb.libralign.alignmentarea.AlignmentArea;
15 import info.bioinfweb.libralign.alignmentarea.selection.SelectionModel;
16 import info.bioinfweb.libralign.alignmentarea.tokenpainter.NucleotideTokenPainter;
17 import info.bioinfweb.libralign.dataarea.implementations.ConsensusSequenceArea;
18 import info.bioinfweb.libralign.dataarea.implementations.SequenceIndexArea;
19 import info.bioinfweb.libralign.dataarea.implementations.pherogram.PherogramAlignmentModel;
20 import info.bioinfweb.libralign.dataarea.implementations.pherogram.PherogramArea;
21 import info.bioinfweb.libralign.dataarea.implementations.pherogram.ShiftChange;
22 import info.bioinfweb.libralign.editsettings.EditSettingsChangeEvent;
23 import info.bioinfweb.libralign.editsettings.EditSettingsListener;
24 import info.bioinfweb.libralign.model.AlignmentModel;
25 import info.bioinfweb.libralign.model.AlignmentModelChangeListener;
26 import info.bioinfweb.libralign.model.SequenceUtils;
27 import info.bioinfweb.libralign.model.adapters.StringAdapter;
28 import info.bioinfweb.libralign.model.events.SequenceChangeEvent;
29 import info.bioinfweb.libralign.model.events.SequenceRenamedEvent;
30 import info.bioinfweb.libralign.model.events.TokenChangeEvent;
31 import info.bioinfweb.libralign.model.implementations.PackedAlignmentModel;
32 import info.bioinfweb.libralign.model.tokenset.BioJavaTokenSet;
33 import info.bioinfweb.libralign.model.tokenset.TokenSet;
34 import info.bioinfweb.libralign.multiplealignments.AlignmentAreaList;
35 import info.bioinfweb.libralign.multiplealignments.MultipleAlignmentsContainer;
36 import info.bioinfweb.libralign.pherogram.model.BioJavaPherogramModel;
37 import info.bioinfweb.libralign.pherogram.model.PherogramModel;
38 import info.bioinfweb.libralign.pherogram.model.ReverseComplementPherogramModel;
39
40 import java.io.File;
41 import java.io.IOException;
42 import java.io.InputStream;
43 import java.net.URI;
44 import java.util.ArrayList;
45 import java.util.Collection;
46 import java.util.Collections;
47 import java.util.Iterator;
48 import java.util.Map;
49 import java.util.TreeMap;
50
51 import org.biojava.bio.chromatogram.ChromatogramFactory;
52 import org.biojava.bio.chromatogram.UnsupportedChromatogramFormatException;
53 import org.biojava3.core.sequence.compound.DNACompoundSet;
54 import org.biojava3.core.sequence.compound.NucleotideCompound;
55 import org.eclipse.core.runtime.IProgressMonitor;
56 import org.eclipse.swt.SWT;
57 import org.eclipse.swt.widgets.Composite;
58 import org.eclipse.ui.IActionBars;
59 import org.eclipse.ui.IEditorInput;
60 import org.eclipse.ui.IEditorPart;
61 import org.eclipse.ui.IEditorSite;
62 import org.eclipse.ui.PartInitException;
63 import org.eclipse.ui.PlatformUI;
64 import org.eclipse.ui.commands.ICommandService;
65 import org.eclipse.ui.part.EditorPart;
66
67 import eu.etaxonomy.cdm.api.conversation.ConversationHolder;
68 import eu.etaxonomy.cdm.api.service.molecular.ISequenceService;
69 import eu.etaxonomy.cdm.model.media.MediaUtils;
70 import eu.etaxonomy.cdm.model.molecular.Sequence;
71 import eu.etaxonomy.cdm.model.molecular.SequenceString;
72 import eu.etaxonomy.cdm.model.molecular.SingleRead;
73 import eu.etaxonomy.cdm.model.molecular.SingleReadAlignment;
74 import eu.etaxonomy.taxeditor.editor.handler.alignmenteditor.ToggleInsertOverwriteHandler;
75 import eu.etaxonomy.taxeditor.editor.handler.alignmenteditor.ToggleLeftRightInsertionHandler;
76 import eu.etaxonomy.taxeditor.store.CdmStore;
77
78
79
80 /**
81 * Editor component to edit a contig alignment used to combine different overlapping pherograms from Sanger sequencing to
82 * a consensus sequence.
83 * <p>
84 * The contained GUI components used to edit the alignment come from <a href="http://bioinfweb.info/LibrAlign/">LibrAlign</a>.
85 *
86 * @author Ben Stöver
87 * @author pplitzner
88 * @date 04.08.2014
89 */
90 public class AlignmentEditor extends EditorPart {
91 public static final String ID = "eu.etaxonomy.taxeditor.editor.molecular.AlignmentEditor";
92
93 public static final int READS_AREA_INDEX = 1;
94 public static final int CONSENSUS_AREA_INDEX = READS_AREA_INDEX + 1;
95 public static final int PHEROGRAM_AREA_INDEX = 0;
96 public static final String DEFAULT_READ_NAME_PREFIX = "Read ";
97 public static final String CONSENSUS_NAME = "Consensus";
98
99
100 private final ConversationHolder conversationHolder;
101
102 private final AlignmentModelChangeListener DIRTY_LISTENER = new AlignmentModelChangeListener() {
103 @Override
104 public <T> void afterTokenChange(TokenChangeEvent<T> e) {
105 setDirty();
106 }
107
108 @Override
109 public <T> void afterSequenceRenamed(SequenceRenamedEvent<T> e) {
110 setDirty();
111 }
112
113 @Override
114 public <T> void afterSequenceChange(SequenceChangeEvent<T> e) {
115 setDirty();
116 }
117
118 @Override
119 public <T, U> void afterProviderChanged(AlignmentModel<T> oldProvider,
120 AlignmentModel<U> newProvider) { // Not expected.
121
122 setDirty();
123 }
124 };
125
126 private MultipleAlignmentsContainer alignmentsContainer = null;
127 private final Map<Integer, SingleReadAlignment> cdmMap = new TreeMap<Integer, SingleReadAlignment>(); //TODO Move this to ContigSequenceDataProvider
128 private boolean dirty = false;
129
130
131 public AlignmentEditor() {
132 super();
133 //conversationHolder = CdmStore.createConversation();
134 conversationHolder = null;
135 }
136
137
138 private void refreshToolbarElement(String id) {
139 ICommandService commandService =
140 (ICommandService)PlatformUI.getWorkbench().getActiveWorkbenchWindow().getService(ICommandService.class);
141 if (commandService != null) {
142 commandService.refreshElements(id, Collections.EMPTY_MAP);
143 }
144 }
145
146
147 private void registerEditSettingListener(MultipleAlignmentsContainer container) {
148 container.getEditSettings().addListener(new EditSettingsListener() {
149 @Override
150 public void workingModeChanged(EditSettingsChangeEvent e) {} // Currently nothing to do
151
152 @Override
153 public void insertLeftInDataAreaChanged(EditSettingsChangeEvent e) {
154 updateStatusBar();
155 refreshToolbarElement(ToggleLeftRightInsertionHandler.COMMAND_ID);
156 }
157
158 @Override
159 public void insertChanged(EditSettingsChangeEvent e) {
160 updateStatusBar();
161 refreshToolbarElement(ToggleInsertOverwriteHandler.COMMAND_ID);
162 }
163 });
164 }
165
166
167 private AlignmentArea createIndexArea(MultipleAlignmentsContainer container, AlignmentArea labeledArea) {
168 AlignmentArea result = new AlignmentArea(container);
169 result.setAllowVerticalScrolling(false);
170 result.getDataAreas().getTopAreas().add(new SequenceIndexArea(result.getContentArea(), labeledArea));
171 return result;
172 }
173
174
175 private AlignmentArea createEditableAlignmentArea(MultipleAlignmentsContainer container, boolean allowVerticalScrolling) {
176 AlignmentArea result = new AlignmentArea(container);
177 result.setAllowVerticalScrolling(allowVerticalScrolling);
178
179 TokenSet<NucleotideCompound> tokenSet = new BioJavaTokenSet<NucleotideCompound>(
180 TokenSetType.DNA, new DNACompoundSet(), true); //TODO Should NUCLEOTIDE be used instead?
181 AlignmentModel<NucleotideCompound> provider = new PackedAlignmentModel<NucleotideCompound>(tokenSet);
182 result.setAlignmentModel(provider, false);
183 provider.getChangeListeners().add(DIRTY_LISTENER);
184 result.getPaintSettings().getTokenPainterList().set(0, new NucleotideTokenPainter());
185
186 return result;
187 }
188
189
190 private AlignmentArea createConsensusHintArea(MultipleAlignmentsContainer container,
191 AlignmentArea labeledArea) {
192
193 AlignmentArea result = new AlignmentArea(container);
194 result.setAllowVerticalScrolling(false);
195 result.getDataAreas().getBottomAreas().add(
196 new ConsensusSequenceArea(result.getContentArea(), labeledArea));
197 return result;
198 }
199
200
201 private MultipleAlignmentsContainer getAlignmentsContainer() {
202 if (alignmentsContainer == null) {
203 alignmentsContainer = new MultipleAlignmentsContainer();
204
205 AlignmentAreaList list = alignmentsContainer.getAlignmentAreas();
206 AlignmentArea readsArea = createEditableAlignmentArea(alignmentsContainer, true);
207 list.add(createIndexArea(alignmentsContainer, readsArea));
208 list.add(readsArea); // Make sure READS_AREA_INDEX is correct.
209 list.add(createEditableAlignmentArea(alignmentsContainer, false)); // Make sure COMSENSUS_AREA_INDEX is correct.
210 list.add(createConsensusHintArea(alignmentsContainer, readsArea));
211
212 registerEditSettingListener(alignmentsContainer);
213 }
214 return alignmentsContainer;
215 }
216
217
218 private AlignmentArea getReadsArea() {
219 return getAlignmentsContainer().getAlignmentAreas().get(READS_AREA_INDEX);
220 }
221
222
223 private AlignmentArea getConsensusArea() {
224 return getAlignmentsContainer().getAlignmentAreas().get(CONSENSUS_AREA_INDEX);
225 }
226
227
228 private PherogramArea getPherogramArea(int sequenceID) {
229 return (PherogramArea)getReadsArea().getDataAreas().getSequenceAreas(sequenceID).get(PHEROGRAM_AREA_INDEX);
230 }
231
232
233 private void createTestContents() {
234 // Just for testing:
235 try {
236 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);
237 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);
238
239 // Add test consensus sequence:
240 AlignmentModel consensusProvider = getConsensusArea().getAlignmentModel();
241 int id = consensusProvider.addSequence(CONSENSUS_NAME);
242 Collection<Object> tokens = new ArrayList<Object>(); // First save tokens in a collection to avoid GUI updated for each token.
243 tokens.add(consensusProvider.getTokenSet().tokenByKeyChar('A'));
244 tokens.add(consensusProvider.getTokenSet().tokenByKeyChar('C'));
245 tokens.add(consensusProvider.getTokenSet().tokenByKeyChar('G'));
246 tokens.add(consensusProvider.getTokenSet().tokenByKeyChar('T'));
247 consensusProvider.insertTokensAt(id, 0, tokens);
248 }
249 catch (Exception e) {
250 throw new RuntimeException(e);
251 }
252 }
253
254
255 private void readCDMData(Sequence sequenceNode) {
256 //TODO If called from somewhere else than createPartControl() the editorInput needs to be checked and previous contents need to be cleared (or updated).
257
258 // Add reads:
259 for (SingleReadAlignment singleReadAlignment : sequenceNode.getSingleReadAlignments()) {
260 try {
261 SingleRead pherogramInfo = singleReadAlignment.getSingleRead();
262 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?
263 MediaUtils.getFirstMediaRepresentationPart(pherogramInfo.getPherogram()).getUri(),
264 singleReadAlignment.isReverseComplement(),
265 singleReadAlignment.getEditedSequence(),
266 singleReadAlignment.getShifts());
267 cdmMap.put(id, singleReadAlignment);
268 }
269 catch (IOException e) {
270 e.printStackTrace(); //TODO Output to user (Possibly collect for all pherograms and display in the end.)
271 }
272 catch (UnsupportedChromatogramFormatException e) {
273 e.printStackTrace(); //TODO Output to user (Possibly collect for all pherograms and display in the end.)
274 }
275 }
276
277 // Set consensus sequence:
278 AlignmentModel consensusProvider = getConsensusArea().getAlignmentModel();
279 int id = consensusProvider.addSequence(CONSENSUS_NAME);
280 consensusProvider.insertTokensAt(id, 0, SequenceUtils.stringToTokenList(
281 sequenceNode.getConsensusSequence().getString(), consensusProvider.getTokenSet()));
282 //TODO Can the consensus sequence also be null? / Should it be created here, if nothing is in the DB?
283 }
284
285
286 /* (non-Javadoc)
287 * @see org.eclipse.ui.part.WorkbenchPart#createPartControl(org.eclipse.swt.widgets.Composite)
288 */
289 @Override
290 public void createPartControl(Composite parent) {
291 getAlignmentsContainer().createSWTWidget(parent, SWT.NONE);
292 updateStatusBar();
293
294 if (getEditorInput() instanceof AlignmentEditorInput) {
295 if (((AlignmentEditorInput)getEditorInput()).getSequenceNode() != null) {
296 Sequence sequenceNode = ((AlignmentEditorInput)getEditorInput()).getSequenceNode();
297 //re-load into the current session if it is already persisted in the DB
298 if(sequenceNode!=null && sequenceNode.getId()!=0){
299 sequenceNode = CdmStore.getService(ISequenceService.class).load(sequenceNode.getUuid());
300 }
301 readCDMData(sequenceNode);
302 }
303 else {
304 createTestContents(); // This case will removed after the test phase and an exception should probably be thrown.
305 }
306 }
307 else {
308 throw new IllegalArgumentException("The editor input must have the type " +
309 AlignmentEditorInput.class.getCanonicalName()); //TODO What should be done here?
310 }
311 }
312
313
314 private void updateStatusBar() {
315 IActionBars bars = getEditorSite().getActionBars();
316 bars.getStatusLineManager().setMessage("Edit mode: " +
317 (getReadsArea().getEditSettings().isInsert() ? "Insert" : "Overwrite") + " " +
318 "Insertion in pherogram: " +
319 (getReadsArea().getEditSettings().isInsertLeftInDataArea() ? "Left" : "Right"));
320 }
321
322
323 private SingleReadAlignment.Shift[] convertToCDMShifts(PherogramAlignmentModel alignmentModel) {
324 SingleReadAlignment.Shift[] result = new SingleReadAlignment.Shift[alignmentModel.getShiftChangeCount()];
325 Iterator<ShiftChange> iterator = alignmentModel.shiftChangeIterator();
326 int pos = 0;
327 while (iterator.hasNext()) {
328 ShiftChange shiftChange = iterator.next();
329 result[pos] = new SingleReadAlignment.Shift(shiftChange.getBaseCallIndex(), shiftChange.getShiftChange());
330 }
331 return result;
332 }
333
334
335 /* (non-Javadoc)
336 * @see org.eclipse.ui.part.EditorPart#doSave(org.eclipse.core.runtime.IProgressMonitor)
337 */
338 @Override
339 public void doSave(IProgressMonitor monitor) {
340 if (getEditorInput() instanceof AlignmentEditorInput) {
341 String taskName = "Saving alignment";
342 monitor.beginTask(taskName, 3);
343
344 Sequence sequenceNode = ((AlignmentEditorInput)getEditorInput()).getSequenceNode();
345 StringAdapter stringProvider = new StringAdapter(getConsensusArea().getAlignmentModel(), false); // Throws an exception if a token has more than one character.
346
347 // Write consensus sequence:
348 SequenceString consensusSequenceObj = sequenceNode.getConsensusSequence();
349 String newConsensusSequence = stringProvider.getSequence(
350 getConsensusArea().getAlignmentModel().sequenceIDByName(CONSENSUS_NAME));
351 if (consensusSequenceObj == null) {
352 sequenceNode.setConsensusSequence(SequenceString.NewInstance(newConsensusSequence));
353 }
354 else {
355 consensusSequenceObj.setString(newConsensusSequence);
356 }
357
358 // Write single reads:
359 stringProvider.setUnderlyingProvider(getReadsArea().getAlignmentModel());
360 sequenceNode.getSingleReadAlignments().retainAll(cdmMap.values()); // Remove all reads that are not in the alignment anymore.
361 Iterator<Integer> iterator = getReadsArea().getAlignmentModel().sequenceIDIterator();
362 while (iterator.hasNext()) {
363 int id = iterator.next();
364 SingleReadAlignment singleRead = cdmMap.get(id);
365 if (singleRead == null) {
366 //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?
367 //singleRead = SingleReadAlignment.NewInstance(consensusSequence, singleRead, shifts, editedSequence);
368 }
369
370 singleRead.setEditedSequence(stringProvider.getSequence(id));
371 singleRead.setReverseComplement(getPherogramArea(id).getPherogramModel() instanceof ReverseComplementPherogramModel); // Works only if ReverseComplementPherogramProvider instances are not nested.
372 singleRead.setShifts(convertToCDMShifts(getPherogramArea(id).getPherogramAlignmentModel()));
373 }
374
375 if (!conversationHolder.isBound()) {
376 conversationHolder.bind();
377 }
378 monitor.worked(1);
379
380 // Commit the conversation and start a new transaction immediately:
381 conversationHolder.commit(true);
382 monitor.worked(1);
383
384 dirty = false;
385 monitor.worked(1);
386 monitor.done();
387 firePropertyChange(PROP_DIRTY);
388 }
389 else {
390 //TODO Throw exception as soon as testing period which allows unlinked AlignmentEditor is over.
391 }
392 }
393
394
395 /* (non-Javadoc)
396 * @see org.eclipse.ui.part.EditorPart#doSaveAs()
397 */
398 @Override
399 public void doSaveAs() {}
400
401
402 /* (non-Javadoc)
403 * @see org.eclipse.ui.part.EditorPart#init(org.eclipse.ui.IEditorSite, org.eclipse.ui.IEditorInput)
404 */
405 @Override
406 public void init(IEditorSite site, IEditorInput input) throws PartInitException {
407 setSite(site);
408 setInput(input);
409 }
410
411
412 /* (non-Javadoc)
413 * @see org.eclipse.ui.part.EditorPart#isDirty()
414 */
415 @Override
416 public boolean isDirty() {
417 return dirty;
418 }
419
420
421 private void setDirty() {
422 dirty = true;
423 firePropertyChange(IEditorPart.PROP_DIRTY);
424 }
425
426
427 /* (non-Javadoc)
428 * @see org.eclipse.ui.part.EditorPart#isSaveAsAllowed()
429 */
430 @Override
431 public boolean isSaveAsAllowed() {
432 return false; // "Save as" not allowed.
433 }
434
435
436 @Override
437 public void setFocus() {
438 if(conversationHolder!=null){
439 conversationHolder.bind();
440 }
441 }
442
443
444 public boolean isInsertMode() {
445 return getAlignmentsContainer().getEditSettings().isInsert();
446 }
447
448
449 public boolean isInsertLeftInPherogram() {
450 return getAlignmentsContainer().getEditSettings().isInsertLeftInDataArea();
451 }
452
453
454 public void toggleLeftRightInsertionInPherogram() {
455 getAlignmentsContainer().getEditSettings().toggleInsertLeftInDataArea();
456 }
457
458
459 public void toggleInsertOverwrite() {
460 getAlignmentsContainer().getEditSettings().toggleInsert();
461 }
462
463
464 public void reverseComplementSelection() {
465 SelectionModel selection = getReadsArea().getSelection();
466 AlignmentModel<?> provider = getReadsArea().getAlignmentModel();
467 for (int row = selection.getStartRow(); row < selection.getStartRow() + selection.getCursorHeight(); row++) {
468 int sequenceID = getReadsArea().getSequenceOrder().idByIndex(row);
469 //TODO rc edited sequence
470
471 if (getPherogramArea(sequenceID).getPherogramModel() instanceof ReverseComplementPherogramModel) {
472 //getPherogramArea(sequenceID).
473 //TODO Allow to set new provider in PherogramArea or create new PherogramArea
474 //TODO Reposition pherogram according to previous position in edited sequence and length
475 }
476 }
477 }
478
479
480 public static PherogramModel readPherogram(URI uri) throws IOException, UnsupportedChromatogramFormatException {
481 PherogramModel result;
482 InputStream stream = uri.toURL().openStream();
483 try {
484 result = new BioJavaPherogramModel(ChromatogramFactory.create(stream));
485 }
486 finally {
487 stream.close();
488 }
489 return result;
490 }
491
492
493 private String newReadName() {
494 int index = 1;
495 while (getReadsArea().getAlignmentModel().sequenceIDByName(DEFAULT_READ_NAME_PREFIX + index)
496 != AlignmentModel.NO_SEQUENCE_FOUND) {
497
498 index++;
499 }
500 return DEFAULT_READ_NAME_PREFIX + index;
501 }
502
503
504 public void addRead(URI pherogramURI, boolean reverseComplemented) throws IOException, UnsupportedChromatogramFormatException {
505 addRead(newReadName(), pherogramURI, reverseComplemented, null, null);
506 }
507
508
509 /**
510 * Adds a new sequence with attached phergram data area to the reads alignment.
511 * <p>
512 * If {@code null} is specified as {@code editedSequence} the base call sequence from the pherogram will
513 * be set as the edited sequence. If {@code null} is specified as {@code shifts} no shifts between the edited
514 * and the base calls sequence are assumed.
515 *
516 * @param name the name of the new sequence
517 * @param pherogramURI the URI where the associated pherogram file is located
518 * @param reverseComplemented Specify {@code true} here, if the reverse complement of the pherogram data should
519 * be added, {@code false} otherwise.
520 * @param editedSequence the edited version of the base call sequence (May be {@code null}.)
521 * @param shifts the alignment information that links the edited and the base call sequence (May be {@code null}.)
522 * @return the sequence ID of the added read
523 * @throws IOException if an error occurred when trying to read the pherogram file
524 * @throws UnsupportedChromatogramFormatException if the format of the pherogram file is not supported
525 */
526 public int addRead(String name, URI pherogramURI, boolean reverseComplemented, String editedSequence,
527 SingleReadAlignment.Shift[] shifts) throws IOException, UnsupportedChromatogramFormatException {
528
529 AlignmentModel provider = getReadsArea().getAlignmentModel();
530 PherogramModel pherogramProvider = readPherogram(pherogramURI); // Must happen before a sequence is added, because it might throw an exception.
531 if (reverseComplemented) {
532 pherogramProvider = new ReverseComplementPherogramModel(pherogramProvider);
533 }
534
535 // Create sequence:
536 provider.addSequence(name);
537 int id = provider.sequenceIDByName(name);
538
539 // Set edited sequence:
540 Collection<Object> tokens; // First save tokens in a collection to avoid GUI updated for each token.
541 if (editedSequence != null) {
542 tokens = SequenceUtils.stringToTokenList(editedSequence, provider.getTokenSet());
543 }
544 else { // Copy base call sequence into alignment:
545 tokens = new ArrayList<Object>();
546 for (int i = 0; i < pherogramProvider.getSequenceLength(); i++) {
547 tokens.add(provider.getTokenSet().tokenByKeyChar(
548 pherogramProvider.getBaseCall(i).getUpperedBase().charAt(0)));
549 }
550 setDirty();
551 }
552 provider.insertTokensAt(id, 0, tokens);
553
554 // Create pherogram area:
555 PherogramArea pherogramArea = new PherogramArea(getReadsArea().getContentArea(), pherogramProvider);
556
557 // Set shifts:
558 if ((shifts != null) && (shifts.length > 0)) {
559 PherogramAlignmentModel alignmentModel = pherogramArea.getPherogramAlignmentModel();
560 for (int i = 0; i < shifts.length; i++) {
561 alignmentModel.addShiftChange(shifts[i].position, shifts[i].shift);
562 }
563 setDirty();
564 }
565
566 // Add pherogram area to GUI:
567 pherogramArea.addMouseListener(new PherogramMouseListener(pherogramURI));
568 getReadsArea().getDataAreas().getSequenceAreas(id).add(pherogramArea);
569
570 // Save source URI:
571 return id;
572 }
573 }