Project

General

Profile

Download (29.2 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

    
105
    private final ConversationHolder conversationHolder;
106

    
107
	private final AlignmentModelChangeListener DIRTY_LISTENER = new AlignmentModelChangeListener() {
108
				@Override
109
				public <T> void afterTokenChange(TokenChangeEvent<T> e) {
110
					setDirty();
111
				}
112

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

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

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

    
127
					setDirty();
128
				}
129
			};
130

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

    
135

    
136
    public AlignmentEditor() {
137
    	super();
138
    	conversationHolder = CdmStore.createConversation();
139
    	//conversationHolder = null;
140
    }
141

    
142

    
143
    private void refreshToolbarElement(String id) {
144
		ICommandService commandService =
145
				(ICommandService)PlatformUI.getWorkbench().getActiveWorkbenchWindow().getService(ICommandService.class);
146
		if (commandService != null) {
147
			commandService.refreshElements(id, Collections.EMPTY_MAP);
148
		}
149
    }
150

    
151

    
152
    private void registerEditSettingListener(MultipleAlignmentsContainer container) {
153
    	container.getEditSettings().addListener(new EditSettingsListener() {
154
					@Override
155
					public void workingModeChanged(EditSettingsChangeEvent e) {}  // Currently nothing to do
156

    
157
					@Override
158
					public void insertLeftInDataAreaChanged(EditSettingsChangeEvent e) {
159
						updateStatusBar();
160
				    	refreshToolbarElement(ToggleLeftRightInsertionHandler.COMMAND_ID);
161
					}
162

    
163
					@Override
164
					public void insertChanged(EditSettingsChangeEvent e) {
165
						updateStatusBar();
166
				    	refreshToolbarElement(ToggleInsertOverwriteHandler.COMMAND_ID);
167
					}
168
				});
169
    }
170

    
171

    
172
  private AlignmentArea createIndexArea(MultipleAlignmentsContainer container, AlignmentArea labeledArea) {
173
		AlignmentArea result = new AlignmentArea(container);
174
		result.setAllowVerticalScrolling(false);
175
		result.getDataAreas().getTopAreas().add(new SequenceIndexArea(result.getContentArea(), labeledArea));
176
		return result;
177
  }
178

    
179

    
180
  private AlignmentArea createEditableAlignmentArea(MultipleAlignmentsContainer container, boolean allowVerticalScrolling) {
181
		AlignmentArea result = new AlignmentArea(container);
182
		result.setAllowVerticalScrolling(allowVerticalScrolling);
183

    
184
		CharacterTokenSet tokenSet = CharacterTokenSet.newDNAInstance();  //TODO Should NUCLEOTIDE be used instead?
185
		AlignmentModel<Character> provider = new PackedAlignmentModel<Character>(tokenSet);
186
		result.setAlignmentModel(provider, false);
187
		provider.getChangeListeners().add(DIRTY_LISTENER);
188
		result.getPaintSettings().getTokenPainterList().set(0, new NucleotideTokenPainter());
189

    
190
		return result;
191
	}
192

    
193

    
194
    private AlignmentArea createConsensusHintArea(MultipleAlignmentsContainer container,
195
    		AlignmentArea labeledArea) {
196

    
197
    		AlignmentArea result = new AlignmentArea(container);
198
    		result.setAllowVerticalScrolling(false);
199
    		result.getDataAreas().getBottomAreas().add(
200
    				new ConsensusSequenceArea(result.getContentArea(), labeledArea));
201
    		return result;
202
	  }
203

    
204

    
205
    private MultipleAlignmentsContainer getAlignmentsContainer() {
206
    	if (alignmentsContainer == null) {
207
    		alignmentsContainer = new MultipleAlignmentsContainer();
208

    
209
    		AlignmentAreaList list = alignmentsContainer.getAlignmentAreas();
210
    		AlignmentArea readsArea = createEditableAlignmentArea(alignmentsContainer, true);
211
    	    list.add(createIndexArea(alignmentsContainer, readsArea));
212
    		list.add(readsArea);  // Make sure READS_AREA_INDEX is correct.
213
    		list.add(createEditableAlignmentArea(alignmentsContainer, false));  // Make sure COMSENSUS_AREA_INDEX is correct.
214
    		list.add(createConsensusHintArea(alignmentsContainer, readsArea));
215

    
216
    		registerEditSettingListener(alignmentsContainer);
217
       	}
218
		return alignmentsContainer;
219
	}
220

    
221

    
222
    public AlignmentArea getReadsArea() {
223
    	return getAlignmentsContainer().getAlignmentAreas().get(READS_AREA_INDEX);
224
    }
225

    
226

    
227
    private AlignmentArea getEditableConsensusArea() {
228
    	return getAlignmentsContainer().getAlignmentAreas().get(EDITABLE_CONSENSUS_AREA_INDEX);
229
    }
230

    
231

    
232
    public boolean hasPherogram(int sequenceID) {
233
        return getReadsArea().getDataAreas().getSequenceAreas(sequenceID).size() > PHEROGRAM_AREA_INDEX;
234
    }
235

    
236

    
237
    public PherogramArea getPherogramArea(int sequenceID) {
238
        if (hasPherogram(sequenceID)) {
239
            return (PherogramArea)getReadsArea().getDataAreas().getSequenceAreas(sequenceID).get(PHEROGRAM_AREA_INDEX);
240
        }
241
        else {
242
            return null;
243
        }
244
    }
245

    
246

    
247
    private ConsensusSequenceArea getConsensusHintDataArea() {
248
        return (ConsensusSequenceArea)getAlignmentsContainer().getAlignmentAreas().
249
                get(CONSENSUS_HINT_AREA_INDEX).getDataAreas().getBottomAreas().
250
                get(CONSENSUS_DATA_AREA_INDEX);
251
    }
252

    
253

    
254
    @Deprecated  //TODO Remove as soon as testing period is over
255
    private void createTestContents() {
256
		// Just for testing:
257
		try {
258
			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);
259
            //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);
260
            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);
261

    
262
			// Add test consensus sequence:
263
			AlignmentModel consensusModel = getEditableConsensusArea().getAlignmentModel();
264
			int id = consensusModel.addSequence(CONSENSUS_NAME);
265
			Collection<Object> tokens = new ArrayList<Object>();  // First save tokens in a collection to avoid GUI updated for each token.
266
			tokens.add(consensusModel.getTokenSet().tokenByRepresentation("A"));
267
			tokens.add(consensusModel.getTokenSet().tokenByRepresentation("C"));
268
			tokens.add(consensusModel.getTokenSet().tokenByRepresentation("G"));
269
			tokens.add(consensusModel.getTokenSet().tokenByRepresentation("T"));
270
			consensusModel.insertTokensAt(id, 0, tokens);
271
		}
272
		catch (Exception e) {
273
			throw new RuntimeException(e);
274
		}
275
    }
276

    
277

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

    
281
		// Add reads:
282
		for (SingleReadAlignment singleReadAlignment : sequenceNode.getSingleReadAlignments()) {
283
			try {
284
				SingleRead pherogramInfo = singleReadAlignment.getSingleRead();
285
				URI uri = null;
286
				if (pherogramInfo.getPherogram() != null) {
287
				    uri = MediaUtils.getFirstMediaRepresentationPart(pherogramInfo.getPherogram()).getUri();
288
				}
289
				int id = addRead(DerivateLabelProvider.getDerivateText(pherogramInfo, conversationHolder),
290
						uri,
291
						singleReadAlignment.isReverseComplement(),
292
						singleReadAlignment.getEditedSequence(),
293
						singleReadAlignment.getFirstSeqPosition(),
294
						singleReadAlignment.getLeftCutPosition(),
295
						singleReadAlignment.getRightCutPosition(),
296
						singleReadAlignment.getShifts());
297
				cdmMap.put(id, singleReadAlignment);
298
			}
299
			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).
300
                MessagingUtils.errorDialog("Error", null, "A single read was skipped because of the following error:\n\n" +
301
                        e.getLocalizedMessage(), TaxeditorMolecularPlugin.PLUGIN_ID, e, false);
302
			}
303
		}
304

    
305
		// Set consensus sequence:
306
		AlignmentModel consensusProvider = getEditableConsensusArea().getAlignmentModel();
307
		int id = consensusProvider.addSequence(CONSENSUS_NAME);
308
		consensusProvider.insertTokensAt(id, 0, AlignmentModelUtils.charSequenceToTokenList(
309
				sequenceNode.getConsensusSequence().getString(), consensusProvider.getTokenSet()));
310
		//TODO Can the consensus sequence also be null? / Should it be created here, if nothing is in the DB?
311
    }
312

    
313

    
314
	/* (non-Javadoc)
315
     * @see org.eclipse.ui.part.WorkbenchPart#createPartControl(org.eclipse.swt.widgets.Composite)
316
     */
317
    @Override
318
    public void createPartControl(Composite parent) {
319
		SWTComponentFactory.getInstance().getSWTComponent(getAlignmentsContainer(), parent, SWT.NONE);
320
		updateStatusBar();
321

    
322
		if (getEditorInput() instanceof AlignmentEditorInput) {
323
			if (((AlignmentEditorInput)getEditorInput()).getSequenceNodeUuid() != null) {
324
			    Sequence sequenceNode = CdmStore.getService(ISequenceService.class).load(((AlignmentEditorInput)getEditorInput()).getSequenceNodeUuid());
325
		        //re-load into the current session if it is already persisted in the DB
326
		        if(sequenceNode!=null && sequenceNode.getId()!=0){
327
		            sequenceNode = CdmStore.getService(ISequenceService.class).load(sequenceNode.getUuid());
328
		        }
329
				readCDMData(sequenceNode);
330
			}
331
			else {
332
				createTestContents();  // This case will removed after the test phase and an exception should probably be thrown.
333
			}
334
		}
335
		else {
336
			throw new IllegalArgumentException("The editor input must have the type " +
337
					AlignmentEditorInput.class.getCanonicalName());  //TODO What should be done here?
338
		}
339
	}
340

    
341

    
342
    private void updateStatusBar() {
343
        IActionBars bars = getEditorSite().getActionBars();
344
        bars.getStatusLineManager().setMessage("Edit mode: " +
345
        		(getReadsArea().getEditSettings().isInsert() ? "Insert" : "Overwrite") + "  " +
346
        		"Insertion in pherogram: " +
347
	       		(getReadsArea().getEditSettings().isInsertLeftInDataArea() ? "Left" : "Right"));
348
    }
349

    
350

    
351
    private SingleReadAlignment.Shift[] convertToCDMShifts(PherogramAreaModel model) {
352
    	Iterator<ShiftChange> iterator = model.shiftChangeIterator();
353
    	List<Shift> shifts = new ArrayList<SingleReadAlignment.Shift>();
354
    	while (iterator.hasNext()) {
355
    		ShiftChange shiftChange = iterator.next();
356
    		shifts.add(new SingleReadAlignment.Shift(shiftChange.getBaseCallIndex(), shiftChange.getShiftChange()));
357
    	}
358
    	return shifts.toArray(new Shift[]{});
359
    }
360

    
361

    
362
    /* (non-Javadoc)
363
     * @see org.eclipse.ui.part.EditorPart#doSave(org.eclipse.core.runtime.IProgressMonitor)
364
     */
365
    @Override
366
    public void doSave(IProgressMonitor monitor) {
367
    	if (getEditorInput() instanceof AlignmentEditorInput) {
368
        	String taskName = "Saving alignment";
369
            monitor.beginTask(taskName, 3);
370

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

    
375
    		// Write consensus sequence:
376
    		SequenceString consensusSequenceObj = sequenceNode.getConsensusSequence();
377
    		String newConsensusSequence = stringProvider.getSequence(
378
    				getEditableConsensusArea().getAlignmentModel().sequenceIDByName(CONSENSUS_NAME));
379
    		if (consensusSequenceObj == null) {
380
    			sequenceNode.setConsensusSequence(SequenceString.NewInstance(newConsensusSequence));
381
    		}
382
    		else {
383
    			consensusSequenceObj.setString(newConsensusSequence);
384
    		}
385

    
386
    		// Write single reads:
387
    		stringProvider.setUnderlyingProvider(getReadsArea().getAlignmentModel());
388
    		sequenceNode.getSingleReadAlignments().retainAll(cdmMap.values());  // Remove all reads that are not in the alignment anymore.
389
    		Iterator<Integer> iterator = getReadsArea().getAlignmentModel().sequenceIDIterator();
390
    		while (iterator.hasNext()) {
391
    			int id = iterator.next();
392
    			SingleReadAlignment singleRead = cdmMap.get(id);
393
    			if (singleRead == null) {
394
    			    throw new InternalError("Creating new reads from AlignmentEditor not implemented.");
395
    				//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?
396
    				//singleRead = SingleReadAlignment.NewInstance(consensusSequence, singleRead, shifts, editedSequence);
397
    			}
398

    
399
    			singleRead.setEditedSequence(stringProvider.getSequence(id));
400

    
401
    			PherogramArea pherogramArea = getPherogramArea(id);
402
    			if (pherogramArea != null) {
403
        			PherogramAreaModel model = pherogramArea.getModel();
404
        			singleRead.setReverseComplement(model.getPherogramProvider() instanceof ReverseComplementPherogramProvider);  // Works only if ReverseComplementPherogramProvider instances are not nested.
405
        			singleRead.setShifts(convertToCDMShifts(getPherogramArea(id).getModel()));
406
        			singleRead.setFirstSeqPosition(model.getFirstSeqPos());
407
        			singleRead.setLeftCutPosition(model.getLeftCutPosition());
408
        			singleRead.setRightCutPosition(model.getRightCutPosition());
409
    			}
410
    		}
411

    
412
    		if (!conversationHolder.isBound()) {
413
                conversationHolder.bind();
414
            }
415
            monitor.worked(1);
416

    
417
            // Commit the conversation and start a new transaction immediately:
418
            conversationHolder.commit(true);
419
            monitor.worked(1);
420

    
421
            dirty = false;
422
            monitor.worked(1);
423
            monitor.done();
424
            firePropertyChange(PROP_DIRTY);
425
    	}
426
    	else {
427
    		//TODO Throw exception as soon as testing period which allows unlinked AlignmentEditor is over.
428
    	}
429
    }
430

    
431

    
432
    /* (non-Javadoc)
433
     * @see org.eclipse.ui.part.EditorPart#doSaveAs()
434
     */
435
    @Override
436
    public void doSaveAs() {}
437

    
438

    
439
    /* (non-Javadoc)
440
     * @see org.eclipse.ui.part.EditorPart#init(org.eclipse.ui.IEditorSite, org.eclipse.ui.IEditorInput)
441
     */
442
    @Override
443
    public void init(IEditorSite site, IEditorInput input) throws PartInitException {
444
        setSite(site);
445
        setInput(input);
446
    }
447

    
448

    
449
    /* (non-Javadoc)
450
     * @see org.eclipse.ui.part.EditorPart#isDirty()
451
     */
452
    @Override
453
    public boolean isDirty() {
454
        return dirty;
455
    }
456

    
457

    
458
    private void setDirty() {
459
    	dirty = true;
460
    	firePropertyChange(IEditorPart.PROP_DIRTY);
461
    }
462

    
463

    
464
    /* (non-Javadoc)
465
     * @see org.eclipse.ui.part.EditorPart#isSaveAsAllowed()
466
     */
467
    @Override
468
    public boolean isSaveAsAllowed() {
469
        return false;  // "Save as" not allowed.
470
    }
471

    
472

    
473
    @Override
474
    public void setFocus() {
475
        if(conversationHolder!=null){
476
            conversationHolder.bind();
477
        }
478
    }
479

    
480

    
481
    public boolean isInsertMode() {
482
        return getAlignmentsContainer().getEditSettings().isInsert();
483
    }
484

    
485

    
486
    public boolean isInsertLeftInPherogram() {
487
        return getAlignmentsContainer().getEditSettings().isInsertLeftInDataArea();
488
    }
489

    
490

    
491
    public void toggleLeftRightInsertionInPherogram() {
492
    	getAlignmentsContainer().getEditSettings().toggleInsertLeftInDataArea();
493
    }
494

    
495

    
496
    public void toggleInsertOverwrite() {
497
    	getAlignmentsContainer().getEditSettings().toggleInsert();
498
    }
499

    
500

    
501
    private String cutPherogram(boolean left) {
502
        SelectionModel selection = getReadsArea().getSelection();
503
        if (selection.getCursorHeight() != 1) {
504
            return "Cutting pherograms is only possible if exactly one row is selected.";
505
        }
506
        else {
507
            PherogramArea pherogramArea =
508
                    getPherogramArea(getReadsArea().getSequenceOrder().idByIndex(selection.getCursorRow()));
509
            if (pherogramArea == null) {
510
                return "There is no pherogram attached to the current sequence.";
511
            }
512
            else {
513
                if (left) {
514
                    if (pherogramArea.setLeftCutPositionBySelection()) {
515
                        return null;
516
                    }
517
                    else {
518
                        return "The left end of the selection lies outside the pherogram attached to this sequence.";
519
                    }
520
                }
521
                else {
522
                    if (pherogramArea.setRightCutPositionBySelection()) {
523
                        return null;
524
                    }
525
                    else {
526
                        return "The right end of the selection lies outside the pherogram attached to this sequence.";
527
                    }
528
                }
529
            }
530
        }
531
    }
532

    
533

    
534
    public String cutPherogramLeft() {
535
        return cutPherogram(true);
536
    }
537

    
538

    
539
    public String cutPherogramRight() {
540
        return cutPherogram(false);
541
    }
542

    
543

    
544
    public void reverseComplementSelectedSequences() {
545
    	SelectionModel selection = getReadsArea().getSelection();
546
    	AlignmentModel<?> model = getReadsArea().getAlignmentModel();
547
    	for (int row = selection.getFirstRow(); row < selection.getFirstRow() + selection.getCursorHeight(); row++) {
548
			int sequenceID = getReadsArea().getSequenceOrder().idByIndex(row);
549
			PherogramArea area = getPherogramArea(sequenceID);
550
			PherogramAreaModel pherogramAlignmentModel = area.getModel();
551
			AlignmentModelUtils.reverseComplement(model, sequenceID,
552
			        pherogramAlignmentModel.editableIndexByBaseCallIndex(
553
			                pherogramAlignmentModel.getLeftCutPosition()).getBeforeValidIndex(),
554
			        pherogramAlignmentModel.editableIndexByBaseCallIndex(
555
			                pherogramAlignmentModel.getRightCutPosition()).getAfterValidIndex());
556
			pherogramAlignmentModel.reverseComplement();
557
		}
558
    }
559

    
560

    
561
    /**
562
     * Recreates the whole consensus sequence from all single read sequences. The previous consensus
563
     * sequence is overwritte.
564
     */
565
    @SuppressWarnings("unchecked")
566
    public <T> void createConsensusSequence() {
567
        ConsensusSequenceArea area = getConsensusHintDataArea();
568
        AlignmentModel<T> model = (AlignmentModel<T>)getEditableConsensusArea().getAlignmentModel();
569
        int sequenceID = model.sequenceIDIterator().next();  // There is always one sequence contained.
570
        int length = getReadsArea().getAlignmentModel().getMaxSequenceLength();
571

    
572
        Collection<T> tokens = new ArrayList<T>(length);
573
        for (int column = 0; column < length; column++) {
574
            tokens.add(model.getTokenSet().tokenByRepresentation(area.getConsensusToken(column)));
575
        }
576

    
577
        model.removeTokensAt(sequenceID, 0, model.getSequenceLength(sequenceID));
578
        model.insertTokensAt(sequenceID, 0, tokens);
579
    }
580

    
581

    
582
    /**
583
     * Updates the current consensus sequence by replacing gaps by the according consensus tokens
584
     * calculated from the single read sequences and extends the consensus sequence if necessary.
585
     */
586
    @SuppressWarnings("unchecked")
587
    public <T> void updateConsensusSequence() {
588
        ConsensusSequenceArea area = getConsensusHintDataArea();
589
        AlignmentModel<T> model = (AlignmentModel<T>)getEditableConsensusArea().getAlignmentModel();
590
        TokenSet<T> tokenSet = model.getTokenSet();
591
        int sequenceID = model.sequenceIDIterator().next();  // There is always one sequence contained.
592
        int currentConsensusLength = model.getSequenceLength(sequenceID);
593
        int overallLength = getReadsArea().getAlignmentModel().getMaxSequenceLength();
594

    
595
        // Replace gaps by new information:
596
        for (int column = 0; column < currentConsensusLength; column++) {
597
            if (tokenSet.isGapToken(model.getTokenAt(sequenceID, column))) {
598
                T newToken = tokenSet.tokenByRepresentation(area.getConsensusToken(column));
599
                if (!tokenSet.isGapToken(newToken)) {
600
                    model.setTokenAt(sequenceID, column, newToken);
601
                }
602
            }
603
        }
604

    
605
        // Append additional tokens:
606
        if (overallLength > currentConsensusLength) {
607
            Collection<T> tokens = new ArrayList<T>(overallLength);
608
            for (int column = currentConsensusLength; column < overallLength; column++) {
609
                tokens.add(tokenSet.tokenByRepresentation(area.getConsensusToken(column)));
610
            }
611
            model.appendTokens(sequenceID, tokens);
612
        }
613
    }
614

    
615

    
616
	public static PherogramProvider readPherogram(URI uri) throws IOException, UnsupportedChromatogramFormatException {
617
	    PherogramProvider result;
618
		InputStream stream = uri.toURL().openStream();
619
		try {
620
			result = new BioJavaPherogramProvider(ChromatogramFactory.create(stream));
621
		}
622
		finally {
623
			stream.close();
624
		}
625
		return result;
626
	}
627

    
628

    
629
	private String newReadName() {
630
		int index = 1;
631
		while (getReadsArea().getAlignmentModel().sequenceIDByName(DEFAULT_READ_NAME_PREFIX + index)
632
				!= AlignmentModel.NO_SEQUENCE_FOUND) {
633

    
634
			index++;
635
		}
636
		return DEFAULT_READ_NAME_PREFIX + index;
637
	}
638

    
639

    
640
    public void addRead(URI pherogramURI, boolean reverseComplemented) throws IOException, UnsupportedChromatogramFormatException {
641
    	addRead(newReadName(), pherogramURI, reverseComplemented, null, null, null, null, null);
642
    }
643

    
644

    
645
    /**
646
     * Adds a new sequence with attached phergram data area to the reads alignment.
647
     * <p>
648
     * If {@code null} is specified as {@code editedSequence} the base call sequence from the pherogram will
649
     * be set as the edited sequence. If {@code null} is specified as {@code shifts} no shifts between the edited
650
     * and the base calls sequence are assumed.
651
     *
652
     * @param name the name of the new sequence
653
     * @param pherogramURI the URI where the associated pherogram file is located
654
     * @param reverseComplemented Specify {@code true} here, if the reverse complement of the pherogram data should
655
     *        be added, {@code false} otherwise.
656
     * @param editedSequence the edited version of the base call sequence (May be {@code null}.)
657
     * @param shifts the alignment information that links the edited and the base call sequence (May be {@code null}.)
658
     * @return the sequence ID of the added read
659
     * @throws IOException if an error occurred when trying to read the pherogram file
660
     * @throws UnsupportedChromatogramFormatException if the format of the pherogram file is not supported
661
     */
662
    public int addRead(String name, URI pherogramURI, boolean reverseComplemented, String editedSequence,
663
            Integer firstSeqPos, Integer leftCutPos, Integer rightCutPos, SingleReadAlignment.Shift[] shifts)
664
            throws IOException, UnsupportedChromatogramFormatException {
665

    
666
		AlignmentModel provider = getReadsArea().getAlignmentModel();
667
		PherogramProvider pherogramProvider = null;
668
		if (pherogramURI != null) {
669
		    pherogramProvider = readPherogram(pherogramURI);  // Must happen before a sequence is added, because it might throw an exception.
670
            if (reverseComplemented) {
671
                pherogramProvider = new ReverseComplementPherogramProvider(pherogramProvider);
672
            }
673
		}
674

    
675
        // Create sequence:
676
		provider.addSequence(name);
677
		int id = provider.sequenceIDByName(name);
678

    
679
		// Set edited sequence:
680
		Collection<Object> tokens = null;  // First save tokens in a collection to avoid GUI updated for each token.
681
		if (editedSequence != null) {
682
			tokens = AlignmentModelUtils.charSequenceToTokenList(editedSequence, provider.getTokenSet());
683
		}
684
		else if (pherogramProvider != null) {  // Copy base call sequence into alignment:
685
			tokens = new ArrayList<Object>();
686
			for (int i = 0; i < pherogramProvider.getSequenceLength(); i++) {
687
				tokens.add(provider.getTokenSet().tokenByRepresentation(
688
					Character.toString(pherogramProvider.getBaseCall(i))));
689
			}
690
			setDirty();
691
		}
692

    
693
		if (tokens != null) {  // If either an edited sequence or a pherogram URI was provided.
694
		    provider.insertTokensAt(id, 0, tokens);
695
		    
696
		    if (pherogramProvider != null) {
697
		        // Create pherogram area:
698
		        PherogramArea pherogramArea = new PherogramArea(getReadsArea().getContentArea(),
699
		                new PherogramAreaModel(pherogramProvider));
700
	
701
		        // Set position properties and shifts:
702
		        PherogramAreaModel model = pherogramArea.getModel();
703
		        if ((firstSeqPos != null) && (leftCutPos != null)) {
704
		            model.setFirstSeqLeftCutPos(firstSeqPos, leftCutPos);
705
		        }
706
		        if (rightCutPos != null) {
707
		            model.setRightCutPosition(rightCutPos);
708
		        }
709
		        if ((shifts != null) && (shifts.length > 0)) {
710
		            for (int i = 0; i < shifts.length; i++) {
711
		                model.addShiftChange(shifts[i].position, shifts[i].shift);
712
		            }
713
		            setDirty();
714
		        }
715

    
716
		        // Add pherogram area to GUI:
717
		        pherogramArea.addMouseListener(new PherogramMouseListener(pherogramArea));
718
		        getReadsArea().getDataAreas().getSequenceAreas(id).add(pherogramArea);
719
		    }
720
		}
721
		return id;
722
	}
723
}
(1-1/4)