Project

General

Profile

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

    
41
import java.io.File;
42
import java.io.IOException;
43
import java.io.InputStream;
44
import java.net.URI;
45
import java.util.ArrayList;
46
import java.util.Collection;
47
import java.util.Collections;
48
import java.util.Iterator;
49
import java.util.List;
50
import java.util.Map;
51
import java.util.TreeMap;
52

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

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

    
84

    
85

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

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

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

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

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

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

    
129
					setDirty();
130
				}
131
			};
132
	private final AlignmentEditorActionUpdater ACTION_UPDATER = new AlignmentEditorActionUpdater();
133
	public final Clipboard CLIPBOARD = new Clipboard(Display.getCurrent());  //TODO Move to global EDITor class. 
134
			
135

    
136
    private MultipleAlignmentsContainer alignmentsContainer = null;
137
    private final Map<Integer, SingleReadAlignment> cdmMap = new TreeMap<Integer, SingleReadAlignment>();  //TODO Move this to ContigSequenceDataProvider
138
    private boolean dirty = false;
139

    
140
    public AlignmentEditor()
141
    {
142
    	super();
143
    	conversationHolder = CdmStore.createConversation();
144
    	//conversationHolder = null;
145
    }
146

    
147
    
148
    private void refreshToolbarElement(String id) {
149
		ICommandService commandService =
150
				(ICommandService)PlatformUI.getWorkbench().getActiveWorkbenchWindow().getService(ICommandService.class);
151
		if (commandService != null) {
152
			commandService.refreshElements(id, Collections.EMPTY_MAP);
153
		}
154
    }
155

    
156
    
157
    private void registerEditSettingListener(MultipleAlignmentsContainer container) {
158
        container.getEditSettings().addListener(new EditSettingsListener() {
159
            @Override
160
            public void workingModeChanged(EditSettingsChangeEvent e) {}  // Currently nothing to do
161

    
162
            @Override
163
            public void insertLeftInDataAreaChanged(EditSettingsChangeEvent e) {
164
                updateStatusBar();
165
                refreshToolbarElement(ToggleLeftRightInsertionHandler.COMMAND_ID);
166
            }
167

    
168
            @Override
169
            public void insertChanged(EditSettingsChangeEvent e) {
170
                updateStatusBar();
171
                refreshToolbarElement(ToggleInsertOverwriteHandler.COMMAND_ID);
172
            }
173
        });
174
    }
175

    
176

    
177
    private AlignmentArea createIndexArea(MultipleAlignmentsContainer container, AlignmentArea labeledArea) {
178
		AlignmentArea result = new AlignmentArea(container);
179
		result.setAllowVerticalScrolling(false);
180
		result.getDataAreas().getTopAreas().add(new SequenceIndexArea(result.getContentArea(), labeledArea));
181
		return result;
182
    }
183

    
184

    
185
    private AlignmentArea createEditableAlignmentArea(MultipleAlignmentsContainer container, boolean allowVerticalScrolling) {
186
		AlignmentArea result = new AlignmentArea(container);
187
		result.setAllowVerticalScrolling(allowVerticalScrolling);
188

    
189
		CharacterTokenSet tokenSet = CharacterTokenSet.newDNAInstance();  //TODO Should NUCLEOTIDE be used instead?
190
		AlignmentModel<Character> provider = new PackedAlignmentModel<Character>(tokenSet);
191
		result.setAlignmentModel(provider, false);
192
		provider.getChangeListeners().add(DIRTY_LISTENER);
193
		result.getPaintSettings().getTokenPainterList().set(0, new NucleotideTokenPainter());
194

    
195
		return result;
196
	}
197

    
198
    
199
    private AlignmentArea createConsensusHintArea(MultipleAlignmentsContainer container,
200
    		AlignmentArea labeledArea) {
201

    
202
		AlignmentArea result = new AlignmentArea(container);
203
		result.setAllowVerticalScrolling(false);
204
		result.getDataAreas().getBottomAreas().add(
205
				new ConsensusSequenceArea(result.getContentArea(), labeledArea));
206
		return result;
207
    }
208

    
209
    
210
    private MultipleAlignmentsContainer getAlignmentsContainer() {
211
    	if (alignmentsContainer == null) {
212
    		alignmentsContainer = new MultipleAlignmentsContainer();
213
    		
214
    		AlignmentAreaList list = alignmentsContainer.getAlignmentAreas();
215
    		AlignmentArea readsArea = createEditableAlignmentArea(alignmentsContainer, true);
216
    		readsArea.getSelection().addSelectionListener(ACTION_UPDATER);
217
    	    list.add(createIndexArea(alignmentsContainer, readsArea));
218
    		list.add(readsArea);  // Make sure READS_AREA_INDEX is correct.
219
    		AlignmentArea editableConsensusArea = createEditableAlignmentArea(alignmentsContainer, false);
220
    		editableConsensusArea.getSelection().addSelectionListener(ACTION_UPDATER);
221
    		list.add(editableConsensusArea);  // Make sure COMSENSUS_AREA_INDEX is correct.
222
    		list.add(createConsensusHintArea(alignmentsContainer, readsArea));
223
    		
224
    		registerEditSettingListener(alignmentsContainer);
225
       	}
226
		return alignmentsContainer;
227
	}
228

    
229
    
230
    public AlignmentArea getReadsArea() {
231
    	return getAlignmentsContainer().getAlignmentAreas().get(READS_AREA_INDEX);
232
    }
233

    
234
    
235
    public AlignmentArea getEditableConsensusArea() {
236
    	return getAlignmentsContainer().getAlignmentAreas().get(EDITABLE_CONSENSUS_AREA_INDEX);
237
    }
238
    
239
    
240
    /**
241
     * Checks whether {@link #getReadsArea()} or {@link #getEditableConsensusArea()} currently
242
     * have the user focus and returns the according component. 
243
     * 
244
     * @return either the reads or the consensus alignment area or {@code null} if none of these
245
     *         components is currently focused
246
     */
247
    public AlignmentArea getFocusedArea() {
248
    	AlignmentArea result = getReadsArea();
249
    	if (hasFocus(result)) {
250
    		return result;
251
    	}
252
    	else {
253
    		result = getEditableConsensusArea();
254
        	if (hasFocus(result)) {
255
        		return result;
256
        	}
257
        	else {
258
        		return null;
259
        	}
260
    	}
261
    }
262
    
263
    
264
    /**
265
     * Checks whether the specified alignment area or one of its subcomponents currently has the
266
     * focus.
267
     * 
268
     * @param area the alignment area to be checked (Can only be {@link #getReadsArea()} or 
269
     *        {@link #getEditableConsensusArea()}.)
270
     * @return {@code true} if the specified component is focused and is either equal to
271
     *         {@link #getReadsArea()} or {@link #getEditableConsensusArea()}or {@code false} otherwise
272
     */
273
    private boolean hasFocus(AlignmentArea area) {
274
    	return SWTUtils.childHasFocus((Composite)area.getToolkitComponent());
275
    }
276
    
277
    
278
    public boolean hasPherogram(int sequenceID) {
279
        return getReadsArea().getDataAreas().getSequenceAreas(sequenceID).size() > PHEROGRAM_AREA_INDEX;
280
    }
281

    
282
    
283
    public PherogramArea getPherogramArea(int sequenceID) {
284
        if (hasPherogram(sequenceID)) {
285
            return (PherogramArea)getReadsArea().getDataAreas().getSequenceAreas(sequenceID).get(PHEROGRAM_AREA_INDEX);
286
        }
287
        else {
288
            return null;
289
        }
290
    }
291

    
292
    
293
    private ConsensusSequenceArea getConsensusHintDataArea() {
294
        return (ConsensusSequenceArea)getAlignmentsContainer().getAlignmentAreas().
295
                get(CONSENSUS_HINT_AREA_INDEX).getDataAreas().getBottomAreas().
296
                get(CONSENSUS_DATA_AREA_INDEX);
297
    }
298

    
299
    
300
    @Deprecated  //TODO Remove as soon as testing period is over
301
    private void createTestContents() {
302
		// Just for testing:
303
		try {
304
			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);
305
            //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);
306
            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);
307

    
308
			// Add test consensus sequence:
309
			AlignmentModel consensusModel = getEditableConsensusArea().getAlignmentModel();
310
			int id = consensusModel.addSequence(CONSENSUS_NAME);
311
			Collection<Object> tokens = new ArrayList<Object>();  // First save tokens in a collection to avoid GUI updated for each token.
312
			tokens.add(consensusModel.getTokenSet().tokenByRepresentation("A"));
313
			tokens.add(consensusModel.getTokenSet().tokenByRepresentation("C"));
314
			tokens.add(consensusModel.getTokenSet().tokenByRepresentation("G"));
315
			tokens.add(consensusModel.getTokenSet().tokenByRepresentation("T"));
316
			consensusModel.insertTokensAt(id, 0, tokens);
317
		}
318
		catch (Exception e) {
319
			throw new RuntimeException(e);
320
		}
321
    }
322

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

    
327
		// Add reads:
328
		for (SingleReadAlignment singleReadAlignment : sequenceNode.getSingleReadAlignments()) {
329
			try {
330
				SingleRead pherogramInfo = singleReadAlignment.getSingleRead();
331
				URI uri = null;
332
				if (pherogramInfo.getPherogram() != null) {
333
				    uri = MediaUtils.getFirstMediaRepresentationPart(pherogramInfo.getPherogram()).getUri();
334
				}
335
				int id = addRead(DerivateLabelProvider.getDerivateText(pherogramInfo, conversationHolder),
336
						uri,
337
						singleReadAlignment.isReverseComplement(),
338
						singleReadAlignment.getEditedSequence(),
339
						singleReadAlignment.getFirstSeqPosition(),
340
						singleReadAlignment.getLeftCutPosition(),
341
						singleReadAlignment.getRightCutPosition(),
342
						singleReadAlignment.getShifts());
343
				cdmMap.put(id, singleReadAlignment);
344
			}
345
			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).
346
                MessagingUtils.errorDialog("Error", null, "A single read was skipped because of the following error:\n\n" +
347
                        e.getLocalizedMessage(), TaxeditorMolecularPlugin.PLUGIN_ID, e, false);
348
			}
349
		}
350

    
351
		// Set consensus sequence:
352
		AlignmentModel consensusProvider = getEditableConsensusArea().getAlignmentModel();
353
		int id = consensusProvider.addSequence(CONSENSUS_NAME);
354
		consensusProvider.insertTokensAt(id, 0, AlignmentModelUtils.charSequenceToTokenList(
355
				sequenceNode.getConsensusSequence().getString(), consensusProvider.getTokenSet()));
356
		//TODO Can the consensus sequence also be null? / Should it be created here, if nothing is in the DB?
357
    }
358

    
359
    
360
    @Override
361
    public void createPartControl(Composite parent) {
362
		SWTComponentFactory.getInstance().getSWTComponent(getAlignmentsContainer(), parent, SWT.NONE);
363
		Display.getCurrent().addFilter(SWT.FocusIn, ACTION_UPDATER);
364
		Display.getCurrent().addFilter(SWT.FocusOut, ACTION_UPDATER);
365
		updateStatusBar();
366

    
367
		if (getEditorInput() instanceof AlignmentEditorInput) {
368
			if (((AlignmentEditorInput)getEditorInput()).getSequenceNodeUuid() != null) {
369
			    Sequence sequenceNode = CdmStore.getService(ISequenceService.class).load(((AlignmentEditorInput)getEditorInput()).getSequenceNodeUuid());
370
		        //re-load into the current session if it is already persisted in the DB
371
		        if(sequenceNode!=null && sequenceNode.getId()!=0){
372
		            sequenceNode = CdmStore.getService(ISequenceService.class).load(sequenceNode.getUuid());
373
		        }
374
				readCDMData(sequenceNode);
375
			}
376
			else {
377
				createTestContents();  // This case will removed after the test phase and an exception should probably be thrown.
378
			}
379
		}
380
		else {
381
			throw new IllegalArgumentException("The editor input must have the type " +
382
					AlignmentEditorInput.class.getCanonicalName());  //TODO What should be done here?
383
		}
384
	}
385

    
386
    
387
    @Override
388
	public void dispose() {
389
		Display.getCurrent().removeFilter(SWT.FocusIn, ACTION_UPDATER);
390
		Display.getCurrent().removeFilter(SWT.FocusOut, ACTION_UPDATER);
391
		CLIPBOARD.dispose();
392
		super.dispose();
393
	}
394

    
395

    
396
	private void updateStatusBar() {
397
        IActionBars bars = getEditorSite().getActionBars();
398
        bars.getStatusLineManager().setMessage("Edit mode: " +
399
        		(getReadsArea().getEditSettings().isInsert() ? "Insert" : "Overwrite") + "  " +
400
        		"Insertion in pherogram: " +
401
	       		(getReadsArea().getEditSettings().isInsertLeftInDataArea() ? "Left" : "Right"));
402
    }
403

    
404
    
405
    private SingleReadAlignment.Shift[] convertToCDMShifts(PherogramAreaModel model) {
406
    	Iterator<ShiftChange> iterator = model.shiftChangeIterator();
407
    	List<Shift> shifts = new ArrayList<SingleReadAlignment.Shift>();
408
    	while (iterator.hasNext()) {
409
    		ShiftChange shiftChange = iterator.next();
410
    		shifts.add(new SingleReadAlignment.Shift(shiftChange.getBaseCallIndex(), shiftChange.getShiftChange()));
411
    	}
412
    	return shifts.toArray(new Shift[]{});
413
    }
414

    
415
    
416
    @Override
417
    public void doSave(IProgressMonitor monitor) {
418
    	if (getEditorInput() instanceof AlignmentEditorInput) {
419
        	String taskName = "Saving alignment";
420
            monitor.beginTask(taskName, 3);
421

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

    
426
    		// Write consensus sequence:
427
    		SequenceString consensusSequenceObj = sequenceNode.getConsensusSequence();
428
    		String newConsensusSequence = stringProvider.getSequence(
429
    				getEditableConsensusArea().getAlignmentModel().sequenceIDByName(CONSENSUS_NAME));
430
    		if (consensusSequenceObj == null) {
431
    			sequenceNode.setConsensusSequence(SequenceString.NewInstance(newConsensusSequence));
432
    		}
433
    		else {
434
    			consensusSequenceObj.setString(newConsensusSequence);
435
    		}
436

    
437
    		// Write single reads:
438
    		stringProvider.setUnderlyingProvider(getReadsArea().getAlignmentModel());
439
    		sequenceNode.getSingleReadAlignments().retainAll(cdmMap.values());  // Remove all reads that are not in the alignment anymore.
440
    		Iterator<Integer> iterator = getReadsArea().getAlignmentModel().sequenceIDIterator();
441
    		while (iterator.hasNext()) {
442
    			int id = iterator.next();
443
    			SingleReadAlignment singleRead = cdmMap.get(id);
444
    			if (singleRead == null) {
445
    			    throw new InternalError("Creating new reads from AlignmentEditor not implemented.");
446
    				//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?
447
    				//singleRead = SingleReadAlignment.NewInstance(consensusSequence, singleRead, shifts, editedSequence);
448
    			}
449

    
450
    			singleRead.setEditedSequence(stringProvider.getSequence(id));
451

    
452
    			PherogramArea pherogramArea = getPherogramArea(id);
453
    			if (pherogramArea != null) {
454
        			PherogramAreaModel model = pherogramArea.getModel();
455
        			singleRead.setReverseComplement(model.getPherogramProvider() instanceof ReverseComplementPherogramProvider);  // Works only if ReverseComplementPherogramProvider instances are not nested.
456
        			singleRead.setShifts(convertToCDMShifts(getPherogramArea(id).getModel()));
457
        			singleRead.setFirstSeqPosition(model.getFirstSeqPos());
458
        			singleRead.setLeftCutPosition(model.getLeftCutPosition());
459
        			singleRead.setRightCutPosition(model.getRightCutPosition());
460
    			}
461
    		}
462

    
463
    		if (!conversationHolder.isBound()) {
464
                conversationHolder.bind();
465
            }
466
            monitor.worked(1);
467

    
468
            // Commit the conversation and start a new transaction immediately:
469
            conversationHolder.commit(true);
470
            monitor.worked(1);
471

    
472
            dirty = false;
473
            monitor.worked(1);
474
            monitor.done();
475
            firePropertyChange(PROP_DIRTY);
476
    	}
477
    	else {
478
    		//TODO Throw exception as soon as testing period which allows unlinked AlignmentEditor is over.
479
    	}
480
    }
481

    
482
    
483
    @Override
484
    public void doSaveAs() {}
485

    
486
    
487
    @Override
488
    public void init(IEditorSite site, IEditorInput input) throws PartInitException {
489
        setSite(site);
490
        setInput(input);
491
    }
492

    
493
    
494
    @Override
495
    public boolean isDirty() {
496
        return dirty;
497
    }
498

    
499
    
500
    private void setDirty() {
501
    	dirty = true;
502
    	firePropertyChange(IEditorPart.PROP_DIRTY);
503
    }
504

    
505
    
506
    @Override
507
    public boolean isSaveAsAllowed() {
508
        return false;  // "Save as" not allowed.
509
    }
510

    
511
    
512
    @Override
513
    public void setFocus() {
514
        if(conversationHolder != null){
515
            conversationHolder.bind();
516
        }
517
    }
518

    
519
    
520
    public boolean isInsertMode() {
521
        return getAlignmentsContainer().getEditSettings().isInsert();
522
    }
523

    
524

    
525
    public boolean isInsertLeftInPherogram() {
526
        return getAlignmentsContainer().getEditSettings().isInsertLeftInDataArea();
527
    }
528

    
529
    
530
    public void toggleLeftRightInsertionInPherogram() {
531
    	getAlignmentsContainer().getEditSettings().toggleInsertLeftInDataArea();
532
    }
533

    
534
    
535
    public void toggleInsertOverwrite() {
536
    	getAlignmentsContainer().getEditSettings().toggleInsert();
537
    }
538

    
539
    
540
    private String cutPherogram(boolean left) {
541
        SelectionModel selection = getReadsArea().getSelection();
542
        if (selection.getCursorHeight() != 1) {
543
            return "Cutting pherograms is only possible if exactly one row is selected.";
544
        }
545
        else {
546
            PherogramArea pherogramArea =
547
                    getPherogramArea(getReadsArea().getSequenceOrder().idByIndex(selection.getCursorRow()));
548
            if (pherogramArea == null) {
549
                return "There is no pherogram attached to the current sequence.";
550
            }
551
            else {
552
                if (left) {
553
                    if (pherogramArea.setLeftCutPositionBySelection()) {
554
                        return null;
555
                    }
556
                    else {
557
                        return "The left end of the selection lies outside the pherogram attached to this sequence.";
558
                    }
559
                }
560
                else {
561
                    if (pherogramArea.setRightCutPositionBySelection()) {
562
                        return null;
563
                    }
564
                    else {
565
                        return "The right end of the selection lies outside the pherogram attached to this sequence.";
566
                    }
567
                }
568
            }
569
        }
570
    }
571

    
572
    
573
    public String cutPherogramLeft() {
574
        return cutPherogram(true);
575
    }
576

    
577
    
578
    public String cutPherogramRight() {
579
        return cutPherogram(false);
580
    }
581

    
582
    
583
    public void reverseComplementSelectedSequences() {
584
    	SelectionModel selection = getReadsArea().getSelection();
585
    	AlignmentModel<?> model = getReadsArea().getAlignmentModel();
586
    	for (int row = selection.getFirstRow(); row < selection.getFirstRow() + selection.getCursorHeight(); row++) {
587
			int sequenceID = getReadsArea().getSequenceOrder().idByIndex(row);
588
			PherogramArea area = getPherogramArea(sequenceID);
589
			PherogramAreaModel pherogramAlignmentModel = area.getModel();
590
			AlignmentModelUtils.reverseComplement(model, sequenceID,
591
			        pherogramAlignmentModel.editableIndexByBaseCallIndex(
592
			                pherogramAlignmentModel.getLeftCutPosition()).getBeforeValidIndex(),
593
			        pherogramAlignmentModel.editableIndexByBaseCallIndex(
594
			                pherogramAlignmentModel.getRightCutPosition()).getAfterValidIndex());
595
			pherogramAlignmentModel.reverseComplement();
596
		}
597
    }
598

    
599
    
600
    /**
601
     * Recreates the whole consensus sequence from all single read sequences. The previous consensus
602
     * sequence is overwritte.
603
     */
604
    @SuppressWarnings("unchecked")
605
    public <T> void createConsensusSequence() {
606
        ConsensusSequenceArea area = getConsensusHintDataArea();
607
        AlignmentModel<T> model = (AlignmentModel<T>)getEditableConsensusArea().getAlignmentModel();
608
        int sequenceID = model.sequenceIDIterator().next();  // There is always one sequence contained.
609
        int length = getReadsArea().getAlignmentModel().getMaxSequenceLength();
610

    
611
        Collection<T> tokens = new ArrayList<T>(length);
612
        for (int column = 0; column < length; column++) {
613
            tokens.add(model.getTokenSet().tokenByRepresentation(area.getConsensusToken(column)));
614
        }
615

    
616
        model.removeTokensAt(sequenceID, 0, model.getSequenceLength(sequenceID));
617
        model.insertTokensAt(sequenceID, 0, tokens);
618
    }
619

    
620
    
621
    /**
622
     * Updates the current consensus sequence by replacing gaps by the according consensus tokens
623
     * calculated from the single read sequences and extends the consensus sequence if necessary.
624
     */
625
    @SuppressWarnings("unchecked")
626
    public <T> void updateConsensusSequence() {
627
        ConsensusSequenceArea area = getConsensusHintDataArea();
628
        AlignmentModel<T> model = (AlignmentModel<T>)getEditableConsensusArea().getAlignmentModel();
629
        TokenSet<T> tokenSet = model.getTokenSet();
630
        int sequenceID = model.sequenceIDIterator().next();  // There is always one sequence contained.
631
        int currentConsensusLength = model.getSequenceLength(sequenceID);
632
        int overallLength = getReadsArea().getAlignmentModel().getMaxSequenceLength();
633

    
634
        // Replace gaps by new information:
635
        for (int column = 0; column < currentConsensusLength; column++) {
636
            if (tokenSet.isGapToken(model.getTokenAt(sequenceID, column))) {
637
                T newToken = tokenSet.tokenByRepresentation(area.getConsensusToken(column));
638
                if (!tokenSet.isGapToken(newToken)) {
639
                    model.setTokenAt(sequenceID, column, newToken);
640
                }
641
            }
642
        }
643

    
644
        // Append additional tokens:
645
        if (overallLength > currentConsensusLength) {
646
            Collection<T> tokens = new ArrayList<T>(overallLength);
647
            for (int column = currentConsensusLength; column < overallLength; column++) {
648
                tokens.add(tokenSet.tokenByRepresentation(area.getConsensusToken(column)));
649
            }
650
            model.appendTokens(sequenceID, tokens);
651
        }
652
    }
653

    
654
    
655
	public static PherogramProvider readPherogram(URI uri) throws IOException, UnsupportedChromatogramFormatException {
656
	    PherogramProvider result;
657
		InputStream stream = uri.toURL().openStream();
658
		try {
659
			result = new BioJavaPherogramProvider(ChromatogramFactory.create(stream));
660
		}
661
		finally {
662
			stream.close();
663
		}
664
		return result;
665
	}
666

    
667
	
668
	private String newReadName() {
669
		int index = 1;
670
		while (getReadsArea().getAlignmentModel().sequenceIDByName(DEFAULT_READ_NAME_PREFIX + index)
671
				!= AlignmentModel.NO_SEQUENCE_FOUND) {
672

    
673
			index++;
674
		}
675
		return DEFAULT_READ_NAME_PREFIX + index;
676
	}
677

    
678
	
679
    public void addRead(URI pherogramURI, boolean reverseComplemented) throws IOException, UnsupportedChromatogramFormatException {
680
    	addRead(newReadName(), pherogramURI, reverseComplemented, null, null, null, null, null);
681
    }
682

    
683
    
684
    /**
685
     * Adds a new sequence with attached phergram data area to the reads alignment.
686
     * <p>
687
     * If {@code null} is specified as {@code editedSequence} the base call sequence from the pherogram will
688
     * be set as the edited sequence. If {@code null} is specified as {@code shifts} no shifts between the edited
689
     * and the base calls sequence are assumed.
690
     *
691
     * @param name the name of the new sequence
692
     * @param pherogramURI the URI where the associated pherogram file is located
693
     * @param reverseComplemented Specify {@code true} here, if the reverse complement of the pherogram data should
694
     *        be added, {@code false} otherwise.
695
     * @param editedSequence the edited version of the base call sequence (May be {@code null}.)
696
     * @param shifts the alignment information that links the edited and the base call sequence (May be {@code null}.)
697
     * @return the sequence ID of the added read
698
     * @throws IOException if an error occurred when trying to read the pherogram file
699
     * @throws UnsupportedChromatogramFormatException if the format of the pherogram file is not supported
700
     */
701
    public int addRead(String name, URI pherogramURI, boolean reverseComplemented, String editedSequence,
702
            Integer firstSeqPos, Integer leftCutPos, Integer rightCutPos, SingleReadAlignment.Shift[] shifts)
703
            throws IOException, UnsupportedChromatogramFormatException {
704

    
705
		AlignmentModel provider = getReadsArea().getAlignmentModel();
706
		PherogramProvider pherogramProvider = null;
707
		if (pherogramURI != null) {
708
		    pherogramProvider = readPherogram(pherogramURI);  // Must happen before a sequence is added, because it might throw an exception.
709
            if (reverseComplemented) {
710
                pherogramProvider = new ReverseComplementPherogramProvider(pherogramProvider);
711
            }
712
		}
713

    
714
        // Create sequence:
715
		provider.addSequence(name);
716
		int id = provider.sequenceIDByName(name);
717

    
718
		// Set edited sequence:
719
		Collection<Object> tokens = null;  // First save tokens in a collection to avoid GUI updated for each token.
720
		if (editedSequence != null) {
721
			tokens = AlignmentModelUtils.charSequenceToTokenList(editedSequence, provider.getTokenSet());
722
		}
723
		else if (pherogramProvider != null) {  // Copy base call sequence into alignment:
724
			tokens = new ArrayList<Object>();
725
			for (int i = 0; i < pherogramProvider.getSequenceLength(); i++) {
726
				tokens.add(provider.getTokenSet().tokenByRepresentation(
727
					Character.toString(pherogramProvider.getBaseCall(i))));
728
			}
729
			setDirty();
730
		}
731

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

    
735
		    if (pherogramProvider != null) {
736
		        // Create pherogram area:
737
		        PherogramArea pherogramArea = new PherogramArea(getReadsArea().getContentArea(),
738
		                new PherogramAreaModel(pherogramProvider));
739

    
740
		        // Set position properties and shifts:
741
		        PherogramAreaModel model = pherogramArea.getModel();
742
		        if ((firstSeqPos != null) && (leftCutPos != null)) {
743
		            model.setFirstSeqLeftCutPos(firstSeqPos, leftCutPos);
744
		        }
745
		        if (rightCutPos != null) {
746
		            model.setRightCutPosition(rightCutPos);
747
		        }
748
		        if ((shifts != null) && (shifts.length > 0)) {
749
		            for (int i = 0; i < shifts.length; i++) {
750
		                model.addShiftChange(shifts[i].position, shifts[i].shift);
751
		            }
752
		            setDirty();
753
		        }
754

    
755
		        // Add pherogram area to GUI:
756
		        pherogramArea.addMouseListener(new PherogramMouseListener(pherogramArea));
757
		        getReadsArea().getDataAreas().getSequenceAreas(id).add(pherogramArea);
758
		    }
759
		}
760
		return id;
761
	}
762
}
(1-1/5)