1 /**
2  * UGENE - Integrated Bioinformatics Tools.
3  * Copyright (C) 2008-2021 UniPro <ugene@unipro.ru>
4  * http://ugene.net
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19  * MA 02110-1301, USA.
20  */
21 
22 #include "ExportSequenceViewItems.h"
23 
24 #include <QDir>
25 #include <QMainWindow>
26 #include <QMessageBox>
27 
28 #include <U2Core/AnnotationSelection.h>
29 #include <U2Core/AnnotationTableObject.h>
30 #include <U2Core/AppContext.h>
31 #include <U2Core/BaseDocumentFormats.h>
32 #include <U2Core/DNAAlphabet.h>
33 #include <U2Core/DNASequenceObject.h>
34 #include <U2Core/DNASequenceSelection.h>
35 #include <U2Core/DNATranslation.h>
36 #include <U2Core/DocumentUtils.h>
37 #include <U2Core/GObjectUtils.h>
38 #include <U2Core/GUrlUtils.h>
39 #include <U2Core/L10n.h>
40 #include <U2Core/LoadRemoteDocumentTask.h>
41 #include <U2Core/QObjectScopedPointer.h>
42 #include <U2Core/SelectionUtils.h>
43 #include <U2Core/U2DbiRegistry.h>
44 #include <U2Core/U2ObjectDbi.h>
45 #include <U2Core/U2OpStatusUtils.h>
46 #include <U2Core/U2SafePoints.h>
47 
48 #include <U2Formats/ExportTasks.h>
49 
50 #include <U2Gui/ExportAnnotations2CSVTask.h>
51 #include <U2Gui/ExportAnnotationsDialog.h>
52 #include <U2Gui/ExportObjectUtils.h>
53 #include <U2Gui/GUIUtils.h>
54 #include <U2Gui/OpenViewTask.h>
55 
56 #include <U2View/ADVConstants.h>
57 #include <U2View/ADVSequenceObjectContext.h>
58 #include <U2View/ADVUtils.h>
59 #include <U2View/AnnotatedDNAView.h>
60 
61 #include "ExportBlastResultDialog.h"
62 #include "ExportSelectedSeqRegionsTask.h"
63 #include "ExportSequences2MSADialog.h"
64 #include "ExportSequencesDialog.h"
65 #include "ExportUtils.h"
66 #include "GetSequenceByIdDialog.h"
67 
68 namespace U2 {
69 
70 //////////////////////////////////////////////////////////////////////////
71 // ExportSequenceViewItemsController
72 
ExportSequenceViewItemsController(QObject * p)73 ExportSequenceViewItemsController::ExportSequenceViewItemsController(QObject *p)
74     : GObjectViewWindowContext(p, ANNOTATED_DNA_VIEW_FACTORY_ID),
75       av(nullptr) {
76 }
77 
initViewContext(GObjectView * v)78 void ExportSequenceViewItemsController::initViewContext(GObjectView *v) {
79     av = qobject_cast<AnnotatedDNAView *>(v);
80     ADVExportContext *vc = new ADVExportContext(av);
81     addViewResource(av, vc);
82 }
83 
buildStaticOrContextMenu(GObjectView * v,QMenu * m)84 void ExportSequenceViewItemsController::buildStaticOrContextMenu(GObjectView *v, QMenu *m) {
85     QList<QObject *> resources = viewResources.value(v);
86     assert(resources.size() == 1);
87     QObject *r = resources.first();
88     ADVExportContext *vc = qobject_cast<ADVExportContext *>(r);
89     assert(vc != nullptr);
90     vc->buildMenu(m);
91 }
92 
init()93 void ExportSequenceViewItemsController::init() {
94     GObjectViewWindowContext::init();
95     if (!viewResources.value(av).isEmpty()) {
96         QMenu *actions = AppContext::getMainWindow()->getTopLevelMenu(MWMENU_ACTIONS);
97         SAFE_POINT(nullptr != actions, "Actions menu not found.", );
98         actions->clear();
99         AppContext::getMainWindow()->getMDIManager()->getActiveWindow()->setupViewMenu(actions);
100     }
101 }
102 
103 //////////////////////////////////////////////////////////////////////////
104 // ADV view context
105 
106 // TODO: define global BLAST text constants in CoreLibs
107 #define BLAST_ANNOTATION_NAME "blast result"
108 
ADVExportContext(AnnotatedDNAView * v)109 ADVExportContext::ADVExportContext(AnnotatedDNAView *v)
110     : view(v) {
111     sequence2SequenceAction = new QAction(tr("Export selected sequence region..."), this);
112     sequence2SequenceAction->setObjectName("action_export_selected_sequence_region");
113     connect(sequence2SequenceAction, SIGNAL(triggered()), SLOT(sl_saveSelectedSequences()));
114 
115     annotations2SequenceAction = new QAction(tr("Export sequence of selected annotations..."), this);
116     annotations2SequenceAction->setObjectName("action_export_sequence_of_selected_annotations");
117     connect(annotations2SequenceAction, SIGNAL(triggered()), SLOT(sl_saveSelectedAnnotationsSequence()));
118 
119     annotations2CSVAction = new QAction(tr("Export annotations..."), this);
120     annotations2CSVAction->setObjectName(ACTION_EXPORT_ANNOTATIONS);
121     connect(annotations2CSVAction, SIGNAL(triggered()), SLOT(sl_saveSelectedAnnotations()));
122 
123     annotationsToAlignmentAction = new QAction(QIcon(":core/images/msa.png"), tr("Align selected annotations..."), this);
124     annotationsToAlignmentAction->setObjectName("Align selected annotations");
125     connect(annotationsToAlignmentAction, SIGNAL(triggered()), SLOT(sl_saveSelectedAnnotationsToAlignment()));
126 
127     annotationsToAlignmentWithTranslatedAction = new QAction(QIcon(":core/images/msa.png"), tr("Align selected annotations (amino acids)..."), this);
128     annotationsToAlignmentWithTranslatedAction->setObjectName("Align selected annotations (amino acids)...");
129     connect(annotationsToAlignmentWithTranslatedAction, SIGNAL(triggered()), SLOT(sl_saveSelectedAnnotationsToAlignmentWithTranslation()));
130 
131     sequenceToAlignmentAction = new QAction(QIcon(":core/images/msa.png"), tr("Align selected sequence regions..."), this);
132     sequenceToAlignmentAction->setObjectName("action_align_selected_sequence_regions");
133     connect(sequenceToAlignmentAction, SIGNAL(triggered()), SLOT(sl_saveSelectedSequenceToAlignment()));
134 
135     sequenceToAlignmentWithTranslationAction = new QAction(QIcon(":core/images/msa.png"), tr("Align selected sequence regions (amino acids)..."), this);
136     sequenceToAlignmentWithTranslationAction->setObjectName("Align selected sequence regions (amino acids)");
137     connect(sequenceToAlignmentWithTranslationAction, SIGNAL(triggered()), SLOT(sl_saveSelectedSequenceToAlignmentWithTranslation()));
138 
139     sequenceById = new QAction(tr("Export sequences by 'id'"), this);
140     connect(sequenceById, SIGNAL(triggered()), SLOT(sl_getSequenceById()));
141     sequenceByAccession = new QAction(tr("Export sequences by 'accession'"), this);
142     connect(sequenceByAccession, SIGNAL(triggered()), SLOT(sl_getSequenceByAccession()));
143     sequenceByDBXref = new QAction(tr("Export sequences by 'db_xref'"), this);
144     connect(sequenceByDBXref, SIGNAL(triggered()), SLOT(sl_getSequenceByDBXref()));
145 
146     blastResultToAlignmentAction = new QAction(tr("Export BLAST result to alignment"), this);
147     blastResultToAlignmentAction->setObjectName("export_BLAST_result_to_alignment");
148     connect(blastResultToAlignmentAction, SIGNAL(triggered()), SLOT(sl_exportBlastResultToAlignment()));
149 
150     connect(view->getAnnotationsSelection(),
151             SIGNAL(si_selectionChanged(AnnotationSelection *, const QList<Annotation *> &, const QList<Annotation *> &)),
152             SLOT(updateActions()));
153 
154     connect(view->getAnnotationsGroupSelection(),
155             SIGNAL(si_selectionChanged(AnnotationGroupSelection *, const QList<AnnotationGroup *> &, const QList<AnnotationGroup *> &)),
156             SLOT(updateActions()));
157 
158     connect(view, SIGNAL(si_sequenceAdded(ADVSequenceObjectContext *)), SLOT(sl_onSequenceContextAdded(ADVSequenceObjectContext *)));
159     connect(view, SIGNAL(si_sequenceRemoved(ADVSequenceObjectContext *)), SLOT(sl_onSequenceContextRemoved(ADVSequenceObjectContext *)));
160     foreach (ADVSequenceObjectContext *sCtx, view->getSequenceContexts()) {
161         sl_onSequenceContextAdded(sCtx);
162     }
163 }
164 
sl_onSequenceContextAdded(ADVSequenceObjectContext * c)165 void ADVExportContext::sl_onSequenceContextAdded(ADVSequenceObjectContext *c) {
166     connect(c->getSequenceSelection(),
167             SIGNAL(si_selectionChanged(LRegionsSelection *, const QVector<U2Region> &, const QVector<U2Region> &)),
168             SLOT(updateActions()));
169 
170     updateActions();
171 }
172 
sl_onSequenceContextRemoved(ADVSequenceObjectContext * c)173 void ADVExportContext::sl_onSequenceContextRemoved(ADVSequenceObjectContext *c) {
174     c->getSequenceSelection()->disconnect(this);
175     updateActions();
176 }
177 
allNucleic(const QList<ADVSequenceObjectContext * > & seqs)178 static bool allNucleic(const QList<ADVSequenceObjectContext *> &seqs) {
179     foreach (const ADVSequenceObjectContext *s, seqs) {
180         if (!s->getAlphabet()->isNucleic()) {
181             return false;
182         }
183     }
184     return true;
185 }
186 
updateActions()187 void ADVExportContext::updateActions() {
188     bool hasSelectedAnnotations = !view->getAnnotationsSelection()->isEmpty();
189     bool hasSelectedGroups = !view->getAnnotationsGroupSelection()->isEmpty();
190     int nSequenceSelections = 0;
191     foreach (ADVSequenceObjectContext *c, view->getSequenceContexts()) {
192         nSequenceSelections += c->getSequenceSelection()->getSelectedRegions().count();
193     }
194 
195     sequence2SequenceAction->setEnabled(nSequenceSelections >= 1);
196     annotations2SequenceAction->setEnabled(hasSelectedAnnotations);
197     annotations2CSVAction->setEnabled(hasSelectedAnnotations || hasSelectedGroups);
198 
199     bool _allNucleic = allNucleic(view->getSequenceContexts());
200 
201     bool hasMultipleAnnotationsSelected = view->getAnnotationsSelection()->getAnnotations().size() > 1;
202     annotationsToAlignmentAction->setEnabled(hasMultipleAnnotationsSelected);
203     annotationsToAlignmentWithTranslatedAction->setEnabled(hasMultipleAnnotationsSelected && _allNucleic);
204 
205     bool hasMultiSequenceSelection = nSequenceSelections > 1;
206     sequenceToAlignmentAction->setEnabled(hasMultiSequenceSelection);
207     sequenceToAlignmentWithTranslationAction->setEnabled(hasMultiSequenceSelection && _allNucleic);
208 }
209 
buildMenu(QMenu * m)210 void ADVExportContext::buildMenu(QMenu *m) {
211     QMenu *alignMenu = GUIUtils::findSubMenu(m, ADV_MENU_ALIGN);
212     SAFE_POINT(alignMenu != nullptr, "alignMenu", );
213     alignMenu->addAction(sequenceToAlignmentAction);
214     alignMenu->addAction(sequenceToAlignmentWithTranslationAction);
215     alignMenu->addAction(annotationsToAlignmentAction);
216     alignMenu->addAction(annotationsToAlignmentWithTranslatedAction);
217 
218     QMenu *exportMenu = GUIUtils::findSubMenu(m, ADV_MENU_EXPORT);
219     SAFE_POINT(exportMenu != nullptr, "exportMenu", );
220     exportMenu->addAction(sequence2SequenceAction);
221     exportMenu->addAction(annotations2SequenceAction);
222     exportMenu->addAction(annotations2CSVAction);
223 
224     bool isShowId = false;
225     bool isShowAccession = false;
226     bool isShowDBXref = false;
227     bool isBlastResult = false;
228 
229     QString name;
230     if (!view->getAnnotationsSelection()->getAnnotations().isEmpty()) {
231         name = view->getAnnotationsSelection()->getAnnotations().first()->getName();
232     }
233     foreach (const Annotation *annotation, view->getAnnotationsSelection()->getAnnotations()) {
234         if (name != annotation->getName()) {
235             name = "";
236         }
237 
238         if (!isShowId && !annotation->findFirstQualifierValue("id").isEmpty()) {
239             isShowId = true;
240         } else if (!isShowAccession && !annotation->findFirstQualifierValue("accession").isEmpty()) {
241             isShowAccession = true;
242         } else if (!isShowDBXref && !annotation->findFirstQualifierValue("db_xref").isEmpty()) {
243             isShowDBXref = true;
244         }
245 
246         isBlastResult = name == BLAST_ANNOTATION_NAME;
247     }
248 
249     if (isShowId || isShowAccession || isShowDBXref) {
250         name = name.isEmpty() ? "" : tr("from '") + name + "'";
251         QMenu *fetchMenu = new QMenu(tr("Fetch sequences from remote database"));
252         m->insertMenu(exportMenu->menuAction(), fetchMenu);
253         if (isShowId) {
254             sequenceById->setText(tr("Fetch sequences by 'id' %1").arg(name));
255             fetchMenu->addAction(sequenceById);
256         }
257         if (isShowAccession) {
258             sequenceByAccession->setText(tr("Fetch sequences by 'accession' %1").arg(name));
259             fetchMenu->addAction(sequenceByAccession);
260         }
261         if (isShowDBXref) {
262             sequenceByDBXref->setText(tr("Fetch sequences by 'db_xref' %1").arg(name));
263             fetchMenu->addAction(sequenceByDBXref);
264         }
265     }
266     if (isBlastResult) {
267         exportMenu->addAction(blastResultToAlignmentAction);
268     }
269 }
270 
sl_saveSelectedAnnotationsSequence()271 void ADVExportContext::sl_saveSelectedAnnotationsSequence() {
272     AnnotationSelection *as = view->getAnnotationsSelection();
273     AnnotationGroupSelection *ags = view->getAnnotationsGroupSelection();
274 
275     QList<Annotation *> annotations = as->getAnnotations();
276     const QList<AnnotationGroup *> groups = ags->getSelection();
277     foreach (AnnotationGroup *g, groups) {
278         g->findAllAnnotationsInGroupSubTree(annotations);
279     }
280 
281     if (annotations.isEmpty()) {
282         QMessageBox::warning(view->getWidget(), L10N::warningTitle(), tr("No annotations selected!"));
283         return;
284     }
285 
286     bool allowComplement = true;
287     bool allowTranslation = true;
288     bool allowBackTranslation = true;
289 
290     QMap<const ADVSequenceObjectContext *, QList<SharedAnnotationData>> annotationsPerSeq;
291     foreach (Annotation *a, annotations) {
292         ADVSequenceObjectContext *seqCtx = view->getSequenceContext(a->getGObject());
293         if (seqCtx == nullptr) {
294             continue;
295         }
296 
297         QList<SharedAnnotationData> &annsPerSeq = annotationsPerSeq[seqCtx];
298         annsPerSeq.append(a->getData());
299         if (annsPerSeq.size() > 1) {
300             continue;
301         }
302         U2SequenceObject *seqObj = seqCtx->getSequenceObject();
303         if (GObjectUtils::findComplementTT(seqObj->getAlphabet()) == nullptr) {
304             allowComplement = false;
305         }
306         if (GObjectUtils::findAminoTT(seqObj, false) == nullptr) {
307             allowTranslation = false;
308         }
309         if (GObjectUtils::findBackTranslationTT(seqObj) == nullptr) {
310             allowBackTranslation = false;
311         }
312     }
313 
314     QString fileExt = AppContext::getDocumentFormatRegistry()->getFormatById(BaseDocumentFormats::FASTA)->getSupportedDocumentFileExtensions().first();
315     QString dirPath;
316     QString fileBaseName;
317 
318     GUrl seqUrl = view->getActiveSequenceContext()->getSequenceGObject()->getDocument()->getURL();
319     GUrlUtils::getLocalPathFromUrl(seqUrl, view->getActiveSequenceContext()->getSequenceGObject()->getGObjectName(), dirPath, fileBaseName);
320     GUrl defaultUrl = GUrlUtils::rollFileName(dirPath + QDir::separator() + fileBaseName + "_annotation." + fileExt, DocumentUtils::getNewDocFileNameExcludesHint());
321 
322     QObjectScopedPointer<ExportSequencesDialog> d = new ExportSequencesDialog(true,
323                                                                               allowComplement,
324                                                                               allowTranslation,
325                                                                               allowBackTranslation,
326                                                                               defaultUrl.getURLString(),
327                                                                               fileBaseName,
328                                                                               BaseDocumentFormats::FASTA,
329                                                                               AppContext::getMainWindow()->getQMainWindow());
330     d->setWindowTitle("Export Sequence of Selected Annotations");
331     d->disableAllFramesOption(true);  // only 1 frame is suitable
332     d->disableStrandOption(true);  // strand is already recorded in annotation
333     d->disableAnnotationsOption(true);  // here we do not export annotations for sequence under another annotations
334     const int rc = d->exec();
335     CHECK(!d.isNull(), );
336 
337     if (rc == QDialog::Rejected) {
338         return;
339     }
340     assert(d->file.length() > 0);
341 
342     ExportAnnotationSequenceTaskSettings s;
343     ExportUtils::loadDNAExportSettingsFromDlg(s.exportSequenceSettings, d.data());
344     foreach (const ADVSequenceObjectContext *seqCtx, annotationsPerSeq.keys()) {
345         ExportSequenceAItem ei;
346         ei.sequence = seqCtx->getSequenceObject();
347         ei.complTT = seqCtx->getComplementTT();
348         ei.aminoTT = d->translate ? seqCtx->getAminoTT() : nullptr;
349         if (d->useSpecificTable && ei.sequence->getAlphabet()->isNucleic()) {
350             DNATranslationRegistry *tr = AppContext::getDNATranslationRegistry();
351             ei.aminoTT = tr->lookupTranslation(ei.sequence->getAlphabet(), DNATranslationType_NUCL_2_AMINO, d->translationTable);
352         }
353         ei.annotations = annotationsPerSeq.value(seqCtx);
354         s.items.append(ei);
355     }
356     Task *t = ExportUtils::wrapExportTask(new ExportAnnotationSequenceTask(s), d->addToProject);
357     AppContext::getTaskScheduler()->registerTopLevelTask(t);
358 }
359 
sl_saveSelectedSequences()360 void ADVExportContext::sl_saveSelectedSequences() {
361     ADVSequenceObjectContext *seqCtx = view->getActiveSequenceContext();
362     DNASequenceSelection *sel = nullptr;
363     if (seqCtx != nullptr) {
364         // TODO: support multi-export..
365         sel = seqCtx->getSequenceSelection();
366     }
367     if (sel == nullptr || sel->isEmpty()) {
368         QMessageBox::warning(view->getWidget(), L10N::warningTitle(), tr("No sequence regions selected!"));
369         return;
370     }
371 
372     const QVector<U2Region> &regions = sel->getSelectedRegions();
373     bool merge = regions.size() > 1;
374     bool complement = seqCtx->getComplementTT() != nullptr;
375     bool amino = seqCtx->getAminoTT() != nullptr;
376     bool nucleic = GObjectUtils::findBackTranslationTT(seqCtx->getSequenceObject()) != nullptr;
377 
378     QString fileExt = AppContext::getDocumentFormatRegistry()->getFormatById(BaseDocumentFormats::FASTA)->getSupportedDocumentFileExtensions().first();
379     QString dirPath;
380     QString fileBaseName;
381 
382     GUrl seqUrl = seqCtx->getSequenceGObject()->getDocument()->getURL();
383     GUrlUtils::getLocalPathFromUrl(seqUrl, seqCtx->getSequenceGObject()->getGObjectName(), dirPath, fileBaseName);
384     GUrl defaultUrl = GUrlUtils::rollFileName(dirPath + QDir::separator() + fileBaseName + "_region." + fileExt, DocumentUtils::getNewDocFileNameExcludesHint());
385 
386     QObjectScopedPointer<ExportSequencesDialog> d = new ExportSequencesDialog(merge,
387                                                                               complement,
388                                                                               amino,
389                                                                               nucleic,
390                                                                               defaultUrl.getURLString(),
391                                                                               fileBaseName,
392                                                                               BaseDocumentFormats::FASTA,
393                                                                               AppContext::getMainWindow()->getQMainWindow());
394     d->setWindowTitle("Export Selected Sequence Region");
395     const int rc = d->exec();
396     CHECK(!d.isNull(), );
397     CHECK(rc != QDialog::Rejected, );
398     SAFE_POINT(!d->file.isEmpty(), "Invalid file path", );
399 
400     ExportSequenceTaskSettings s;
401     ExportUtils::loadDNAExportSettingsFromDlg(s, d.data());
402 
403     const DNATranslation *aminoTrans = nullptr;
404     if (d->translate) {
405         aminoTrans = d->useSpecificTable ? GObjectUtils::findAminoTT(seqCtx->getSequenceObject(), false, d->translationTable) : seqCtx->getAminoTT();
406     }
407     const DNATranslation *backTrans = d->backTranslate ? GObjectUtils::findBackTranslationTT(seqCtx->getSequenceObject(), d->translationTable) : nullptr;
408     const DNATranslation *complTrans = seqCtx->getComplementTT();
409     Task *t = ExportUtils::wrapExportTask(new ExportSelectedSeqRegionsTask(seqCtx->getSequenceObject(), seqCtx->getAnnotationObjects(true), regions, s, aminoTrans, backTrans, complTrans), d->addToProject);
410     AppContext::getTaskScheduler()->registerTopLevelTask(t);
411 }
412 
sl_saveSelectedAnnotations()413 void ADVExportContext::sl_saveSelectedAnnotations() {
414     // find annotations: selected annotations, selected groups
415     AnnotationSelection *as = view->getAnnotationsSelection();
416     QList<Annotation *> annotationSet = as->getAnnotations();
417     foreach (AnnotationGroup *group, view->getAnnotationsGroupSelection()->getSelection()) {
418         group->findAllAnnotationsInGroupSubTree(annotationSet);
419     }
420 
421     if (annotationSet.isEmpty()) {
422         QMessageBox::warning(view->getWidget(), L10N::warningTitle(), tr("No annotations selected!"));
423         return;
424     }
425 
426     Annotation *first = *annotationSet.begin();
427     Document *doc = first->getGObject()->getDocument();
428     ADVSequenceObjectContext *sequenceContext = view->getActiveSequenceContext();
429 
430     GUrl url;
431     if (doc != nullptr) {
432         url = doc->getURL();
433     } else if (sequenceContext != nullptr) {
434         url = sequenceContext->getSequenceGObject()->getDocument()->getURL();
435     } else {
436         url = GUrl("newfile");
437     }
438 
439     QString fileName = GUrlUtils::getNewLocalUrlByExtension(url, "newfile", ".csv", "_annotations");
440     QObjectScopedPointer<ExportAnnotationsDialog> d = new ExportAnnotationsDialog(fileName, AppContext::getMainWindow()->getQMainWindow());
441     d->exec();
442     CHECK(!d.isNull(), );
443 
444     if (QDialog::Accepted != d->result()) {
445         return;
446     }
447 
448     // TODO: lock documents or use shared-data objects
449     std::stable_sort(annotationSet.begin(), annotationSet.end(), Annotation::annotationLessThan);
450 
451     // run task
452     Task *t = nullptr;
453     if (d->fileFormat() == ExportAnnotationsDialog::CSV_FORMAT_ID) {
454         U2OpStatusImpl os;
455         QByteArray seqData = sequenceContext->getSequenceObject()->getWholeSequenceData(os);
456         CHECK_OP_EXT(os, QMessageBox::critical(QApplication::activeWindow(), L10N::errorTitle(), os.getError()), );
457         t = new ExportAnnotations2CSVTask(annotationSet, seqData, sequenceContext->getSequenceObject()->getSequenceName(), sequenceContext->getComplementTT(), d->exportSequence(), d->exportSequenceNames(), d->filePath());
458     } else {
459         t = ExportObjectUtils::saveAnnotationsTask(d->filePath(), d->fileFormat(), annotationSet, d->addToProject());
460     }
461     AppContext::getTaskScheduler()->registerTopLevelTask(t);
462 }
463 
464 //////////////////////////////////////////////////////////////////////////
465 // alignment part
466 
467 #define MAX_ALI_MODEL (10 * 1000 * 1000)
468 
prepareMAFromBlastAnnotations(MultipleSequenceAlignment & ma,const QString & qualiferId,bool includeRef,U2OpStatus & os)469 void ADVExportContext::prepareMAFromBlastAnnotations(MultipleSequenceAlignment &ma, const QString &qualiferId, bool includeRef, U2OpStatus &os) {
470     SAFE_POINT_EXT(ma->isEmpty(), os.setError(tr("Illegal parameter: input alignment is not empty!")), );
471     const QList<Annotation *> &selection = view->getAnnotationsSelection()->getAnnotations();
472     CHECK_EXT(selection.size() >= 2, os.setError(tr("At least 2 annotations are required")), );
473 
474     AnnotationTableObject *ao = selection.first()->getGObject();
475     ADVSequenceObjectContext *commonSeq = view->getSequenceContext(ao);
476     qint64 maxLen = commonSeq->getSequenceLength();
477     ma->setAlphabet(commonSeq->getAlphabet());
478     QSet<QString> names;
479     int rowIdx = 0;
480 
481     for (const Annotation *annotation : qAsConst(selection)) {
482         SAFE_POINT(annotation->getName() == BLAST_ANNOTATION_NAME, tr("%1 is not a BLAST annotation").arg(annotation->getName()), );
483 
484         ADVSequenceObjectContext *seqCtx = view->getSequenceContext(annotation->getGObject());
485         CHECK_EXT(seqCtx != nullptr, os.setError(tr("No sequence object found")), );
486         CHECK_EXT(seqCtx == commonSeq, os.setError(tr("Can not export BLAST annotations from different sequences")), );
487 
488         QString qVal = annotation->findFirstQualifierValue(qualiferId);
489         CHECK_EXT(!qVal.isEmpty(), os.setError(tr("Can not find qualifier to set as a name for BLAST sequence")), );
490 
491         QString rowName = ExportUtils::genUniqueName(names, qVal);
492         U2EntityRef seqRef = seqCtx->getSequenceObject()->getSequenceRef();
493 
494         maxLen = qMax(maxLen, annotation->getRegionsLen());
495         CHECK_EXT(maxLen * ma->getNumRows() <= MAX_ALI_MODEL, os.setError(tr("Alignment is too large")), );
496 
497         QByteArray rowSequence;
498         QString subjSeq = annotation->findFirstQualifierValue("subj_seq");
499         if (!subjSeq.isEmpty()) {
500             ma->addRow(rowName, subjSeq.toLatin1());
501         } else {
502             AnnotationSelection::getSequenceInRegions(rowSequence, annotation->getRegions(), U2Msa::GAP_CHAR, seqRef, nullptr, nullptr, os);
503             CHECK_OP(os, );
504             ma->addRow(rowName, rowSequence);
505         }
506 
507         int offset = annotation->getLocation()->regions.first().startPos;
508         ma->insertGaps(rowIdx, 0, offset, os);
509         CHECK_OP(os, );
510 
511         names.insert(rowName);
512         ++rowIdx;
513     }
514 
515     if (includeRef) {
516         QByteArray rowSequence = commonSeq->getSequenceObject()->getWholeSequenceData(os);
517         CHECK_OP(os, );
518         ma->addRow(commonSeq->getSequenceGObject()->getGObjectName(), rowSequence, 0);
519     }
520 }
521 
prepareMAFromAnnotations(MultipleSequenceAlignment & ma,bool translate,U2OpStatus & os)522 void ADVExportContext::prepareMAFromAnnotations(MultipleSequenceAlignment &ma, bool translate, U2OpStatus &os) {
523     SAFE_POINT_EXT(ma->isEmpty(), os.setError(tr("Illegal parameter: input alignment is not empty!")), );
524     const QList<Annotation *> &selection = view->getAnnotationsSelection()->getAnnotations();
525     CHECK_EXT(selection.size() >= 2, os.setError(tr("At least 2 annotations are required")), );
526 
527     // check that all sequences are present and have the same alphabets
528     const DNAAlphabet *al = nullptr;
529     foreach (const Annotation *annotation, selection) {
530         AnnotationTableObject *ao = annotation->getGObject();
531         ADVSequenceObjectContext *seqCtx = view->getSequenceContext(ao);
532         CHECK_EXT(seqCtx != nullptr, os.setError(tr("No sequence object found")), );
533         if (al == nullptr) {
534             al = seqCtx->getAlphabet();
535         } else {
536             const DNAAlphabet *al2 = seqCtx->getAlphabet();
537             // BUG524: support alphabet reduction
538             CHECK_EXT(al->getType() == al2->getType(), os.setError(tr("Different sequence alphabets")), );
539             al = al->getMap().count(true) >= al2->getMap().count(true) ? al : al2;
540         }
541     }
542     qint64 maxLen = 0;
543     ma->setAlphabet(al);
544     QSet<QString> names;
545     foreach (const Annotation *annotation, selection) {
546         QString rowName = annotation->getName();
547         AnnotationTableObject *ao = annotation->getGObject();
548         ADVSequenceObjectContext *seqCtx = view->getSequenceContext(ao);
549         U2EntityRef seqRef = seqCtx->getSequenceObject()->getSequenceRef();
550 
551         maxLen = qMax(maxLen, annotation->getRegionsLen());
552         CHECK_EXT(maxLen * ma->getNumRows() <= MAX_ALI_MODEL, os.setError(tr("Alignment is too large")), );
553         const DNATranslation *complTT = annotation->getStrand().isCompementary() ? seqCtx->getComplementTT() : nullptr;
554         const DNATranslation *aminoTT = translate ? seqCtx->getAminoTT() : nullptr;
555         QByteArray rowSequence;
556         AnnotationSelection::getSequenceInRegions(rowSequence, annotation->getRegions(), U2Msa::GAP_CHAR, seqRef, complTT, aminoTT, os);
557         CHECK_OP(os, );
558 
559         ma->addRow(rowName, rowSequence);
560 
561         names.insert(rowName);
562     }
563 }
564 
prepareMAFromSequences(MultipleSequenceAlignment & ma,bool translate,U2OpStatus & os)565 void ADVExportContext::prepareMAFromSequences(MultipleSequenceAlignment &ma, bool translate, U2OpStatus &os) {
566     SAFE_POINT_EXT(ma->isEmpty(), os.setError(tr("Illegal parameter: Input alignment is not empty!")), );
567 
568     const DNAAlphabet *al = translate ? AppContext::getDNAAlphabetRegistry()->findById(BaseDNAAlphabetIds::AMINO_DEFAULT()) : nullptr;
569 
570     // derive alphabet
571     int nItems = 0;
572     bool forceTranslation = false;
573     foreach (ADVSequenceObjectContext *c, view->getSequenceContexts()) {
574         if (c->getSequenceSelection()->isEmpty()) {
575             continue;
576         }
577         nItems += c->getSequenceSelection()->getSelectedRegions().count();
578         const DNAAlphabet *seqAl = c->getAlphabet();
579         if (al == nullptr) {
580             al = seqAl;
581         } else if (al != seqAl) {
582             if (al->isNucleic() && seqAl->isAmino()) {
583                 forceTranslation = true;
584                 al = seqAl;
585             } else if (al->isAmino() && seqAl->isNucleic()) {
586                 forceTranslation = true;
587             } else {
588                 os.setError(tr("Can't derive alignment alphabet"));
589                 return;
590             }
591         }
592     }
593 
594     CHECK_EXT(nItems >= 2, os.setError(tr("At least 2 sequences required")), );
595     ma->setAlphabet(al);
596 
597     // cache sequences
598     QSet<QString> names;
599     qint64 maxLen = 0;
600     foreach (ADVSequenceObjectContext *seqCtx, view->getSequenceContexts()) {
601         if (seqCtx->getSequenceSelection()->isEmpty()) {
602             continue;
603         }
604         const DNAAlphabet *seqAl = seqCtx->getAlphabet();
605         DNATranslation *aminoTT = ((translate || forceTranslation) && seqAl->isNucleic()) ? seqCtx->getAminoTT() : nullptr;
606         foreach (const U2Region &r, seqCtx->getSequenceSelection()->getSelectedRegions()) {
607             maxLen = qMax(maxLen, r.length);
608             CHECK_EXT(maxLen * ma->getNumRows() <= MAX_ALI_MODEL, os.setError(tr("Alignment is too large")), );
609             QByteArray seq = seqCtx->getSequenceData(r, os);
610             CHECK_OP(os, );
611             if (aminoTT != nullptr) {
612                 int len = aminoTT->translate(seq.data(), seq.size());
613                 seq.resize(len);
614             }
615             QString rowName = ExportUtils::genUniqueName(names, seqCtx->getSequenceGObject()->getGObjectName());
616             names.insert(rowName);
617             ma->addRow(rowName, seq);
618         }
619     }
620 }
621 
selectionToAlignment(const QString & title,bool annotations,bool translate)622 void ADVExportContext::selectionToAlignment(const QString &title, bool annotations, bool translate) {
623     MultipleSequenceAlignment ma(MA_OBJECT_NAME);
624     U2OpStatusImpl os;
625     if (annotations) {
626         prepareMAFromAnnotations(ma, translate, os);
627     } else {
628         prepareMAFromSequences(ma, translate, os);
629     }
630     if (os.hasError()) {
631         QMessageBox::critical(nullptr, L10N::errorTitle(), os.getError());
632         return;
633     }
634 
635     DocumentFormatConstraints c;
636     c.addFlagToSupport(DocumentFormatFlag_SupportWriting);
637     c.supportedObjectTypes += GObjectTypes::MULTIPLE_SEQUENCE_ALIGNMENT;
638 
639     QObjectScopedPointer<ExportSequences2MSADialog> d = new ExportSequences2MSADialog(view->getWidget());
640     d->setWindowTitle(title);
641     d->setOkButtonText(tr("Create alignment"));
642     d->setFileLabelText(tr("Save alignment to file"));
643     const int rc = d->exec();
644     CHECK(!d.isNull(), );
645 
646     if (rc != QDialog::Accepted) {
647         return;
648     }
649     Task *t = ExportUtils::wrapExportTask(new ExportAlignmentTask(ma, d->url, d->format), d->addToProjectFlag);
650     AppContext::getTaskScheduler()->registerTopLevelTask(t);
651 }
652 
sl_saveSelectedAnnotationsToAlignment()653 void ADVExportContext::sl_saveSelectedAnnotationsToAlignment() {
654     selectionToAlignment(annotationsToAlignmentAction->text(), true, false);
655 }
656 
sl_saveSelectedAnnotationsToAlignmentWithTranslation()657 void ADVExportContext::sl_saveSelectedAnnotationsToAlignmentWithTranslation() {
658     selectionToAlignment(annotationsToAlignmentAction->text(), true, true);
659 }
660 
sl_saveSelectedSequenceToAlignment()661 void ADVExportContext::sl_saveSelectedSequenceToAlignment() {
662     selectionToAlignment(sequenceToAlignmentAction->text(), false, false);
663 }
664 
sl_saveSelectedSequenceToAlignmentWithTranslation()665 void ADVExportContext::sl_saveSelectedSequenceToAlignmentWithTranslation() {
666     selectionToAlignment(sequenceToAlignmentWithTranslationAction->text(), false, true);
667 }
668 
sl_getSequenceByDBXref()669 void ADVExportContext::sl_getSequenceByDBXref() {
670     const QList<Annotation *> &selection = view->getAnnotationsSelection()->getAnnotations();
671 
672     QStringList genbankID;
673     foreach (const Annotation *ann, selection) {
674         const QString tmp = ann->findFirstQualifierValue("db_xref");
675         if (!tmp.isEmpty()) {
676             genbankID << tmp.split(":").last();
677         }
678     }
679     QString listId = genbankID.join(",");
680     fetchSequencesFromRemoteDB(listId);
681 }
682 
sl_getSequenceByAccession()683 void ADVExportContext::sl_getSequenceByAccession() {
684     const QList<Annotation *> &selection = view->getAnnotationsSelection()->getAnnotations();
685 
686     QStringList genbankID;
687     foreach (const Annotation *ann, selection) {
688         const QString tmp = ann->findFirstQualifierValue("accession");
689         if (!tmp.isEmpty()) {
690             genbankID << tmp;
691         }
692     }
693     QString listId = genbankID.join(",");
694     fetchSequencesFromRemoteDB(listId);
695 }
696 
sl_getSequenceById()697 void ADVExportContext::sl_getSequenceById() {
698     const QList<Annotation *> &selection = view->getAnnotationsSelection()->getAnnotations();
699 
700     QStringList genbankID;
701     foreach (const Annotation *ann, selection) {
702         const QString tmp = ann->findFirstQualifierValue("id");
703         if (!tmp.isEmpty()) {
704             int off = tmp.indexOf("|");
705             int off1 = tmp.indexOf("|", off + 1);
706             genbankID << tmp.mid(off + 1, off1 - off - 1);
707         }
708     }
709     QString listId = genbankID.join(",");
710     fetchSequencesFromRemoteDB(listId);
711 }
712 
fetchSequencesFromRemoteDB(const QString & listId)713 void ADVExportContext::fetchSequencesFromRemoteDB(const QString &listId) {
714     const DNAAlphabet *seqAl = view->getSequenceObjectsWithContexts().first()->getAlphabet();
715 
716     QString db;
717     if (seqAl->getId() == BaseDNAAlphabetIds::NUCL_DNA_DEFAULT()) {
718         db = "NCBI GenBank (DNA sequence)";
719     } else if (seqAl->getId() == BaseDNAAlphabetIds::AMINO_DEFAULT()) {
720         db = "NCBI protein sequence database";
721     } else {
722         return;
723     }
724 
725     QObjectScopedPointer<GetSequenceByIdDialog> dlg = new GetSequenceByIdDialog(view->getWidget());
726     dlg->exec();
727     CHECK(!dlg.isNull(), );
728 
729     if (dlg->result() == QDialog::Accepted) {
730         QString dir = dlg->getDirectory();
731         Task *t;
732         if (dlg->isAddToProject()) {
733             t = new LoadRemoteDocumentAndAddToProjectTask(listId, db, dir);
734         } else {
735             t = new LoadRemoteDocumentTask(listId, db, dir);
736         }
737         AppContext::getTaskScheduler()->registerTopLevelTask(t);
738     }
739 }
740 
sl_exportBlastResultToAlignment()741 void ADVExportContext::sl_exportBlastResultToAlignment() {
742     DocumentFormatConstraints c;
743     c.addFlagToSupport(DocumentFormatFlag_SupportWriting);
744     c.supportedObjectTypes += GObjectTypes::MULTIPLE_SEQUENCE_ALIGNMENT;
745 
746     QObjectScopedPointer<ExportBlastResultDialog> d = new ExportBlastResultDialog(view->getWidget());
747     const int rc = d->exec();
748     CHECK(!d.isNull(), );
749     if (rc != QDialog::Accepted) {
750         return;
751     }
752 
753     MultipleSequenceAlignment ma(MA_OBJECT_NAME);
754     U2OpStatusImpl os;
755 
756     prepareMAFromBlastAnnotations(ma, d->qualiferId, d->addRefFlag, os);
757 
758     if (os.hasError()) {
759         QMessageBox::critical(nullptr, L10N::errorTitle(), os.getError());
760         return;
761     }
762 
763     Task *t = ExportUtils::wrapExportTask(new ExportAlignmentTask(ma, d->url, d->format), d->addToProjectFlag);
764     AppContext::getTaskScheduler()->registerTopLevelTask(t);
765 }
766 
767 }  // namespace U2
768