Project

General

Profile

Download (30.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.libralign.alignmentarea.AlignmentArea;
14
import info.bioinfweb.libralign.alignmentarea.selection.SelectionModel;
15
import info.bioinfweb.libralign.alignmentarea.tokenpainter.NucleotideTokenPainter;
16
import info.bioinfweb.libralign.dataarea.implementations.ConsensusSequenceArea;
17
import info.bioinfweb.libralign.dataarea.implementations.SequenceIndexArea;
18
import info.bioinfweb.libralign.dataarea.implementations.pherogram.PherogramArea;
19
import info.bioinfweb.libralign.editsettings.EditSettingsChangeEvent;
20
import info.bioinfweb.libralign.editsettings.EditSettingsListener;
21
import info.bioinfweb.libralign.model.AlignmentModel;
22
import info.bioinfweb.libralign.model.AlignmentModelChangeListener;
23
import info.bioinfweb.libralign.model.AlignmentModelUtils;
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.multiplealignments.AlignmentAreaList;
32
import info.bioinfweb.libralign.multiplealignments.MultipleAlignmentsContainer;
33
import info.bioinfweb.libralign.pherogram.model.PherogramAreaModel;
34
import info.bioinfweb.libralign.pherogram.model.ShiftChange;
35
import info.bioinfweb.libralign.pherogram.provider.BioJavaPherogramProvider;
36
import info.bioinfweb.libralign.pherogram.provider.PherogramProvider;
37
import info.bioinfweb.libralign.pherogram.provider.ReverseComplementPherogramProvider;
38
import info.bioinfweb.tic.SWTComponentFactory;
39

    
40
import java.io.File;
41
import java.io.IOException;
42
import java.io.InputStream;
43
import java.net.URI;
44
import java.util.ArrayList;
45
import java.util.Collection;
46
import java.util.Collections;
47
import java.util.Iterator;
48
import java.util.List;
49
import java.util.Map;
50
import java.util.TreeMap;
51

    
52
import org.biojava.bio.chromatogram.ChromatogramFactory;
53
import org.biojava.bio.chromatogram.UnsupportedChromatogramFormatException;
54
import org.eclipse.core.runtime.IProgressMonitor;
55
import org.eclipse.jface.action.Action;
56
import org.eclipse.swt.SWT;
57
import org.eclipse.swt.widgets.Composite;
58
import org.eclipse.ui.IActionBars;
59
import org.eclipse.ui.IEditorInput;
60
import org.eclipse.ui.IEditorPart;
61
import org.eclipse.ui.IEditorSite;
62
import org.eclipse.ui.PartInitException;
63
import org.eclipse.ui.PlatformUI;
64
import org.eclipse.ui.actions.ActionFactory;
65
import org.eclipse.ui.commands.ICommandService;
66
import org.eclipse.ui.part.EditorPart;
67

    
68
import eu.etaxonomy.cdm.api.conversation.ConversationHolder;
69
import eu.etaxonomy.cdm.api.service.molecular.ISequenceService;
70
import eu.etaxonomy.cdm.model.media.MediaUtils;
71
import eu.etaxonomy.cdm.model.molecular.Sequence;
72
import eu.etaxonomy.cdm.model.molecular.SequenceString;
73
import eu.etaxonomy.cdm.model.molecular.SingleRead;
74
import eu.etaxonomy.cdm.model.molecular.SingleReadAlignment;
75
import eu.etaxonomy.cdm.model.molecular.SingleReadAlignment.Shift;
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

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

    
136

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

    
143

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

    
152

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

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

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

    
172

    
173
  private AlignmentArea createIndexArea(MultipleAlignmentsContainer container, AlignmentArea labeledArea) {
174
		AlignmentArea result = new AlignmentArea(container);
175
		result.setAllowVerticalScrolling(false);
176
		result.getDataAreas().getTopAreas().add(new SequenceIndexArea(result.getContentArea(), labeledArea));
177
		return result;
178
  }
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> provider = new PackedAlignmentModel<Character>(tokenSet);
187
		result.setAlignmentModel(provider, false);
188
		provider.getChangeListeners().add(DIRTY_LISTENER);
189
		result.getPaintSettings().getTokenPainterList().set(0, new NucleotideTokenPainter());
190

    
191
		return result;
192
	}
193

    
194

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

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

    
205

    
206
    private MultipleAlignmentsContainer getAlignmentsContainer() {
207
    	if (alignmentsContainer == null) {
208
    		alignmentsContainer = new MultipleAlignmentsContainer();
209

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

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

    
222

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

    
227

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

    
232

    
233
    public boolean hasPherogram(int sequenceID) {
234
        return getReadsArea().getDataAreas().getSequenceAreas(sequenceID).size() > PHEROGRAM_AREA_INDEX;
235
    }
236

    
237

    
238
    public PherogramArea getPherogramArea(int sequenceID) {
239
        if (hasPherogram(sequenceID)) {
240
            return (PherogramArea)getReadsArea().getDataAreas().getSequenceAreas(sequenceID).get(PHEROGRAM_AREA_INDEX);
241
        }
242
        else {
243
            return null;
244
        }
245
    }
246

    
247

    
248
    private ConsensusSequenceArea getConsensusHintDataArea() {
249
        return (ConsensusSequenceArea)getAlignmentsContainer().getAlignmentAreas().
250
                get(CONSENSUS_HINT_AREA_INDEX).getDataAreas().getBottomAreas().
251
                get(CONSENSUS_DATA_AREA_INDEX);
252
    }
253

    
254

    
255
    @Deprecated  //TODO Remove as soon as testing period is over
256
    private void createTestContents() {
257
		// Just for testing:
258
		try {
259
			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);
260
            //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);
261
            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);
262

    
263
			// Add test consensus sequence:
264
			AlignmentModel consensusModel = getEditableConsensusArea().getAlignmentModel();
265
			int id = consensusModel.addSequence(CONSENSUS_NAME);
266
			Collection<Object> tokens = new ArrayList<Object>();  // First save tokens in a collection to avoid GUI updated for each token.
267
			tokens.add(consensusModel.getTokenSet().tokenByRepresentation("A"));
268
			tokens.add(consensusModel.getTokenSet().tokenByRepresentation("C"));
269
			tokens.add(consensusModel.getTokenSet().tokenByRepresentation("G"));
270
			tokens.add(consensusModel.getTokenSet().tokenByRepresentation("T"));
271
			consensusModel.insertTokensAt(id, 0, tokens);
272
		}
273
		catch (Exception e) {
274
			throw new RuntimeException(e);
275
		}
276
    }
277

    
278

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

    
282
		// Add reads:
283
		for (SingleReadAlignment singleReadAlignment : sequenceNode.getSingleReadAlignments()) {
284
			try {
285
				SingleRead pherogramInfo = singleReadAlignment.getSingleRead();
286
				URI uri = null;
287
				if (pherogramInfo.getPherogram() != null) {
288
				    uri = MediaUtils.getFirstMediaRepresentationPart(pherogramInfo.getPherogram()).getUri();
289
				}
290
				int id = addRead(DerivateLabelProvider.getDerivateText(pherogramInfo, conversationHolder),
291
						uri,
292
						singleReadAlignment.isReverseComplement(),
293
						singleReadAlignment.getEditedSequence(),
294
						singleReadAlignment.getFirstSeqPosition(),
295
						singleReadAlignment.getLeftCutPosition(),
296
						singleReadAlignment.getRightCutPosition(),
297
						singleReadAlignment.getShifts());
298
				cdmMap.put(id, singleReadAlignment);
299
			}
300
			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).
301
                MessagingUtils.errorDialog("Error", null, "A single read was skipped because of the following error:\n\n" +
302
                        e.getLocalizedMessage(), TaxeditorMolecularPlugin.PLUGIN_ID, e, false);
303
			}
304
		}
305

    
306
		// Set consensus sequence:
307
		AlignmentModel consensusProvider = getEditableConsensusArea().getAlignmentModel();
308
		int id = consensusProvider.addSequence(CONSENSUS_NAME);
309
		consensusProvider.insertTokensAt(id, 0, AlignmentModelUtils.charSequenceToTokenList(
310
				sequenceNode.getConsensusSequence().getString(), consensusProvider.getTokenSet()));
311
		//TODO Can the consensus sequence also be null? / Should it be created here, if nothing is in the DB?
312
    }
313

    
314

    
315
	/* (non-Javadoc)
316
     * @see org.eclipse.ui.part.WorkbenchPart#createPartControl(org.eclipse.swt.widgets.Composite)
317
     */
318
    @Override
319
    public void createPartControl(Composite parent) {
320
		SWTComponentFactory.getInstance().getSWTComponent(getAlignmentsContainer(), parent, SWT.NONE);
321
		updateStatusBar();
322

    
323
		if (getEditorInput() instanceof AlignmentEditorInput) {
324
			if (((AlignmentEditorInput)getEditorInput()).getSequenceNodeUuid() != null) {
325
			    Sequence sequenceNode = CdmStore.getService(ISequenceService.class).load(((AlignmentEditorInput)getEditorInput()).getSequenceNodeUuid());
326
		        //re-load into the current session if it is already persisted in the DB
327
		        if(sequenceNode!=null && sequenceNode.getId()!=0){
328
		            sequenceNode = CdmStore.getService(ISequenceService.class).load(sequenceNode.getUuid());
329
		        }
330
				readCDMData(sequenceNode);
331
			}
332
			else {
333
				createTestContents();  // This case will removed after the test phase and an exception should probably be thrown.
334
			}
335
		}
336
		else {
337
			throw new IllegalArgumentException("The editor input must have the type " +
338
					AlignmentEditorInput.class.getCanonicalName());  //TODO What should be done here?
339
		}
340
	}
341

    
342

    
343
    private void updateStatusBar() {
344
        IActionBars bars = getEditorSite().getActionBars();
345
        bars.getStatusLineManager().setMessage("Edit mode: " +
346
        		(getReadsArea().getEditSettings().isInsert() ? "Insert" : "Overwrite") + "  " +
347
        		"Insertion in pherogram: " +
348
	       		(getReadsArea().getEditSettings().isInsertLeftInDataArea() ? "Left" : "Right"));
349
    }
350

    
351

    
352
    private SingleReadAlignment.Shift[] convertToCDMShifts(PherogramAreaModel model) {
353
    	Iterator<ShiftChange> iterator = model.shiftChangeIterator();
354
    	List<Shift> shifts = new ArrayList<SingleReadAlignment.Shift>();
355
    	while (iterator.hasNext()) {
356
    		ShiftChange shiftChange = iterator.next();
357
    		shifts.add(new SingleReadAlignment.Shift(shiftChange.getBaseCallIndex(), shiftChange.getShiftChange()));
358
    	}
359
    	return shifts.toArray(new Shift[]{});
360
    }
361

    
362

    
363
    /* (non-Javadoc)
364
     * @see org.eclipse.ui.part.EditorPart#doSave(org.eclipse.core.runtime.IProgressMonitor)
365
     */
366
    @Override
367
    public void doSave(IProgressMonitor monitor) {
368
    	if (getEditorInput() instanceof AlignmentEditorInput) {
369
        	String taskName = "Saving alignment";
370
            monitor.beginTask(taskName, 3);
371

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

    
377
    		// Write consensus sequence:
378
    		SequenceString consensusSequenceObj = sequenceNode.getConsensusSequence();
379
    		String newConsensusSequence = stringProvider.getSequence(
380
    				getEditableConsensusArea().getAlignmentModel().sequenceIDByName(CONSENSUS_NAME));
381
    		if (consensusSequenceObj == null) {
382
    			sequenceNode.setConsensusSequence(SequenceString.NewInstance(newConsensusSequence));
383
    		}
384
    		else {
385
    			consensusSequenceObj.setString(newConsensusSequence);
386
    		}
387

    
388
    		// Write single reads:
389
    		stringProvider.setUnderlyingProvider(getReadsArea().getAlignmentModel());
390
    		sequenceNode.getSingleReadAlignments().retainAll(cdmMap.values());  // Remove all reads that are not in the alignment anymore.
391
    		Iterator<Integer> iterator = getReadsArea().getAlignmentModel().sequenceIDIterator();
392
    		while (iterator.hasNext()) {
393
    			int id = iterator.next();
394
    			SingleReadAlignment singleRead = cdmMap.get(id);
395
    			if (singleRead == null) {
396
    			    throw new InternalError("Creating new reads from AlignmentEditor not implemented.");
397
    				//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?
398
    				//singleRead = SingleReadAlignment.NewInstance(consensusSequence, singleRead, shifts, editedSequence);
399
    			}
400

    
401
    			singleRead.setEditedSequence(stringProvider.getSequence(id));
402

    
403
    			PherogramArea pherogramArea = getPherogramArea(id);
404
    			if (pherogramArea != null) {
405
        			PherogramAreaModel model = pherogramArea.getModel();
406
        			singleRead.setReverseComplement(model.getPherogramProvider() instanceof ReverseComplementPherogramProvider);  // Works only if ReverseComplementPherogramProvider instances are not nested.
407
        			singleRead.setShifts(convertToCDMShifts(getPherogramArea(id).getModel()));
408
        			singleRead.setFirstSeqPosition(model.getFirstSeqPos());
409
        			singleRead.setLeftCutPosition(model.getLeftCutPosition());
410
        			singleRead.setRightCutPosition(model.getRightCutPosition());
411
    			}
412
    		}
413

    
414
    		if (!conversationHolder.isBound()) {
415
                conversationHolder.bind();
416
            }
417
            monitor.worked(1);
418

    
419
            ((AlignmentEditorInput)getEditorInput()).merge();
420
            // Commit the conversation and start a new transaction immediately:
421
            conversationHolder.commit(true);
422
            monitor.worked(1);
423

    
424
            dirty = false;
425
            monitor.worked(1);
426
            monitor.done();
427
            firePropertyChange(PROP_DIRTY);
428
    	}
429
    	else {
430
    		//TODO Throw exception as soon as testing period which allows unlinked AlignmentEditor is over.
431
    	}
432
    }
433

    
434

    
435
    /* (non-Javadoc)
436
     * @see org.eclipse.ui.part.EditorPart#doSaveAs()
437
     */
438
    @Override
439
    public void doSaveAs() {}
440

    
441

    
442
    /* (non-Javadoc)
443
     * @see org.eclipse.ui.part.EditorPart#init(org.eclipse.ui.IEditorSite, org.eclipse.ui.IEditorInput)
444
     */
445
    @Override
446
    public void init(IEditorSite site, IEditorInput input) throws PartInitException {
447
        setSite(site);
448
        setInput(input);
449
        System.out.println("AlignmentEditor.init(): " + ActionFactory.COPY.getId());
450
        site.getActionBars().setGlobalActionHandler(ActionFactory.COPY.getId(), new Action(ActionFactory.COPY.getId()) {
451
            @Override
452
            public boolean isEnabled() {
453
                System.out.println("isEnabled()");
454
                return true;
455
            }
456

    
457
            @Override
458
            public void run() {
459
                System.out.println("run");
460
                    super.run();
461
                }
462
            });
463
    }
464

    
465

    
466
    /* (non-Javadoc)
467
     * @see org.eclipse.ui.part.EditorPart#isDirty()
468
     */
469
    @Override
470
    public boolean isDirty() {
471
        return dirty;
472
    }
473

    
474

    
475
    private void setDirty() {
476
    	dirty = true;
477
    	firePropertyChange(IEditorPart.PROP_DIRTY);
478
    }
479

    
480

    
481
    /* (non-Javadoc)
482
     * @see org.eclipse.ui.part.EditorPart#isSaveAsAllowed()
483
     */
484
    @Override
485
    public boolean isSaveAsAllowed() {
486
        return false;  // "Save as" not allowed.
487
    }
488

    
489

    
490
    @Override
491
    public void setFocus() {
492
        if(conversationHolder!=null){
493
            conversationHolder.bind();
494
        }
495
        ((AlignmentEditorInput)getEditorInput()).bind();
496
    }
497

    
498
    @Override
499
    public void dispose() {
500
        ((AlignmentEditorInput)getEditorInput()).dispose();
501
    }
502

    
503
    public boolean isInsertMode() {
504
        return getAlignmentsContainer().getEditSettings().isInsert();
505
    }
506

    
507

    
508
    public boolean isInsertLeftInPherogram() {
509
        return getAlignmentsContainer().getEditSettings().isInsertLeftInDataArea();
510
    }
511

    
512

    
513
    public void toggleLeftRightInsertionInPherogram() {
514
    	getAlignmentsContainer().getEditSettings().toggleInsertLeftInDataArea();
515
    }
516

    
517

    
518
    public void toggleInsertOverwrite() {
519
    	getAlignmentsContainer().getEditSettings().toggleInsert();
520
    }
521

    
522

    
523
    private String cutPherogram(boolean left) {
524
        SelectionModel selection = getReadsArea().getSelection();
525
        if (selection.getCursorHeight() != 1) {
526
            return "Cutting pherograms is only possible if exactly one row is selected.";
527
        }
528
        else {
529
            PherogramArea pherogramArea =
530
                    getPherogramArea(getReadsArea().getSequenceOrder().idByIndex(selection.getCursorRow()));
531
            if (pherogramArea == null) {
532
                return "There is no pherogram attached to the current sequence.";
533
            }
534
            else {
535
                if (left) {
536
                    if (pherogramArea.setLeftCutPositionBySelection()) {
537
                        return null;
538
                    }
539
                    else {
540
                        return "The left end of the selection lies outside the pherogram attached to this sequence.";
541
                    }
542
                }
543
                else {
544
                    if (pherogramArea.setRightCutPositionBySelection()) {
545
                        return null;
546
                    }
547
                    else {
548
                        return "The right end of the selection lies outside the pherogram attached to this sequence.";
549
                    }
550
                }
551
            }
552
        }
553
    }
554

    
555

    
556
    public String cutPherogramLeft() {
557
        return cutPherogram(true);
558
    }
559

    
560

    
561
    public String cutPherogramRight() {
562
        return cutPherogram(false);
563
    }
564

    
565

    
566
    public void reverseComplementSelectedSequences() {
567
    	SelectionModel selection = getReadsArea().getSelection();
568
    	AlignmentModel<?> model = getReadsArea().getAlignmentModel();
569
    	for (int row = selection.getFirstRow(); row < selection.getFirstRow() + selection.getCursorHeight(); row++) {
570
			int sequenceID = getReadsArea().getSequenceOrder().idByIndex(row);
571
			PherogramArea area = getPherogramArea(sequenceID);
572
			PherogramAreaModel pherogramAlignmentModel = area.getModel();
573
			AlignmentModelUtils.reverseComplement(model, sequenceID,
574
			        pherogramAlignmentModel.editableIndexByBaseCallIndex(
575
			                pherogramAlignmentModel.getLeftCutPosition()).getBeforeValidIndex(),
576
			        pherogramAlignmentModel.editableIndexByBaseCallIndex(
577
			                pherogramAlignmentModel.getRightCutPosition()).getAfterValidIndex());
578
			pherogramAlignmentModel.reverseComplement();
579
		}
580
    }
581

    
582

    
583
    /**
584
     * Recreates the whole consensus sequence from all single read sequences. The previous consensus
585
     * sequence is overwritte.
586
     */
587
    @SuppressWarnings("unchecked")
588
    public <T> void createConsensusSequence() {
589
        ConsensusSequenceArea area = getConsensusHintDataArea();
590
        AlignmentModel<T> model = (AlignmentModel<T>)getEditableConsensusArea().getAlignmentModel();
591
        int sequenceID = model.sequenceIDIterator().next();  // There is always one sequence contained.
592
        int length = getReadsArea().getAlignmentModel().getMaxSequenceLength();
593

    
594
        Collection<T> tokens = new ArrayList<T>(length);
595
        for (int column = 0; column < length; column++) {
596
            tokens.add(model.getTokenSet().tokenByRepresentation(area.getConsensusToken(column)));
597
        }
598

    
599
        model.removeTokensAt(sequenceID, 0, model.getSequenceLength(sequenceID));
600
        model.insertTokensAt(sequenceID, 0, tokens);
601
    }
602

    
603

    
604
    /**
605
     * Updates the current consensus sequence by replacing gaps by the according consensus tokens
606
     * calculated from the single read sequences and extends the consensus sequence if necessary.
607
     */
608
    @SuppressWarnings("unchecked")
609
    public <T> void updateConsensusSequence() {
610
        ConsensusSequenceArea area = getConsensusHintDataArea();
611
        AlignmentModel<T> model = (AlignmentModel<T>)getEditableConsensusArea().getAlignmentModel();
612
        TokenSet<T> tokenSet = model.getTokenSet();
613
        int sequenceID = model.sequenceIDIterator().next();  // There is always one sequence contained.
614
        int currentConsensusLength = model.getSequenceLength(sequenceID);
615
        int overallLength = getReadsArea().getAlignmentModel().getMaxSequenceLength();
616

    
617
        // Replace gaps by new information:
618
        for (int column = 0; column < currentConsensusLength; column++) {
619
            if (tokenSet.isGapToken(model.getTokenAt(sequenceID, column))) {
620
                T newToken = tokenSet.tokenByRepresentation(area.getConsensusToken(column));
621
                if (!tokenSet.isGapToken(newToken)) {
622
                    model.setTokenAt(sequenceID, column, newToken);
623
                }
624
            }
625
        }
626

    
627
        // Append additional tokens:
628
        if (overallLength > currentConsensusLength) {
629
            Collection<T> tokens = new ArrayList<T>(overallLength);
630
            for (int column = currentConsensusLength; column < overallLength; column++) {
631
                tokens.add(tokenSet.tokenByRepresentation(area.getConsensusToken(column)));
632
            }
633
            model.appendTokens(sequenceID, tokens);
634
        }
635
    }
636

    
637

    
638
	public static PherogramProvider readPherogram(URI uri) throws IOException, UnsupportedChromatogramFormatException {
639
	    PherogramProvider result;
640
		InputStream stream = uri.toURL().openStream();
641
		try {
642
			result = new BioJavaPherogramProvider(ChromatogramFactory.create(stream));
643
		}
644
		finally {
645
			stream.close();
646
		}
647
		return result;
648
	}
649

    
650

    
651
	private String newReadName() {
652
		int index = 1;
653
		while (getReadsArea().getAlignmentModel().sequenceIDByName(DEFAULT_READ_NAME_PREFIX + index)
654
				!= AlignmentModel.NO_SEQUENCE_FOUND) {
655

    
656
			index++;
657
		}
658
		return DEFAULT_READ_NAME_PREFIX + index;
659
	}
660

    
661

    
662
    public void addRead(URI pherogramURI, boolean reverseComplemented) throws IOException, UnsupportedChromatogramFormatException {
663
    	addRead(newReadName(), pherogramURI, reverseComplemented, null, null, null, null, null);
664
    }
665

    
666

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

    
688
		AlignmentModel provider = getReadsArea().getAlignmentModel();
689
		PherogramProvider pherogramProvider = null;
690
		if (pherogramURI != null) {
691
		    pherogramProvider = readPherogram(pherogramURI);  // Must happen before a sequence is added, because it might throw an exception.
692
            if (reverseComplemented) {
693
                pherogramProvider = new ReverseComplementPherogramProvider(pherogramProvider);
694
            }
695
		}
696

    
697
        // Create sequence:
698
		provider.addSequence(name);
699
		int id = provider.sequenceIDByName(name);
700

    
701
		// Set edited sequence:
702
		Collection<Object> tokens = null;  // First save tokens in a collection to avoid GUI updated for each token.
703
		if (editedSequence != null) {
704
			tokens = AlignmentModelUtils.charSequenceToTokenList(editedSequence, provider.getTokenSet());
705
		}
706
		else if (pherogramProvider != null) {  // Copy base call sequence into alignment:
707
			tokens = new ArrayList<Object>();
708
			for (int i = 0; i < pherogramProvider.getSequenceLength(); i++) {
709
				tokens.add(provider.getTokenSet().tokenByRepresentation(
710
					Character.toString(pherogramProvider.getBaseCall(i))));
711
			}
712
			setDirty();
713
		}
714

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

    
718
		    if (pherogramProvider != null) {
719
		        // Create pherogram area:
720
		        PherogramArea pherogramArea = new PherogramArea(getReadsArea().getContentArea(),
721
		                new PherogramAreaModel(pherogramProvider));
722

    
723
		        // Set position properties and shifts:
724
		        PherogramAreaModel model = pherogramArea.getModel();
725
		        if ((firstSeqPos != null) && (leftCutPos != null)) {
726
		            model.setFirstSeqLeftCutPos(firstSeqPos, leftCutPos);
727
		        }
728
		        if (rightCutPos != null) {
729
		            model.setRightCutPosition(rightCutPos);
730
		        }
731
		        if ((shifts != null) && (shifts.length > 0)) {
732
		            for (int i = 0; i < shifts.length; i++) {
733
		                model.addShiftChange(shifts[i].position, shifts[i].shift);
734
		            }
735
		            setDirty();
736
		        }
737

    
738
		        // Add pherogram area to GUI:
739
		        pherogramArea.addMouseListener(new PherogramMouseListener(pherogramArea));
740
		        getReadsArea().getDataAreas().getSequenceAreas(id).add(pherogramArea);
741
		    }
742
		}
743
		return id;
744
	}
745
}
(1-1/4)