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