Project

General

Profile

Download (29 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
    			PherogramAreaModel model = getPherogramArea(id).getModel();
402
    			singleRead.setReverseComplement(model.getPherogramProvider() instanceof ReverseComplementPherogramProvider);  // Works only if ReverseComplementPherogramProvider instances are not nested.
403
    			singleRead.setShifts(convertToCDMShifts(getPherogramArea(id).getModel()));
404
    			singleRead.setFirstSeqPosition(model.getFirstSeqPos());
405
    			singleRead.setLeftCutPosition(model.getLeftCutPosition());
406
    			singleRead.setRightCutPosition(model.getRightCutPosition());
407
    		}
408

    
409
    		if (!conversationHolder.isBound()) {
410
                conversationHolder.bind();
411
            }
412
            monitor.worked(1);
413

    
414
            // Commit the conversation and start a new transaction immediately:
415
            conversationHolder.commit(true);
416
            monitor.worked(1);
417

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

    
428

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

    
435

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

    
445

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

    
454

    
455
    private void setDirty() {
456
    	dirty = true;
457
    	firePropertyChange(IEditorPart.PROP_DIRTY);
458
    }
459

    
460

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

    
469

    
470
    @Override
471
    public void setFocus() {
472
        if(conversationHolder!=null){
473
            conversationHolder.bind();
474
        }
475
    }
476

    
477

    
478
    public boolean isInsertMode() {
479
        return getAlignmentsContainer().getEditSettings().isInsert();
480
    }
481

    
482

    
483
    public boolean isInsertLeftInPherogram() {
484
        return getAlignmentsContainer().getEditSettings().isInsertLeftInDataArea();
485
    }
486

    
487

    
488
    public void toggleLeftRightInsertionInPherogram() {
489
    	getAlignmentsContainer().getEditSettings().toggleInsertLeftInDataArea();
490
    }
491

    
492

    
493
    public void toggleInsertOverwrite() {
494
    	getAlignmentsContainer().getEditSettings().toggleInsert();
495
    }
496

    
497

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

    
530

    
531
    public String cutPherogramLeft() {
532
        return cutPherogram(true);
533
    }
534

    
535

    
536
    public String cutPherogramRight() {
537
        return cutPherogram(false);
538
    }
539

    
540

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

    
557

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

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

    
574
        model.removeTokensAt(sequenceID, 0, model.getSequenceLength(sequenceID));
575
        model.insertTokensAt(sequenceID, 0, tokens);
576
    }
577

    
578

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

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

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

    
612

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

    
625

    
626
	private String newReadName() {
627
		int index = 1;
628
		while (getReadsArea().getAlignmentModel().sequenceIDByName(DEFAULT_READ_NAME_PREFIX + index)
629
				!= AlignmentModel.NO_SEQUENCE_FOUND) {
630

    
631
			index++;
632
		}
633
		return DEFAULT_READ_NAME_PREFIX + index;
634
	}
635

    
636

    
637
    public void addRead(URI pherogramURI, boolean reverseComplemented) throws IOException, UnsupportedChromatogramFormatException {
638
    	addRead(newReadName(), pherogramURI, reverseComplemented, null, null, null, null, null);
639
    }
640

    
641

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

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

    
672
        // Create sequence:
673
		provider.addSequence(name);
674
		int id = provider.sequenceIDByName(name);
675

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

    
690
		if (tokens != null) {  // If either an edited sequence or a pherogram URI was provided.
691
		    provider.insertTokensAt(id, 0, tokens);
692
	        // Create pherogram area:
693
	        PherogramArea pherogramArea = new PherogramArea(getReadsArea().getContentArea(),
694
	                new PherogramAreaModel(pherogramProvider));
695

    
696
	        // Set position properties and shifts:
697
	        PherogramAreaModel model = pherogramArea.getModel();
698
	        if ((firstSeqPos != null) && (leftCutPos != null)) {
699
	            model.setFirstSeqLeftCutPos(firstSeqPos, leftCutPos);
700
	        }
701
	        if (rightCutPos != null) {
702
	            model.setRightCutPosition(rightCutPos);
703
	        }
704
	        if ((shifts != null) && (shifts.length > 0)) {
705
	            for (int i = 0; i < shifts.length; i++) {
706
	                model.addShiftChange(shifts[i].position, shifts[i].shift);
707
	            }
708
	            setDirty();
709
	        }
710

    
711
	        // Add pherogram area to GUI:
712
	        pherogramArea.addMouseListener(new PherogramMouseListener(pherogramArea));
713
	        getReadsArea().getDataAreas().getSequenceAreas(id).add(pherogramArea);
714
		}
715

    
716
		return id;
717
	}
718
}
(1-1/4)