Project

General

Profile

Download (28.6 KB) Statistics
| Branch: | Tag: | Revision:
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.molecular.editor;
11

    
12

    
13
import info.bioinfweb.libralign.alignmentarea.AlignmentArea;
14
import info.bioinfweb.libralign.alignmentarea.selection.SelectionModel;
15
import info.bioinfweb.libralign.alignmentarea.tokenpainter.NucleotideTokenPainter;
16
import info.bioinfweb.libralign.dataarea.implementations.ConsensusSequenceArea;
17
import info.bioinfweb.libralign.dataarea.implementations.SequenceIndexArea;
18
import info.bioinfweb.libralign.dataarea.implementations.pherogram.PherogramArea;
19
import info.bioinfweb.libralign.editsettings.EditSettingsChangeEvent;
20
import info.bioinfweb.libralign.editsettings.EditSettingsListener;
21
import info.bioinfweb.libralign.model.AlignmentModel;
22
import info.bioinfweb.libralign.model.AlignmentModelChangeListener;
23
import info.bioinfweb.libralign.model.AlignmentModelUtils;
24
import info.bioinfweb.libralign.model.adapters.StringAdapter;
25
import info.bioinfweb.libralign.model.events.SequenceChangeEvent;
26
import info.bioinfweb.libralign.model.events.SequenceRenamedEvent;
27
import info.bioinfweb.libralign.model.events.TokenChangeEvent;
28
import info.bioinfweb.libralign.model.implementations.PackedAlignmentModel;
29
import info.bioinfweb.libralign.model.tokenset.CharacterTokenSet;
30
import info.bioinfweb.libralign.model.tokenset.TokenSet;
31
import info.bioinfweb.libralign.multiplealignments.AlignmentAreaList;
32
import info.bioinfweb.libralign.multiplealignments.MultipleAlignmentsContainer;
33
import info.bioinfweb.libralign.pherogram.model.PherogramAreaModel;
34
import info.bioinfweb.libralign.pherogram.model.ShiftChange;
35
import info.bioinfweb.libralign.pherogram.provider.BioJavaPherogramProvider;
36
import info.bioinfweb.libralign.pherogram.provider.PherogramProvider;
37
import info.bioinfweb.libralign.pherogram.provider.ReverseComplementPherogramProvider;
38
import info.bioinfweb.tic.SWTComponentFactory;
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.List;
49
import java.util.Map;
50
import java.util.TreeMap;
51

    
52
import org.biojava.bio.chromatogram.ChromatogramFactory;
53
import org.biojava.bio.chromatogram.UnsupportedChromatogramFormatException;
54
import org.eclipse.core.runtime.IProgressMonitor;
55
import org.eclipse.swt.SWT;
56
import org.eclipse.swt.widgets.Composite;
57
import org.eclipse.ui.IActionBars;
58
import org.eclipse.ui.IEditorInput;
59
import org.eclipse.ui.IEditorPart;
60
import org.eclipse.ui.IEditorSite;
61
import org.eclipse.ui.PartInitException;
62
import org.eclipse.ui.PlatformUI;
63
import org.eclipse.ui.commands.ICommandService;
64
import org.eclipse.ui.part.EditorPart;
65

    
66
import eu.etaxonomy.cdm.api.conversation.ConversationHolder;
67
import eu.etaxonomy.cdm.api.service.molecular.ISequenceService;
68
import eu.etaxonomy.cdm.model.media.MediaUtils;
69
import eu.etaxonomy.cdm.model.molecular.Sequence;
70
import eu.etaxonomy.cdm.model.molecular.SequenceString;
71
import eu.etaxonomy.cdm.model.molecular.SingleRead;
72
import eu.etaxonomy.cdm.model.molecular.SingleReadAlignment;
73
import eu.etaxonomy.cdm.model.molecular.SingleReadAlignment.Shift;
74
import eu.etaxonomy.taxeditor.model.MessagingUtils;
75
import eu.etaxonomy.taxeditor.molecular.TaxeditorMolecularPlugin;
76
import eu.etaxonomy.taxeditor.molecular.handler.ToggleInsertOverwriteHandler;
77
import eu.etaxonomy.taxeditor.molecular.handler.ToggleLeftRightInsertionHandler;
78
import eu.etaxonomy.taxeditor.store.CdmStore;
79
import eu.etaxonomy.taxeditor.view.derivateSearch.DerivateLabelProvider;
80

    
81

    
82

    
83
/**
84
 * Editor component to edit a contig alignment used to combine different overlapping pherograms from Sanger sequencing to
85
 * a consensus sequence.
86
 * <p>
87
 * The contained GUI components used to edit the alignment come from <a href="http://bioinfweb.info/LibrAlign/">LibrAlign</a>.
88
 *
89
 * @author Ben Stöver
90
 * @author pplitzner
91
 * @date 04.08.2014
92
 */
93
public class AlignmentEditor extends EditorPart {
94
    public static final String ID = "eu.etaxonomy.taxeditor.molecular.AlignmentEditor";
95

    
96
	public static final int READS_AREA_INDEX = 1;
97
    public static final int EDITABLE_CONSENSUS_AREA_INDEX = READS_AREA_INDEX + 1;
98
    public static final int CONSENSUS_HINT_AREA_INDEX = EDITABLE_CONSENSUS_AREA_INDEX + 1;
99
	public static final int PHEROGRAM_AREA_INDEX = 0;
100
	public static final int CONSENSUS_DATA_AREA_INDEX = 0;
101
	public static final String DEFAULT_READ_NAME_PREFIX = "Read ";
102
	public static final String CONSENSUS_NAME = "Consensus";
103

    
104
    private final ConversationHolder conversationHolder;
105
	private final AlignmentModelChangeListener DIRTY_LISTENER = new AlignmentModelChangeListener() {
106
				@Override
107
				public <T> void afterTokenChange(TokenChangeEvent<T> e) {
108
					setDirty();
109
				}
110

    
111
				@Override
112
				public <T> void afterSequenceRenamed(SequenceRenamedEvent<T> e) {
113
					setDirty();
114
				}
115

    
116
				@Override
117
				public <T> void afterSequenceChange(SequenceChangeEvent<T> e) {
118
					setDirty();
119
				}
120

    
121
				@Override
122
				public <T, U> void afterProviderChanged(AlignmentModel<T> oldProvider,
123
						AlignmentModel<U> newProvider) {  // Not expected.
124

    
125
					setDirty();
126
				}
127
			};
128

    
129
    private MultipleAlignmentsContainer alignmentsContainer = null;
130
    private final Map<Integer, SingleReadAlignment> cdmMap = new TreeMap<Integer, SingleReadAlignment>();  //TODO Move this to ContigSequenceDataProvider
131
    private boolean dirty = false;
132

    
133
    public AlignmentEditor() {
134
    	super();
135
    	conversationHolder = CdmStore.createConversation();
136
    	//conversationHolder = null;
137
    }
138

    
139
    private void refreshToolbarElement(String id) {
140
		ICommandService commandService =
141
				(ICommandService)PlatformUI.getWorkbench().getActiveWorkbenchWindow().getService(ICommandService.class);
142
		if (commandService != null) {
143
			commandService.refreshElements(id, Collections.EMPTY_MAP);
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
		CharacterTokenSet tokenSet = CharacterTokenSet.newDNAInstance();  //TODO Should NUCLEOTIDE be used instead?
180
		AlignmentModel<Character> provider = new PackedAlignmentModel<Character>(tokenSet);
181
		result.setAlignmentModel(provider, false);
182
		provider.getChangeListeners().add(DIRTY_LISTENER);
183
		result.getPaintSettings().getTokenPainterList().set(0, new NucleotideTokenPainter());
184

    
185
		return result;
186
	}
187

    
188
    private AlignmentArea createConsensusHintArea(MultipleAlignmentsContainer container,
189
    		AlignmentArea labeledArea) {
190

    
191
    		AlignmentArea result = new AlignmentArea(container);
192
    		result.setAllowVerticalScrolling(false);
193
    		result.getDataAreas().getBottomAreas().add(
194
    				new ConsensusSequenceArea(result.getContentArea(), labeledArea));
195
    		return result;
196
	  }
197

    
198
    private MultipleAlignmentsContainer getAlignmentsContainer() {
199
    	if (alignmentsContainer == null) {
200
    		alignmentsContainer = new MultipleAlignmentsContainer();
201

    
202
    		AlignmentAreaList list = alignmentsContainer.getAlignmentAreas();
203
    		AlignmentArea readsArea = createEditableAlignmentArea(alignmentsContainer, true);
204
    	    list.add(createIndexArea(alignmentsContainer, readsArea));
205
    		list.add(readsArea);  // Make sure READS_AREA_INDEX is correct.
206
    		list.add(createEditableAlignmentArea(alignmentsContainer, false));  // Make sure COMSENSUS_AREA_INDEX is correct.
207
    		list.add(createConsensusHintArea(alignmentsContainer, readsArea));
208

    
209
    		registerEditSettingListener(alignmentsContainer);
210
       	}
211
		return alignmentsContainer;
212
	}
213

    
214
    public AlignmentArea getReadsArea() {
215
    	return getAlignmentsContainer().getAlignmentAreas().get(READS_AREA_INDEX);
216
    }
217

    
218
    private AlignmentArea getEditableConsensusArea() {
219
    	return getAlignmentsContainer().getAlignmentAreas().get(EDITABLE_CONSENSUS_AREA_INDEX);
220
    }
221

    
222
    public boolean hasPherogram(int sequenceID) {
223
        return getReadsArea().getDataAreas().getSequenceAreas(sequenceID).size() > PHEROGRAM_AREA_INDEX;
224
    }
225

    
226
    public PherogramArea getPherogramArea(int sequenceID) {
227
        if (hasPherogram(sequenceID)) {
228
            return (PherogramArea)getReadsArea().getDataAreas().getSequenceAreas(sequenceID).get(PHEROGRAM_AREA_INDEX);
229
        }
230
        else {
231
            return null;
232
        }
233
    }
234

    
235
    private ConsensusSequenceArea getConsensusHintDataArea() {
236
        return (ConsensusSequenceArea)getAlignmentsContainer().getAlignmentAreas().
237
                get(CONSENSUS_HINT_AREA_INDEX).getDataAreas().getBottomAreas().
238
                get(CONSENSUS_DATA_AREA_INDEX);
239
    }
240

    
241
    @Deprecated  //TODO Remove as soon as testing period is over
242
    private void createTestContents() {
243
		// Just for testing:
244
		try {
245
			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);
246
            //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);
247
            addRead(new File("D:/Users/BenStoever/ownCloud/Dokumente/Projekte/EDITor/Quelltexte/LibrAlign branch/Repository/eu.etaxonomy.taxeditor.editor/src/main/resources/AlignmentTestData/Test_qualityScore.scf").toURI(), false);
248

    
249
			// Add test consensus sequence:
250
			AlignmentModel consensusModel = getEditableConsensusArea().getAlignmentModel();
251
			int id = consensusModel.addSequence(CONSENSUS_NAME);
252
			Collection<Object> tokens = new ArrayList<Object>();  // First save tokens in a collection to avoid GUI updated for each token.
253
			tokens.add(consensusModel.getTokenSet().tokenByRepresentation("A"));
254
			tokens.add(consensusModel.getTokenSet().tokenByRepresentation("C"));
255
			tokens.add(consensusModel.getTokenSet().tokenByRepresentation("G"));
256
			tokens.add(consensusModel.getTokenSet().tokenByRepresentation("T"));
257
			consensusModel.insertTokensAt(id, 0, tokens);
258
		}
259
		catch (Exception e) {
260
			throw new RuntimeException(e);
261
		}
262
    }
263

    
264
    private void readCDMData(Sequence sequenceNode) {
265
    	//TODO If called from somewhere else than createPartControl() the editorInput needs to be checked and previous contents need to be cleared (or updated).
266

    
267
		// Add reads:
268
		for (SingleReadAlignment singleReadAlignment : sequenceNode.getSingleReadAlignments()) {
269
			try {
270
				SingleRead pherogramInfo = singleReadAlignment.getSingleRead();
271
				URI uri = null;
272
				if (pherogramInfo.getPherogram() != null) {
273
				    uri = MediaUtils.getFirstMediaRepresentationPart(pherogramInfo.getPherogram()).getUri();
274
				}
275
				int id = addRead(DerivateLabelProvider.getDerivateText(pherogramInfo, conversationHolder),
276
						uri,
277
						singleReadAlignment.isReverseComplement(),
278
						singleReadAlignment.getEditedSequence(),
279
						singleReadAlignment.getFirstSeqPosition(),
280
						singleReadAlignment.getLeftCutPosition(),
281
						singleReadAlignment.getRightCutPosition(),
282
						singleReadAlignment.getShifts());
283
				cdmMap.put(id, singleReadAlignment);
284
			}
285
			catch (Exception e) {  // Usually due to an error while trying to read the pherogram (e.g. due to an unsupported format or an invalid URI).
286
                MessagingUtils.errorDialog("Error", null, "A single read was skipped because of the following error:\n\n" +
287
                        e.getLocalizedMessage(), TaxeditorMolecularPlugin.PLUGIN_ID, e, false);
288
			}
289
		}
290

    
291
		// Set consensus sequence:
292
		AlignmentModel consensusProvider = getEditableConsensusArea().getAlignmentModel();
293
		int id = consensusProvider.addSequence(CONSENSUS_NAME);
294
		consensusProvider.insertTokensAt(id, 0, AlignmentModelUtils.charSequenceToTokenList(
295
				sequenceNode.getConsensusSequence().getString(), consensusProvider.getTokenSet()));
296
		//TODO Can the consensus sequence also be null? / Should it be created here, if nothing is in the DB?
297
    }
298

    
299
    @Override
300
    public void createPartControl(Composite parent) {
301
		SWTComponentFactory.getInstance().getSWTComponent(getAlignmentsContainer(), parent, SWT.NONE);
302
		updateStatusBar();
303

    
304
		if (getEditorInput() instanceof AlignmentEditorInput) {
305
			if (((AlignmentEditorInput)getEditorInput()).getSequenceNodeUuid() != null) {
306
			    Sequence sequenceNode = CdmStore.getService(ISequenceService.class).load(((AlignmentEditorInput)getEditorInput()).getSequenceNodeUuid());
307
		        //re-load into the current session if it is already persisted in the DB
308
		        if(sequenceNode!=null && sequenceNode.getId()!=0){
309
		            sequenceNode = CdmStore.getService(ISequenceService.class).load(sequenceNode.getUuid());
310
		        }
311
				readCDMData(sequenceNode);
312
			}
313
			else {
314
				createTestContents();  // This case will removed after the test phase and an exception should probably be thrown.
315
			}
316
		}
317
		else {
318
			throw new IllegalArgumentException("The editor input must have the type " +
319
					AlignmentEditorInput.class.getCanonicalName());  //TODO What should be done here?
320
		}
321
	}
322

    
323
    private void updateStatusBar() {
324
        IActionBars bars = getEditorSite().getActionBars();
325
        bars.getStatusLineManager().setMessage("Edit mode: " +
326
        		(getReadsArea().getEditSettings().isInsert() ? "Insert" : "Overwrite") + "  " +
327
        		"Insertion in pherogram: " +
328
	       		(getReadsArea().getEditSettings().isInsertLeftInDataArea() ? "Left" : "Right"));
329
    }
330

    
331
    private SingleReadAlignment.Shift[] convertToCDMShifts(PherogramAreaModel model) {
332
    	Iterator<ShiftChange> iterator = model.shiftChangeIterator();
333
    	List<Shift> shifts = new ArrayList<SingleReadAlignment.Shift>();
334
    	while (iterator.hasNext()) {
335
    		ShiftChange shiftChange = iterator.next();
336
    		shifts.add(new SingleReadAlignment.Shift(shiftChange.getBaseCallIndex(), shiftChange.getShiftChange()));
337
    	}
338
    	return shifts.toArray(new Shift[]{});
339
    }
340

    
341
    @Override
342
    public void doSave(IProgressMonitor monitor) {
343
    	if (getEditorInput() instanceof AlignmentEditorInput) {
344
        	String taskName = "Saving alignment";
345
            monitor.beginTask(taskName, 3);
346

    
347
            //re-loading sequence to avoid session conflicts
348
    		Sequence sequenceNode = CdmStore.getService(ISequenceService.class).load(((AlignmentEditorInput)getEditorInput()).getSequenceNodeUuid());
349
    		StringAdapter stringProvider = new StringAdapter(getEditableConsensusArea().getAlignmentModel(), false);  // Throws an exception if a token has more than one character.
350

    
351
    		// Write consensus sequence:
352
    		SequenceString consensusSequenceObj = sequenceNode.getConsensusSequence();
353
    		String newConsensusSequence = stringProvider.getSequence(
354
    				getEditableConsensusArea().getAlignmentModel().sequenceIDByName(CONSENSUS_NAME));
355
    		if (consensusSequenceObj == null) {
356
    			sequenceNode.setConsensusSequence(SequenceString.NewInstance(newConsensusSequence));
357
    		}
358
    		else {
359
    			consensusSequenceObj.setString(newConsensusSequence);
360
    		}
361

    
362
    		// Write single reads:
363
    		stringProvider.setUnderlyingProvider(getReadsArea().getAlignmentModel());
364
    		sequenceNode.getSingleReadAlignments().retainAll(cdmMap.values());  // Remove all reads that are not in the alignment anymore.
365
    		Iterator<Integer> iterator = getReadsArea().getAlignmentModel().sequenceIDIterator();
366
    		while (iterator.hasNext()) {
367
    			int id = iterator.next();
368
    			SingleReadAlignment singleRead = cdmMap.get(id);
369
    			if (singleRead == null) {
370
    			    throw new InternalError("Creating new reads from AlignmentEditor not implemented.");
371
    				//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?
372
    				//singleRead = SingleReadAlignment.NewInstance(consensusSequence, singleRead, shifts, editedSequence);
373
    			}
374

    
375
    			singleRead.setEditedSequence(stringProvider.getSequence(id));
376

    
377
    			PherogramArea pherogramArea = getPherogramArea(id);
378
    			if (pherogramArea != null) {
379
        			PherogramAreaModel model = pherogramArea.getModel();
380
        			singleRead.setReverseComplement(model.getPherogramProvider() instanceof ReverseComplementPherogramProvider);  // Works only if ReverseComplementPherogramProvider instances are not nested.
381
        			singleRead.setShifts(convertToCDMShifts(getPherogramArea(id).getModel()));
382
        			singleRead.setFirstSeqPosition(model.getFirstSeqPos());
383
        			singleRead.setLeftCutPosition(model.getLeftCutPosition());
384
        			singleRead.setRightCutPosition(model.getRightCutPosition());
385
    			}
386
    		}
387

    
388
    		if (!conversationHolder.isBound()) {
389
                conversationHolder.bind();
390
            }
391
            monitor.worked(1);
392

    
393
            // Commit the conversation and start a new transaction immediately:
394
            conversationHolder.commit(true);
395
            monitor.worked(1);
396

    
397
            dirty = false;
398
            monitor.worked(1);
399
            monitor.done();
400
            firePropertyChange(PROP_DIRTY);
401
    	}
402
    	else {
403
    		//TODO Throw exception as soon as testing period which allows unlinked AlignmentEditor is over.
404
    	}
405
    }
406

    
407
    @Override
408
    public void doSaveAs() {}
409

    
410
    @Override
411
    public void init(IEditorSite site, IEditorInput input) throws PartInitException {
412
        setSite(site);
413
        setInput(input);
414
    }
415

    
416
    @Override
417
    public boolean isDirty() {
418
        return dirty;
419
    }
420

    
421
    private void setDirty() {
422
    	dirty = true;
423
    	firePropertyChange(IEditorPart.PROP_DIRTY);
424
    }
425

    
426
    @Override
427
    public boolean isSaveAsAllowed() {
428
        return false;  // "Save as" not allowed.
429
    }
430

    
431
    @Override
432
    public void setFocus() {
433
        if(conversationHolder!=null){
434
            conversationHolder.bind();
435
        }
436
    }
437

    
438
    public boolean isInsertMode() {
439
        return getAlignmentsContainer().getEditSettings().isInsert();
440
    }
441

    
442

    
443
    public boolean isInsertLeftInPherogram() {
444
        return getAlignmentsContainer().getEditSettings().isInsertLeftInDataArea();
445
    }
446

    
447
    public void toggleLeftRightInsertionInPherogram() {
448
    	getAlignmentsContainer().getEditSettings().toggleInsertLeftInDataArea();
449
    }
450

    
451
    public void toggleInsertOverwrite() {
452
    	getAlignmentsContainer().getEditSettings().toggleInsert();
453
    }
454

    
455
    private String cutPherogram(boolean left) {
456
        SelectionModel selection = getReadsArea().getSelection();
457
        if (selection.getCursorHeight() != 1) {
458
            return "Cutting pherograms is only possible if exactly one row is selected.";
459
        }
460
        else {
461
            PherogramArea pherogramArea =
462
                    getPherogramArea(getReadsArea().getSequenceOrder().idByIndex(selection.getCursorRow()));
463
            if (pherogramArea == null) {
464
                return "There is no pherogram attached to the current sequence.";
465
            }
466
            else {
467
                if (left) {
468
                    if (pherogramArea.setLeftCutPositionBySelection()) {
469
                        return null;
470
                    }
471
                    else {
472
                        return "The left end of the selection lies outside the pherogram attached to this sequence.";
473
                    }
474
                }
475
                else {
476
                    if (pherogramArea.setRightCutPositionBySelection()) {
477
                        return null;
478
                    }
479
                    else {
480
                        return "The right end of the selection lies outside the pherogram attached to this sequence.";
481
                    }
482
                }
483
            }
484
        }
485
    }
486

    
487
    public String cutPherogramLeft() {
488
        return cutPherogram(true);
489
    }
490

    
491
    public String cutPherogramRight() {
492
        return cutPherogram(false);
493
    }
494

    
495
    public void reverseComplementSelectedSequences() {
496
    	SelectionModel selection = getReadsArea().getSelection();
497
    	AlignmentModel<?> model = getReadsArea().getAlignmentModel();
498
    	for (int row = selection.getFirstRow(); row < selection.getFirstRow() + selection.getCursorHeight(); row++) {
499
			int sequenceID = getReadsArea().getSequenceOrder().idByIndex(row);
500
			PherogramArea area = getPherogramArea(sequenceID);
501
			PherogramAreaModel pherogramAlignmentModel = area.getModel();
502
			AlignmentModelUtils.reverseComplement(model, sequenceID,
503
			        pherogramAlignmentModel.editableIndexByBaseCallIndex(
504
			                pherogramAlignmentModel.getLeftCutPosition()).getBeforeValidIndex(),
505
			        pherogramAlignmentModel.editableIndexByBaseCallIndex(
506
			                pherogramAlignmentModel.getRightCutPosition()).getAfterValidIndex());
507
			pherogramAlignmentModel.reverseComplement();
508
		}
509
    }
510

    
511
    /**
512
     * Recreates the whole consensus sequence from all single read sequences. The previous consensus
513
     * sequence is overwritte.
514
     */
515
    @SuppressWarnings("unchecked")
516
    public <T> void createConsensusSequence() {
517
        ConsensusSequenceArea area = getConsensusHintDataArea();
518
        AlignmentModel<T> model = (AlignmentModel<T>)getEditableConsensusArea().getAlignmentModel();
519
        int sequenceID = model.sequenceIDIterator().next();  // There is always one sequence contained.
520
        int length = getReadsArea().getAlignmentModel().getMaxSequenceLength();
521

    
522
        Collection<T> tokens = new ArrayList<T>(length);
523
        for (int column = 0; column < length; column++) {
524
            tokens.add(model.getTokenSet().tokenByRepresentation(area.getConsensusToken(column)));
525
        }
526

    
527
        model.removeTokensAt(sequenceID, 0, model.getSequenceLength(sequenceID));
528
        model.insertTokensAt(sequenceID, 0, tokens);
529
    }
530

    
531
    /**
532
     * Updates the current consensus sequence by replacing gaps by the according consensus tokens
533
     * calculated from the single read sequences and extends the consensus sequence if necessary.
534
     */
535
    @SuppressWarnings("unchecked")
536
    public <T> void updateConsensusSequence() {
537
        ConsensusSequenceArea area = getConsensusHintDataArea();
538
        AlignmentModel<T> model = (AlignmentModel<T>)getEditableConsensusArea().getAlignmentModel();
539
        TokenSet<T> tokenSet = model.getTokenSet();
540
        int sequenceID = model.sequenceIDIterator().next();  // There is always one sequence contained.
541
        int currentConsensusLength = model.getSequenceLength(sequenceID);
542
        int overallLength = getReadsArea().getAlignmentModel().getMaxSequenceLength();
543

    
544
        // Replace gaps by new information:
545
        for (int column = 0; column < currentConsensusLength; column++) {
546
            if (tokenSet.isGapToken(model.getTokenAt(sequenceID, column))) {
547
                T newToken = tokenSet.tokenByRepresentation(area.getConsensusToken(column));
548
                if (!tokenSet.isGapToken(newToken)) {
549
                    model.setTokenAt(sequenceID, column, newToken);
550
                }
551
            }
552
        }
553

    
554
        // Append additional tokens:
555
        if (overallLength > currentConsensusLength) {
556
            Collection<T> tokens = new ArrayList<T>(overallLength);
557
            for (int column = currentConsensusLength; column < overallLength; column++) {
558
                tokens.add(tokenSet.tokenByRepresentation(area.getConsensusToken(column)));
559
            }
560
            model.appendTokens(sequenceID, tokens);
561
        }
562
    }
563

    
564
	public static PherogramProvider readPherogram(URI uri) throws IOException, UnsupportedChromatogramFormatException {
565
	    PherogramProvider result;
566
		InputStream stream = uri.toURL().openStream();
567
		try {
568
			result = new BioJavaPherogramProvider(ChromatogramFactory.create(stream));
569
		}
570
		finally {
571
			stream.close();
572
		}
573
		return result;
574
	}
575

    
576
	private String newReadName() {
577
		int index = 1;
578
		while (getReadsArea().getAlignmentModel().sequenceIDByName(DEFAULT_READ_NAME_PREFIX + index)
579
				!= AlignmentModel.NO_SEQUENCE_FOUND) {
580

    
581
			index++;
582
		}
583
		return DEFAULT_READ_NAME_PREFIX + index;
584
	}
585

    
586
    public void addRead(URI pherogramURI, boolean reverseComplemented) throws IOException, UnsupportedChromatogramFormatException {
587
    	addRead(newReadName(), pherogramURI, reverseComplemented, null, null, null, null, null);
588
    }
589

    
590
    /**
591
     * Adds a new sequence with attached phergram data area to the reads alignment.
592
     * <p>
593
     * If {@code null} is specified as {@code editedSequence} the base call sequence from the pherogram will
594
     * be set as the edited sequence. If {@code null} is specified as {@code shifts} no shifts between the edited
595
     * and the base calls sequence are assumed.
596
     *
597
     * @param name the name of the new sequence
598
     * @param pherogramURI the URI where the associated pherogram file is located
599
     * @param reverseComplemented Specify {@code true} here, if the reverse complement of the pherogram data should
600
     *        be added, {@code false} otherwise.
601
     * @param editedSequence the edited version of the base call sequence (May be {@code null}.)
602
     * @param shifts the alignment information that links the edited and the base call sequence (May be {@code null}.)
603
     * @return the sequence ID of the added read
604
     * @throws IOException if an error occurred when trying to read the pherogram file
605
     * @throws UnsupportedChromatogramFormatException if the format of the pherogram file is not supported
606
     */
607
    public int addRead(String name, URI pherogramURI, boolean reverseComplemented, String editedSequence,
608
            Integer firstSeqPos, Integer leftCutPos, Integer rightCutPos, SingleReadAlignment.Shift[] shifts)
609
            throws IOException, UnsupportedChromatogramFormatException {
610

    
611
		AlignmentModel provider = getReadsArea().getAlignmentModel();
612
		PherogramProvider pherogramProvider = null;
613
		if (pherogramURI != null) {
614
		    pherogramProvider = readPherogram(pherogramURI);  // Must happen before a sequence is added, because it might throw an exception.
615
            if (reverseComplemented) {
616
                pherogramProvider = new ReverseComplementPherogramProvider(pherogramProvider);
617
            }
618
		}
619

    
620
        // Create sequence:
621
		provider.addSequence(name);
622
		int id = provider.sequenceIDByName(name);
623

    
624
		// Set edited sequence:
625
		Collection<Object> tokens = null;  // First save tokens in a collection to avoid GUI updated for each token.
626
		if (editedSequence != null) {
627
			tokens = AlignmentModelUtils.charSequenceToTokenList(editedSequence, provider.getTokenSet());
628
		}
629
		else if (pherogramProvider != null) {  // Copy base call sequence into alignment:
630
			tokens = new ArrayList<Object>();
631
			for (int i = 0; i < pherogramProvider.getSequenceLength(); i++) {
632
				tokens.add(provider.getTokenSet().tokenByRepresentation(
633
					Character.toString(pherogramProvider.getBaseCall(i))));
634
			}
635
			setDirty();
636
		}
637

    
638
		if (tokens != null) {  // If either an edited sequence or a pherogram URI was provided.
639
		    provider.insertTokensAt(id, 0, tokens);
640

    
641
		    if (pherogramProvider != null) {
642
		        // Create pherogram area:
643
		        PherogramArea pherogramArea = new PherogramArea(getReadsArea().getContentArea(),
644
		                new PherogramAreaModel(pherogramProvider));
645

    
646
		        // Set position properties and shifts:
647
		        PherogramAreaModel model = pherogramArea.getModel();
648
		        if ((firstSeqPos != null) && (leftCutPos != null)) {
649
		            model.setFirstSeqLeftCutPos(firstSeqPos, leftCutPos);
650
		        }
651
		        if (rightCutPos != null) {
652
		            model.setRightCutPosition(rightCutPos);
653
		        }
654
		        if ((shifts != null) && (shifts.length > 0)) {
655
		            for (int i = 0; i < shifts.length; i++) {
656
		                model.addShiftChange(shifts[i].position, shifts[i].shift);
657
		            }
658
		            setDirty();
659
		        }
660

    
661
		        // Add pherogram area to GUI:
662
		        pherogramArea.addMouseListener(new PherogramMouseListener(pherogramArea));
663
		        getReadsArea().getDataAreas().getSequenceAreas(id).add(pherogramArea);
664
		    }
665
		}
666
		return id;
667
	}
668
}
(1-1/4)