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 <math.h>
23 
24 #include <QColorDialog>
25 #include <QDesktopWidget>
26 
27 #include <U2Core/AppContext.h>
28 #include <U2Core/DNAAlphabet.h>
29 #include <U2Core/DNASequenceObject.h>
30 #include <U2Core/GObjectUtils.h>
31 #include <U2Core/ProjectModel.h>
32 #include <U2Core/QObjectScopedPointer.h>
33 #include <U2Core/U2SafePoints.h>
34 #include <U2Core/global.h>
35 
36 #include <U2Gui/CreateAnnotationWidgetController.h>
37 #include <U2Gui/DialogUtils.h>
38 #include <U2Gui/HelpButton.h>
39 #include <U2Gui/LastUsedDirHelper.h>
40 #include <U2Gui/U2FileDialog.h>
41 
42 #include <U2View/ADVSequenceObjectContext.h>
43 #include <U2View/AnnotatedDNAView.h>
44 
45 #include "DotPlotDialog.h"
46 #include "DotPlotTasks.h"
47 
48 namespace U2 {
49 
DotPlotDialog(QWidget * parent,AnnotatedDNAView * currentADV,int minLen,int identity,ADVSequenceObjectContext * sequenceX,ADVSequenceObjectContext * sequenceY,bool dir,bool inv,const QColor & dColor,const QColor & iColor,bool hideLoadSequences)50 DotPlotDialog::DotPlotDialog(QWidget *parent, AnnotatedDNAView *currentADV, int minLen, int identity, ADVSequenceObjectContext *sequenceX, ADVSequenceObjectContext *sequenceY, bool dir, bool inv, const QColor &dColor, const QColor &iColor, bool hideLoadSequences)
51     : QDialog(parent), xSeq(sequenceX), ySeq(sequenceY), adv(currentADV), directColor(dColor), invertedColor(iColor), openSequenceTask(nullptr) {
52     setupUi(this);
53 
54     new HelpButton(this, buttonBox, "65929583");
55     buttonBox->button(QDialogButtonBox::Ok)->setText(tr("OK"));
56     buttonBox->button(QDialogButtonBox::Cancel)->setText(tr("Cancel"));
57 
58     SAFE_POINT(adv != nullptr, "DotPlotDialog called without view context!", );
59 
60     directCheckBox->setChecked(dir);
61     invertedCheckBox->setChecked(inv);
62 
63     updateColors();
64 
65     // set algorithms
66     algoCombo->addItem(tr("Auto"), RFAlgorithm_Auto);
67     algoCombo->addItem(tr("Suffix index"), RFAlgorithm_Suffix);
68     algoCombo->addItem(tr("Diagonals"), RFAlgorithm_Diagonal);
69 
70     minLenBox->setValue(minLen);
71     identityBox->setValue(identity);
72 
73     connect(minLenHeuristicsButton, SIGNAL(clicked()), SLOT(sl_minLenHeuristics()));
74     connect(hundredPercentButton, SIGNAL(clicked()), SLOT(sl_hundredPercent()));
75 
76     connect(directCheckBox, SIGNAL(clicked()), SLOT(sl_directInvertedCheckBox()));
77     connect(invertedCheckBox, SIGNAL(clicked()), SLOT(sl_directInvertedCheckBox()));
78 
79     connect(directColorButton, SIGNAL(clicked()), SLOT(sl_directColorButton()));
80     connect(invertedColorButton, SIGNAL(clicked()), SLOT(sl_invertedColorButton()));
81 
82     connect(directDefaultColorButton, SIGNAL(clicked()), SLOT(sl_directDefaultColorButton()));
83     connect(invertedDefaultColorButton, SIGNAL(clicked()), SLOT(sl_invertedDefaultColorButton()));
84 
85     connect(loadSequenceButton, SIGNAL(clicked()), SLOT(sl_loadSequenceButton()));
86 
87     // listen to project modification to update list of available sequence objects.
88     Project *project = AppContext::getProject();
89     connect(project, SIGNAL(si_documentAdded(Document *)), SLOT(sl_documentAddedOrRemoved()));
90     connect(project, SIGNAL(si_documentRemoved(Document *)), SLOT(sl_documentAddedOrRemoved()));
91     reconnectAllProjectDocuments();
92     updateSequenceSelectors();
93 
94     connect(xAxisCombo, SIGNAL(currentIndexChanged(int)), SLOT(sl_sequenceSelectorIndexChanged()));
95     connect(yAxisCombo, SIGNAL(currentIndexChanged(int)), SLOT(sl_sequenceSelectorIndexChanged()));
96     sl_sequenceSelectorIndexChanged();
97 
98     if (hideLoadSequences) {
99         loadSequenceButton->hide();
100     }
101 }
102 
reconnectAllProjectDocuments()103 void DotPlotDialog::reconnectAllProjectDocuments() {
104     Project *project = AppContext::getProject();
105     foreach (Document *d, project->getDocuments()) {
106         d->disconnect(this);
107         connect(d, SIGNAL(si_objectAdded(GObject *)), SLOT(sl_objectAddedOrRemoved()));
108         connect(d, SIGNAL(si_objectRemoved(GObject *)), SLOT(sl_objectAddedOrRemoved()));
109         connect(d, SIGNAL(si_loadedStateChanged()), SLOT(sl_loadedStateChanged()));
110     }
111 }
112 
updateSequenceSelectors()113 void DotPlotDialog::updateSequenceSelectors() {
114     xAxisCombo->clear();
115     yAxisCombo->clear();
116 
117     int xSeqIndex = -1, ySeqIndex = -1, curIndex = 0;
118 
119     // sequences in the project
120     QList<GObject *> sequenceObjects = GObjectUtils::findAllObjects(UOF_LoadedOnly, GObjectTypes::SEQUENCE);
121     foreach (GObject *obj, sequenceObjects) {
122         U2SequenceObject *seqObj = qobject_cast<U2SequenceObject *>(obj);
123         QString name = seqObj->getGObjectName();
124 
125         xAxisCombo->addItem(name);
126         yAxisCombo->addItem(name);
127 
128         if (xSeq && (xSeq->getSequenceGObject() == seqObj)) {
129             xSeqIndex = curIndex;
130         }
131         if (ySeq && (ySeq->getSequenceGObject() == seqObj)) {
132             ySeqIndex = curIndex;
133         }
134         curIndex++;
135     }
136 
137     if (xSeqIndex >= 0) {
138         xAxisCombo->setCurrentIndex(xSeqIndex);
139     }
140     if (ySeqIndex >= 0) {
141         yAxisCombo->setCurrentIndex(ySeqIndex);
142     } else if (sequenceObjects.size() > 1) {  // choose the second sequence for Y axis by default
143         yAxisCombo->setCurrentIndex(1);
144     }
145 }
146 
sl_documentAddedOrRemoved()147 void DotPlotDialog::sl_documentAddedOrRemoved() {
148     reconnectAllProjectDocuments();
149     updateSequenceSelectors();
150 }
151 
sl_objectAddedOrRemoved()152 void DotPlotDialog::sl_objectAddedOrRemoved() {
153     updateSequenceSelectors();
154 }
155 
sl_loadedStateChanged()156 void DotPlotDialog::sl_loadedStateChanged() {
157     updateSequenceSelectors();
158 }
159 
sl_sequenceSelectorIndexChanged()160 void DotPlotDialog::sl_sequenceSelectorIndexChanged() {
161     int xIdx = xAxisCombo->currentIndex();
162     int yIdx = yAxisCombo->currentIndex();
163 
164     QList<GObject *> sequenceObjects = GObjectUtils::findAllObjects(UOF_LoadedOnly, GObjectTypes::SEQUENCE);
165     SAFE_POINT(xIdx >= 0 && xIdx < sequenceObjects.length(), QString("DotPlotDialog: index is out of range: %1").arg(xIdx), );
166     SAFE_POINT(yIdx >= 0 && yIdx < sequenceObjects.length(), QString("DotPlotDialog: index is out of range: %1").arg(yIdx), );
167 
168     U2SequenceObject *objX = qobject_cast<U2SequenceObject *>(sequenceObjects[xIdx]);
169     U2SequenceObject *objY = qobject_cast<U2SequenceObject *>(sequenceObjects[yIdx]);
170     if (!objX->getAlphabet()->isNucleic() || !objY->getAlphabet()->isNucleic()) {
171         invertedCheckBox->setDisabled(true);
172         invertedColorButton->setDisabled(true);
173         invertedDefaultColorButton->setDisabled(true);
174     } else {
175         invertedCheckBox->setDisabled(false);
176         invertedColorButton->setDisabled(false);
177         invertedDefaultColorButton->setDisabled(false);
178     }
179     int defaultWindow = qMin(objX->getSequenceLength(), objY->getSequenceLength());
180     defaultWindow = defaultWindow < 100 ? defaultWindow : 100;
181     if (minLenBox->value() > defaultWindow) {
182         minLenBox->setValue(defaultWindow);
183     }
184 }
185 
accept()186 void DotPlotDialog::accept() {
187     int xIdx = xAxisCombo->currentIndex();
188     int yIdx = yAxisCombo->currentIndex();
189 
190     QList<GObject *> sequenceObjects = GObjectUtils::findAllObjects(UOF_LoadedOnly, GObjectTypes::SEQUENCE);
191     SAFE_POINT(xIdx >= 0 && xIdx < sequenceObjects.length(), QString("DotPlotDialog: index is out of range: %1").arg(xIdx), );
192     SAFE_POINT(yIdx >= 0 && yIdx < sequenceObjects.length(), QString("DotPlotDialog: index is out of range: %1").arg(yIdx), );
193 
194     U2SequenceObject *objX = qobject_cast<U2SequenceObject *>(sequenceObjects[xIdx]);
195     U2SequenceObject *objY = qobject_cast<U2SequenceObject *>(sequenceObjects[yIdx]);
196 
197     if (!isObjectInADV(objX)) {
198         adv->addObject(objX);
199     }
200 
201     if (!isObjectInADV(objY)) {
202         adv->addObject(objY);
203     }
204 
205     xSeq = adv->getSequenceContext(objX);
206     ySeq = adv->getSequenceContext(objY);
207 
208     QDialog::accept();
209 }
210 
sl_minLenHeuristics()211 void DotPlotDialog::sl_minLenHeuristics() {
212     identityBox->setValue(100);
213 
214     // formula used here: nVariations / lenVariations = wantedResCount (==1000)
215     // where nVariations == area size
216     // lenVariations = 4^len where len is result
217     // so we have len = ln(nVariations/wantedResCount)/ln(4)
218 
219     int xIdx = xAxisCombo->currentIndex();
220     int yIdx = yAxisCombo->currentIndex();
221 
222     QList<GObject *> sequenceObjects = GObjectUtils::findAllObjects(UOF_LoadedOnly, GObjectTypes::SEQUENCE);
223     SAFE_POINT(xIdx >= 0 && xIdx < sequenceObjects.length(), QString("DotPlotDialog: index is out of range: %1").arg(xIdx), );
224     SAFE_POINT(yIdx >= 0 && yIdx < sequenceObjects.length(), QString("DotPlotDialog: index is out of range: %1").arg(yIdx), );
225 
226     U2SequenceObject *objX = qobject_cast<U2SequenceObject *>(sequenceObjects[xIdx]);
227     U2SequenceObject *objY = qobject_cast<U2SequenceObject *>(sequenceObjects[yIdx]);
228 
229     qint64 xSeqLen = objX->getSequenceLength();
230     qint64 ySeqLen = objY->getSequenceLength();
231 
232     double nVariations = xSeqLen * ySeqLen;
233     double resCount = 1000;
234     double len = log(nVariations / resCount) / log(double(4));
235 
236     minLenBox->setValue((int)len);
237 }
238 
sl_hundredPercent()239 void DotPlotDialog::sl_hundredPercent() {
240     identityBox->setValue(100);
241 }
242 
getMismatches() const243 int DotPlotDialog::getMismatches() const {
244     return (100 - identityBox->value()) * minLenBox->value() / 100;
245 }
246 
247 // which algorithm
getAlgo() const248 RFAlgorithm DotPlotDialog::getAlgo() const {
249     if (algoCheck->isChecked()) {
250         int index = algoCombo->currentIndex();
251         return RFAlgorithm(algoCombo->itemData(index).toInt());
252     }
253     return RFAlgorithm_Auto;
254 }
255 
getMinLen() const256 int DotPlotDialog::getMinLen() const {
257     return minLenBox->value();
258 }
259 
isDirect() const260 bool DotPlotDialog::isDirect() const {
261     return directCheckBox->isChecked();
262 }
263 
isInverted() const264 bool DotPlotDialog::isInverted() const {
265     return invertedCheckBox->isChecked() && invertedCheckBox->isEnabled();
266 }
267 
sl_directInvertedCheckBox()268 void DotPlotDialog::sl_directInvertedCheckBox() {
269     buttonBox->button(QDialogButtonBox::Ok)->setEnabled(isDirect() || isInverted());
270 }
271 
272 static const QString COLOR_STYLE("QPushButton { background-color: %1 }");
273 
sl_directColorButton()274 void DotPlotDialog::sl_directColorButton() {
275     QObjectScopedPointer<QColorDialog> d = new QColorDialog(directColor, this);
276     d->exec();
277     CHECK(!d.isNull(), );
278 
279     if (QDialog::Accepted == d->result()) {
280         directColor = d->selectedColor();
281         directCheckBox->setChecked(true);
282     }
283 
284     updateColors();
285 }
286 
sl_invertedColorButton()287 void DotPlotDialog::sl_invertedColorButton() {
288     QObjectScopedPointer<QColorDialog> d = new QColorDialog(invertedColor, this);
289     d->exec();
290     CHECK(!d.isNull(), );
291 
292     if (QDialog::Accepted == d->result()) {
293         invertedColor = d->selectedColor();
294         invertedCheckBox->setChecked(true);
295     }
296 
297     updateColors();
298 }
299 
sl_directDefaultColorButton()300 void DotPlotDialog::sl_directDefaultColorButton() {
301     directColor = QColor();
302     directCheckBox->setChecked(true);
303     updateColors();
304 }
305 
sl_invertedDefaultColorButton()306 void DotPlotDialog::sl_invertedDefaultColorButton() {
307     invertedColor = QColor();
308     invertedCheckBox->setChecked(true);
309     updateColors();
310 }
311 
sl_loadSequenceButton()312 void DotPlotDialog::sl_loadSequenceButton() {
313     QString filter = DialogUtils::prepareDocumentsFileFilterByObjType(GObjectTypes::SEQUENCE, true);
314     LastUsedDirHelper lod("DotPlot file");
315     lod.url = U2FileDialog::getOpenFileName(this, tr("Open file"), lod.dir, filter);
316     if (!lod.url.isEmpty()) {
317         Task *tasks = new Task("Adding document to the project", TaskFlag_NoRun);
318 
319         if (!AppContext::getProject()) {
320             tasks->addSubTask(AppContext::getProjectLoader()->createNewProjectTask());
321         }
322 
323         QVariantMap hints;
324         hints[ProjectLoaderHint_LoadWithoutView] = false;
325         hints[ProjectLoaderHint_LoadUnloadedDocument] = true;
326         openSequenceTask = AppContext::getProjectLoader()->openWithProjectTask(lod.url, hints);
327         if (openSequenceTask == nullptr) {
328             return;
329         }
330         tasks->addSubTask(openSequenceTask);
331 
332         connect(AppContext::getTaskScheduler(), SIGNAL(si_stateChanged(Task *)), SLOT(sl_loadTaskStateChanged(Task *)));
333 
334         AppContext::getTaskScheduler()->registerTopLevelTask(tasks);
335     }
336 }
337 
sl_loadTaskStateChanged(Task * t)338 void DotPlotDialog::sl_loadTaskStateChanged(Task *t) {
339     DotPlotLoadDocumentsTask *loadTask = qobject_cast<DotPlotLoadDocumentsTask *>(t);
340     if (loadTask == nullptr) {
341         return;
342     }
343     if (loadTask->getStateInfo().hasError()) {
344         QMessageBox::critical(this, tr("Error"), tr("Error opening files"));
345         return;
346     }
347 }
348 
updateColors()349 void DotPlotDialog::updateColors() {
350     directColorButton->setStyleSheet(COLOR_STYLE.arg(directColor.name()));
351     invertedColorButton->setStyleSheet(COLOR_STYLE.arg(invertedColor.name()));
352 }
353 
isObjectInADV(GObject * obj)354 bool DotPlotDialog::isObjectInADV(GObject *obj) {
355     SAFE_POINT(obj != nullptr, "Object is NULL in DotPlotDialog::isObjectInADV(GObject* obj)", false);
356 
357     return adv->containsObject(obj);
358 }
359 
getGObjectByName(const QString & gObjectName)360 GObject *DotPlotDialog::getGObjectByName(const QString &gObjectName) {
361     QList<GObject *> allSequences = GObjectUtils::findAllObjects(UOF_LoadedOnly, GObjectTypes::SEQUENCE);
362     GObject *obj = nullptr;
363     foreach (GObject *s, allSequences) {
364         if (gObjectName == s->getGObjectName()) {
365             obj = s;
366         }
367     }
368     return obj;
369 }
370 
371 }  // namespace U2
372