Project

General

Profile

Download (31.1 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.taxeditor.model.MessagingUtils;
77
import eu.etaxonomy.taxeditor.molecular.TaxeditorMolecularPlugin;
78
import eu.etaxonomy.taxeditor.molecular.handler.ToggleInsertOverwriteHandler;
79
import eu.etaxonomy.taxeditor.molecular.handler.ToggleLeftRightInsertionHandler;
80
import eu.etaxonomy.taxeditor.store.CdmStore;
81
import eu.etaxonomy.taxeditor.view.derivateSearch.DerivateLabelProvider;
82

    
83

    
84

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

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

    
106

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

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

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

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

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

    
134

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

    
139

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

    
146

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

    
155

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

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

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

    
175

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

    
183

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

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

    
194
		return result;
195
	}
196

    
197

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

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

    
208

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

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

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

    
228

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

    
233

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

    
238

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

    
262

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

    
276

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

    
281

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

    
291

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

    
298

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

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

    
322

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

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

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

    
354

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

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

    
381

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

    
391

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

    
400

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

    
411

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

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

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

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

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

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

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

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

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

    
480

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

    
484

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

    
491

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

    
497

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

    
503

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

    
509

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

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

    
522

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

    
527

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

    
532

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

    
537

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

    
570

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

    
575

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

    
580

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

    
597

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

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

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

    
618

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

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

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

    
652

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

    
665

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

    
674

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

    
679

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

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

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

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

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

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

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

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

    
759

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