Project

General

Profile

Download (31 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.pherogram.PherogramArea;
19
import info.bioinfweb.libralign.dataarea.implementations.sequenceindex.SequenceIndexArea;
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.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.model.utils.AlignmentModelUtils;
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
        ((AlignmentEditorInput)getEditorInput()).dispose();
393
		super.dispose();
394
	}
395

    
396

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

    
405

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

    
416

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

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

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

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

    
452
    			singleRead.setEditedSequence(stringProvider.getSequence(id));
453

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

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

    
470
            ((AlignmentEditorInput)getEditorInput()).merge();
471
            // Commit the conversation and start a new transaction immediately:
472
            conversationHolder.commit(true);
473
            monitor.worked(1);
474

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

    
485

    
486
    @Override
487
    public void doSaveAs() {}
488

    
489

    
490
    @Override
491
    public void init(IEditorSite site, IEditorInput input) throws PartInitException {
492
        setSite(site);
493
        setInput(input);
494
    }
495

    
496

    
497
    @Override
498
    public boolean isDirty() {
499
        return dirty;
500
    }
501

    
502

    
503
    private void setDirty() {
504
    	dirty = true;
505
    	firePropertyChange(IEditorPart.PROP_DIRTY);
506
    }
507

    
508

    
509
    @Override
510
    public boolean isSaveAsAllowed() {
511
        return false;  // "Save as" not allowed.
512
    }
513

    
514

    
515
    @Override
516
    public void setFocus() {
517
        if(conversationHolder != null){
518
            conversationHolder.bind();
519
        }
520
        ((AlignmentEditorInput)getEditorInput()).bind();
521
    }
522

    
523
    public boolean isInsertMode() {
524
        return getAlignmentsContainer().getEditSettings().isInsert();
525
    }
526

    
527

    
528
    public boolean isInsertLeftInPherogram() {
529
        return getAlignmentsContainer().getEditSettings().isInsertLeftInDataArea();
530
    }
531

    
532

    
533
    public void toggleLeftRightInsertionInPherogram() {
534
    	getAlignmentsContainer().getEditSettings().toggleInsertLeftInDataArea();
535
    }
536

    
537

    
538
    public void toggleInsertOverwrite() {
539
    	getAlignmentsContainer().getEditSettings().toggleInsert();
540
    }
541

    
542

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

    
575

    
576
    public String cutPherogramLeft() {
577
        return cutPherogram(true);
578
    }
579

    
580

    
581
    public String cutPherogramRight() {
582
        return cutPherogram(false);
583
    }
584

    
585

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

    
602

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

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

    
619
        model.removeTokensAt(sequenceID, 0, model.getSequenceLength(sequenceID));
620
        model.insertTokensAt(sequenceID, 0, tokens);
621
    }
622

    
623

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

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

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

    
657

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

    
670

    
671
	private String newReadName() {
672
		int index = 1;
673
		while (getReadsArea().getAlignmentModel().sequenceIDByName(DEFAULT_READ_NAME_PREFIX + index)
674
				!= AlignmentModel.NO_SEQUENCE_FOUND) {
675

    
676
			index++;
677
		}
678
		return DEFAULT_READ_NAME_PREFIX + index;
679
	}
680

    
681

    
682
    public void addRead(URI pherogramURI, boolean reverseComplemented) throws IOException, UnsupportedChromatogramFormatException {
683
    	addRead(newReadName(), pherogramURI, reverseComplemented, null, null, null, null, null);
684
    }
685

    
686

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

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

    
717
        // Create sequence:
718
		provider.addSequence(name);
719
		int id = provider.sequenceIDByName(name);
720

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

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

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

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

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