Project

General

Profile

Download (31.1 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
import java.io.File;
12
import java.io.IOException;
13
import java.io.InputStream;
14
import java.util.ArrayList;
15
import java.util.Collection;
16
import java.util.Collections;
17
import java.util.Iterator;
18
import java.util.List;
19
import java.util.Map;
20
import java.util.TreeMap;
21

    
22
import javax.annotation.PostConstruct;
23
import javax.annotation.PreDestroy;
24
import javax.inject.Inject;
25

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

    
40
import eu.etaxonomy.cdm.api.service.molecular.ISequenceService;
41
import eu.etaxonomy.cdm.common.URI;
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.editor.AlignmentEditorActionUpdater;
50
import eu.etaxonomy.taxeditor.molecular.editor.AlignmentEditorInput;
51
import eu.etaxonomy.taxeditor.molecular.editor.PherogramMouseListener;
52
import eu.etaxonomy.taxeditor.molecular.editor.e4.handler.ToggleInsertOverwriteHandlerE4;
53
import eu.etaxonomy.taxeditor.molecular.editor.e4.handler.ToggleLeftRightInsertionHandlerE4;
54
import eu.etaxonomy.taxeditor.molecular.l10n.Messages;
55
import eu.etaxonomy.taxeditor.store.CdmStore;
56
import eu.etaxonomy.taxeditor.view.search.derivative.DerivateLabelProvider;
57
import info.bioinfweb.commons.swt.SWTUtils;
58
import info.bioinfweb.libralign.alignmentarea.AlignmentArea;
59
import info.bioinfweb.libralign.alignmentarea.selection.SelectionModel;
60
import info.bioinfweb.libralign.alignmentarea.tokenpainter.NucleotideTokenPainter;
61
import info.bioinfweb.libralign.dataarea.implementations.ConsensusSequenceArea;
62
import info.bioinfweb.libralign.dataarea.implementations.pherogram.PherogramArea;
63
import info.bioinfweb.libralign.dataarea.implementations.sequenceindex.SequenceIndexArea;
64
import info.bioinfweb.libralign.editsettings.EditSettingsChangeEvent;
65
import info.bioinfweb.libralign.editsettings.EditSettingsListener;
66
import info.bioinfweb.libralign.model.AlignmentModel;
67
import info.bioinfweb.libralign.model.AlignmentModelChangeListener;
68
import info.bioinfweb.libralign.model.adapters.StringAdapter;
69
import info.bioinfweb.libralign.model.events.SequenceChangeEvent;
70
import info.bioinfweb.libralign.model.events.SequenceRenamedEvent;
71
import info.bioinfweb.libralign.model.events.TokenChangeEvent;
72
import info.bioinfweb.libralign.model.implementations.PackedAlignmentModel;
73
import info.bioinfweb.libralign.model.tokenset.CharacterTokenSet;
74
import info.bioinfweb.libralign.model.tokenset.TokenSet;
75
import info.bioinfweb.libralign.model.utils.AlignmentModelUtils;
76
import info.bioinfweb.libralign.multiplealignments.AlignmentAreaList;
77
import info.bioinfweb.libralign.multiplealignments.MultipleAlignmentsContainer;
78
import info.bioinfweb.libralign.pherogram.model.PherogramAlignmentRelation;
79
import info.bioinfweb.libralign.pherogram.model.PherogramAreaModel;
80
import info.bioinfweb.libralign.pherogram.model.ShiftChange;
81
import info.bioinfweb.libralign.pherogram.provider.BioJavaPherogramProvider;
82
import info.bioinfweb.libralign.pherogram.provider.PherogramProvider;
83
import info.bioinfweb.libralign.pherogram.provider.ReverseComplementPherogramProvider;
84
import info.bioinfweb.tic.SWTComponentFactory;
85

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

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

    
107

    
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<>();  //TODO Move this to ContigSequenceDataProvider
137

    
138
    @Inject
139
    private MDirtyable dirty;
140

    
141
    private AlignmentEditorInput input;
142

    
143
    @Inject
144
    public AlignmentEditorE4() {
145
    }
146

    
147
    private void refreshToolbarElement(String id) {
148
		ICommandService commandService =
149
				PlatformUI.getWorkbench().getActiveWorkbenchWindow().getService(ICommandService.class);
150
		if (commandService != null) {
151
			commandService.refreshElements(id, Collections.EMPTY_MAP);
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(ToggleLeftRightInsertionHandlerE4.COMMAND_ID);
164
            }
165

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

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

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

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

    
191
		return result;
192
	}
193

    
194
    private AlignmentArea createConsensusHintArea(MultipleAlignmentsContainer container,
195
    		AlignmentArea labeledArea) {
196

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

    
204
    private MultipleAlignmentsContainer getAlignmentsContainer() {
205
    	if (alignmentsContainer == null) {
206
    		alignmentsContainer = new MultipleAlignmentsContainer();
207

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

    
218
    		registerEditSettingListener(alignmentsContainer);
219
       	}
220
		return alignmentsContainer;
221
	}
222

    
223

    
224
    public AlignmentArea getReadsArea() {
225
    	return getAlignmentsContainer().getAlignmentAreas().get(READS_AREA_INDEX);
226
    }
227

    
228

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

    
233

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

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

    
270
    public boolean hasPherogram(String sequenceID) {
271
        return getReadsArea().getDataAreas().getSequenceAreas(sequenceID).size() > PHEROGRAM_AREA_INDEX;
272
    }
273

    
274
    public PherogramArea getPherogramArea(String sequenceID) {
275
        if (hasPherogram(sequenceID)) {
276
            return (PherogramArea)getReadsArea().getDataAreas().getSequenceAreas(sequenceID).get(PHEROGRAM_AREA_INDEX);
277
        }
278
        else {
279
            return null;
280
        }
281
    }
282

    
283
    private ConsensusSequenceArea getConsensusHintDataArea() {
284
        return (ConsensusSequenceArea)getAlignmentsContainer().getAlignmentAreas().
285
                get(CONSENSUS_HINT_AREA_INDEX).getDataAreas().getBottomAreas().
286
                get(CONSENSUS_DATA_AREA_INDEX);
287
    }
288

    
289
    @Deprecated  //TODO Remove as soon as testing period is over
290
    private void createTestContents() {
291
		// Just for testing:
292
		try {
293
			addRead(URI.fromFile(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")), false); //$NON-NLS-1$
294
            //addRead(URI.fromFile(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")), false);
295
            addRead(URI.fromFile(new File("D:/Users/BenStoever/ownCloud/Dokumente/Projekte/EDITor/Quelltexte/LibrAlign branch/Repository/eu.etaxonomy.taxeditor.editor/src/main/resources/AlignmentTestData/Test_qualityScore.scf")), false); //$NON-NLS-1$
296

    
297
			// Add test consensus sequence:
298
			AlignmentModel consensusModel = getEditableConsensusArea().getAlignmentModel();
299
			String id = consensusModel.addSequence(CONSENSUS_NAME);
300
			Collection<Object> tokens = new ArrayList<Object>();  // First save tokens in a collection to avoid GUI updated for each token.
301
			tokens.add(consensusModel.getTokenSet().tokenByRepresentation("A")); //$NON-NLS-1$
302
			tokens.add(consensusModel.getTokenSet().tokenByRepresentation("C")); //$NON-NLS-1$
303
			tokens.add(consensusModel.getTokenSet().tokenByRepresentation("G")); //$NON-NLS-1$
304
			tokens.add(consensusModel.getTokenSet().tokenByRepresentation("T")); //$NON-NLS-1$
305
			consensusModel.insertTokensAt(id, 0, tokens);
306
		}
307
		catch (Exception e) {
308
			throw new RuntimeException(e);
309
		}
310
    }
311

    
312
    private void readCdmData(Sequence sequenceNode) {
313
    	//TODO If called from somewhere else than createPartControl() the editorInput needs to be checked and previous contents need to be cleared (or updated).
314

    
315
		// Add reads:
316
		for (SingleReadAlignment singleReadAlignment : sequenceNode.getSingleReadAlignments()) {
317
			try {
318
				SingleRead pherogramInfo = singleReadAlignment.getSingleRead();
319
				String id = addRead(DerivateLabelProvider.getDerivateText(pherogramInfo),
320
						getPherogramURI(pherogramInfo),
321
						singleReadAlignment.isReverseComplement(),
322
						singleReadAlignment.getEditedSequence(),
323
						singleReadAlignment.getFirstSeqPosition(),
324
						singleReadAlignment.getLeftCutPosition(),
325
						singleReadAlignment.getRightCutPosition(),
326
						singleReadAlignment.getShifts());
327
				cdmMap.put(id, singleReadAlignment);
328
			}
329
			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).
330
                MessagingUtils.errorDialog(Messages.AlignmentEditor_ERROR_SINGLE_READ, null, Messages.AlignmentEditor_ERROR_SINGLE_READ_MESSAGE +
331
                        e.getLocalizedMessage(), TaxeditorMolecularPlugin.PLUGIN_ID, e, false);
332
			}
333
		}
334

    
335
		// Set consensus sequence:
336
		AlignmentModel consensusModel = getEditableConsensusArea().getAlignmentModel();
337
		String id = consensusModel.addSequence(CONSENSUS_NAME);
338
		consensusModel.insertTokensAt(id, 0, AlignmentModelUtils.charSequenceToTokenList(
339
				sequenceNode.getConsensusSequence().getString(), consensusModel.getTokenSet()));
340
		//TODO Can the consensus sequence also be null? / Should it be created here, if nothing is in the DB?
341
    }
342

    
343
    @PostConstruct
344
    public void createPartControl(Composite parent) {
345
    	SWTComponentFactory.getInstance().getSWTComponent(getAlignmentsContainer(), parent, SWT.NONE);
346
        Display.getCurrent().addFilter(SWT.FocusIn, ACTION_UPDATER);
347
        Display.getCurrent().addFilter(SWT.FocusOut, ACTION_UPDATER);
348
	}
349

    
350

    
351
    @PreDestroy
352
    public void dispose() {
353
        Display.getCurrent().removeFilter(SWT.FocusIn, ACTION_UPDATER);
354
        Display.getCurrent().removeFilter(SWT.FocusOut, ACTION_UPDATER);
355
        CLIPBOARD.dispose();
356
        input.dispose();
357

    
358
        if(input!=null){
359
            input.dispose();
360
        }
361
        dirty.setDirty(false);
362
    }
363

    
364

    
365
	private void updateStatusBar() {
366
	    //FIXME E4 migrate
367
//        IActionBars bars = getEditorSite().getActionBars();
368
//        bars.getStatusLineManager().setMessage(
369
//                Messages.AlignmentEditor_EDIT_MODE + (getReadsArea().getEditSettings().isInsert() ? Messages.AlignmentEditor_INSERT : Messages.AlignmentEditor_OVERWRITE) + "  " + //$NON-NLS-1$
370
//        		Messages.AlignmentEditor_INSERTION_PHEROGRAM +
371
//	       		(getReadsArea().getEditSettings().isInsertLeftInDataArea() ? Messages.AlignmentEditor_LEFT : Messages.AlignmentEditor_RIGHT));  //TODO multi language
372
    }
373

    
374

    
375
    private SingleReadAlignment.Shift[] convertToCDMShifts(PherogramAreaModel model) {
376
    	Iterator<ShiftChange> iterator = model.shiftChangeIterator();
377
    	List<SingleReadAlignment.Shift> shifts = new ArrayList<SingleReadAlignment.Shift>();
378
    	while (iterator.hasNext()) {
379
    		ShiftChange shiftChange = iterator.next();
380
    		shifts.add(new SingleReadAlignment.Shift(shiftChange.getBaseCallIndex(), shiftChange.getShiftChange()));
381
    	}
382
    	return shifts.toArray(new SingleReadAlignment.Shift[shifts.size()]);
383
    }
384

    
385

    
386
    @Persist
387
    public void doSave(IProgressMonitor monitor) {
388
        String taskName = Messages.AlignmentEditor_SAVING_ALIGNMENT;  //TODO multi language
389
        monitor.beginTask(taskName, 3);
390

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

    
396
        // Write consensus sequence:
397
        SequenceString consensusSequenceObj = sequenceNode.getConsensusSequence();
398
        String newConsensusSequence = stringProvider.getSequence(
399
                getEditableConsensusArea().getAlignmentModel().sequenceIDByName(CONSENSUS_NAME));
400
        if (consensusSequenceObj == null) {
401
            sequenceNode.setConsensusSequence(SequenceString.NewInstance(newConsensusSequence));
402
        }
403
        else {
404
            consensusSequenceObj.setString(newConsensusSequence);
405
        }
406

    
407
        // Write single reads:
408
        stringProvider.setUnderlyingModel(getReadsArea().getAlignmentModel());
409
        sequenceNode.getSingleReadAlignments().retainAll(cdmMap.values());  // Remove all reads that are not in the alignment anymore.
410
        Iterator<String> iterator = getReadsArea().getAlignmentModel().sequenceIDIterator();
411
        while (iterator.hasNext()) {
412
            String id = iterator.next();
413
            SingleReadAlignment singleRead = cdmMap.get(id);
414
            if (singleRead == null) {
415
                throw new InternalError(Messages.AlignmentEditor_NEW_READ_FAILURE);  //TODO multi language
416
                //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?
417
                //singleRead = SingleReadAlignment.NewInstance(consensusSequence, singleRead, shifts, editedSequence);
418
            }
419

    
420
            singleRead.setEditedSequence(stringProvider.getSequence(id));
421

    
422
            PherogramArea pherogramArea = getPherogramArea(id);
423
            if (pherogramArea != null) {
424
                PherogramAreaModel model = pherogramArea.getModel();
425
                singleRead.setReverseComplement(model.getPherogramProvider() instanceof ReverseComplementPherogramProvider);  // Works only if ReverseComplementPherogramProvider instances are not nested.
426
                singleRead.setShifts(convertToCDMShifts(getPherogramArea(id).getModel()));
427
                singleRead.setFirstSeqPosition(model.getFirstSeqPos());
428
                singleRead.setLeftCutPosition(model.getLeftCutPosition());
429
                singleRead.setRightCutPosition(model.getRightCutPosition());
430
            }
431
        }
432

    
433
        monitor.worked(1);
434

    
435
        input.merge();
436
        monitor.worked(1);
437

    
438
        dirty.setDirty(false);
439
        monitor.worked(1);
440
        monitor.done();
441
    }
442

    
443

    
444
    public void init(AlignmentEditorInput input) throws PartInitException {
445
        this.input = input;
446

    
447
        updateStatusBar();
448

    
449
        if (input.getSequenceNodeUuid() != null) {
450
            Sequence sequenceNode = CdmStore.getService(ISequenceService.class).load(input.getSequenceNodeUuid());
451
            //re-load into the current session if it is already persisted in the DB
452
            if(sequenceNode!=null && sequenceNode.getId()!=0){
453
                sequenceNode = CdmStore.getService(ISequenceService.class).load(sequenceNode.getUuid());
454
            }
455
            readCdmData(sequenceNode);
456
        }
457
        else {
458
            createTestContents();  // This case will removed after the test phase and an exception should probably be thrown.
459
        }
460
    }
461

    
462

    
463
    public boolean isDirty() {
464
        return dirty.isDirty();
465
    }
466

    
467

    
468
    private void setDirty() {
469
    	dirty.setDirty(true);
470
    }
471

    
472

    
473
    @Focus
474
    public void setFocus() {
475
        if(input!=null){
476
            input.bind();
477
        }
478
    }
479

    
480
    public boolean isInsertMode() {
481
        return getAlignmentsContainer().getEditSettings().isInsert();
482
    }
483

    
484

    
485
    public boolean isInsertLeftInPherogram() {
486
        return getAlignmentsContainer().getEditSettings().isInsertLeftInDataArea();
487
    }
488

    
489

    
490
    public void toggleLeftRightInsertionInPherogram() {
491
    	getAlignmentsContainer().getEditSettings().toggleInsertLeftInDataArea();
492
    }
493

    
494

    
495
    public void toggleInsertOverwrite() {
496
    	getAlignmentsContainer().getEditSettings().toggleInsert();
497
    }
498

    
499

    
500
    private String cutPherogram(boolean left) {
501
        SelectionModel selection = getReadsArea().getSelection();
502
        if (selection.getCursorHeight() != 1) {
503
            return Messages.AlignmentEditor_CUTTING_FAILURE;  //TODO multi language
504
        }
505
        else {
506
            PherogramArea pherogramArea =
507
                    getPherogramArea(getReadsArea().getSequenceOrder().idByIndex(selection.getCursorRow()));
508
            if (pherogramArea == null) {
509
                return Messages.AlignmentEditor_NO_ATTACHED_PHEROGRAM;  //TODO multi language
510
            }
511
            else {
512
                if (left) {
513
                    if (pherogramArea.setLeftCutPositionBySelection()) {
514
                        return null;
515
                    }
516
                    else {
517
                        return Messages.AlignmentEditor_LEFT_END_OUTSIDE;  //TODO multi language
518
                    }
519
                }
520
                else {
521
                    if (pherogramArea.setRightCutPositionBySelection()) {
522
                        return null;
523
                    }
524
                    else {
525
                        return Messages.AlignmentEditor_RIGHT_END_OUTSIDE;  //TODO multi language
526
                    }
527
                }
528
            }
529
        }
530
    }
531

    
532

    
533
    public String cutPherogramLeft() {
534
        return cutPherogram(true);
535
    }
536

    
537

    
538
    public String cutPherogramRight() {
539
        return cutPherogram(false);
540
    }
541

    
542

    
543
    public void reverseComplementSelectedSequences() {
544
    	SelectionModel selection = getReadsArea().getSelection();
545
    	AlignmentModel<?> model = getReadsArea().getAlignmentModel();
546
    	for (int row = selection.getFirstRow(); row < selection.getFirstRow() + selection.getCursorHeight(); row++) {
547
    		String sequenceID = getReadsArea().getSequenceOrder().idByIndex(row);
548
			PherogramArea area = getPherogramArea(sequenceID);
549
			PherogramAreaModel pherogramAlignmentModel = area.getModel();
550

    
551
            PherogramAlignmentRelation rightRelation = pherogramAlignmentModel.editableIndexByBaseCallIndex(
552
                    pherogramAlignmentModel.getRightCutPosition());
553
            int rightBorder;
554
            if (rightRelation.getCorresponding() == PherogramAlignmentRelation.OUT_OF_RANGE) {
555
                rightBorder = rightRelation.getBeforeValidIndex() + 1;
556
            }
557
            else {
558
                rightBorder = rightRelation.getAfterValidIndex();
559
            }
560

    
561
			AlignmentModelUtils.reverseComplement(model, sequenceID,
562
			        pherogramAlignmentModel.editableIndexByBaseCallIndex(
563
			                pherogramAlignmentModel.getLeftCutPosition()).getBeforeValidIndex(),
564
			        rightBorder);
565
			pherogramAlignmentModel.reverseComplement();
566
		}
567
    }
568

    
569

    
570
    /**
571
     * Recreates the whole consensus sequence from all single read sequences. The previous consensus
572
     * sequence is overwritten.
573
     */
574
    @SuppressWarnings("unchecked")
575
    public <T> void createConsensusSequence() {
576
        ConsensusSequenceArea area = getConsensusHintDataArea();
577
        AlignmentModel<T> model = (AlignmentModel<T>)getEditableConsensusArea().getAlignmentModel();
578
        String sequenceID = model.sequenceIDIterator().next();  // There is always one sequence contained.
579
        int length = getReadsArea().getAlignmentModel().getMaxSequenceLength();
580

    
581
        Collection<T> tokens = new ArrayList<T>(length);
582
        for (int column = 0; column < length; column++) {
583
            tokens.add(model.getTokenSet().tokenByRepresentation(area.getConsensusToken(column)));
584
        }
585

    
586
        model.removeTokensAt(sequenceID, 0, model.getSequenceLength(sequenceID));
587
        model.insertTokensAt(sequenceID, 0, tokens);
588
    }
589

    
590

    
591
    /**
592
     * Updates the current consensus sequence by replacing gaps by the according consensus tokens
593
     * calculated from the single read sequences and extends the consensus sequence if necessary.
594
     */
595
    @SuppressWarnings("unchecked")
596
    public <T> void updateConsensusSequence() {
597
        ConsensusSequenceArea area = getConsensusHintDataArea();
598
        AlignmentModel<T> model = (AlignmentModel<T>)getEditableConsensusArea().getAlignmentModel();
599
        TokenSet<T> tokenSet = model.getTokenSet();
600
        String sequenceID = model.sequenceIDIterator().next();  // There is always one sequence contained.
601
        int currentConsensusLength = model.getSequenceLength(sequenceID);
602
        int overallLength = getReadsArea().getAlignmentModel().getMaxSequenceLength();
603

    
604
        // Replace gaps by new information:
605
        for (int column = 0; column < currentConsensusLength; column++) {
606
            if (tokenSet.isGapToken(model.getTokenAt(sequenceID, column))) {
607
                T newToken = tokenSet.tokenByRepresentation(area.getConsensusToken(column));
608
                if (!tokenSet.isGapToken(newToken)) {
609
                    model.setTokenAt(sequenceID, column, newToken);
610
                }
611
            }
612
        }
613

    
614
        // Append additional tokens:
615
        if (overallLength > currentConsensusLength) {
616
            Collection<T> tokens = new ArrayList<T>(overallLength);
617
            for (int column = currentConsensusLength; column < overallLength; column++) {
618
                tokens.add(tokenSet.tokenByRepresentation(area.getConsensusToken(column)));
619
            }
620
            model.appendTokens(sequenceID, tokens);
621
        }
622
    }
623

    
624

    
625
	public static PherogramProvider readPherogram(URI uri) throws IOException, UnsupportedChromatogramFormatException {
626
	    PherogramProvider result;
627
		InputStream stream = uri.toURL().openStream();
628
		try {
629
			result = new BioJavaPherogramProvider(ChromatogramFactory.create(stream));
630
		}
631
		finally {
632
			stream.close();
633
		}
634
		return result;
635
	}
636

    
637

    
638
	private String newReadName() {
639
		int index = 1;
640
		while (getReadsArea().getAlignmentModel().sequenceIDByName(DEFAULT_READ_NAME_PREFIX + index) != null) {
641
			index++;
642
		}
643
		return DEFAULT_READ_NAME_PREFIX + index;
644
	}
645

    
646

    
647
    public void addRead(URI pherogramURI, boolean reverseComplemented) throws IOException, UnsupportedChromatogramFormatException {
648
    	addRead(newReadName(), pherogramURI, reverseComplemented, null, null, null, null, null);
649
    }
650

    
651

    
652
    /**
653
     * Adds a new sequence with attached phergram data area to the reads alignment.
654
     * <p>
655
     * If {@code null} is specified as {@code editedSequence} the base call sequence from the pherogram will
656
     * be set as the edited sequence. If {@code null} is specified as {@code shifts} no shifts between the edited
657
     * and the base calls sequence are assumed.
658
     *
659
     * @param name the name of the new sequence
660
     * @param pherogramURI the URI where the associated pherogram file is located
661
     * @param reverseComplemented Specify {@code true} here, if the reverse complement of the pherogram data should
662
     *        be added, {@code false} otherwise.
663
     * @param editedSequence the edited version of the base call sequence (May be {@code null}.)
664
     * @param shifts the alignment information that links the edited and the base call sequence (May be {@code null}.)
665
     * @return the sequence ID of the added read
666
     * @throws IOException if an error occurred when trying to read the pherogram file
667
     * @throws UnsupportedChromatogramFormatException if the format of the pherogram file is not supported
668
     */
669
    public String addRead(String name, URI pherogramURI, boolean reverseComplemented, String editedSequence,
670
            Integer firstSeqPos, Integer leftCutPos, Integer rightCutPos, SingleReadAlignment.Shift[] shifts)
671
            throws IOException, UnsupportedChromatogramFormatException {
672

    
673
		AlignmentModel model = getReadsArea().getAlignmentModel();
674
		PherogramProvider pherogramProvider = null;
675
		if (pherogramURI != null) {
676
		    pherogramProvider = readPherogram(pherogramURI);  // Must happen before a sequence is added, because it might throw an exception.
677
            if (reverseComplemented) {
678
                pherogramProvider = new ReverseComplementPherogramProvider(pherogramProvider);
679
            }
680
		}
681

    
682
        // Create sequence:
683
		model.addSequence(name);
684
		String id = model.sequenceIDByName(name);
685

    
686
		// Set edited sequence:
687
		Collection<Object> tokens = null;  // First save tokens in a collection to avoid GUI updated for each token.
688
		if (editedSequence != null) {
689
			tokens = AlignmentModelUtils.charSequenceToTokenList(editedSequence, model.getTokenSet());
690
		}
691
		else if (pherogramProvider != null) {  // Copy base call sequence into alignment:
692
			tokens = new ArrayList<Object>();
693
			for (int i = 0; i < pherogramProvider.getSequenceLength(); i++) {
694
				tokens.add(model.getTokenSet().tokenByRepresentation(
695
					Character.toString(pherogramProvider.getBaseCall(i))));
696
			}
697
			setDirty();
698
		}
699

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

    
703
		    if (pherogramProvider != null) {
704
		        // Create pherogram area:
705
		        PherogramArea pherogramArea = new PherogramArea(getReadsArea().getContentArea(),
706
		                new PherogramAreaModel(pherogramProvider));
707

    
708
		        // Set position properties and shifts:
709
		        PherogramAreaModel phergramModel = pherogramArea.getModel();
710
		        if ((firstSeqPos != null) && (leftCutPos != null)) {
711
		            phergramModel.setFirstSeqLeftCutPos(firstSeqPos, leftCutPos);
712
		        }
713
		        if (rightCutPos != null) {
714
		            phergramModel.setRightCutPosition(rightCutPos);
715
		        }
716
		        if ((shifts != null) && (shifts.length > 0)) {
717
		            for (int i = 0; i < shifts.length; i++) {
718
		                phergramModel.addShiftChange(shifts[i].position, shifts[i].shift);
719
		            }
720
		            setDirty();
721
		        }
722

    
723
		        // Add pherogram area to GUI:
724
		        pherogramArea.addMouseListener(new PherogramMouseListener(pherogramArea));
725
		        getReadsArea().getDataAreas().getSequenceAreas(id).add(pherogramArea);
726
		    }
727
		}
728
		return id;
729
	}
730

    
731

    
732
    public static URI getPherogramURI(SingleRead pherogramInfo) {
733
        if (pherogramInfo.getPherogram() != null) {
734
            return MediaUtils.getFirstMediaRepresentationPart(pherogramInfo.getPherogram()).getUri();
735
        }
736
        else {
737
            return null;
738
        }
739
    }
740
}
(1-1/2)