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 "GenomeAssemblyDialog.h"
23 
24 #include <QKeyEvent>
25 #include <QMessageBox>
26 
27 #include <U2Algorithm/GenomeAssemblyRegistry.h>
28 
29 #include <U2Core/AppContext.h>
30 #include <U2Core/BaseDocumentFormats.h>
31 #include <U2Core/DocumentModel.h>
32 #include <U2Core/DocumentUtils.h>
33 #include <U2Core/ExternalToolRegistry.h>
34 #include <U2Core/FileAndDirectoryUtils.h>
35 #include <U2Core/GUrlUtils.h>
36 #include <U2Core/U2SafePoints.h>
37 
38 #include <U2Gui/AppSettingsGUI.h>
39 #include <U2Gui/HelpButton.h>
40 #include <U2Gui/LastUsedDirHelper.h>
41 #include <U2Gui/U2FileDialog.h>
42 
43 #include <U2View/DnaAssemblyGUIExtension.h>
44 #include <U2View/DnaAssemblyUtils.h>
45 
46 namespace U2 {
47 
48 QString GenomeAssemblyDialog::methodName;
49 QString GenomeAssemblyDialog::library;
50 
GenomeAssemblyDialog(QWidget * p)51 GenomeAssemblyDialog::GenomeAssemblyDialog(QWidget *p)
52     : QDialog(p),
53       assemblyRegistry(AppContext::getGenomeAssemblyAlgRegistry()),
54       customGUI(nullptr) {
55     setupUi(this);
56 
57     QMap<QString, QString> helpPagesMap;
58     helpPagesMap.insert("SPAdes", "65930903");
59     new ComboboxDependentHelpButton(this, buttonBox, methodNamesBox, helpPagesMap);
60     buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Start"));
61     buttonBox->button(QDialogButtonBox::Cancel)->setText(tr("Cancel"));
62 
63     QStringList names = assemblyRegistry->getRegisteredAlgorithmIds();
64     methodNamesBox->addItems(names);
65     // TODO: change the way default method is set
66     if (names.size() > 0) {
67         int res = -1;
68         if (!methodName.isEmpty()) {
69             res = methodNamesBox->findText(methodName);
70         }
71         if (-1 == res) {
72             methodNamesBox->setCurrentIndex(names.size() - 1);
73         } else {
74             methodNamesBox->setCurrentIndex(res);
75         }
76     }
77 
78     //    libraryComboBox->addItems(GenomeAssemblyUtils::getLibraryTypes());
79 
80     QHeaderView *header1 = propertiesReadsTable->header();
81     QHeaderView *header2 = leftReadsTable->header();
82     QHeaderView *header3 = rightReadsTable->header();
83 
84     header1->setStretchLastSection(false);
85     header2->setStretchLastSection(false);
86     header3->setStretchLastSection(false);
87     header1->setSectionsClickable(false);
88     header1->setSectionResizeMode(0, QHeaderView::Stretch);
89     header2->setSectionsClickable(false);
90     header2->setSectionResizeMode(0, QHeaderView::Stretch);
91     header3->setSectionsClickable(false);
92     header3->setSectionResizeMode(0, QHeaderView::Stretch);
93 
94     sl_onLibraryTypeChanged();
95     sl_onAlgorithmChanged(methodNamesBox->currentText());
96 
97     connect(addLeftButton, SIGNAL(clicked()), SLOT(sl_onAddShortReadsButtonClicked()));
98     connect(addRightButton, SIGNAL(clicked()), SLOT(sl_onAddShortReadsButtonClicked()));
99     connect(removeLeftButton, SIGNAL(clicked()), SLOT(sl_onRemoveShortReadsButtonClicked()));
100     connect(removeRightButton, SIGNAL(clicked()), SLOT(sl_onRemoveShortReadsButtonClicked()));
101     connect(setResultDirNameButton, SIGNAL(clicked()), SLOT(sl_onOutDirButtonClicked()));
102     connect(methodNamesBox, SIGNAL(currentIndexChanged(const QString &)), SLOT(sl_onAlgorithmChanged(const QString &)));
103     connect(libraryComboBox, SIGNAL(currentIndexChanged(int)), SLOT(sl_onLibraryTypeChanged()));
104 
105     QString defaultOutputDir = GUrlUtils::getDefaultDataPath() + "/" + methodNamesBox->currentText() + "_output";
106     resultDirNameEdit->setText(GUrlUtils::rollFileName(defaultOutputDir, QSet<QString>() << defaultOutputDir));
107 
108     if (!library.isEmpty()) {
109         int index = libraryComboBox->findText(library);
110         if (index != -1) {
111             libraryComboBox->setCurrentIndex(index);
112         }
113     }
114 }
115 
updateState()116 void GenomeAssemblyDialog::updateState() {
117     addGuiExtension();
118 }
119 
updateProperties()120 void GenomeAssemblyDialog::updateProperties() {
121     int numProperties = propertiesReadsTable->topLevelItemCount();
122     int numberOfReads = leftReadsTable->topLevelItemCount();
123     //    if(GenomeAssemblyUtils::hasRightReads(libraryComboBox->currentText())){
124     //        numberOfReads = qMax(leftReadsTable->topLevelItemCount(), rightReadsTable->topLevelItemCount());
125     //    }
126     if (numProperties > numberOfReads) {
127         // remove items
128         for (int i = numProperties - 1; i >= numberOfReads; --i) {
129             propertiesReadsTable->takeTopLevelItem(i);
130         }
131     } else if (numProperties < numberOfReads) {
132         // add items
133         for (int i = numProperties; i < numberOfReads; i++) {
134             ReadPropertiesItem *item = new ReadPropertiesItem(propertiesReadsTable);
135             item->setLibraryType(libraryComboBox->currentText());
136             ReadPropertiesItem::addItemToTable(item, propertiesReadsTable);
137         }
138     }
139     // update numbers
140     numProperties = propertiesReadsTable->topLevelItemCount();
141     for (int i = 0; i < numProperties; ++i) {
142         QTreeWidgetItem *item = propertiesReadsTable->topLevelItem(i);
143         item->setData(0, 0, i + 1);
144     }
145 }
146 
addReads(QStringList fileNames,QTreeWidget * readsWidget)147 void GenomeAssemblyDialog::addReads(QStringList fileNames, QTreeWidget *readsWidget) {
148     foreach (const QString &f, fileNames) {
149         QTreeWidgetItem *item = new QTreeWidgetItem();
150         item->setToolTip(0, f);
151         item->setText(0, GUrl(f).fileName());
152         item->setData(0, Qt::UserRole, f);
153         readsWidget->addTopLevelItem(item);
154         item->setSizeHint(0, QComboBox().sizeHint());
155     }
156 
157     updateProperties();
158 }
159 
sl_onAddShortReadsButtonClicked()160 void GenomeAssemblyDialog::sl_onAddShortReadsButtonClicked() {
161     QTreeWidget *readsWidget = nullptr;
162     QObject *obj = sender();
163     if (obj == addLeftButton) {
164         readsWidget = leftReadsTable;
165     } else if (obj == addRightButton) {
166         readsWidget = rightReadsTable;
167     } else {
168         return;
169     }
170 
171     LastUsedDirHelper lod("AssemblyReads");
172     QStringList fileNames;
173 #ifdef Q_OS_DARWIN
174     if (qgetenv(ENV_GUI_TEST).toInt() == 1 && qgetenv(ENV_USE_NATIVE_DIALOGS).toInt() == 0) {
175         fileNames = U2FileDialog::getOpenFileNames(this, tr("Add short reads"), lod.dir, QString(), 0, QFileDialog::DontUseNativeDialog);
176     } else
177 #endif
178         fileNames = U2FileDialog::getOpenFileNames(this, tr("Add short reads"), lod.dir);
179     if (fileNames.isEmpty()) {
180         return;
181     }
182     lod.url = fileNames.at(fileNames.count() - 1);
183 
184     addReads(fileNames, readsWidget);
185 }
186 
accept()187 void GenomeAssemblyDialog::accept() {
188     bool validated = true;
189     if (nullptr != customGUI) {
190         QString error;
191         if (!customGUI->isParametersOk(error)) {
192             if (!error.isEmpty()) {
193                 QMessageBox::information(this, tr("Genome Assembly"), error);
194             }
195             validated = false;
196         }
197     }
198 
199     if (resultDirNameEdit->text().isEmpty()) {
200         QMessageBox::information(this, tr("Genome Assembly"), tr("Result assembly folder is not set!"));
201         validated = false;
202     } else {
203         //        if(GenomeAssemblyUtils::hasRightReads(libraryComboBox->currentText())){
204         //            if(leftReadsTable->topLevelItemCount() == 0 && rightReadsTable->topLevelItemCount() == 0){
205         //                QMessageBox::information(this, tr("Genome Assembly"),
206         //                    tr("No reads. Please, add file(s) with short reads.") );
207         //                validated = false;
208         //            }
209 
210         //            if(leftReadsTable->topLevelItemCount() != rightReadsTable->topLevelItemCount()){
211         //                QMessageBox::information(this, tr("Genome Assembly"),
212         //                    tr("In the paired-end mode a number of lift and right reads must be equal.") );
213         //                validated = false;
214         //            }
215 
216         //        }else{
217         //            if(leftReadsTable->topLevelItemCount() == 0){
218         //                QMessageBox::information(this, tr("Genome Assembly"),
219         //                    tr("No reads. Please, add file(s) with short reads.") );
220         //                validated = false;
221         //            }
222         //        }
223 
224         if (validated) {
225             library = libraryComboBox->currentText();
226             library = libraryComboBox->currentText();
227 
228             // check formats
229             QStringList reads;
230             int numItems = leftReadsTable->topLevelItemCount();
231             for (int i = 0; i < numItems; ++i) {
232                 reads.append(leftReadsTable->topLevelItem(i)->data(0, Qt::UserRole).toString());
233             }
234 
235             numItems = rightReadsTable->topLevelItemCount();
236             for (int i = 0; i < numItems; ++i) {
237                 reads.append(rightReadsTable->topLevelItem(i)->data(0, Qt::UserRole).toString());
238             }
239 
240             GenomeAssemblyAlgorithmEnv *env = AppContext::getGenomeAssemblyAlgRegistry()->getAlgorithm(methodNamesBox->currentText());
241             SAFE_POINT(nullptr != env, "Unknown algorithm: " + methodNamesBox->currentText(), );
242             QStringList formats = env->getReadsFormats();
243 
244             foreach (const QString &r, reads) {
245                 const QString detectedFormat = FileAndDirectoryUtils::detectFormat(r);
246                 if (detectedFormat.isEmpty()) {
247                     QMessageBox::information(this, tr("Genome Assembly"), tr("Unknown file format of %1.").arg(r));
248                     return;
249                 }
250 
251                 if (!formats.contains(detectedFormat)) {
252                     QMessageBox::information(this, tr("Genome Assembly"), tr("File format of %1 is %2. Supported file formats of reads: %3.").arg(r).arg(detectedFormat).arg(formats.join(", ")));
253                     return;
254                 }
255             }
256             QString outputDirUrl = resultDirNameEdit->text();
257             QDir d(outputDirUrl);
258             if (!d.exists()) {
259                 if (!d.mkdir(outputDirUrl)) {
260                     QMessageBox::information(this, tr("Genome Assembly"), tr("Unable to create output folder for result assembly.\r\nDirectory Path: %1").arg(outputDirUrl));
261                 }
262             }
263             QDialog::accept();
264         }
265     }
266 }
267 
getAlgorithmName()268 const QString GenomeAssemblyDialog::getAlgorithmName() {
269     return methodNamesBox->currentText();
270 }
271 
getOutDir()272 const QString GenomeAssemblyDialog::getOutDir() {
273     return resultDirNameEdit->text();
274 }
275 
getReads()276 QList<AssemblyReads> GenomeAssemblyDialog::getReads() {
277     QList<AssemblyReads> result;
278 
279     int numProperties = propertiesReadsTable->topLevelItemCount();
280     int numLeftReads = leftReadsTable->topLevelItemCount();
281     int numRightReads = rightReadsTable->topLevelItemCount();
282 
283     for (int i = 0; i < numProperties; i++) {
284         AssemblyReads read;
285         QTreeWidgetItem *item = propertiesReadsTable->topLevelItem(i);
286         ReadPropertiesItem *pitem = dynamic_cast<ReadPropertiesItem *>(item);
287         if (pitem) {
288             //            read.libNumber = pitem->getNumber();
289             //            read.libType = pitem->getType();
290             read.orientation = pitem->getOrientation();
291             read.libName = libraryComboBox->currentText();
292 
293             if (i < numLeftReads) {
294                 read.left << leftReadsTable->topLevelItem(i)->data(0, Qt::UserRole).toString();
295                 if (i < numRightReads) {
296                     read.right << rightReadsTable->topLevelItem(i)->data(0, Qt::UserRole).toString();
297                 }
298                 result.append(read);
299             }
300         }
301     }
302     return result;
303 }
304 
sl_onRemoveShortReadsButtonClicked()305 void GenomeAssemblyDialog::sl_onRemoveShortReadsButtonClicked() {
306     QTreeWidget *readsWidget = nullptr;
307     QObject *obj = sender();
308     if (obj == removeLeftButton) {
309         readsWidget = leftReadsTable;
310     } else if (obj == removeRightButton) {
311         readsWidget = rightReadsTable;
312     } else {
313         return;
314     }
315 
316     int currentRow = readsWidget->currentIndex().row();
317     readsWidget->takeTopLevelItem(currentRow);
318 
319     updateProperties();
320 }
321 
sl_onOutDirButtonClicked()322 void GenomeAssemblyDialog::sl_onOutDirButtonClicked() {
323     LastUsedDirHelper lod("assemblyRes");
324 
325     lod.url = U2FileDialog::getExistingDirectory(this, tr("Select output folder"), lod.dir);
326     if (lod.url.isEmpty()) {
327         return;
328     }
329 
330     resultDirNameEdit->setText(lod.url);
331 }
332 
sl_onAlgorithmChanged(const QString & text)333 void GenomeAssemblyDialog::sl_onAlgorithmChanged(const QString &text) {
334     methodName = text;
335     updateState();
336 }
337 
getCustomSettings()338 QMap<QString, QVariant> GenomeAssemblyDialog::getCustomSettings() {
339     if (customGUI != nullptr) {
340         return customGUI->getGenomeAssemblyCustomSettings();
341     } else {
342         return QMap<QString, QVariant>();
343     }
344 }
345 
addGuiExtension()346 void GenomeAssemblyDialog::addGuiExtension() {
347     static const int insertPos = verticalLayout->count() - 2;
348 
349     int macFixDelta = 50;
350 
351     // cleanup previous extension
352     if (customGUI != nullptr) {
353         layout()->removeWidget(customGUI);
354         setMinimumHeight(minimumHeight() - customGUI->minimumHeight());
355         delete customGUI;
356         customGUI = nullptr;
357         macFixDelta = 0;
358     }
359 
360     // insert new extension widget
361     GenomeAssemblyAlgorithmEnv *env = assemblyRegistry->getAlgorithm(methodNamesBox->currentText());
362 
363     if (nullptr == env) {
364         adjustSize();
365         return;
366     }
367 
368     GenomeAssemblyGUIExtensionsFactory *gui = env->getGUIExtFactory();
369     if (gui != nullptr && gui->hasMainWidget()) {
370         customGUI = gui->createMainWidget(this);
371         int extensionMinWidth = customGUI->sizeHint().width();
372         int extensionMinHeight = customGUI->sizeHint().height();
373         customGUI->setMinimumWidth(extensionMinWidth);
374         customGUI->setMinimumHeight(extensionMinHeight);
375         verticalLayout->insertWidget(insertPos, customGUI);
376         // adjust sizes
377         // HACK: add 50 to min height when dialog first shown, 50 to width always (fix for Mac OS)
378         // TODO: handle margins in proper way so this hack not needed
379         setMinimumHeight(customGUI->minimumHeight() + minimumHeight() + macFixDelta);
380         if (minimumWidth() < customGUI->minimumWidth() + 50) {
381             setMinimumWidth(customGUI->minimumWidth() + 50);
382         };
383 
384         customGUI->show();
385         adjustSize();
386     } else {
387         adjustSize();
388     }
389 }
390 
sl_onLibraryTypeChanged()391 void GenomeAssemblyDialog::sl_onLibraryTypeChanged() {
392     QString libraryType = libraryComboBox->currentText();
393     //    if(GenomeAssemblyUtils::hasRightReads(libraryType)){
394     //        rightReadsTable->setEnabled(true);
395     //        addRightButton->setEnabled(true);
396     //        removeRightButton->setEnabled(true);
397     //    }else{
398     //        rightReadsTable->setEnabled(false);
399     //        addRightButton->setEnabled(false);
400     //        removeRightButton->setEnabled(false);
401     //    }
402 
403     int size = propertiesReadsTable->topLevelItemCount();
404     for (int i = 0; i < size; i++) {
405         QTreeWidgetItem *item = propertiesReadsTable->topLevelItem(i);
406         ReadPropertiesItem *pitem = dynamic_cast<ReadPropertiesItem *>(item);
407         if (pitem) {
408             pitem->setLibraryType(libraryType);
409         }
410     }
411 
412     updateProperties();
413 }
414 
ReadPropertiesItem(QTreeWidget * widget)415 ReadPropertiesItem::ReadPropertiesItem(QTreeWidget *widget)
416     : QTreeWidgetItem(widget) {
417     typeBox = new QComboBox(widget);
418     //    typeBox->addItems(GenomeAssemblyUtils::getPairTypes());
419 
420     orientationBox = new QComboBox(widget);
421     orientationBox->addItems(GenomeAssemblyUtils::getOrientationTypes());
422 }
423 
getNumber() const424 QString ReadPropertiesItem::getNumber() const {
425     return data(0, 0).toString();
426 }
427 
getType() const428 QString ReadPropertiesItem::getType() const {
429     return typeBox->currentText();
430 }
431 
getOrientation() const432 QString ReadPropertiesItem::getOrientation() const {
433     return orientationBox->currentText();
434 }
435 
setLibraryType(const QString & libraryType)436 void ReadPropertiesItem::setLibraryType(const QString &libraryType) {
437     if (GenomeAssemblyUtils::isLibraryPaired(libraryType)) {
438         orientationBox->setEnabled(true);
439         typeBox->setEnabled(true);
440     } else {
441         orientationBox->setEnabled(false);
442         typeBox->setEnabled(false);
443     }
444 }
445 
addItemToTable(ReadPropertiesItem * item,QTreeWidget * treeWidget)446 void ReadPropertiesItem::addItemToTable(ReadPropertiesItem *item, QTreeWidget *treeWidget) {
447     treeWidget->addTopLevelItem(item);
448     treeWidget->setItemWidget(item, 1, item->typeBox);
449     treeWidget->setItemWidget(item, 2, item->orientationBox);
450 }
451 
452 }  // namespace U2
453