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