Merge branch 'develop' into LibrAlign
[taxeditor.git] / eu.etaxonomy.taxeditor.molecular / src / main / java / eu / etaxonomy / taxeditor / molecular / editor / AlignmentEditor.java
1 /**
2 * Copyright (C) 2014 EDIT
3 * European Distributed Institute of Taxonomy
4 * http://www.e-taxonomy.eu
5 *
6 * The contents of this file are subject to the Mozilla Public License Version 1.1
7 * See LICENSE.TXT at the top of this package for the full license terms.
8 */
9 package eu.etaxonomy.taxeditor.molecular.editor;
10
11
12 import info.bioinfweb.commons.swt.SWTUtils;
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.pherogram.PherogramArea;
18 import info.bioinfweb.libralign.dataarea.implementations.sequenceindex.SequenceIndexArea;
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.adapters.StringAdapter;
24 import info.bioinfweb.libralign.model.events.SequenceChangeEvent;
25 import info.bioinfweb.libralign.model.events.SequenceRenamedEvent;
26 import info.bioinfweb.libralign.model.events.TokenChangeEvent;
27 import info.bioinfweb.libralign.model.implementations.PackedAlignmentModel;
28 import info.bioinfweb.libralign.model.tokenset.CharacterTokenSet;
29 import info.bioinfweb.libralign.model.tokenset.TokenSet;
30 import info.bioinfweb.libralign.model.utils.AlignmentModelUtils;
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.dnd.Clipboard;
57 import org.eclipse.swt.widgets.Composite;
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.taxeditor.model.MessagingUtils;
76 import eu.etaxonomy.taxeditor.molecular.TaxeditorMolecularPlugin;
77 import eu.etaxonomy.taxeditor.molecular.handler.ToggleInsertOverwriteHandler;
78 import eu.etaxonomy.taxeditor.molecular.handler.ToggleLeftRightInsertionHandler;
79 import eu.etaxonomy.taxeditor.store.CdmStore;
80 import eu.etaxonomy.taxeditor.view.derivateSearch.DerivateLabelProvider;
81
82
83
84 /**
85 * Editor component to edit a contig alignment used to combine different overlapping pherograms from Sanger sequencing to
86 * a consensus sequence.
87 * <p>
88 * The contained GUI components used to edit the alignment come from <a href="http://bioinfweb.info/LibrAlign/">LibrAlign</a>.
89 *
90 * @author Ben Stöver
91 * @author pplitzner
92 * @date 04.08.2014
93 */
94 public class AlignmentEditor extends EditorPart {
95 public static final String ID = "eu.etaxonomy.taxeditor.molecular.AlignmentEditor";
96
97 public static final int READS_AREA_INDEX = 1;
98 public static final int EDITABLE_CONSENSUS_AREA_INDEX = READS_AREA_INDEX + 1;
99 public static final int CONSENSUS_HINT_AREA_INDEX = EDITABLE_CONSENSUS_AREA_INDEX + 1;
100 public static final int PHEROGRAM_AREA_INDEX = 0;
101 public static final int CONSENSUS_DATA_AREA_INDEX = 0;
102 public static final String DEFAULT_READ_NAME_PREFIX = "Read ";
103 public static final String CONSENSUS_NAME = "Consensus";
104
105
106 private final ConversationHolder conversationHolder;
107 private final AlignmentModelChangeListener DIRTY_LISTENER = new AlignmentModelChangeListener() {
108 @Override
109 public <T> void afterTokenChange(TokenChangeEvent<T> e) {
110 setDirty();
111 }
112
113 @Override
114 public <T> void afterSequenceRenamed(SequenceRenamedEvent<T> e) {
115 setDirty();
116 }
117
118 @Override
119 public <T> void afterSequenceChange(SequenceChangeEvent<T> e) {
120 setDirty();
121 }
122
123 @Override
124 public <T, U> void afterProviderChanged(AlignmentModel<T> oldProvider,
125 AlignmentModel<U> newProvider) { // Not expected.
126
127 setDirty();
128 }
129 };
130 private final AlignmentEditorActionUpdater ACTION_UPDATER = new AlignmentEditorActionUpdater();
131 public final Clipboard CLIPBOARD = new Clipboard(Display.getCurrent()); //TODO Move to global EDITor class.
132
133
134 private MultipleAlignmentsContainer alignmentsContainer = null;
135 private final Map<String, SingleReadAlignment> cdmMap = new TreeMap<String, SingleReadAlignment>(); //TODO Move this to ContigSequenceDataProvider
136 private boolean dirty = false;
137
138
139 public AlignmentEditor() {
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> model = new PackedAlignmentModel<Character>(tokenSet);
189 result.setAlignmentModel(model, false);
190 model.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 SWTUtils.childHasFocus((Composite)area.getToolkitComponent());
273 }
274
275
276 public boolean hasPherogram(String sequenceID) {
277 return getReadsArea().getDataAreas().getSequenceAreas(sequenceID).size() > PHEROGRAM_AREA_INDEX;
278 }
279
280
281 public PherogramArea getPherogramArea(String sequenceID) {
282 if (hasPherogram(sequenceID)) {
283 return (PherogramArea)getReadsArea().getDataAreas().getSequenceAreas(sequenceID).get(PHEROGRAM_AREA_INDEX);
284 }
285 else {
286 return null;
287 }
288 }
289
290
291 private ConsensusSequenceArea getConsensusHintDataArea() {
292 return (ConsensusSequenceArea)getAlignmentsContainer().getAlignmentAreas().
293 get(CONSENSUS_HINT_AREA_INDEX).getDataAreas().getBottomAreas().
294 get(CONSENSUS_DATA_AREA_INDEX);
295 }
296
297
298 @Deprecated //TODO Remove as soon as testing period is over
299 private void createTestContents() {
300 // Just for testing:
301 try {
302 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);
303 //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);
304 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);
305
306 // Add test consensus sequence:
307 AlignmentModel consensusModel = getEditableConsensusArea().getAlignmentModel();
308 String id = consensusModel.addSequence(CONSENSUS_NAME);
309 Collection<Object> tokens = new ArrayList<Object>(); // First save tokens in a collection to avoid GUI updated for each token.
310 tokens.add(consensusModel.getTokenSet().tokenByRepresentation("A"));
311 tokens.add(consensusModel.getTokenSet().tokenByRepresentation("C"));
312 tokens.add(consensusModel.getTokenSet().tokenByRepresentation("G"));
313 tokens.add(consensusModel.getTokenSet().tokenByRepresentation("T"));
314 consensusModel.insertTokensAt(id, 0, tokens);
315 }
316 catch (Exception e) {
317 throw new RuntimeException(e);
318 }
319 }
320
321
322 private void readCDMData(Sequence sequenceNode) {
323 //TODO If called from somewhere else than createPartControl() the editorInput needs to be checked and previous contents need to be cleared (or updated).
324
325 // Add reads:
326 for (SingleReadAlignment singleReadAlignment : sequenceNode.getSingleReadAlignments()) {
327 try {
328 SingleRead pherogramInfo = singleReadAlignment.getSingleRead();
329 String id = addRead(DerivateLabelProvider.getDerivateText(pherogramInfo, conversationHolder),
330 getPherogramURI(pherogramInfo),
331 singleReadAlignment.isReverseComplement(),
332 singleReadAlignment.getEditedSequence(),
333 singleReadAlignment.getFirstSeqPosition(),
334 singleReadAlignment.getLeftCutPosition(),
335 singleReadAlignment.getRightCutPosition(),
336 singleReadAlignment.getShifts());
337 cdmMap.put(id, singleReadAlignment);
338 }
339 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).
340 MessagingUtils.errorDialog("Error", null, "A single read was skipped because of the following error:\n\n" +
341 e.getLocalizedMessage(), TaxeditorMolecularPlugin.PLUGIN_ID, e, false);
342 }
343 }
344
345 // Set consensus sequence:
346 AlignmentModel consensusModel = getEditableConsensusArea().getAlignmentModel();
347 String id = consensusModel.addSequence(CONSENSUS_NAME);
348 consensusModel.insertTokensAt(id, 0, AlignmentModelUtils.charSequenceToTokenList(
349 sequenceNode.getConsensusSequence().getString(), consensusModel.getTokenSet()));
350 //TODO Can the consensus sequence also be null? / Should it be created here, if nothing is in the DB?
351 }
352
353
354 @Override
355 public void createPartControl(Composite parent) {
356 SWTComponentFactory.getInstance().getSWTComponent(getAlignmentsContainer(), parent, SWT.NONE);
357 Display.getCurrent().addFilter(SWT.FocusIn, ACTION_UPDATER);
358 Display.getCurrent().addFilter(SWT.FocusOut, ACTION_UPDATER);
359 updateStatusBar();
360
361 if (getEditorInput() instanceof AlignmentEditorInput) {
362 if (((AlignmentEditorInput)getEditorInput()).getSequenceNodeUuid() != null) {
363 Sequence sequenceNode = CdmStore.getService(ISequenceService.class).load(((AlignmentEditorInput)getEditorInput()).getSequenceNodeUuid());
364 //re-load into the current session if it is already persisted in the DB
365 if(sequenceNode!=null && sequenceNode.getId()!=0){
366 sequenceNode = CdmStore.getService(ISequenceService.class).load(sequenceNode.getUuid());
367 }
368 readCDMData(sequenceNode);
369 }
370 else {
371 createTestContents(); // This case will removed after the test phase and an exception should probably be thrown.
372 }
373 }
374 else {
375 throw new IllegalArgumentException("The editor input must have the type " +
376 AlignmentEditorInput.class.getCanonicalName()); //TODO What should be done here?
377 }
378 }
379
380
381 @Override
382 public void dispose() {
383 Display.getCurrent().removeFilter(SWT.FocusIn, ACTION_UPDATER);
384 Display.getCurrent().removeFilter(SWT.FocusOut, ACTION_UPDATER);
385 CLIPBOARD.dispose();
386 ((AlignmentEditorInput)getEditorInput()).dispose();
387 super.dispose();
388 }
389
390
391 private void updateStatusBar() {
392 IActionBars bars = getEditorSite().getActionBars();
393 bars.getStatusLineManager().setMessage("Edit mode: " +
394 (getReadsArea().getEditSettings().isInsert() ? "Insert" : "Overwrite") + " " +
395 "Insertion in pherogram: " +
396 (getReadsArea().getEditSettings().isInsertLeftInDataArea() ? "Left" : "Right")); //TODO multi language
397 }
398
399
400 private SingleReadAlignment.Shift[] convertToCDMShifts(PherogramAreaModel model) {
401 Iterator<ShiftChange> iterator = model.shiftChangeIterator();
402 List<SingleReadAlignment.Shift> shifts = new ArrayList<SingleReadAlignment.Shift>();
403 while (iterator.hasNext()) {
404 ShiftChange shiftChange = iterator.next();
405 shifts.add(new SingleReadAlignment.Shift(shiftChange.getBaseCallIndex(), shiftChange.getShiftChange()));
406 }
407 return shifts.toArray(new SingleReadAlignment.Shift[shifts.size()]);
408 }
409
410
411 @Override
412 public void doSave(IProgressMonitor monitor) {
413 if (getEditorInput() instanceof AlignmentEditorInput) {
414 String taskName = "Saving alignment"; //TODO multi language
415 monitor.beginTask(taskName, 3);
416
417 //re-loading sequence to avoid session conflicts
418 Sequence sequenceNode = CdmStore.getService(ISequenceService.class).load(((AlignmentEditorInput)getEditorInput()).getSequenceNodeUuid());
419 ((AlignmentEditorInput)getEditorInput()).setSequenceNode(sequenceNode);
420 StringAdapter stringProvider = new StringAdapter(getEditableConsensusArea().getAlignmentModel(), false); // Throws an exception if a token has more than one character.
421
422 // Write consensus sequence:
423 SequenceString consensusSequenceObj = sequenceNode.getConsensusSequence();
424 String newConsensusSequence = stringProvider.getSequence(
425 getEditableConsensusArea().getAlignmentModel().sequenceIDByName(CONSENSUS_NAME));
426 if (consensusSequenceObj == null) {
427 sequenceNode.setConsensusSequence(SequenceString.NewInstance(newConsensusSequence));
428 }
429 else {
430 consensusSequenceObj.setString(newConsensusSequence);
431 }
432
433 // Write single reads:
434 stringProvider.setUnderlyingModel(getReadsArea().getAlignmentModel());
435 sequenceNode.getSingleReadAlignments().retainAll(cdmMap.values()); // Remove all reads that are not in the alignment anymore.
436 Iterator<String> iterator = getReadsArea().getAlignmentModel().sequenceIDIterator();
437 while (iterator.hasNext()) {
438 String id = iterator.next();
439 SingleReadAlignment singleRead = cdmMap.get(id);
440 if (singleRead == null) {
441 throw new InternalError("Creating new reads from AlignmentEditor not implemented."); //TODO multi language
442 //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?
443 //singleRead = SingleReadAlignment.NewInstance(consensusSequence, singleRead, shifts, editedSequence);
444 }
445
446 singleRead.setEditedSequence(stringProvider.getSequence(id));
447
448 PherogramArea pherogramArea = getPherogramArea(id);
449 if (pherogramArea != null) {
450 PherogramAreaModel model = pherogramArea.getModel();
451 singleRead.setReverseComplement(model.getPherogramProvider() instanceof ReverseComplementPherogramProvider); // Works only if ReverseComplementPherogramProvider instances are not nested.
452 singleRead.setShifts(convertToCDMShifts(getPherogramArea(id).getModel()));
453 singleRead.setFirstSeqPosition(model.getFirstSeqPos());
454 singleRead.setLeftCutPosition(model.getLeftCutPosition());
455 singleRead.setRightCutPosition(model.getRightCutPosition());
456 }
457 }
458
459 if (!conversationHolder.isBound()) {
460 conversationHolder.bind();
461 }
462 monitor.worked(1);
463
464 ((AlignmentEditorInput)getEditorInput()).merge();
465 // Commit the conversation and start a new transaction immediately:
466 conversationHolder.commit(true);
467 monitor.worked(1);
468
469 dirty = false;
470 monitor.worked(1);
471 monitor.done();
472 firePropertyChange(PROP_DIRTY);
473 }
474 else {
475 //TODO Throw exception as soon as testing period which allows unlinked AlignmentEditor is over.
476 }
477 }
478
479
480 @Override
481 public void doSaveAs() {}
482
483
484 @Override
485 public void init(IEditorSite site, IEditorInput input) throws PartInitException {
486 setSite(site);
487 setInput(input);
488 }
489
490
491 @Override
492 public boolean isDirty() {
493 return dirty;
494 }
495
496
497 private void setDirty() {
498 dirty = true;
499 firePropertyChange(IEditorPart.PROP_DIRTY);
500 }
501
502
503 @Override
504 public boolean isSaveAsAllowed() {
505 return false; // "Save as" not allowed.
506 }
507
508
509 @Override
510 public void setFocus() {
511 if(conversationHolder != null){
512 conversationHolder.bind();
513 }
514 ((AlignmentEditorInput)getEditorInput()).bind();
515 }
516
517 public boolean isInsertMode() {
518 return getAlignmentsContainer().getEditSettings().isInsert();
519 }
520
521
522 public boolean isInsertLeftInPherogram() {
523 return getAlignmentsContainer().getEditSettings().isInsertLeftInDataArea();
524 }
525
526
527 public void toggleLeftRightInsertionInPherogram() {
528 getAlignmentsContainer().getEditSettings().toggleInsertLeftInDataArea();
529 }
530
531
532 public void toggleInsertOverwrite() {
533 getAlignmentsContainer().getEditSettings().toggleInsert();
534 }
535
536
537 private String cutPherogram(boolean left) {
538 SelectionModel selection = getReadsArea().getSelection();
539 if (selection.getCursorHeight() != 1) {
540 return "Cutting pherograms is only possible if exactly one row is selected."; //TODO multi language
541 }
542 else {
543 PherogramArea pherogramArea =
544 getPherogramArea(getReadsArea().getSequenceOrder().idByIndex(selection.getCursorRow()));
545 if (pherogramArea == null) {
546 return "There is no pherogram attached to the current sequence."; //TODO multi language
547 }
548 else {
549 if (left) {
550 if (pherogramArea.setLeftCutPositionBySelection()) {
551 return null;
552 }
553 else {
554 return "The left end of the selection lies outside the pherogram attached to this sequence."; //TODO multi language
555 }
556 }
557 else {
558 if (pherogramArea.setRightCutPositionBySelection()) {
559 return null;
560 }
561 else {
562 return "The right end of the selection lies outside the pherogram attached to this sequence."; //TODO multi language
563 }
564 }
565 }
566 }
567 }
568
569
570 public String cutPherogramLeft() {
571 return cutPherogram(true);
572 }
573
574
575 public String cutPherogramRight() {
576 return cutPherogram(false);
577 }
578
579
580 public void reverseComplementSelectedSequences() {
581 SelectionModel selection = getReadsArea().getSelection();
582 AlignmentModel<?> model = getReadsArea().getAlignmentModel();
583 for (int row = selection.getFirstRow(); row < selection.getFirstRow() + selection.getCursorHeight(); row++) {
584 String sequenceID = getReadsArea().getSequenceOrder().idByIndex(row);
585 PherogramArea area = getPherogramArea(sequenceID);
586 PherogramAreaModel pherogramAlignmentModel = area.getModel();
587 AlignmentModelUtils.reverseComplement(model, sequenceID,
588 pherogramAlignmentModel.editableIndexByBaseCallIndex(
589 pherogramAlignmentModel.getLeftCutPosition()).getBeforeValidIndex(),
590 pherogramAlignmentModel.editableIndexByBaseCallIndex(
591 pherogramAlignmentModel.getRightCutPosition()).getAfterValidIndex());
592 pherogramAlignmentModel.reverseComplement();
593 }
594 }
595
596
597 /**
598 * Recreates the whole consensus sequence from all single read sequences. The previous consensus
599 * sequence is overwritte.
600 */
601 @SuppressWarnings("unchecked")
602 public <T> void createConsensusSequence() {
603 ConsensusSequenceArea area = getConsensusHintDataArea();
604 AlignmentModel<T> model = (AlignmentModel<T>)getEditableConsensusArea().getAlignmentModel();
605 String sequenceID = model.sequenceIDIterator().next(); // There is always one sequence contained.
606 int length = getReadsArea().getAlignmentModel().getMaxSequenceLength();
607
608 Collection<T> tokens = new ArrayList<T>(length);
609 for (int column = 0; column < length; column++) {
610 tokens.add(model.getTokenSet().tokenByRepresentation(area.getConsensusToken(column)));
611 }
612
613 model.removeTokensAt(sequenceID, 0, model.getSequenceLength(sequenceID));
614 model.insertTokensAt(sequenceID, 0, tokens);
615 }
616
617
618 /**
619 * Updates the current consensus sequence by replacing gaps by the according consensus tokens
620 * calculated from the single read sequences and extends the consensus sequence if necessary.
621 */
622 @SuppressWarnings("unchecked")
623 public <T> void updateConsensusSequence() {
624 ConsensusSequenceArea area = getConsensusHintDataArea();
625 AlignmentModel<T> model = (AlignmentModel<T>)getEditableConsensusArea().getAlignmentModel();
626 TokenSet<T> tokenSet = model.getTokenSet();
627 String sequenceID = model.sequenceIDIterator().next(); // There is always one sequence contained.
628 int currentConsensusLength = model.getSequenceLength(sequenceID);
629 int overallLength = getReadsArea().getAlignmentModel().getMaxSequenceLength();
630
631 // Replace gaps by new information:
632 for (int column = 0; column < currentConsensusLength; column++) {
633 if (tokenSet.isGapToken(model.getTokenAt(sequenceID, column))) {
634 T newToken = tokenSet.tokenByRepresentation(area.getConsensusToken(column));
635 if (!tokenSet.isGapToken(newToken)) {
636 model.setTokenAt(sequenceID, column, newToken);
637 }
638 }
639 }
640
641 // Append additional tokens:
642 if (overallLength > currentConsensusLength) {
643 Collection<T> tokens = new ArrayList<T>(overallLength);
644 for (int column = currentConsensusLength; column < overallLength; column++) {
645 tokens.add(tokenSet.tokenByRepresentation(area.getConsensusToken(column)));
646 }
647 model.appendTokens(sequenceID, tokens);
648 }
649 }
650
651
652 public static PherogramProvider readPherogram(URI uri) throws IOException, UnsupportedChromatogramFormatException {
653 PherogramProvider result;
654 InputStream stream = uri.toURL().openStream();
655 try {
656 result = new BioJavaPherogramProvider(ChromatogramFactory.create(stream));
657 }
658 finally {
659 stream.close();
660 }
661 return result;
662 }
663
664
665 private String newReadName() {
666 int index = 1;
667 while (getReadsArea().getAlignmentModel().sequenceIDByName(DEFAULT_READ_NAME_PREFIX + index) != null) {
668 index++;
669 }
670 return DEFAULT_READ_NAME_PREFIX + index;
671 }
672
673
674 public void addRead(URI pherogramURI, boolean reverseComplemented) throws IOException, UnsupportedChromatogramFormatException {
675 addRead(newReadName(), pherogramURI, reverseComplemented, null, null, null, null, null);
676 }
677
678
679 /**
680 * Adds a new sequence with attached phergram data area to the reads alignment.
681 * <p>
682 * If {@code null} is specified as {@code editedSequence} the base call sequence from the pherogram will
683 * be set as the edited sequence. If {@code null} is specified as {@code shifts} no shifts between the edited
684 * and the base calls sequence are assumed.
685 *
686 * @param name the name of the new sequence
687 * @param pherogramURI the URI where the associated pherogram file is located
688 * @param reverseComplemented Specify {@code true} here, if the reverse complement of the pherogram data should
689 * be added, {@code false} otherwise.
690 * @param editedSequence the edited version of the base call sequence (May be {@code null}.)
691 * @param shifts the alignment information that links the edited and the base call sequence (May be {@code null}.)
692 * @return the sequence ID of the added read
693 * @throws IOException if an error occurred when trying to read the pherogram file
694 * @throws UnsupportedChromatogramFormatException if the format of the pherogram file is not supported
695 */
696 public String addRead(String name, URI pherogramURI, boolean reverseComplemented, String editedSequence,
697 Integer firstSeqPos, Integer leftCutPos, Integer rightCutPos, SingleReadAlignment.Shift[] shifts)
698 throws IOException, UnsupportedChromatogramFormatException {
699
700 AlignmentModel model = getReadsArea().getAlignmentModel();
701 PherogramProvider pherogramProvider = null;
702 if (pherogramURI != null) {
703 pherogramProvider = readPherogram(pherogramURI); // Must happen before a sequence is added, because it might throw an exception.
704 if (reverseComplemented) {
705 pherogramProvider = new ReverseComplementPherogramProvider(pherogramProvider);
706 }
707 }
708
709 // Create sequence:
710 model.addSequence(name);
711 String id = model.sequenceIDByName(name);
712
713 // Set edited sequence:
714 Collection<Object> tokens = null; // First save tokens in a collection to avoid GUI updated for each token.
715 if (editedSequence != null) {
716 tokens = AlignmentModelUtils.charSequenceToTokenList(editedSequence, model.getTokenSet());
717 }
718 else if (pherogramProvider != null) { // Copy base call sequence into alignment:
719 tokens = new ArrayList<Object>();
720 for (int i = 0; i < pherogramProvider.getSequenceLength(); i++) {
721 tokens.add(model.getTokenSet().tokenByRepresentation(
722 Character.toString(pherogramProvider.getBaseCall(i))));
723 }
724 setDirty();
725 }
726
727 if (tokens != null) { // If either an edited sequence or a pherogram URI was provided.
728 model.insertTokensAt(id, 0, tokens);
729
730 if (pherogramProvider != null) {
731 // Create pherogram area:
732 PherogramArea pherogramArea = new PherogramArea(getReadsArea().getContentArea(),
733 new PherogramAreaModel(pherogramProvider));
734
735 // Set position properties and shifts:
736 PherogramAreaModel phergramModel = pherogramArea.getModel();
737 if ((firstSeqPos != null) && (leftCutPos != null)) {
738 phergramModel.setFirstSeqLeftCutPos(firstSeqPos, leftCutPos);
739 }
740 if (rightCutPos != null) {
741 phergramModel.setRightCutPosition(rightCutPos);
742 }
743 if ((shifts != null) && (shifts.length > 0)) {
744 for (int i = 0; i < shifts.length; i++) {
745 phergramModel.addShiftChange(shifts[i].position, shifts[i].shift);
746 }
747 setDirty();
748 }
749
750 // Add pherogram area to GUI:
751 pherogramArea.addMouseListener(new PherogramMouseListener(pherogramArea));
752 getReadsArea().getDataAreas().getSequenceAreas(id).add(pherogramArea);
753 }
754 }
755 return id;
756 }
757
758
759 public static URI getPherogramURI(SingleRead pherogramInfo) {
760 if (pherogramInfo.getPherogram() != null) {
761 return MediaUtils.getFirstMediaRepresentationPart(pherogramInfo.getPherogram()).getUri();
762 }
763 else {
764 return null;
765 }
766 }
767 }