Merge branch 'develop' into LibrAlign
[taxeditor.git] / eu.etaxonomy.taxeditor.molecular / src / main / java / eu / etaxonomy / taxeditor / molecular / editor / AlignmentEditor.java
1 // $Id$
2 /**
3 * Copyright (C) 2014 EDIT
4 * European Distributed Institute of Taxonomy
5 * http://www.e-taxonomy.eu
6 *
7 * The contents of this file are subject to the Mozilla Public License Version 1.1
8 * See LICENSE.TXT at the top of this package for the full license terms.
9 */
10 package eu.etaxonomy.taxeditor.molecular.editor;
11
12
13 import info.bioinfweb.commons.swt.SWTUtils;
14 import info.bioinfweb.libralign.alignmentarea.AlignmentArea;
15 import info.bioinfweb.libralign.alignmentarea.selection.SelectionModel;
16 import info.bioinfweb.libralign.alignmentarea.tokenpainter.NucleotideTokenPainter;
17 import info.bioinfweb.libralign.dataarea.implementations.ConsensusSequenceArea;
18 import info.bioinfweb.libralign.dataarea.implementations.SequenceIndexArea;
19 import info.bioinfweb.libralign.dataarea.implementations.pherogram.PherogramArea;
20 import info.bioinfweb.libralign.editsettings.EditSettingsChangeEvent;
21 import info.bioinfweb.libralign.editsettings.EditSettingsListener;
22 import info.bioinfweb.libralign.model.AlignmentModel;
23 import info.bioinfweb.libralign.model.AlignmentModelChangeListener;
24 import info.bioinfweb.libralign.model.AlignmentModelUtils;
25 import info.bioinfweb.libralign.model.adapters.StringAdapter;
26 import info.bioinfweb.libralign.model.events.SequenceChangeEvent;
27 import info.bioinfweb.libralign.model.events.SequenceRenamedEvent;
28 import info.bioinfweb.libralign.model.events.TokenChangeEvent;
29 import info.bioinfweb.libralign.model.implementations.PackedAlignmentModel;
30 import info.bioinfweb.libralign.model.tokenset.CharacterTokenSet;
31 import info.bioinfweb.libralign.model.tokenset.TokenSet;
32 import info.bioinfweb.libralign.multiplealignments.AlignmentAreaList;
33 import info.bioinfweb.libralign.multiplealignments.MultipleAlignmentsContainer;
34 import info.bioinfweb.libralign.pherogram.model.PherogramAreaModel;
35 import info.bioinfweb.libralign.pherogram.model.ShiftChange;
36 import info.bioinfweb.libralign.pherogram.provider.BioJavaPherogramProvider;
37 import info.bioinfweb.libralign.pherogram.provider.PherogramProvider;
38 import info.bioinfweb.libralign.pherogram.provider.ReverseComplementPherogramProvider;
39 import info.bioinfweb.tic.SWTComponentFactory;
40
41 import java.io.File;
42 import java.io.IOException;
43 import java.io.InputStream;
44 import java.net.URI;
45 import java.util.ArrayList;
46 import java.util.Collection;
47 import java.util.Collections;
48 import java.util.Iterator;
49 import java.util.List;
50 import java.util.Map;
51 import java.util.TreeMap;
52
53 import org.biojava.bio.chromatogram.ChromatogramFactory;
54 import org.biojava.bio.chromatogram.UnsupportedChromatogramFormatException;
55 import org.eclipse.core.runtime.IProgressMonitor;
56 import org.eclipse.swt.SWT;
57 import org.eclipse.swt.dnd.Clipboard;
58 import org.eclipse.swt.widgets.Composite;
59 import org.eclipse.swt.widgets.Display;
60 import org.eclipse.ui.IActionBars;
61 import org.eclipse.ui.IEditorInput;
62 import org.eclipse.ui.IEditorPart;
63 import org.eclipse.ui.IEditorSite;
64 import org.eclipse.ui.PartInitException;
65 import org.eclipse.ui.PlatformUI;
66 import org.eclipse.ui.commands.ICommandService;
67 import org.eclipse.ui.part.EditorPart;
68
69 import eu.etaxonomy.cdm.api.conversation.ConversationHolder;
70 import eu.etaxonomy.cdm.api.service.molecular.ISequenceService;
71 import eu.etaxonomy.cdm.model.media.MediaUtils;
72 import eu.etaxonomy.cdm.model.molecular.Sequence;
73 import eu.etaxonomy.cdm.model.molecular.SequenceString;
74 import eu.etaxonomy.cdm.model.molecular.SingleRead;
75 import eu.etaxonomy.cdm.model.molecular.SingleReadAlignment;
76 import eu.etaxonomy.cdm.model.molecular.SingleReadAlignment.Shift;
77 import eu.etaxonomy.taxeditor.model.MessagingUtils;
78 import eu.etaxonomy.taxeditor.molecular.TaxeditorMolecularPlugin;
79 import eu.etaxonomy.taxeditor.molecular.handler.ToggleInsertOverwriteHandler;
80 import eu.etaxonomy.taxeditor.molecular.handler.ToggleLeftRightInsertionHandler;
81 import eu.etaxonomy.taxeditor.store.CdmStore;
82 import eu.etaxonomy.taxeditor.view.derivateSearch.DerivateLabelProvider;
83
84
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 AlignmentEditor extends EditorPart {
97 public static final String ID = "eu.etaxonomy.taxeditor.molecular.AlignmentEditor";
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 ";
105 public static final String CONSENSUS_NAME = "Consensus";
106
107
108 private final ConversationHolder conversationHolder;
109 private final AlignmentModelChangeListener DIRTY_LISTENER = new AlignmentModelChangeListener() {
110 @Override
111 public <T> void afterTokenChange(TokenChangeEvent<T> e) {
112 setDirty();
113 }
114
115 @Override
116 public <T> void afterSequenceRenamed(SequenceRenamedEvent<T> e) {
117 setDirty();
118 }
119
120 @Override
121 public <T> void afterSequenceChange(SequenceChangeEvent<T> e) {
122 setDirty();
123 }
124
125 @Override
126 public <T, U> void afterProviderChanged(AlignmentModel<T> oldProvider,
127 AlignmentModel<U> newProvider) { // Not expected.
128
129 setDirty();
130 }
131 };
132 private final AlignmentEditorActionUpdater ACTION_UPDATER = new AlignmentEditorActionUpdater();
133 public final Clipboard CLIPBOARD = new Clipboard(Display.getCurrent()); //TODO Move to global EDITor class.
134
135
136 private MultipleAlignmentsContainer alignmentsContainer = null;
137 private final Map<Integer, SingleReadAlignment> cdmMap = new TreeMap<Integer, SingleReadAlignment>(); //TODO Move this to ContigSequenceDataProvider
138 private boolean dirty = false;
139
140 public AlignmentEditor()
141 {
142 super();
143 conversationHolder = CdmStore.createConversation();
144 //conversationHolder = null;
145 }
146
147
148 private void refreshToolbarElement(String id) {
149 ICommandService commandService =
150 (ICommandService)PlatformUI.getWorkbench().getActiveWorkbenchWindow().getService(ICommandService.class);
151 if (commandService != null) {
152 commandService.refreshElements(id, Collections.EMPTY_MAP);
153 }
154 }
155
156
157 private void registerEditSettingListener(MultipleAlignmentsContainer container) {
158 container.getEditSettings().addListener(new EditSettingsListener() {
159 @Override
160 public void workingModeChanged(EditSettingsChangeEvent e) {} // Currently nothing to do
161
162 @Override
163 public void insertLeftInDataAreaChanged(EditSettingsChangeEvent e) {
164 updateStatusBar();
165 refreshToolbarElement(ToggleLeftRightInsertionHandler.COMMAND_ID);
166 }
167
168 @Override
169 public void insertChanged(EditSettingsChangeEvent e) {
170 updateStatusBar();
171 refreshToolbarElement(ToggleInsertOverwriteHandler.COMMAND_ID);
172 }
173 });
174 }
175
176
177 private AlignmentArea createIndexArea(MultipleAlignmentsContainer container, AlignmentArea labeledArea) {
178 AlignmentArea result = new AlignmentArea(container);
179 result.setAllowVerticalScrolling(false);
180 result.getDataAreas().getTopAreas().add(new SequenceIndexArea(result.getContentArea(), labeledArea));
181 return result;
182 }
183
184
185 private AlignmentArea createEditableAlignmentArea(MultipleAlignmentsContainer container, boolean allowVerticalScrolling) {
186 AlignmentArea result = new AlignmentArea(container);
187 result.setAllowVerticalScrolling(allowVerticalScrolling);
188
189 CharacterTokenSet tokenSet = CharacterTokenSet.newDNAInstance(); //TODO Should NUCLEOTIDE be used instead?
190 AlignmentModel<Character> provider = new PackedAlignmentModel<Character>(tokenSet);
191 result.setAlignmentModel(provider, false);
192 provider.getChangeListeners().add(DIRTY_LISTENER);
193 result.getPaintSettings().getTokenPainterList().set(0, new NucleotideTokenPainter());
194
195 return result;
196 }
197
198
199 private AlignmentArea createConsensusHintArea(MultipleAlignmentsContainer container,
200 AlignmentArea labeledArea) {
201
202 AlignmentArea result = new AlignmentArea(container);
203 result.setAllowVerticalScrolling(false);
204 result.getDataAreas().getBottomAreas().add(
205 new ConsensusSequenceArea(result.getContentArea(), labeledArea));
206 return result;
207 }
208
209
210 private MultipleAlignmentsContainer getAlignmentsContainer() {
211 if (alignmentsContainer == null) {
212 alignmentsContainer = new MultipleAlignmentsContainer();
213
214 AlignmentAreaList list = alignmentsContainer.getAlignmentAreas();
215 AlignmentArea readsArea = createEditableAlignmentArea(alignmentsContainer, true);
216 readsArea.getSelection().addSelectionListener(ACTION_UPDATER);
217 list.add(createIndexArea(alignmentsContainer, readsArea));
218 list.add(readsArea); // Make sure READS_AREA_INDEX is correct.
219 AlignmentArea editableConsensusArea = createEditableAlignmentArea(alignmentsContainer, false);
220 editableConsensusArea.getSelection().addSelectionListener(ACTION_UPDATER);
221 list.add(editableConsensusArea); // Make sure COMSENSUS_AREA_INDEX is correct.
222 list.add(createConsensusHintArea(alignmentsContainer, readsArea));
223
224 registerEditSettingListener(alignmentsContainer);
225 }
226 return alignmentsContainer;
227 }
228
229
230 public AlignmentArea getReadsArea() {
231 return getAlignmentsContainer().getAlignmentAreas().get(READS_AREA_INDEX);
232 }
233
234
235 public AlignmentArea getEditableConsensusArea() {
236 return getAlignmentsContainer().getAlignmentAreas().get(EDITABLE_CONSENSUS_AREA_INDEX);
237 }
238
239
240 /**
241 * Checks whether {@link #getReadsArea()} or {@link #getEditableConsensusArea()} currently
242 * have the user focus and returns the according component.
243 *
244 * @return either the reads or the consensus alignment area or {@code null} if none of these
245 * components is currently focused
246 */
247 public AlignmentArea getFocusedArea() {
248 AlignmentArea result = getReadsArea();
249 if (hasFocus(result)) {
250 return result;
251 }
252 else {
253 result = getEditableConsensusArea();
254 if (hasFocus(result)) {
255 return result;
256 }
257 else {
258 return null;
259 }
260 }
261 }
262
263
264 /**
265 * Checks whether the specified alignment area or one of its subcomponents currently has the
266 * focus.
267 *
268 * @param area the alignment area to be checked (Can only be {@link #getReadsArea()} or
269 * {@link #getEditableConsensusArea()}.)
270 * @return {@code true} if the specified component is focused and is either equal to
271 * {@link #getReadsArea()} or {@link #getEditableConsensusArea()}or {@code false} otherwise
272 */
273 private boolean hasFocus(AlignmentArea area) {
274 return SWTUtils.childHasFocus((Composite)area.getToolkitComponent());
275 }
276
277
278 public boolean hasPherogram(int sequenceID) {
279 return getReadsArea().getDataAreas().getSequenceAreas(sequenceID).size() > PHEROGRAM_AREA_INDEX;
280 }
281
282
283 public PherogramArea getPherogramArea(int sequenceID) {
284 if (hasPherogram(sequenceID)) {
285 return (PherogramArea)getReadsArea().getDataAreas().getSequenceAreas(sequenceID).get(PHEROGRAM_AREA_INDEX);
286 }
287 else {
288 return null;
289 }
290 }
291
292
293 private ConsensusSequenceArea getConsensusHintDataArea() {
294 return (ConsensusSequenceArea)getAlignmentsContainer().getAlignmentAreas().
295 get(CONSENSUS_HINT_AREA_INDEX).getDataAreas().getBottomAreas().
296 get(CONSENSUS_DATA_AREA_INDEX);
297 }
298
299
300 @Deprecated //TODO Remove as soon as testing period is over
301 private void createTestContents() {
302 // Just for testing:
303 try {
304 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);
305 //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);
306 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);
307
308 // Add test consensus sequence:
309 AlignmentModel consensusModel = getEditableConsensusArea().getAlignmentModel();
310 int id = consensusModel.addSequence(CONSENSUS_NAME);
311 Collection<Object> tokens = new ArrayList<Object>(); // First save tokens in a collection to avoid GUI updated for each token.
312 tokens.add(consensusModel.getTokenSet().tokenByRepresentation("A"));
313 tokens.add(consensusModel.getTokenSet().tokenByRepresentation("C"));
314 tokens.add(consensusModel.getTokenSet().tokenByRepresentation("G"));
315 tokens.add(consensusModel.getTokenSet().tokenByRepresentation("T"));
316 consensusModel.insertTokensAt(id, 0, tokens);
317 }
318 catch (Exception e) {
319 throw new RuntimeException(e);
320 }
321 }
322
323
324 private void readCDMData(Sequence sequenceNode) {
325 //TODO If called from somewhere else than createPartControl() the editorInput needs to be checked and previous contents need to be cleared (or updated).
326
327 // Add reads:
328 for (SingleReadAlignment singleReadAlignment : sequenceNode.getSingleReadAlignments()) {
329 try {
330 SingleRead pherogramInfo = singleReadAlignment.getSingleRead();
331 URI uri = null;
332 if (pherogramInfo.getPherogram() != null) {
333 uri = MediaUtils.getFirstMediaRepresentationPart(pherogramInfo.getPherogram()).getUri();
334 }
335 int id = addRead(DerivateLabelProvider.getDerivateText(pherogramInfo, conversationHolder),
336 uri,
337 singleReadAlignment.isReverseComplement(),
338 singleReadAlignment.getEditedSequence(),
339 singleReadAlignment.getFirstSeqPosition(),
340 singleReadAlignment.getLeftCutPosition(),
341 singleReadAlignment.getRightCutPosition(),
342 singleReadAlignment.getShifts());
343 cdmMap.put(id, singleReadAlignment);
344 }
345 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).
346 MessagingUtils.errorDialog("Error", null, "A single read was skipped because of the following error:\n\n" +
347 e.getLocalizedMessage(), TaxeditorMolecularPlugin.PLUGIN_ID, e, false);
348 }
349 }
350
351 // Set consensus sequence:
352 AlignmentModel consensusProvider = getEditableConsensusArea().getAlignmentModel();
353 int id = consensusProvider.addSequence(CONSENSUS_NAME);
354 consensusProvider.insertTokensAt(id, 0, AlignmentModelUtils.charSequenceToTokenList(
355 sequenceNode.getConsensusSequence().getString(), consensusProvider.getTokenSet()));
356 //TODO Can the consensus sequence also be null? / Should it be created here, if nothing is in the DB?
357 }
358
359
360 @Override
361 public void createPartControl(Composite parent) {
362 SWTComponentFactory.getInstance().getSWTComponent(getAlignmentsContainer(), parent, SWT.NONE);
363 Display.getCurrent().addFilter(SWT.FocusIn, ACTION_UPDATER);
364 Display.getCurrent().addFilter(SWT.FocusOut, ACTION_UPDATER);
365 updateStatusBar();
366
367 if (getEditorInput() instanceof AlignmentEditorInput) {
368 if (((AlignmentEditorInput)getEditorInput()).getSequenceNodeUuid() != null) {
369 Sequence sequenceNode = CdmStore.getService(ISequenceService.class).load(((AlignmentEditorInput)getEditorInput()).getSequenceNodeUuid());
370 //re-load into the current session if it is already persisted in the DB
371 if(sequenceNode!=null && sequenceNode.getId()!=0){
372 sequenceNode = CdmStore.getService(ISequenceService.class).load(sequenceNode.getUuid());
373 }
374 readCDMData(sequenceNode);
375 }
376 else {
377 createTestContents(); // This case will removed after the test phase and an exception should probably be thrown.
378 }
379 }
380 else {
381 throw new IllegalArgumentException("The editor input must have the type " +
382 AlignmentEditorInput.class.getCanonicalName()); //TODO What should be done here?
383 }
384 }
385
386
387 @Override
388 public void dispose() {
389 Display.getCurrent().removeFilter(SWT.FocusIn, ACTION_UPDATER);
390 Display.getCurrent().removeFilter(SWT.FocusOut, ACTION_UPDATER);
391 CLIPBOARD.dispose();
392 ((AlignmentEditorInput)getEditorInput()).dispose();
393 super.dispose();
394 }
395
396
397 private void updateStatusBar() {
398 IActionBars bars = getEditorSite().getActionBars();
399 bars.getStatusLineManager().setMessage("Edit mode: " +
400 (getReadsArea().getEditSettings().isInsert() ? "Insert" : "Overwrite") + " " +
401 "Insertion in pherogram: " +
402 (getReadsArea().getEditSettings().isInsertLeftInDataArea() ? "Left" : "Right"));
403 }
404
405
406 private SingleReadAlignment.Shift[] convertToCDMShifts(PherogramAreaModel model) {
407 Iterator<ShiftChange> iterator = model.shiftChangeIterator();
408 List<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 Shift[]{});
414 }
415
416
417 @Override
418 public void doSave(IProgressMonitor monitor) {
419 if (getEditorInput() instanceof AlignmentEditorInput) {
420 String taskName = "Saving alignment";
421 monitor.beginTask(taskName, 3);
422
423 //re-loading sequence to avoid session conflicts
424 Sequence sequenceNode = CdmStore.getService(ISequenceService.class).load(((AlignmentEditorInput)getEditorInput()).getSequenceNodeUuid());
425 ((AlignmentEditorInput)getEditorInput()).setSequenceNode(sequenceNode);
426 StringAdapter stringProvider = new StringAdapter(getEditableConsensusArea().getAlignmentModel(), false); // Throws an exception if a token has more than one character.
427
428 // Write consensus sequence:
429 SequenceString consensusSequenceObj = sequenceNode.getConsensusSequence();
430 String newConsensusSequence = stringProvider.getSequence(
431 getEditableConsensusArea().getAlignmentModel().sequenceIDByName(CONSENSUS_NAME));
432 if (consensusSequenceObj == null) {
433 sequenceNode.setConsensusSequence(SequenceString.NewInstance(newConsensusSequence));
434 }
435 else {
436 consensusSequenceObj.setString(newConsensusSequence);
437 }
438
439 // Write single reads:
440 stringProvider.setUnderlyingProvider(getReadsArea().getAlignmentModel());
441 sequenceNode.getSingleReadAlignments().retainAll(cdmMap.values()); // Remove all reads that are not in the alignment anymore.
442 Iterator<Integer> iterator = getReadsArea().getAlignmentModel().sequenceIDIterator();
443 while (iterator.hasNext()) {
444 int id = iterator.next();
445 SingleReadAlignment singleRead = cdmMap.get(id);
446 if (singleRead == null) {
447 throw new InternalError("Creating new reads from AlignmentEditor not implemented.");
448 //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?
449 //singleRead = SingleReadAlignment.NewInstance(consensusSequence, singleRead, shifts, editedSequence);
450 }
451
452 singleRead.setEditedSequence(stringProvider.getSequence(id));
453
454 PherogramArea pherogramArea = getPherogramArea(id);
455 if (pherogramArea != null) {
456 PherogramAreaModel model = pherogramArea.getModel();
457 singleRead.setReverseComplement(model.getPherogramProvider() instanceof ReverseComplementPherogramProvider); // Works only if ReverseComplementPherogramProvider instances are not nested.
458 singleRead.setShifts(convertToCDMShifts(getPherogramArea(id).getModel()));
459 singleRead.setFirstSeqPosition(model.getFirstSeqPos());
460 singleRead.setLeftCutPosition(model.getLeftCutPosition());
461 singleRead.setRightCutPosition(model.getRightCutPosition());
462 }
463 }
464
465 if (!conversationHolder.isBound()) {
466 conversationHolder.bind();
467 }
468 monitor.worked(1);
469
470 ((AlignmentEditorInput)getEditorInput()).merge();
471 // Commit the conversation and start a new transaction immediately:
472 conversationHolder.commit(true);
473 monitor.worked(1);
474
475 dirty = false;
476 monitor.worked(1);
477 monitor.done();
478 firePropertyChange(PROP_DIRTY);
479 }
480 else {
481 //TODO Throw exception as soon as testing period which allows unlinked AlignmentEditor is over.
482 }
483 }
484
485
486 @Override
487 public void doSaveAs() {}
488
489
490 @Override
491 public void init(IEditorSite site, IEditorInput input) throws PartInitException {
492 setSite(site);
493 setInput(input);
494 }
495
496
497 @Override
498 public boolean isDirty() {
499 return dirty;
500 }
501
502
503 private void setDirty() {
504 dirty = true;
505 firePropertyChange(IEditorPart.PROP_DIRTY);
506 }
507
508
509 @Override
510 public boolean isSaveAsAllowed() {
511 return false; // "Save as" not allowed.
512 }
513
514
515 @Override
516 public void setFocus() {
517 if(conversationHolder != null){
518 conversationHolder.bind();
519 }
520 ((AlignmentEditorInput)getEditorInput()).bind();
521 }
522
523 public boolean isInsertMode() {
524 return getAlignmentsContainer().getEditSettings().isInsert();
525 }
526
527
528 public boolean isInsertLeftInPherogram() {
529 return getAlignmentsContainer().getEditSettings().isInsertLeftInDataArea();
530 }
531
532
533 public void toggleLeftRightInsertionInPherogram() {
534 getAlignmentsContainer().getEditSettings().toggleInsertLeftInDataArea();
535 }
536
537
538 public void toggleInsertOverwrite() {
539 getAlignmentsContainer().getEditSettings().toggleInsert();
540 }
541
542
543 private String cutPherogram(boolean left) {
544 SelectionModel selection = getReadsArea().getSelection();
545 if (selection.getCursorHeight() != 1) {
546 return "Cutting pherograms is only possible if exactly one row is selected.";
547 }
548 else {
549 PherogramArea pherogramArea =
550 getPherogramArea(getReadsArea().getSequenceOrder().idByIndex(selection.getCursorRow()));
551 if (pherogramArea == null) {
552 return "There is no pherogram attached to the current sequence.";
553 }
554 else {
555 if (left) {
556 if (pherogramArea.setLeftCutPositionBySelection()) {
557 return null;
558 }
559 else {
560 return "The left end of the selection lies outside the pherogram attached to this sequence.";
561 }
562 }
563 else {
564 if (pherogramArea.setRightCutPositionBySelection()) {
565 return null;
566 }
567 else {
568 return "The right end of the selection lies outside the pherogram attached to this sequence.";
569 }
570 }
571 }
572 }
573 }
574
575
576 public String cutPherogramLeft() {
577 return cutPherogram(true);
578 }
579
580
581 public String cutPherogramRight() {
582 return cutPherogram(false);
583 }
584
585
586 public void reverseComplementSelectedSequences() {
587 SelectionModel selection = getReadsArea().getSelection();
588 AlignmentModel<?> model = getReadsArea().getAlignmentModel();
589 for (int row = selection.getFirstRow(); row < selection.getFirstRow() + selection.getCursorHeight(); row++) {
590 int sequenceID = getReadsArea().getSequenceOrder().idByIndex(row);
591 PherogramArea area = getPherogramArea(sequenceID);
592 PherogramAreaModel pherogramAlignmentModel = area.getModel();
593 AlignmentModelUtils.reverseComplement(model, sequenceID,
594 pherogramAlignmentModel.editableIndexByBaseCallIndex(
595 pherogramAlignmentModel.getLeftCutPosition()).getBeforeValidIndex(),
596 pherogramAlignmentModel.editableIndexByBaseCallIndex(
597 pherogramAlignmentModel.getRightCutPosition()).getAfterValidIndex());
598 pherogramAlignmentModel.reverseComplement();
599 }
600 }
601
602
603 /**
604 * Recreates the whole consensus sequence from all single read sequences. The previous consensus
605 * sequence is overwritte.
606 */
607 @SuppressWarnings("unchecked")
608 public <T> void createConsensusSequence() {
609 ConsensusSequenceArea area = getConsensusHintDataArea();
610 AlignmentModel<T> model = (AlignmentModel<T>)getEditableConsensusArea().getAlignmentModel();
611 int sequenceID = model.sequenceIDIterator().next(); // There is always one sequence contained.
612 int length = getReadsArea().getAlignmentModel().getMaxSequenceLength();
613
614 Collection<T> tokens = new ArrayList<T>(length);
615 for (int column = 0; column < length; column++) {
616 tokens.add(model.getTokenSet().tokenByRepresentation(area.getConsensusToken(column)));
617 }
618
619 model.removeTokensAt(sequenceID, 0, model.getSequenceLength(sequenceID));
620 model.insertTokensAt(sequenceID, 0, tokens);
621 }
622
623
624 /**
625 * Updates the current consensus sequence by replacing gaps by the according consensus tokens
626 * calculated from the single read sequences and extends the consensus sequence if necessary.
627 */
628 @SuppressWarnings("unchecked")
629 public <T> void updateConsensusSequence() {
630 ConsensusSequenceArea area = getConsensusHintDataArea();
631 AlignmentModel<T> model = (AlignmentModel<T>)getEditableConsensusArea().getAlignmentModel();
632 TokenSet<T> tokenSet = model.getTokenSet();
633 int sequenceID = model.sequenceIDIterator().next(); // There is always one sequence contained.
634 int currentConsensusLength = model.getSequenceLength(sequenceID);
635 int overallLength = getReadsArea().getAlignmentModel().getMaxSequenceLength();
636
637 // Replace gaps by new information:
638 for (int column = 0; column < currentConsensusLength; column++) {
639 if (tokenSet.isGapToken(model.getTokenAt(sequenceID, column))) {
640 T newToken = tokenSet.tokenByRepresentation(area.getConsensusToken(column));
641 if (!tokenSet.isGapToken(newToken)) {
642 model.setTokenAt(sequenceID, column, newToken);
643 }
644 }
645 }
646
647 // Append additional tokens:
648 if (overallLength > currentConsensusLength) {
649 Collection<T> tokens = new ArrayList<T>(overallLength);
650 for (int column = currentConsensusLength; column < overallLength; column++) {
651 tokens.add(tokenSet.tokenByRepresentation(area.getConsensusToken(column)));
652 }
653 model.appendTokens(sequenceID, tokens);
654 }
655 }
656
657
658 public static PherogramProvider readPherogram(URI uri) throws IOException, UnsupportedChromatogramFormatException {
659 PherogramProvider result;
660 InputStream stream = uri.toURL().openStream();
661 try {
662 result = new BioJavaPherogramProvider(ChromatogramFactory.create(stream));
663 }
664 finally {
665 stream.close();
666 }
667 return result;
668 }
669
670
671 private String newReadName() {
672 int index = 1;
673 while (getReadsArea().getAlignmentModel().sequenceIDByName(DEFAULT_READ_NAME_PREFIX + index)
674 != AlignmentModel.NO_SEQUENCE_FOUND) {
675
676 index++;
677 }
678 return DEFAULT_READ_NAME_PREFIX + index;
679 }
680
681
682 public void addRead(URI pherogramURI, boolean reverseComplemented) throws IOException, UnsupportedChromatogramFormatException {
683 addRead(newReadName(), pherogramURI, reverseComplemented, null, null, null, null, null);
684 }
685
686
687 /**
688 * Adds a new sequence with attached phergram data area to the reads alignment.
689 * <p>
690 * If {@code null} is specified as {@code editedSequence} the base call sequence from the pherogram will
691 * be set as the edited sequence. If {@code null} is specified as {@code shifts} no shifts between the edited
692 * and the base calls sequence are assumed.
693 *
694 * @param name the name of the new sequence
695 * @param pherogramURI the URI where the associated pherogram file is located
696 * @param reverseComplemented Specify {@code true} here, if the reverse complement of the pherogram data should
697 * be added, {@code false} otherwise.
698 * @param editedSequence the edited version of the base call sequence (May be {@code null}.)
699 * @param shifts the alignment information that links the edited and the base call sequence (May be {@code null}.)
700 * @return the sequence ID of the added read
701 * @throws IOException if an error occurred when trying to read the pherogram file
702 * @throws UnsupportedChromatogramFormatException if the format of the pherogram file is not supported
703 */
704 public int addRead(String name, URI pherogramURI, boolean reverseComplemented, String editedSequence,
705 Integer firstSeqPos, Integer leftCutPos, Integer rightCutPos, SingleReadAlignment.Shift[] shifts)
706 throws IOException, UnsupportedChromatogramFormatException {
707
708 AlignmentModel provider = getReadsArea().getAlignmentModel();
709 PherogramProvider pherogramProvider = null;
710 if (pherogramURI != null) {
711 pherogramProvider = readPherogram(pherogramURI); // Must happen before a sequence is added, because it might throw an exception.
712 if (reverseComplemented) {
713 pherogramProvider = new ReverseComplementPherogramProvider(pherogramProvider);
714 }
715 }
716
717 // Create sequence:
718 provider.addSequence(name);
719 int id = provider.sequenceIDByName(name);
720
721 // Set edited sequence:
722 Collection<Object> tokens = null; // First save tokens in a collection to avoid GUI updated for each token.
723 if (editedSequence != null) {
724 tokens = AlignmentModelUtils.charSequenceToTokenList(editedSequence, provider.getTokenSet());
725 }
726 else if (pherogramProvider != null) { // Copy base call sequence into alignment:
727 tokens = new ArrayList<Object>();
728 for (int i = 0; i < pherogramProvider.getSequenceLength(); i++) {
729 tokens.add(provider.getTokenSet().tokenByRepresentation(
730 Character.toString(pherogramProvider.getBaseCall(i))));
731 }
732 setDirty();
733 }
734
735 if (tokens != null) { // If either an edited sequence or a pherogram URI was provided.
736 provider.insertTokensAt(id, 0, tokens);
737
738 if (pherogramProvider != null) {
739 // Create pherogram area:
740 PherogramArea pherogramArea = new PherogramArea(getReadsArea().getContentArea(),
741 new PherogramAreaModel(pherogramProvider));
742
743 // Set position properties and shifts:
744 PherogramAreaModel model = pherogramArea.getModel();
745 if ((firstSeqPos != null) && (leftCutPos != null)) {
746 model.setFirstSeqLeftCutPos(firstSeqPos, leftCutPos);
747 }
748 if (rightCutPos != null) {
749 model.setRightCutPosition(rightCutPos);
750 }
751 if ((shifts != null) && (shifts.length > 0)) {
752 for (int i = 0; i < shifts.length; i++) {
753 model.addShiftChange(shifts[i].position, shifts[i].shift);
754 }
755 setDirty();
756 }
757
758 // Add pherogram area to GUI:
759 pherogramArea.addMouseListener(new PherogramMouseListener(pherogramArea));
760 getReadsArea().getDataAreas().getSequenceAreas(id).add(pherogramArea);
761 }
762 }
763 return id;
764 }
765 }