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 "AssemblyConsensusArea.h"
23 
24 #include <QFileInfo>
25 #include <QMouseEvent>
26 #include <QPainter>
27 
28 #include <U2Algorithm/AssemblyConsensusAlgorithmRegistry.h>
29 #include <U2Algorithm/BuiltInAssemblyConsensusAlgorithms.h>
30 
31 #include <U2Core/BaseDocumentFormats.h>
32 #include <U2Core/DocumentModel.h>
33 #include <U2Core/GUrlUtils.h>
34 #include <U2Core/QObjectScopedPointer.h>
35 #include <U2Core/U2AssemblyUtils.h>
36 #include <U2Core/U2SafePoints.h>
37 
38 #include "AssemblyBrowser.h"
39 #include "AssemblyConsensusTask.h"
40 #include "ExportConsensusDialog.h"
41 #include "ExportConsensusTask.h"
42 #include "ExportConsensusVariationsDialog.h"
43 #include "ExportConsensusVariationsTask.h"
44 
45 namespace U2 {
46 
AssemblyConsensusArea(AssemblyBrowserUi * ui)47 AssemblyConsensusArea::AssemblyConsensusArea(AssemblyBrowserUi *ui)
48     : AssemblySequenceArea(ui, AssemblyConsensusAlgorithm::EMPTY_CHAR), consensusAlgorithmMenu(nullptr), consensusAlgorithm(nullptr), canceled(false) {
49     setToolTip(tr("Consensus sequence"));
50     setObjectName("Consensus area");
51     connect(&consensusTaskRunner, SIGNAL(si_finished()), SLOT(sl_consensusReady()));
52 
53     AssemblyConsensusAlgorithmRegistry *registry = AppContext::getAssemblyConsensusAlgorithmRegistry();
54     QString defaultId = BuiltInAssemblyConsensusAlgorithms::DEFAULT_ALGO;
55     AssemblyConsensusAlgorithmFactory *f = registry->getAlgorithmFactory(defaultId);
56     SAFE_POINT(f != nullptr, QString("consensus algorithm factory %1 not found").arg(defaultId), );
57     consensusAlgorithm = QSharedPointer<AssemblyConsensusAlgorithm>(f->createAlgorithm());
58 
59     setDiffCellRenderer();
60 
61     createContextMenu();
62 }
63 
createContextMenu()64 void AssemblyConsensusArea::createContextMenu() {
65     contextMenu = new QMenu(this);
66 
67     contextMenu->addMenu(getConsensusAlgorithmMenu());
68 
69     QAction *exportCoverage = contextMenu->addAction(tr("Export coverage..."));
70     exportCoverage->setObjectName("Export coverage");
71     connect(exportCoverage, SIGNAL(triggered()), browser, SLOT(sl_exportCoverage()));
72 
73     QAction *exportAction = contextMenu->addAction(tr("Export consensus..."));
74     connect(exportAction, SIGNAL(triggered()), SLOT(sl_exportConsensus()));
75 
76     exportConsensusVariationsAction = contextMenu->addAction(tr("Export consensus variations..."));
77     connect(exportConsensusVariationsAction, SIGNAL(triggered()), SLOT(sl_exportConsensusVariations()));
78 
79     exportConsensusVariationsAction->setDisabled(true);
80 
81     diffAction = contextMenu->addAction(tr("Show difference from reference"));
82     diffAction->setCheckable(true);
83     diffAction->setChecked(true);
84     connect(diffAction, SIGNAL(toggled(bool)), SLOT(sl_drawDifferenceChanged(bool)));
85 }
86 
canDrawSequence()87 bool AssemblyConsensusArea::canDrawSequence() {
88     return !getModel()->isEmpty();
89 }
90 
getSequenceRegion(U2OpStatus & os)91 QByteArray AssemblyConsensusArea::getSequenceRegion(U2OpStatus &os) {
92     Q_UNUSED(os);
93     return lastResult.consensus;
94 }
95 
96 // If required region is not fully included in cache, other positions are filled with AssemblyConsensusAlgorithm::EMPTY_CHAR
getPart(ConsensusInfo cache,U2Region region)97 static ConsensusInfo getPart(ConsensusInfo cache, U2Region region) {
98     ConsensusInfo result;
99     result.region = region;
100     result.algorithmId = cache.algorithmId;
101     result.consensus = QByteArray(region.length, AssemblyConsensusAlgorithm::EMPTY_CHAR);
102     if (!cache.region.isEmpty() && cache.region.intersects(region)) {
103         U2Region intersection = cache.region.intersect(region);
104         SAFE_POINT(!intersection.isEmpty(), "consensus cache: intersection cannot be empty, possible race condition?", result);
105 
106         int offsetInCache = intersection.startPos - cache.region.startPos;
107         int offsetInResult = intersection.startPos - region.startPos;
108         memcpy(result.consensus.data() + offsetInResult, cache.consensus.constData() + offsetInCache, intersection.length);
109     }
110     return result;
111 }
112 
launchConsensusCalculation()113 void AssemblyConsensusArea::launchConsensusCalculation() {
114     if (areCellsVisible()) {
115         U2Region visibleRegion = getVisibleRegion();
116 
117         previousRegion = visibleRegion;
118         if (cache.region.contains(visibleRegion) && cache.algorithmId == consensusAlgorithm->getId()) {
119             lastResult = getPart(cache, visibleRegion);
120             consensusTaskRunner.cancel();
121         } else {
122             AssemblyConsensusTaskSettings settings;
123             settings.region = visibleRegion;
124             settings.model = getModel();
125             settings.consensusAlgorithm = consensusAlgorithm;
126             consensusTaskRunner.run(new AssemblyConsensusTask(settings));
127         }
128     }
129     canceled = false;
130     sl_redraw();
131 }
132 
sl_offsetsChanged()133 void AssemblyConsensusArea::sl_offsetsChanged() {
134     if (areCellsVisible() && getVisibleRegion() != previousRegion) {
135         launchConsensusCalculation();
136     }
137 }
138 
sl_zoomPerformed()139 void AssemblyConsensusArea::sl_zoomPerformed() {
140     launchConsensusCalculation();
141 }
142 
drawSequence(QPainter & p)143 void AssemblyConsensusArea::drawSequence(QPainter &p) {
144     if (areCellsVisible()) {
145         U2Region visibleRegion = getVisibleRegion();
146         if (!consensusTaskRunner.isIdle() || canceled) {
147             if (!cache.region.isEmpty() && cache.region.intersects(visibleRegion)) {
148                 // Draw a known part while others are still being calculated
149                 // To do it, temporarily substitute lastResult with values from cache, then return it back
150                 ConsensusInfo storedLastResult = lastResult;
151                 lastResult = getPart(cache, visibleRegion);
152                 AssemblySequenceArea::drawSequence(p);
153                 p.fillRect(rect(), QColor(0xff, 0xff, 0xff, 0x7f));
154                 lastResult = storedLastResult;
155             }
156             QString message = consensusTaskRunner.isIdle() ? tr("Consensus calculation canceled") : tr("Calculating consensus...");
157             p.drawText(rect(), Qt::AlignCenter, message);
158         } else if (lastResult.region == visibleRegion && lastResult.algorithmId == consensusAlgorithm->getId()) {
159             AssemblySequenceArea::drawSequence(p);
160         } else if (cache.region.contains(visibleRegion) && cache.algorithmId == consensusAlgorithm->getId()) {
161             lastResult = getPart(cache, visibleRegion);
162             AssemblySequenceArea::drawSequence(p);
163         } else {
164             launchConsensusCalculation();
165         }
166     }
167 }
168 
getConsensusAlgorithmMenu()169 QMenu *AssemblyConsensusArea::getConsensusAlgorithmMenu() {
170     if (consensusAlgorithmMenu == nullptr) {
171         consensusAlgorithmMenu = new QMenu(tr("Consensus algorithm"));
172 
173         AssemblyConsensusAlgorithmRegistry *registry = AppContext::getAssemblyConsensusAlgorithmRegistry();
174         QList<AssemblyConsensusAlgorithmFactory *> factories = registry->getAlgorithmFactories();
175 
176         foreach (AssemblyConsensusAlgorithmFactory *f, factories) {
177             QAction *action = consensusAlgorithmMenu->addAction(f->getName());
178             action->setCheckable(true);
179             action->setChecked(f == consensusAlgorithm->getFactory());
180             action->setData(f->getId());
181             connect(consensusAlgorithmMenu, SIGNAL(triggered(QAction *)), SLOT(sl_consensusAlgorithmChanged(QAction *)));
182             algorithmActions << action;
183         }
184     }
185     return consensusAlgorithmMenu;
186 }
187 
getAlgorithmActions()188 QList<QAction *> AssemblyConsensusArea::getAlgorithmActions() {
189     // ensure that menu is created
190     getConsensusAlgorithmMenu();
191     return algorithmActions;
192 }
193 
sl_consensusAlgorithmChanged(QAction * action)194 void AssemblyConsensusArea::sl_consensusAlgorithmChanged(QAction *action) {
195     QString id = action->data().toString();
196     AssemblyConsensusAlgorithmRegistry *registry = AppContext::getAssemblyConsensusAlgorithmRegistry();
197     AssemblyConsensusAlgorithmFactory *f = registry->getAlgorithmFactory(id);
198     SAFE_POINT(f != nullptr, QString("cannot change consensus algorithm, invalid id %1").arg(id), );
199 
200     consensusAlgorithm = QSharedPointer<AssemblyConsensusAlgorithm>(f->createAlgorithm());
201 
202     foreach (QAction *a, consensusAlgorithmMenu->actions()) {
203         a->setChecked(a == action);
204     }
205 
206     launchConsensusCalculation();
207 }
208 
sl_drawDifferenceChanged(bool drawDifference)209 void AssemblyConsensusArea::sl_drawDifferenceChanged(bool drawDifference) {
210     if (drawDifference) {
211         setDiffCellRenderer();
212     } else {
213         setNormalCellRenderer();
214     }
215     sl_redraw();
216 }
217 
mousePressEvent(QMouseEvent * e)218 void AssemblyConsensusArea::mousePressEvent(QMouseEvent *e) {
219     if (e->button() == Qt::RightButton) {
220         updateActions();
221         contextMenu->exec(QCursor::pos());
222     }
223 }
224 
sl_consensusReady()225 void AssemblyConsensusArea::sl_consensusReady() {
226     if (consensusTaskRunner.isIdle()) {
227         if (consensusTaskRunner.isSuccessful()) {
228             cache = lastResult = consensusTaskRunner.getResult();
229             canceled = false;
230         } else {
231             canceled = true;
232         }
233         sl_redraw();
234     }
235 }
236 
sl_exportConsensus()237 void AssemblyConsensusArea::sl_exportConsensus() {
238     const DocumentFormat *defaultFormat = BaseDocumentFormats::get(BaseDocumentFormats::FASTA);
239     SAFE_POINT(defaultFormat != nullptr, "Internal: couldn't find default document format for consensus", );
240 
241     ExportConsensusTaskSettings settings;
242     settings.region = getModel()->getGlobalRegion();
243     settings.model = getModel();
244     settings.consensusAlgorithm = consensusAlgorithm;
245     settings.saveToFile = true;
246     settings.formatId = defaultFormat->getFormatId();
247     settings.seqObjName = getModel()->getAssembly().visualName + "_consensus";
248     settings.addToProject = true;
249     settings.keepGaps = true;
250 
251     GUrl url(U2DbiUtils::ref2Url(getModel()->getDbiConnection().dbi->getDbiRef()));
252     settings.fileName = GUrlUtils::getNewLocalUrlByFormat(url, getModel()->getAssembly().visualName, settings.formatId, "_consensus");
253 
254     QObjectScopedPointer<ExportConsensusDialog> dlg = new ExportConsensusDialog(this, settings, getVisibleRegion());
255     const int dialogResult = dlg->exec();
256     CHECK(!dlg.isNull(), );
257 
258     if (QDialog::Accepted == dialogResult) {
259         settings = dlg->getSettings();
260         AppContext::getTaskScheduler()->registerTopLevelTask(new ExportConsensusTask(settings));
261     }
262 }
sl_exportConsensusVariations()263 void AssemblyConsensusArea::sl_exportConsensusVariations() {
264     const DocumentFormat *defaultFormat = BaseDocumentFormats::get(BaseDocumentFormats::SNP);
265     SAFE_POINT(defaultFormat != nullptr, "Internal: couldn't find default document format for consensus variations", );
266 
267     ExportConsensusVariationsTaskSettings settings;
268     settings.region = getModel()->getGlobalRegion();
269     settings.model = getModel();
270     settings.consensusAlgorithm = consensusAlgorithm;
271     settings.formatId = defaultFormat->getFormatId();
272     settings.seqObjName = getModel()->getAssembly().visualName;
273     settings.addToProject = true;
274     settings.keepGaps = true;
275     settings.mode = Mode_Variations;
276     settings.refSeq = getModel()->getRefereneceEntityRef();
277 
278     GUrl url(U2DbiUtils::ref2Url(getModel()->getDbiConnection().dbi->getDbiRef()));
279     settings.fileName = GUrlUtils::getNewLocalUrlByFormat(url, getModel()->getAssembly().visualName, settings.formatId, "");
280 
281     QObjectScopedPointer<ExportConsensusVariationsDialog> dlg = new ExportConsensusVariationsDialog(this, settings, getVisibleRegion());
282     const int dialogResult = dlg->exec();
283     CHECK(!dlg.isNull(), );
284 
285     if (QDialog::Accepted == dialogResult) {
286         settings = dlg->getSettings();
287         AppContext::getTaskScheduler()->registerTopLevelTask(new ExportConsensusVariationsTask(settings));
288     }
289 }
290 
updateActions()291 void AssemblyConsensusArea::updateActions() {
292     if (!getModel()->hasReference()) {
293         exportConsensusVariationsAction->setDisabled(true);
294     } else {
295         exportConsensusVariationsAction->setDisabled(false);
296     }
297 }
298 
299 }  // namespace U2
300