Project

General

Profile

Download (31.5 KB) Statistics
| Branch: | Tag: | Revision:
1
/**
2
* Copyright (C) 2014 EDIT
3
* European Distributed Institute of Taxonomy
4
* http://www.e-taxonomy.eu
5
*
6
* The contents of this file are subject to the Mozilla Public License Version 1.1
7
* See LICENSE.TXT at the top of this package for the full license terms.
8
*/
9
package eu.etaxonomy.taxeditor.molecular.editor;
10

    
11

    
12
import java.io.File;
13
import java.io.IOException;
14
import java.io.InputStream;
15
import java.net.URI;
16
import java.util.ArrayList;
17
import java.util.Collection;
18
import java.util.Collections;
19
import java.util.Iterator;
20
import java.util.List;
21
import java.util.Map;
22
import java.util.TreeMap;
23

    
24
import org.biojava.bio.chromatogram.ChromatogramFactory;
25
import org.biojava.bio.chromatogram.UnsupportedChromatogramFormatException;
26
import org.eclipse.core.runtime.IProgressMonitor;
27
import org.eclipse.swt.SWT;
28
import org.eclipse.swt.dnd.Clipboard;
29
import org.eclipse.swt.widgets.Composite;
30
import org.eclipse.swt.widgets.Display;
31
import org.eclipse.ui.IActionBars;
32
import org.eclipse.ui.IEditorInput;
33
import org.eclipse.ui.IEditorPart;
34
import org.eclipse.ui.IEditorSite;
35
import org.eclipse.ui.PartInitException;
36
import org.eclipse.ui.PlatformUI;
37
import org.eclipse.ui.commands.ICommandService;
38
import org.eclipse.ui.part.EditorPart;
39

    
40
import eu.etaxonomy.cdm.api.conversation.ConversationHolder;
41
import eu.etaxonomy.cdm.api.service.molecular.ISequenceService;
42
import eu.etaxonomy.cdm.model.media.MediaUtils;
43
import eu.etaxonomy.cdm.model.molecular.Sequence;
44
import eu.etaxonomy.cdm.model.molecular.SequenceString;
45
import eu.etaxonomy.cdm.model.molecular.SingleRead;
46
import eu.etaxonomy.cdm.model.molecular.SingleReadAlignment;
47
import eu.etaxonomy.taxeditor.model.MessagingUtils;
48
import eu.etaxonomy.taxeditor.molecular.TaxeditorMolecularPlugin;
49
import eu.etaxonomy.taxeditor.molecular.handler.ToggleInsertOverwriteHandler;
50
import eu.etaxonomy.taxeditor.molecular.handler.ToggleLeftRightInsertionHandler;
51
import eu.etaxonomy.taxeditor.molecular.l10n.Messages;
52
import eu.etaxonomy.taxeditor.store.CdmStore;
53
import eu.etaxonomy.taxeditor.view.derivateSearch.DerivateLabelProvider;
54
import info.bioinfweb.commons.swt.SWTUtils;
55
import info.bioinfweb.libralign.alignmentarea.AlignmentArea;
56
import info.bioinfweb.libralign.alignmentarea.selection.SelectionModel;
57
import info.bioinfweb.libralign.alignmentarea.tokenpainter.NucleotideTokenPainter;
58
import info.bioinfweb.libralign.dataarea.implementations.ConsensusSequenceArea;
59
import info.bioinfweb.libralign.dataarea.implementations.pherogram.PherogramArea;
60
import info.bioinfweb.libralign.dataarea.implementations.sequenceindex.SequenceIndexArea;
61
import info.bioinfweb.libralign.editsettings.EditSettingsChangeEvent;
62
import info.bioinfweb.libralign.editsettings.EditSettingsListener;
63
import info.bioinfweb.libralign.model.AlignmentModel;
64
import info.bioinfweb.libralign.model.AlignmentModelChangeListener;
65
import info.bioinfweb.libralign.model.adapters.StringAdapter;
66
import info.bioinfweb.libralign.model.events.SequenceChangeEvent;
67
import info.bioinfweb.libralign.model.events.SequenceRenamedEvent;
68
import info.bioinfweb.libralign.model.events.TokenChangeEvent;
69
import info.bioinfweb.libralign.model.implementations.PackedAlignmentModel;
70
import info.bioinfweb.libralign.model.tokenset.CharacterTokenSet;
71
import info.bioinfweb.libralign.model.tokenset.TokenSet;
72
import info.bioinfweb.libralign.model.utils.AlignmentModelUtils;
73
import info.bioinfweb.libralign.multiplealignments.AlignmentAreaList;
74
import info.bioinfweb.libralign.multiplealignments.MultipleAlignmentsContainer;
75
import info.bioinfweb.libralign.pherogram.model.PherogramAreaModel;
76
import info.bioinfweb.libralign.pherogram.model.ShiftChange;
77
import info.bioinfweb.libralign.pherogram.provider.BioJavaPherogramProvider;
78
import info.bioinfweb.libralign.pherogram.provider.PherogramProvider;
79
import info.bioinfweb.libralign.pherogram.provider.ReverseComplementPherogramProvider;
80
import info.bioinfweb.tic.SWTComponentFactory;
81

    
82

    
83

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

    
97
	public static final int READS_AREA_INDEX = 1;
98
    public static final int EDITABLE_CONSENSUS_AREA_INDEX = READS_AREA_INDEX + 1;
99
    public static final int CONSENSUS_HINT_AREA_INDEX = EDITABLE_CONSENSUS_AREA_INDEX + 1;
100
	public static final int PHEROGRAM_AREA_INDEX = 0;
101
	public static final int CONSENSUS_DATA_AREA_INDEX = 0;
102
	public static final String DEFAULT_READ_NAME_PREFIX = "Read "; //$NON-NLS-1$
103
	public static final String CONSENSUS_NAME = "Consensus"; //$NON-NLS-1$
104

    
105

    
106
    private final ConversationHolder conversationHolder;
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
	private final AlignmentEditorActionUpdater ACTION_UPDATER = new AlignmentEditorActionUpdater();
131
	public final Clipboard CLIPBOARD = new Clipboard(Display.getCurrent());  //TODO Move to global EDITor class.
132

    
133

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

    
138

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

    
145

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

    
154

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

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

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

    
174

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

    
182

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

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

    
193
		return result;
194
	}
195

    
196

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

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

    
207

    
208
    private MultipleAlignmentsContainer getAlignmentsContainer() {
209
    	if (alignmentsContainer == null) {
210
    		alignmentsContainer = new MultipleAlignmentsContainer();
211

    
212
    		AlignmentAreaList list = alignmentsContainer.getAlignmentAreas();
213
    		AlignmentArea readsArea = createEditableAlignmentArea(alignmentsContainer, true);
214
    		readsArea.getSelection().addSelectionListener(ACTION_UPDATER);
215
    	    list.add(createIndexArea(alignmentsContainer, readsArea));
216
    		list.add(readsArea);  // Make sure READS_AREA_INDEX is correct.
217
    		AlignmentArea editableConsensusArea = createEditableAlignmentArea(alignmentsContainer, false);
218
    		editableConsensusArea.getSelection().addSelectionListener(ACTION_UPDATER);
219
    		list.add(editableConsensusArea);  // Make sure COMSENSUS_AREA_INDEX is correct.
220
    		list.add(createConsensusHintArea(alignmentsContainer, readsArea));
221

    
222
    		registerEditSettingListener(alignmentsContainer);
223
       	}
224
		return alignmentsContainer;
225
	}
226

    
227

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

    
232

    
233
    public AlignmentArea getEditableConsensusArea() {
234
    	return getAlignmentsContainer().getAlignmentAreas().get(EDITABLE_CONSENSUS_AREA_INDEX);
235
    }
236

    
237

    
238
    /**
239
     * Checks whether {@link #getReadsArea()} or {@link #getEditableConsensusArea()} currently
240
     * have the user focus and returns the according component.
241
     *
242
     * @return either the reads or the consensus alignment area or {@code null} if none of these
243
     *         components is currently focused
244
     */
245
    public AlignmentArea getFocusedArea() {
246
    	AlignmentArea result = getReadsArea();
247
    	if (hasFocus(result)) {
248
    		return result;
249
    	}
250
    	else {
251
    		result = getEditableConsensusArea();
252
        	if (hasFocus(result)) {
253
        		return result;
254
        	}
255
        	else {
256
        		return null;
257
        	}
258
    	}
259
    }
260

    
261

    
262
    /**
263
     * Checks whether the specified alignment area or one of its subcomponents currently has the
264
     * focus.
265
     *
266
     * @param area the alignment area to be checked (Can only be {@link #getReadsArea()} or
267
     *        {@link #getEditableConsensusArea()}.)
268
     * @return {@code true} if the specified component is focused and is either equal to
269
     *         {@link #getReadsArea()} or {@link #getEditableConsensusArea()}or {@code false} otherwise
270
     */
271
    private boolean hasFocus(AlignmentArea area) {
272
    	return SWTUtils.childHasFocus((Composite)area.getToolkitComponent());
273
    }
274

    
275

    
276
    public boolean hasPherogram(String sequenceID) {
277
        return getReadsArea().getDataAreas().getSequenceAreas(sequenceID).size() > PHEROGRAM_AREA_INDEX;
278
    }
279

    
280

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

    
290

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

    
297

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

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

    
321

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

    
325
		// Add reads:
326
		for (SingleReadAlignment singleReadAlignment : sequenceNode.getSingleReadAlignments()) {
327
			try {
328
				SingleRead pherogramInfo = singleReadAlignment.getSingleRead();
329
				String id = addRead(DerivateLabelProvider.getDerivateText(pherogramInfo, conversationHolder),
330
						getPherogramURI(pherogramInfo),
331
						singleReadAlignment.isReverseComplement(),
332
						singleReadAlignment.getEditedSequence(),
333
						singleReadAlignment.getFirstSeqPosition(),
334
						singleReadAlignment.getLeftCutPosition(),
335
						singleReadAlignment.getRightCutPosition(),
336
						singleReadAlignment.getShifts());
337
				cdmMap.put(id, singleReadAlignment);
338
			}
339
			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).
340
                MessagingUtils.errorDialog(Messages.AlignmentEditor_ERROR_SINGLE_READ, null, Messages.AlignmentEditor_ERROR_SINGLE_READ_MESSAGE +
341
                        e.getLocalizedMessage(), TaxeditorMolecularPlugin.PLUGIN_ID, e, false);
342
			}
343
		}
344

    
345
		// Set consensus sequence:
346
		AlignmentModel consensusModel = getEditableConsensusArea().getAlignmentModel();
347
		String id = consensusModel.addSequence(CONSENSUS_NAME);
348
		consensusModel.insertTokensAt(id, 0, AlignmentModelUtils.charSequenceToTokenList(
349
				sequenceNode.getConsensusSequence().getString(), consensusModel.getTokenSet()));
350
		//TODO Can the consensus sequence also be null? / Should it be created here, if nothing is in the DB?
351
    }
352

    
353

    
354
    @Override
355
    public void createPartControl(Composite parent) {
356
		SWTComponentFactory.getInstance().getSWTComponent(getAlignmentsContainer(), parent, SWT.NONE);
357
		Display.getCurrent().addFilter(SWT.FocusIn, ACTION_UPDATER);
358
		Display.getCurrent().addFilter(SWT.FocusOut, ACTION_UPDATER);
359
		updateStatusBar();
360

    
361
		if (getEditorInput() instanceof AlignmentEditorInput) {
362
			if (((AlignmentEditorInput)getEditorInput()).getSequenceNodeUuid() != null) {
363
			    Sequence sequenceNode = CdmStore.getService(ISequenceService.class).load(((AlignmentEditorInput)getEditorInput()).getSequenceNodeUuid());
364
		        //re-load into the current session if it is already persisted in the DB
365
		        if(sequenceNode!=null && sequenceNode.getId()!=0){
366
		            sequenceNode = CdmStore.getService(ISequenceService.class).load(sequenceNode.getUuid());
367
		        }
368
				readCDMData(sequenceNode);
369
			}
370
			else {
371
				createTestContents();  // This case will removed after the test phase and an exception should probably be thrown.
372
			}
373
		}
374
		else {
375
			throw new IllegalArgumentException(String.format(Messages.AlignmentEditor_MUST_HAVE_TYPE,
376
					AlignmentEditorInput.class.getCanonicalName()));  //TODO What should be done here?
377
		}
378
	}
379

    
380

    
381
    @Override
382
	public void dispose() {
383
		Display.getCurrent().removeFilter(SWT.FocusIn, ACTION_UPDATER);
384
		Display.getCurrent().removeFilter(SWT.FocusOut, ACTION_UPDATER);
385
		CLIPBOARD.dispose();
386
        ((AlignmentEditorInput)getEditorInput()).dispose();
387
		super.dispose();
388
	}
389

    
390

    
391
	private void updateStatusBar() {
392
        IActionBars bars = getEditorSite().getActionBars();
393
        bars.getStatusLineManager().setMessage(
394
                Messages.AlignmentEditor_EDIT_MODE + (getReadsArea().getEditSettings().isInsert() ? Messages.AlignmentEditor_INSERT : Messages.AlignmentEditor_OVERWRITE) + "  " + //$NON-NLS-1$
395
        		Messages.AlignmentEditor_INSERTION_PHEROGRAM +
396
	       		(getReadsArea().getEditSettings().isInsertLeftInDataArea() ? Messages.AlignmentEditor_LEFT : Messages.AlignmentEditor_RIGHT));  //TODO multi language
397
    }
398

    
399

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

    
410

    
411
    @Override
412
    public void doSave(IProgressMonitor monitor) {
413
    	if (getEditorInput() instanceof AlignmentEditorInput) {
414
        	String taskName = Messages.AlignmentEditor_SAVING_ALIGNMENT;  //TODO multi language
415
            monitor.beginTask(taskName, 3);
416

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

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

    
433
    		// Write single reads:
434
    		stringProvider.setUnderlyingModel(getReadsArea().getAlignmentModel());
435
    		sequenceNode.getSingleReadAlignments().retainAll(cdmMap.values());  // Remove all reads that are not in the alignment anymore.
436
    		Iterator<String> iterator = getReadsArea().getAlignmentModel().sequenceIDIterator();
437
    		while (iterator.hasNext()) {
438
    			String id = iterator.next();
439
    			SingleReadAlignment singleRead = cdmMap.get(id);
440
    			if (singleRead == null) {
441
    			    throw new InternalError(Messages.AlignmentEditor_NEW_READ_FAILURE);  //TODO multi language
442
    				//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?
443
    				//singleRead = SingleReadAlignment.NewInstance(consensusSequence, singleRead, shifts, editedSequence);
444
    			}
445

    
446
    			singleRead.setEditedSequence(stringProvider.getSequence(id));
447

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

    
459
    		if (!conversationHolder.isBound()) {
460
                conversationHolder.bind();
461
            }
462
            monitor.worked(1);
463

    
464
            ((AlignmentEditorInput)getEditorInput()).merge();
465
            // Commit the conversation and start a new transaction immediately:
466
            conversationHolder.commit(true);
467
            monitor.worked(1);
468

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

    
479

    
480
    @Override
481
    public void doSaveAs() {}
482

    
483

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

    
490

    
491
    @Override
492
    public boolean isDirty() {
493
        return dirty;
494
    }
495

    
496

    
497
    private void setDirty() {
498
    	dirty = true;
499
    	firePropertyChange(IEditorPart.PROP_DIRTY);
500
    }
501

    
502

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

    
508

    
509
    @Override
510
    public void setFocus() {
511
        if(conversationHolder != null){
512
            conversationHolder.bind();
513
        }
514
        ((AlignmentEditorInput)getEditorInput()).bind();
515
    }
516

    
517
    public boolean isInsertMode() {
518
        return getAlignmentsContainer().getEditSettings().isInsert();
519
    }
520

    
521

    
522
    public boolean isInsertLeftInPherogram() {
523
        return getAlignmentsContainer().getEditSettings().isInsertLeftInDataArea();
524
    }
525

    
526

    
527
    public void toggleLeftRightInsertionInPherogram() {
528
    	getAlignmentsContainer().getEditSettings().toggleInsertLeftInDataArea();
529
    }
530

    
531

    
532
    public void toggleInsertOverwrite() {
533
    	getAlignmentsContainer().getEditSettings().toggleInsert();
534
    }
535

    
536

    
537
    private String cutPherogram(boolean left) {
538
        SelectionModel selection = getReadsArea().getSelection();
539
        if (selection.getCursorHeight() != 1) {
540
            return Messages.AlignmentEditor_CUTTING_FAILURE;  //TODO multi language
541
        }
542
        else {
543
            PherogramArea pherogramArea =
544
                    getPherogramArea(getReadsArea().getSequenceOrder().idByIndex(selection.getCursorRow()));
545
            if (pherogramArea == null) {
546
                return Messages.AlignmentEditor_NO_ATTACHED_PHEROGRAM;  //TODO multi language
547
            }
548
            else {
549
                if (left) {
550
                    if (pherogramArea.setLeftCutPositionBySelection()) {
551
                        return null;
552
                    }
553
                    else {
554
                        return Messages.AlignmentEditor_LEFT_END_OUTSIDE;  //TODO multi language
555
                    }
556
                }
557
                else {
558
                    if (pherogramArea.setRightCutPositionBySelection()) {
559
                        return null;
560
                    }
561
                    else {
562
                        return Messages.AlignmentEditor_RIGHT_END_OUTSIDE;  //TODO multi language
563
                    }
564
                }
565
            }
566
        }
567
    }
568

    
569

    
570
    public String cutPherogramLeft() {
571
        return cutPherogram(true);
572
    }
573

    
574

    
575
    public String cutPherogramRight() {
576
        return cutPherogram(false);
577
    }
578

    
579

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

    
596

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

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

    
613
        model.removeTokensAt(sequenceID, 0, model.getSequenceLength(sequenceID));
614
        model.insertTokensAt(sequenceID, 0, tokens);
615
    }
616

    
617

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

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

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

    
651

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

    
664

    
665
	private String newReadName() {
666
		int index = 1;
667
		while (getReadsArea().getAlignmentModel().sequenceIDByName(DEFAULT_READ_NAME_PREFIX + index) != null) {
668
			index++;
669
		}
670
		return DEFAULT_READ_NAME_PREFIX + index;
671
	}
672

    
673

    
674
    public void addRead(URI pherogramURI, boolean reverseComplemented) throws IOException, UnsupportedChromatogramFormatException {
675
    	addRead(newReadName(), pherogramURI, reverseComplemented, null, null, null, null, null);
676
    }
677

    
678

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

    
700
		AlignmentModel model = getReadsArea().getAlignmentModel();
701
		PherogramProvider pherogramProvider = null;
702
		if (pherogramURI != null) {
703
		    pherogramProvider = readPherogram(pherogramURI);  // Must happen before a sequence is added, because it might throw an exception.
704
            if (reverseComplemented) {
705
                pherogramProvider = new ReverseComplementPherogramProvider(pherogramProvider);
706
            }
707
		}
708

    
709
        // Create sequence:
710
		model.addSequence(name);
711
		String id = model.sequenceIDByName(name);
712

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

    
727
		if (tokens != null) {  // If either an edited sequence or a pherogram URI was provided.
728
		    model.insertTokensAt(id, 0, tokens);
729

    
730
		    if (pherogramProvider != null) {
731
		        // Create pherogram area:
732
		        PherogramArea pherogramArea = new PherogramArea(getReadsArea().getContentArea(),
733
		                new PherogramAreaModel(pherogramProvider));
734

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

    
750
		        // Add pherogram area to GUI:
751
		        pherogramArea.addMouseListener(new PherogramMouseListener(pherogramArea));
752
		        getReadsArea().getDataAreas().getSequenceAreas(id).add(pherogramArea);
753
		    }
754
		}
755
		return id;
756
	}
757

    
758

    
759
    public static URI getPherogramURI(SingleRead pherogramInfo) {
760
        if (pherogramInfo.getPherogram() != null) {
761
            return MediaUtils.getFirstMediaRepresentationPart(pherogramInfo.getPherogram()).getUri();
762
        }
763
        else {
764
            return null;
765
        }
766
    }
767
}
(1-1/5)