Project

General

Profile

Download (31.9 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.e4;
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 javax.annotation.PostConstruct;
25
import javax.annotation.PreDestroy;
26
import javax.inject.Inject;
27

    
28
import org.biojava.bio.chromatogram.ChromatogramFactory;
29
import org.biojava.bio.chromatogram.UnsupportedChromatogramFormatException;
30
import org.eclipse.core.runtime.IProgressMonitor;
31
import org.eclipse.e4.ui.di.Focus;
32
import org.eclipse.e4.ui.di.Persist;
33
import org.eclipse.e4.ui.model.application.ui.MDirtyable;
34
import org.eclipse.swt.SWT;
35
import org.eclipse.swt.dnd.Clipboard;
36
import org.eclipse.swt.widgets.Composite;
37
import org.eclipse.swt.widgets.Display;
38
import org.eclipse.ui.PartInitException;
39
import org.eclipse.ui.PlatformUI;
40
import org.eclipse.ui.commands.ICommandService;
41

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

    
88

    
89

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

    
103
	public static final int READS_AREA_INDEX = 1;
104
    public static final int EDITABLE_CONSENSUS_AREA_INDEX = READS_AREA_INDEX + 1;
105
    public static final int CONSENSUS_HINT_AREA_INDEX = EDITABLE_CONSENSUS_AREA_INDEX + 1;
106
	public static final int PHEROGRAM_AREA_INDEX = 0;
107
	public static final int CONSENSUS_DATA_AREA_INDEX = 0;
108
	public static final String DEFAULT_READ_NAME_PREFIX = "Read "; //$NON-NLS-1$
109
	public static final String CONSENSUS_NAME = "Consensus"; //$NON-NLS-1$
110

    
111

    
112
    private ConversationHolder conversationHolder;
113
	private final AlignmentModelChangeListener DIRTY_LISTENER = new AlignmentModelChangeListener() {
114
				@Override
115
				public <T> void afterTokenChange(TokenChangeEvent<T> e) {
116
					setDirty();
117
				}
118

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

    
124
				@Override
125
				public <T> void afterSequenceChange(SequenceChangeEvent<T> e) {
126
					setDirty();
127
				}
128

    
129
				@Override
130
				public <T, U> void afterProviderChanged(AlignmentModel<T> oldProvider,
131
						AlignmentModel<U> newProvider) {  // Not expected.
132

    
133
					setDirty();
134
				}
135
			};
136
	private final AlignmentEditorActionUpdater ACTION_UPDATER = new AlignmentEditorActionUpdater();
137
	public final Clipboard CLIPBOARD = new Clipboard(Display.getCurrent());  //TODO Move to global EDITor class.
138

    
139

    
140
    private MultipleAlignmentsContainer alignmentsContainer = null;
141
    private final Map<String, SingleReadAlignment> cdmMap = new TreeMap<String, SingleReadAlignment>();  //TODO Move this to ContigSequenceDataProvider
142

    
143

    
144
    @Inject
145
    private MDirtyable dirty;
146

    
147
    private AlignmentEditorInput input;
148

    
149
    @Inject
150
    public AlignmentEditorE4() {
151
    }
152

    
153

    
154
    private void refreshToolbarElement(String id) {
155
		ICommandService commandService =
156
				PlatformUI.getWorkbench().getActiveWorkbenchWindow().getService(ICommandService.class);
157
		if (commandService != null) {
158
			commandService.refreshElements(id, Collections.EMPTY_MAP);
159
		}
160
    }
161

    
162

    
163
    private void registerEditSettingListener(MultipleAlignmentsContainer container) {
164
        container.getEditSettings().addListener(new EditSettingsListener() {
165
            @Override
166
            public void workingModeChanged(EditSettingsChangeEvent e) {}  // Currently nothing to do
167

    
168
            @Override
169
            public void insertLeftInDataAreaChanged(EditSettingsChangeEvent e) {
170
                updateStatusBar();
171
                refreshToolbarElement(ToggleLeftRightInsertionHandlerE4.COMMAND_ID);
172
            }
173

    
174
            @Override
175
            public void insertChanged(EditSettingsChangeEvent e) {
176
                updateStatusBar();
177
                refreshToolbarElement(ToggleInsertOverwriteHandlerE4.COMMAND_ID);
178
            }
179
        });
180
    }
181

    
182

    
183
    private AlignmentArea createIndexArea(MultipleAlignmentsContainer container, AlignmentArea labeledArea) {
184
		AlignmentArea result = new AlignmentArea(container);
185
		result.setAllowVerticalScrolling(false);
186
		result.getDataAreas().getTopAreas().add(new SequenceIndexArea(result.getContentArea(), labeledArea));
187
		return result;
188
    }
189

    
190

    
191
    private AlignmentArea createEditableAlignmentArea(MultipleAlignmentsContainer container, boolean allowVerticalScrolling) {
192
		AlignmentArea result = new AlignmentArea(container);
193
		result.setAllowVerticalScrolling(allowVerticalScrolling);
194

    
195
		CharacterTokenSet tokenSet = CharacterTokenSet.newDNAInstance();  //TODO Should NUCLEOTIDE be used instead?
196
		AlignmentModel<Character> model = new PackedAlignmentModel<Character>(tokenSet);
197
		result.setAlignmentModel(model, false);
198
		model.getChangeListeners().add(DIRTY_LISTENER);
199
		result.getPaintSettings().getTokenPainterList().set(0, new NucleotideTokenPainter());
200

    
201
		return result;
202
	}
203

    
204

    
205
    private AlignmentArea createConsensusHintArea(MultipleAlignmentsContainer container,
206
    		AlignmentArea labeledArea) {
207

    
208
		AlignmentArea result = new AlignmentArea(container);
209
		result.setAllowVerticalScrolling(false);
210
		result.getDataAreas().getBottomAreas().add(
211
				new ConsensusSequenceArea(result.getContentArea(), labeledArea));
212
		return result;
213
    }
214

    
215

    
216
    private MultipleAlignmentsContainer getAlignmentsContainer() {
217
    	if (alignmentsContainer == null) {
218
    		alignmentsContainer = new MultipleAlignmentsContainer();
219

    
220
    		AlignmentAreaList list = alignmentsContainer.getAlignmentAreas();
221
    		AlignmentArea readsArea = createEditableAlignmentArea(alignmentsContainer, true);
222
    		readsArea.getSelection().addSelectionListener(ACTION_UPDATER);
223
    	    list.add(createIndexArea(alignmentsContainer, readsArea));
224
    		list.add(readsArea);  // Make sure READS_AREA_INDEX is correct.
225
    		AlignmentArea editableConsensusArea = createEditableAlignmentArea(alignmentsContainer, false);
226
    		editableConsensusArea.getSelection().addSelectionListener(ACTION_UPDATER);
227
    		list.add(editableConsensusArea);  // Make sure COMSENSUS_AREA_INDEX is correct.
228
    		list.add(createConsensusHintArea(alignmentsContainer, readsArea));
229

    
230
    		registerEditSettingListener(alignmentsContainer);
231
       	}
232
		return alignmentsContainer;
233
	}
234

    
235

    
236
    public AlignmentArea getReadsArea() {
237
    	return getAlignmentsContainer().getAlignmentAreas().get(READS_AREA_INDEX);
238
    }
239

    
240

    
241
    public AlignmentArea getEditableConsensusArea() {
242
    	return getAlignmentsContainer().getAlignmentAreas().get(EDITABLE_CONSENSUS_AREA_INDEX);
243
    }
244

    
245

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

    
269

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

    
283

    
284
    public boolean hasPherogram(String sequenceID) {
285
        return getReadsArea().getDataAreas().getSequenceAreas(sequenceID).size() > PHEROGRAM_AREA_INDEX;
286
    }
287

    
288

    
289
    public PherogramArea getPherogramArea(String sequenceID) {
290
        if (hasPherogram(sequenceID)) {
291
            return (PherogramArea)getReadsArea().getDataAreas().getSequenceAreas(sequenceID).get(PHEROGRAM_AREA_INDEX);
292
        }
293
        else {
294
            return null;
295
        }
296
    }
297

    
298

    
299
    private ConsensusSequenceArea getConsensusHintDataArea() {
300
        return (ConsensusSequenceArea)getAlignmentsContainer().getAlignmentAreas().
301
                get(CONSENSUS_HINT_AREA_INDEX).getDataAreas().getBottomAreas().
302
                get(CONSENSUS_DATA_AREA_INDEX);
303
    }
304

    
305

    
306
    @Deprecated  //TODO Remove as soon as testing period is over
307
    private void createTestContents() {
308
		// Just for testing:
309
		try {
310
			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$
311
            //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);
312
            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$
313

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

    
329

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

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

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

    
361

    
362
    @PostConstruct
363
    public void createPartControl(Composite parent) {
364
        if (CdmStore.isActive()){
365
            if(conversationHolder == null){
366
                conversationHolder = CdmStore.createConversation();
367
            }
368
        }
369
        else{
370
            return;
371
        }
372
        SWTComponentFactory.getInstance().getSWTComponent(getAlignmentsContainer(), parent, SWT.NONE);
373
        Display.getCurrent().addFilter(SWT.FocusIn, ACTION_UPDATER);
374
        Display.getCurrent().addFilter(SWT.FocusOut, ACTION_UPDATER);
375
	}
376

    
377

    
378
    @PreDestroy
379
    public void dispose() {
380
        Display.getCurrent().removeFilter(SWT.FocusIn, ACTION_UPDATER);
381
        Display.getCurrent().removeFilter(SWT.FocusOut, ACTION_UPDATER);
382
        CLIPBOARD.dispose();
383
        input.dispose();
384

    
385
        if(conversationHolder!=null){
386
            conversationHolder.close();
387
            conversationHolder = null;
388
        }
389
        if(input!=null){
390
            input.dispose();
391
        }
392
        dirty.setDirty(false);
393
    }
394

    
395

    
396
	private void updateStatusBar() {
397
	    //FIXME E4 migrate
398
//        IActionBars bars = getEditorSite().getActionBars();
399
//        bars.getStatusLineManager().setMessage(
400
//                Messages.AlignmentEditor_EDIT_MODE + (getReadsArea().getEditSettings().isInsert() ? Messages.AlignmentEditor_INSERT : Messages.AlignmentEditor_OVERWRITE) + "  " + //$NON-NLS-1$
401
//        		Messages.AlignmentEditor_INSERTION_PHEROGRAM +
402
//	       		(getReadsArea().getEditSettings().isInsertLeftInDataArea() ? Messages.AlignmentEditor_LEFT : Messages.AlignmentEditor_RIGHT));  //TODO multi language
403
    }
404

    
405

    
406
    private SingleReadAlignment.Shift[] convertToCDMShifts(PherogramAreaModel model) {
407
    	Iterator<ShiftChange> iterator = model.shiftChangeIterator();
408
    	List<SingleReadAlignment.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 SingleReadAlignment.Shift[shifts.size()]);
414
    }
415

    
416

    
417
    @Persist
418
    public void doSave(IProgressMonitor monitor) {
419
        String taskName = Messages.AlignmentEditor_SAVING_ALIGNMENT;  //TODO multi language
420
        monitor.beginTask(taskName, 3);
421

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

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

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

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

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

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

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

    
474
        dirty.setDirty(false);
475
        monitor.worked(1);
476
        monitor.done();
477
    }
478

    
479

    
480
    public void init(AlignmentEditorInput input) throws PartInitException {
481
        this.input = input;
482

    
483
        updateStatusBar();
484

    
485
        if (input.getSequenceNodeUuid() != null) {
486
            Sequence sequenceNode = CdmStore.getService(ISequenceService.class).load(input.getSequenceNodeUuid());
487
            //re-load into the current session if it is already persisted in the DB
488
            if(sequenceNode!=null && sequenceNode.getId()!=0){
489
                sequenceNode = CdmStore.getService(ISequenceService.class).load(sequenceNode.getUuid());
490
            }
491
            readCDMData(sequenceNode);
492
        }
493
        else {
494
            createTestContents();  // This case will removed after the test phase and an exception should probably be thrown.
495
        }
496
    }
497

    
498

    
499
    public boolean isDirty() {
500
        return dirty.isDirty();
501
    }
502

    
503

    
504
    private void setDirty() {
505
    	dirty.setDirty(true);
506
    }
507

    
508

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

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

    
523

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

    
528

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

    
533

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

    
538

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

    
571

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

    
576

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

    
581

    
582
    public void reverseComplementSelectedSequences() {
583
    	SelectionModel selection = getReadsArea().getSelection();
584
    	AlignmentModel<?> model = getReadsArea().getAlignmentModel();
585
    	for (int row = selection.getFirstRow(); row < selection.getFirstRow() + selection.getCursorHeight(); row++) {
586
    		String sequenceID = getReadsArea().getSequenceOrder().idByIndex(row);
587
			PherogramArea area = getPherogramArea(sequenceID);
588
			PherogramAreaModel pherogramAlignmentModel = area.getModel();
589

    
590
            PherogramAlignmentRelation rightRelation = pherogramAlignmentModel.editableIndexByBaseCallIndex(
591
                    pherogramAlignmentModel.getRightCutPosition());
592
            int rightBorder;
593
            if (rightRelation.getCorresponding() == PherogramAlignmentRelation.OUT_OF_RANGE) {
594
                rightBorder = rightRelation.getBeforeValidIndex() + 1;
595
            }
596
            else {
597
                rightBorder = rightRelation.getAfterValidIndex();
598
            }
599

    
600
			AlignmentModelUtils.reverseComplement(model, sequenceID,
601
			        pherogramAlignmentModel.editableIndexByBaseCallIndex(
602
			                pherogramAlignmentModel.getLeftCutPosition()).getBeforeValidIndex(),
603
			        rightBorder);
604
			pherogramAlignmentModel.reverseComplement();
605
		}
606
    }
607

    
608

    
609
    /**
610
     * Recreates the whole consensus sequence from all single read sequences. The previous consensus
611
     * sequence is overwritten.
612
     */
613
    @SuppressWarnings("unchecked")
614
    public <T> void createConsensusSequence() {
615
        ConsensusSequenceArea area = getConsensusHintDataArea();
616
        AlignmentModel<T> model = (AlignmentModel<T>)getEditableConsensusArea().getAlignmentModel();
617
        String sequenceID = model.sequenceIDIterator().next();  // There is always one sequence contained.
618
        int length = getReadsArea().getAlignmentModel().getMaxSequenceLength();
619

    
620
        Collection<T> tokens = new ArrayList<T>(length);
621
        for (int column = 0; column < length; column++) {
622
            tokens.add(model.getTokenSet().tokenByRepresentation(area.getConsensusToken(column)));
623
        }
624

    
625
        model.removeTokensAt(sequenceID, 0, model.getSequenceLength(sequenceID));
626
        model.insertTokensAt(sequenceID, 0, tokens);
627
    }
628

    
629

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

    
643
        // Replace gaps by new information:
644
        for (int column = 0; column < currentConsensusLength; column++) {
645
            if (tokenSet.isGapToken(model.getTokenAt(sequenceID, column))) {
646
                T newToken = tokenSet.tokenByRepresentation(area.getConsensusToken(column));
647
                if (!tokenSet.isGapToken(newToken)) {
648
                    model.setTokenAt(sequenceID, column, newToken);
649
                }
650
            }
651
        }
652

    
653
        // Append additional tokens:
654
        if (overallLength > currentConsensusLength) {
655
            Collection<T> tokens = new ArrayList<T>(overallLength);
656
            for (int column = currentConsensusLength; column < overallLength; column++) {
657
                tokens.add(tokenSet.tokenByRepresentation(area.getConsensusToken(column)));
658
            }
659
            model.appendTokens(sequenceID, tokens);
660
        }
661
    }
662

    
663

    
664
	public static PherogramProvider readPherogram(URI uri) throws IOException, UnsupportedChromatogramFormatException {
665
	    PherogramProvider result;
666
		InputStream stream = uri.toURL().openStream();
667
		try {
668
			result = new BioJavaPherogramProvider(ChromatogramFactory.create(stream));
669
		}
670
		finally {
671
			stream.close();
672
		}
673
		return result;
674
	}
675

    
676

    
677
	private String newReadName() {
678
		int index = 1;
679
		while (getReadsArea().getAlignmentModel().sequenceIDByName(DEFAULT_READ_NAME_PREFIX + index) != null) {
680
			index++;
681
		}
682
		return DEFAULT_READ_NAME_PREFIX + index;
683
	}
684

    
685

    
686
    public void addRead(URI pherogramURI, boolean reverseComplemented) throws IOException, UnsupportedChromatogramFormatException {
687
    	addRead(newReadName(), pherogramURI, reverseComplemented, null, null, null, null, null);
688
    }
689

    
690

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

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

    
721
        // Create sequence:
722
		model.addSequence(name);
723
		String id = model.sequenceIDByName(name);
724

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

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

    
742
		    if (pherogramProvider != null) {
743
		        // Create pherogram area:
744
		        PherogramArea pherogramArea = new PherogramArea(getReadsArea().getContentArea(),
745
		                new PherogramAreaModel(pherogramProvider));
746

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

    
762
		        // Add pherogram area to GUI:
763
		        pherogramArea.addMouseListener(new PherogramMouseListener(pherogramArea));
764
		        getReadsArea().getDataAreas().getSequenceAreas(id).add(pherogramArea);
765
		    }
766
		}
767
		return id;
768
	}
769

    
770

    
771
    public static URI getPherogramURI(SingleRead pherogramInfo) {
772
        if (pherogramInfo.getPherogram() != null) {
773
            return MediaUtils.getFirstMediaRepresentationPart(pherogramInfo.getPherogram()).getUri();
774
        }
775
        else {
776
            return null;
777
        }
778
    }
779
}
(1-1/2)