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