//Copyright 2017 Ryan Wick //This file is part of Bandage //Bandage is free software: you can redistribute it and/or modify //it under the terms of the GNU General Public License as published by //the Free Software Foundation, either version 3 of the License, or //(at your option) any later version. //Bandage is distributed in the hope that it will be useful, //but WITHOUT ANY WARRANTY; without even the implied warranty of //MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //GNU General Public License for more details. //You should have received a copy of the GNU General Public License //along with Bandage. If not, see . #include "blastsearchdialog.h" #include "ui_blastsearchdialog.h" #include #include #include #include #include "../blast/blasthit.h" #include "../blast/blastquery.h" #include #include "../program/globals.h" #include "../program/settings.h" #include "../program/memory.h" #include "../graph/debruijnnode.h" #include #include #include "enteroneblastquerydialog.h" #include "../graph/assemblygraph.h" #include "../blast/blastsearch.h" #include #include #include #include "../blast/buildblastdatabaseworker.h" #include "../blast/runblastsearchworker.h" #include "myprogressdialog.h" #include "colourbutton.h" #include #include "tablewidgetitemname.h" #include "tablewidgetitemint.h" #include "tablewidgetitemdouble.h" #include "tablewidgetitemshown.h" #include #include "querypathspushbutton.h" #include "querypathsdialog.h" #include "blasthitfiltersdialog.h" BlastSearchDialog::BlastSearchDialog(QWidget *parent, QString autoQuery) : QDialog(parent), ui(new Ui::BlastSearchDialog), m_makeblastdbCommand("makeblastdb"), m_blastnCommand("blastn"), m_tblastnCommand("tblastn"), m_queryPathsDialog(0) { ui->setupUi(this); setWindowFlags(windowFlags() | Qt::Tool); ui->blastHitsTableWidget->m_smallFirstColumn = true; ui->blastQueriesTableWidget->m_smallFirstColumn = true; ui->blastQueriesTableWidget->m_smallSecondColumn = true; setFilterText(); //Load any previous parameters the user might have entered when previously using this dialog. ui->parametersLineEdit->setText(g_settings->blastSearchParameters); //If the dialog is given an autoQuery parameter, then it will //carry out the entire process on its own. if (autoQuery != "") { buildBlastDatabase(false); clearAllQueries(); loadBlastQueriesFromFastaFile(autoQuery); runBlastSearches(false); QMetaObject::invokeMethod(this, "close", Qt::QueuedConnection); return; } //Prepare the query and hits tables ui->blastHitsTableWidget->setHorizontalHeaderLabels(QStringList() << "" << "Query\nname" << "Node\nname" << "Percent\nidentity" << "Alignment\nlength" << "Query\ncover" << "Mis-\nmatches" << "Gap\nopens" << "Query\nstart" << "Query\nend" << "Node\nstart" << "Node\nend" <<"E-\nvalue" << "Bit\nscore"); QFont font = ui->blastQueriesTableWidget->horizontalHeader()->font(); font.setBold(true); ui->blastQueriesTableWidget->horizontalHeader()->setFont(font); ui->blastHitsTableWidget->horizontalHeader()->setFont(font); //If a BLAST database already exists, move to step 2. QFile databaseFile(g_blastSearch->m_tempDirectory + "all_nodes.fasta"); if (databaseFile.exists()) setUiStep(BLAST_DB_BUILT_BUT_NO_QUERIES); //If there isn't a BLAST database, clear the entire temporary directory //and move to step 1. else { g_blastSearch->emptyTempDirectory(); setUiStep(BLAST_DB_NOT_YET_BUILT); } //If queries already exist, display them and move to step 3. if (g_blastSearch->m_blastQueries.m_queries.size() > 0) { fillQueriesTable(); setUiStep(READY_FOR_BLAST_SEARCH); } //If results already exist, display them and move to step 4. if (g_blastSearch->m_allHits.size() > 0) { fillHitsTable(); setUiStep(BLAST_SEARCH_COMPLETE); } //Call this function to disable rows in either table that are for queries //the user has hidden. queryShownChanged(); setInfoTexts(); connect(ui->buildBlastDatabaseButton, SIGNAL(clicked()), this, SLOT(buildBlastDatabaseInThread())); connect(ui->loadQueriesFromFastaButton, SIGNAL(clicked()), this, SLOT(loadBlastQueriesFromFastaFileButtonClicked())); connect(ui->enterQueryManuallyButton, SIGNAL(clicked()), this, SLOT(enterQueryManually())); connect(ui->clearAllQueriesButton, SIGNAL(clicked()), this, SLOT(clearAllQueries())); connect(ui->clearSelectedQueriesButton, SIGNAL(clicked(bool)), this, SLOT(clearSelectedQueries())); connect(ui->runBlastSearchButton, SIGNAL(clicked()), this, SLOT(runBlastSearchesInThread())); connect(ui->blastQueriesTableWidget, SIGNAL(cellChanged(int,int)), this, SLOT(queryCellChanged(int,int))); connect(ui->blastQueriesTableWidget, SIGNAL(itemSelectionChanged()), this, SLOT(queryTableSelectionChanged())); connect(ui->blastFiltersButton, SIGNAL(clicked(bool)), this, SLOT(openFiltersDialog())); } BlastSearchDialog::~BlastSearchDialog() { delete ui; deleteQueryPathsDialog(); } void BlastSearchDialog::afterWindowShow() { ui->blastQueriesTableWidget->resizeColumns(); ui->blastHitsTableWidget->resizeColumns(); } void BlastSearchDialog::clearBlastHits() { g_blastSearch->clearBlastHits(); deleteQueryPathsDialog(); ui->blastHitsTableWidget->clearContents(); while (ui->blastHitsTableWidget->rowCount() > 0) ui->blastHitsTableWidget->removeRow(0); g_assemblyGraph->clearAllBlastHitPointers(); } void BlastSearchDialog::fillTablesAfterBlastSearch() { if (g_blastSearch->m_allHits.empty()) QMessageBox::information(this, "No hits", "No BLAST hits were found for the given queries and parameters."); fillQueriesTable(); fillHitsTable(); } void BlastSearchDialog::fillQueriesTable() { //Turn off table widget signals for this function so the //queryCellChanged slot doesn't get called. ui->blastQueriesTableWidget->blockSignals(true); ui->blastQueriesTableWidget->setSortingEnabled(false); ui->blastQueriesTableWidget->clearContents(); int queryCount = int(g_blastSearch->m_blastQueries.m_queries.size()); if (queryCount == 0) return; ui->blastQueriesTableWidget->setRowCount(queryCount); for (int i = 0; i < queryCount; ++i) makeQueryRow(i); ui->blastQueriesTableWidget->resizeColumns(); ui->blastQueriesTableWidget->setSortingEnabled(true); ui->blastQueriesTableWidget->setSortingEnabled(true); ui->blastQueriesTableWidget->blockSignals(false); } void BlastSearchDialog::makeQueryRow(int row) { if (row >= int(g_blastSearch->m_blastQueries.m_queries.size())) return; BlastQuery * query = g_blastSearch->m_blastQueries.m_queries[row]; TableWidgetItemName * name = new TableWidgetItemName(query); name->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable); QTableWidgetItem * type = new QTableWidgetItem(query->getTypeString()); type->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); int queryLength = query->getLength(); TableWidgetItemInt * length = new TableWidgetItemInt(formatIntForDisplay(queryLength), queryLength); length->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); //If the search hasn't yet been run, some of the columns will just have //a dash. TableWidgetItemInt * hits; TableWidgetItemDouble * percent; QTableWidgetItem * paths; QueryPathsPushButton * pathsButton = 0; if (query->wasSearchedFor()) { int hitCount = query->hitCount(); hits = new TableWidgetItemInt(formatIntForDisplay(hitCount), hitCount); percent = new TableWidgetItemDouble(formatDoubleForDisplay(100.0 * query->fractionCoveredByHits(), 2) + "%", query->fractionCoveredByHits()); //The path count isn't displayed in the TableWidgetItem because it will //be shown in a button which will bring up a separate dialog showing a //table of the paths. int pathCount = query->getPathCount(); paths = new TableWidgetItemInt("", pathCount); paths->setFlags(Qt::ItemIsEnabled); pathsButton = new QueryPathsPushButton(pathCount, query); connect(pathsButton, SIGNAL(showPathsDialog(BlastQuery*)), this, SLOT(showPathsDialog(BlastQuery*))); } else { hits = new TableWidgetItemInt("-", 0); percent = new TableWidgetItemDouble("-", 0.0); paths = new QTableWidgetItem("-"); } hits->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); percent->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); paths->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); QTableWidgetItem * colour = new QTableWidgetItem(query->getColour().name()); ColourButton * colourButton = new ColourButton(); colourButton->setColour(query->getColour()); connect(colourButton, SIGNAL(colourChosen(QColor)), query, SLOT(setColour(QColor))); connect(colourButton, SIGNAL(colourChosen(QColor)), this, SLOT(fillHitsTable())); QWidget * showCheckBoxWidget = new QWidget; QCheckBox * showCheckBox = new QCheckBox(); QHBoxLayout * layout = new QHBoxLayout(showCheckBoxWidget); layout->addWidget(showCheckBox); layout->setAlignment(Qt::AlignCenter); layout->setContentsMargins(0, 0, 0, 0); showCheckBoxWidget->setLayout(layout); bool queryShown = query->isShown(); showCheckBox->setChecked(queryShown); QTableWidgetItem * show = new TableWidgetItemShown(queryShown); show->setFlags(Qt::ItemIsEnabled); connect(showCheckBox, SIGNAL(toggled(bool)), query, SLOT(setShown(bool))); connect(showCheckBox, SIGNAL(toggled(bool)), this, SLOT(queryShownChanged())); ui->blastQueriesTableWidget->setCellWidget(row, 0, colourButton); ui->blastQueriesTableWidget->setCellWidget(row, 1, showCheckBoxWidget); ui->blastQueriesTableWidget->setItem(row, 0, colour); ui->blastQueriesTableWidget->setItem(row, 1, show); ui->blastQueriesTableWidget->setItem(row, 2, name); ui->blastQueriesTableWidget->setItem(row, 3, type); ui->blastQueriesTableWidget->setItem(row, 4, length); ui->blastQueriesTableWidget->setItem(row, 5, hits); ui->blastQueriesTableWidget->setItem(row, 6, percent); ui->blastQueriesTableWidget->setItem(row, 7, paths); if (pathsButton != 0) ui->blastQueriesTableWidget->setCellWidget(row, 7, pathsButton); } void BlastSearchDialog::fillHitsTable() { ui->blastHitsTableWidget->clearContents(); ui->blastHitsTableWidget->setSortingEnabled(false); int hitCount = g_blastSearch->m_allHits.size(); ui->blastHitsTableWidget->setRowCount(hitCount); if (hitCount == 0) return; for (int i = 0; i < hitCount; ++i) { BlastHit * hit = g_blastSearch->m_allHits[i].data(); BlastQuery * hitQuery = hit->m_query; QTableWidgetItem * queryColour = new QTableWidgetItem(hitQuery->getColour().name()); queryColour->setFlags(Qt::ItemIsEnabled); queryColour->setBackground(hitQuery->getColour()); QTableWidgetItem * queryName = new QTableWidgetItem(hitQuery->getName()); queryName->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); QTableWidgetItem * nodeName = new QTableWidgetItem(hit->m_node->getName()); nodeName->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); TableWidgetItemDouble * percentIdentity = new TableWidgetItemDouble(formatDoubleForDisplay(hit->m_percentIdentity, 2) + "%", hit->m_percentIdentity); percentIdentity->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); TableWidgetItemInt * alignmentLength = new TableWidgetItemInt(formatIntForDisplay(hit->m_alignmentLength), hit->m_alignmentLength); alignmentLength->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); double queryCoverPercent = 100.0 * hit->getQueryCoverageFraction(); TableWidgetItemDouble * queryCover = new TableWidgetItemDouble(formatDoubleForDisplay(queryCoverPercent, 2) + "%", queryCoverPercent); queryCover->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); TableWidgetItemInt * numberMismatches = new TableWidgetItemInt(formatIntForDisplay(hit->m_numberMismatches), hit->m_numberMismatches); numberMismatches->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); TableWidgetItemInt * numberGapOpens = new TableWidgetItemInt(formatIntForDisplay(hit->m_numberGapOpens), hit->m_numberGapOpens); numberGapOpens->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); TableWidgetItemInt * queryStart = new TableWidgetItemInt(formatIntForDisplay(hit->m_queryStart), hit->m_queryStart); queryStart->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); TableWidgetItemInt * queryEnd = new TableWidgetItemInt(formatIntForDisplay(hit->m_queryEnd), hit->m_queryEnd); queryEnd->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); TableWidgetItemInt * nodeStart = new TableWidgetItemInt(formatIntForDisplay(hit->m_nodeStart), hit->m_nodeStart); nodeStart->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); TableWidgetItemInt * nodeEnd = new TableWidgetItemInt(formatIntForDisplay(hit->m_nodeEnd), hit->m_nodeEnd); nodeEnd->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); TableWidgetItemDouble * eValue = new TableWidgetItemDouble(hit->m_eValue.asString(false), hit->m_eValue.toDouble()); eValue->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); TableWidgetItemDouble * bitScore = new TableWidgetItemDouble(QString::number(hit->m_bitScore), hit->m_bitScore); bitScore->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); ui->blastHitsTableWidget->setItem(i, 0, queryColour); ui->blastHitsTableWidget->setItem(i, 1, queryName); ui->blastHitsTableWidget->setItem(i, 2, nodeName); ui->blastHitsTableWidget->setItem(i, 3, percentIdentity); ui->blastHitsTableWidget->setItem(i, 4, alignmentLength); ui->blastHitsTableWidget->setItem(i, 5, queryCover); ui->blastHitsTableWidget->setItem(i, 6, numberMismatches); ui->blastHitsTableWidget->setItem(i, 7, numberGapOpens); ui->blastHitsTableWidget->setItem(i, 8, queryStart); ui->blastHitsTableWidget->setItem(i, 9, queryEnd); ui->blastHitsTableWidget->setItem(i, 10, nodeStart); ui->blastHitsTableWidget->setItem(i, 11, nodeEnd); ui->blastHitsTableWidget->setItem(i, 12, eValue); ui->blastHitsTableWidget->setItem(i, 13, bitScore); } ui->blastHitsTableWidget->resizeColumns(); ui->blastHitsTableWidget->setEnabled(true); ui->blastHitsTableWidget->setSortingEnabled(true); } void BlastSearchDialog::buildBlastDatabaseInThread() { buildBlastDatabase(true); } void BlastSearchDialog::buildBlastDatabase(bool separateThread) { setUiStep(BLAST_DB_BUILD_IN_PROGRESS); if (!g_blastSearch->findProgram("makeblastdb", &m_makeblastdbCommand)) { QMessageBox::warning(this, "Error", "The program makeblastdb was not found. Please install NCBI BLAST to use this feature."); setUiStep(BLAST_DB_NOT_YET_BUILT); return; } QApplication::processEvents(); MyProgressDialog * progress = new MyProgressDialog(this, "Building BLAST database...", separateThread, "Cancel build", "Cancelling build...", "Clicking this button will stop the BLAST database from being " "built."); progress->setWindowModality(Qt::WindowModal); progress->show(); if (separateThread) { m_buildBlastDatabaseThread = new QThread; BuildBlastDatabaseWorker * buildBlastDatabaseWorker = new BuildBlastDatabaseWorker(m_makeblastdbCommand); buildBlastDatabaseWorker->moveToThread(m_buildBlastDatabaseThread); connect(progress, SIGNAL(halt()), this, SLOT(buildBlastDatabaseCancelled())); connect(m_buildBlastDatabaseThread, SIGNAL(started()), buildBlastDatabaseWorker, SLOT(buildBlastDatabase())); connect(buildBlastDatabaseWorker, SIGNAL(finishedBuild(QString)), m_buildBlastDatabaseThread, SLOT(quit())); connect(buildBlastDatabaseWorker, SIGNAL(finishedBuild(QString)), buildBlastDatabaseWorker, SLOT(deleteLater())); connect(buildBlastDatabaseWorker, SIGNAL(finishedBuild(QString)), this, SLOT(blastDatabaseBuildFinished(QString))); connect(m_buildBlastDatabaseThread, SIGNAL(finished()), m_buildBlastDatabaseThread, SLOT(deleteLater())); connect(m_buildBlastDatabaseThread, SIGNAL(finished()), progress, SLOT(deleteLater())); m_buildBlastDatabaseThread->start(); } else { BuildBlastDatabaseWorker buildBlastDatabaseWorker(m_makeblastdbCommand); buildBlastDatabaseWorker.buildBlastDatabase(); progress->close(); delete progress; blastDatabaseBuildFinished(buildBlastDatabaseWorker.m_error); } } void BlastSearchDialog::blastDatabaseBuildFinished(QString error) { if (error != "") { QMessageBox::warning(this, "Error", error); setUiStep(BLAST_DB_NOT_YET_BUILT); } else setUiStep(BLAST_DB_BUILT_BUT_NO_QUERIES); } void BlastSearchDialog::buildBlastDatabaseCancelled() { g_blastSearch->m_cancelBuildBlastDatabase = true; if (g_blastSearch->m_makeblastdb != 0) g_blastSearch->m_makeblastdb->kill(); } void BlastSearchDialog::loadBlastQueriesFromFastaFileButtonClicked() { QStringList fullFileNames = QFileDialog::getOpenFileNames(this, "Load queries FASTA", g_memory->rememberedPath); if (fullFileNames.size() > 0) //User did not hit cancel { for (int i = 0; i < fullFileNames.size(); ++i) loadBlastQueriesFromFastaFile(fullFileNames.at(i)); } } void BlastSearchDialog::loadBlastQueriesFromFastaFile(QString fullFileName) { MyProgressDialog * progress = new MyProgressDialog(this, "Loading queries...", false); progress->setWindowModality(Qt::WindowModal); progress->show(); int queriesLoaded = g_blastSearch->loadBlastQueriesFromFastaFile(fullFileName); if (queriesLoaded > 0) { clearBlastHits(); fillQueriesTable(); g_memory->rememberedPath = QFileInfo(fullFileName).absolutePath(); setUiStep(READY_FOR_BLAST_SEARCH); } progress->close(); delete progress; if (queriesLoaded == 0) QMessageBox::information(this, "No queries loaded", "No queries could be loaded from the specified file."); } void BlastSearchDialog::enterQueryManually() { EnterOneBlastQueryDialog enterOneBlastQueryDialog(this); if (enterOneBlastQueryDialog.exec()) { QString queryName = g_blastSearch->cleanQueryName(enterOneBlastQueryDialog.getName()); g_blastSearch->m_blastQueries.addQuery(new BlastQuery(queryName, enterOneBlastQueryDialog.getSequence())); clearBlastHits(); fillQueriesTable(); setUiStep(READY_FOR_BLAST_SEARCH); } } void BlastSearchDialog::clearAllQueries() { g_blastSearch->m_blastQueries.clearAllQueries(); ui->blastQueriesTableWidget->clearContents(); ui->clearAllQueriesButton->setEnabled(false); while (ui->blastQueriesTableWidget->rowCount() > 0) ui->blastQueriesTableWidget->removeRow(0); clearBlastHits(); setUiStep(BLAST_DB_BUILT_BUT_NO_QUERIES); emit blastChanged(); } void BlastSearchDialog::clearSelectedQueries() { //Use the table selection to figure out which queries are to be removed. //The table cell containing the query name also has a pointer to the //actual query, and that's what we use. std::vector queriesToRemove; QItemSelectionModel * select = ui->blastQueriesTableWidget->selectionModel(); QModelIndexList selection = select->selectedIndexes(); QSet rowsWithSelectionSet; for (int i = 0; i < selection.size(); ++i) rowsWithSelectionSet.insert(selection[i].row()); for (QSet::const_iterator i = rowsWithSelectionSet.constBegin(); i != rowsWithSelectionSet.constEnd(); ++i) { int row = *i; QTableWidgetItem * tableWidgetItem = ui->blastQueriesTableWidget->item(row, 2); TableWidgetItemName * queryNameItem = dynamic_cast(tableWidgetItem); if (queryNameItem == 0) continue; BlastQuery * query = queryNameItem->getQuery(); queriesToRemove.push_back(query); } if (queriesToRemove.size() == g_blastSearch->m_blastQueries.m_queries.size()) { clearAllQueries(); return; } g_blastSearch->clearSomeQueries(queriesToRemove); fillQueriesTable(); fillHitsTable(); emit blastChanged(); } void BlastSearchDialog::runBlastSearchesInThread() { runBlastSearches(true); } void BlastSearchDialog::runBlastSearches(bool separateThread) { setUiStep(BLAST_SEARCH_IN_PROGRESS); if (!g_blastSearch->findProgram("blastn", &m_blastnCommand)) { QMessageBox::warning(this, "Error", "The program blastn was not found. Please install NCBI BLAST to use this feature."); setUiStep(READY_FOR_BLAST_SEARCH); return; } if (!g_blastSearch->findProgram("tblastn", &m_tblastnCommand)) { QMessageBox::warning(this, "Error", "The program tblastn was not found. Please install NCBI BLAST to use this feature."); setUiStep(READY_FOR_BLAST_SEARCH); return; } clearBlastHits(); MyProgressDialog * progress = new MyProgressDialog(this, "Running BLAST search...", separateThread, "Cancel search", "Cancelling search...", "Clicking this button will stop the BLAST search."); progress->setWindowModality(Qt::WindowModal); progress->show(); if (separateThread) { m_blastSearchThread = new QThread; RunBlastSearchWorker * runBlastSearchWorker = new RunBlastSearchWorker(m_blastnCommand, m_tblastnCommand, ui->parametersLineEdit->text().simplified()); runBlastSearchWorker->moveToThread(m_blastSearchThread); connect(progress, SIGNAL(halt()), this, SLOT(runBlastSearchCancelled())); connect(m_blastSearchThread, SIGNAL(started()), runBlastSearchWorker, SLOT(runBlastSearch())); connect(runBlastSearchWorker, SIGNAL(finishedSearch(QString)), m_blastSearchThread, SLOT(quit())); connect(runBlastSearchWorker, SIGNAL(finishedSearch(QString)), runBlastSearchWorker, SLOT(deleteLater())); connect(runBlastSearchWorker, SIGNAL(finishedSearch(QString)), this, SLOT(runBlastSearchFinished(QString))); connect(m_blastSearchThread, SIGNAL(finished()), m_blastSearchThread, SLOT(deleteLater())); connect(m_blastSearchThread, SIGNAL(finished()), progress, SLOT(deleteLater())); m_blastSearchThread->start(); } else { RunBlastSearchWorker runBlastSearchWorker(m_blastnCommand, m_tblastnCommand, ui->parametersLineEdit->text().simplified());; runBlastSearchWorker.runBlastSearch(); progress->close(); delete progress; runBlastSearchFinished(runBlastSearchWorker.m_error); } } void BlastSearchDialog::runBlastSearchFinished(QString error) { if (error != "") { QMessageBox::warning(this, "Error", error); setUiStep(READY_FOR_BLAST_SEARCH); } else { fillTablesAfterBlastSearch(); g_settings->blastSearchParameters = ui->parametersLineEdit->text().simplified(); setUiStep(BLAST_SEARCH_COMPLETE); } emit blastChanged(); } void BlastSearchDialog::runBlastSearchCancelled() { g_blastSearch->m_cancelRunBlastSearch = true; if (g_blastSearch->m_blast != 0) g_blastSearch->m_blast->kill(); } void BlastSearchDialog::queryCellChanged(int row, int column) { //Suspend signals for this function, as it is might change //the cell value again if the new name isn't unique. ui->blastQueriesTableWidget->blockSignals(true); //If a query name was changed, then we adjust that query name elsewhere. if (column == 2) { QString newName = ui->blastQueriesTableWidget->item(row, column)->text(); BlastQuery * query = g_blastSearch->m_blastQueries.m_queries[row]; if (newName != query->getName()) { QString uniqueName = g_blastSearch->m_blastQueries.renameQuery(query, newName); //It's possible that the user gave the query a non-unique name, in which //case we now have to adjust it. if (uniqueName != newName) ui->blastQueriesTableWidget->item(row, column)->setText(uniqueName); //Resize the query table columns, as the name new might take up more or less space. ui->blastQueriesTableWidget->resizeColumns(); //Rebuild the hits table, if necessary, to show the new name. if (query->hasHits()) fillHitsTable(); emit blastChanged(); } } ui->blastQueriesTableWidget->blockSignals(false); } void BlastSearchDialog::queryTableSelectionChanged() { //If there are any selected items, then the 'Clear selected' button //should be enabled. QItemSelectionModel * select = ui->blastQueriesTableWidget->selectionModel(); bool hasSelection = select->hasSelection(); ui->clearSelectedQueriesButton->setEnabled(hasSelection); } void BlastSearchDialog::setUiStep(BlastUiState blastUiState) { QPixmap tick(":/icons/tick-128.png"); tick.setDevicePixelRatio(devicePixelRatio()); //This is a workaround for a Qt bug. Can possibly remove in the future. https://bugreports.qt.io/browse/QTBUG-46846 switch (blastUiState) { case BLAST_DB_NOT_YET_BUILT: ui->step1Label->setEnabled(true); ui->buildBlastDatabaseButton->setEnabled(true); ui->step2Label->setEnabled(false); ui->loadQueriesFromFastaButton->setEnabled(false); ui->enterQueryManuallyButton->setEnabled(false); ui->blastQueriesTableWidget->setEnabled(false); ui->blastQueriesTableInfoText->setEnabled(false); ui->step3Label->setEnabled(false); ui->parametersLabel->setEnabled(false); ui->parametersLineEdit->setEnabled(false); ui->runBlastSearchButton->setEnabled(false); ui->clearAllQueriesButton->setEnabled(false); ui->clearSelectedQueriesButton->setEnabled(false); ui->hitsLabel->setEnabled(false); ui->step1TickLabel->setPixmap(QPixmap()); ui->step2TickLabel->setPixmap(QPixmap()); ui->step3TickLabel->setPixmap(QPixmap()); ui->buildBlastDatabaseInfoText->setEnabled(true); ui->loadQueriesFromFastaInfoText->setEnabled(false); ui->enterQueryManuallyInfoText->setEnabled(false); ui->clearAllQueriesInfoText->setEnabled(false); ui->clearSelectedQueriesInfoText->setEnabled(false); ui->blastHitsTableWidget->setEnabled(false); ui->blastSearchWidget->setEnabled(false); ui->blastHitsTableInfoText->setEnabled(false); break; case BLAST_DB_BUILD_IN_PROGRESS: ui->step1Label->setEnabled(true); ui->buildBlastDatabaseButton->setEnabled(false); ui->step2Label->setEnabled(false); ui->loadQueriesFromFastaButton->setEnabled(false); ui->enterQueryManuallyButton->setEnabled(false); ui->blastQueriesTableWidget->setEnabled(false); ui->blastQueriesTableInfoText->setEnabled(false); ui->step3Label->setEnabled(false); ui->parametersLabel->setEnabled(false); ui->parametersLineEdit->setEnabled(false); ui->runBlastSearchButton->setEnabled(false); ui->clearAllQueriesButton->setEnabled(false); ui->clearSelectedQueriesButton->setEnabled(false); ui->hitsLabel->setEnabled(false); ui->step1TickLabel->setPixmap(QPixmap()); ui->step2TickLabel->setPixmap(QPixmap()); ui->step3TickLabel->setPixmap(QPixmap()); ui->buildBlastDatabaseInfoText->setEnabled(false); ui->loadQueriesFromFastaInfoText->setEnabled(false); ui->enterQueryManuallyInfoText->setEnabled(false); ui->clearAllQueriesInfoText->setEnabled(false); ui->clearSelectedQueriesInfoText->setEnabled(false); ui->blastHitsTableWidget->setEnabled(false); ui->blastSearchWidget->setEnabled(false); ui->blastHitsTableInfoText->setEnabled(false); break; case BLAST_DB_BUILT_BUT_NO_QUERIES: ui->step1Label->setEnabled(true); ui->buildBlastDatabaseButton->setEnabled(false); ui->step2Label->setEnabled(true); ui->loadQueriesFromFastaButton->setEnabled(true); ui->enterQueryManuallyButton->setEnabled(true); ui->blastQueriesTableWidget->setEnabled(true); ui->blastQueriesTableInfoText->setEnabled(true); ui->step3Label->setEnabled(false); ui->parametersLabel->setEnabled(false); ui->parametersLineEdit->setEnabled(false); ui->runBlastSearchButton->setEnabled(false); ui->clearAllQueriesButton->setEnabled(false); ui->clearAllQueriesButton->setEnabled(false); ui->hitsLabel->setEnabled(false); ui->step1TickLabel->setPixmap(tick); ui->step2TickLabel->setPixmap(QPixmap()); ui->step3TickLabel->setPixmap(QPixmap()); ui->buildBlastDatabaseInfoText->setEnabled(true); ui->loadQueriesFromFastaInfoText->setEnabled(true); ui->enterQueryManuallyInfoText->setEnabled(true); ui->clearSelectedQueriesInfoText->setEnabled(false); ui->clearSelectedQueriesInfoText->setEnabled(false); ui->blastHitsTableWidget->setEnabled(false); ui->blastSearchWidget->setEnabled(false); ui->blastHitsTableInfoText->setEnabled(false); break; case READY_FOR_BLAST_SEARCH: ui->step1Label->setEnabled(true); ui->buildBlastDatabaseButton->setEnabled(false); ui->step2Label->setEnabled(true); ui->loadQueriesFromFastaButton->setEnabled(true); ui->enterQueryManuallyButton->setEnabled(true); ui->blastQueriesTableWidget->setEnabled(true); ui->blastQueriesTableInfoText->setEnabled(true); ui->step3Label->setEnabled(true); ui->parametersLabel->setEnabled(true); ui->parametersLineEdit->setEnabled(true); ui->runBlastSearchButton->setEnabled(true); ui->clearAllQueriesButton->setEnabled(true); queryTableSelectionChanged(); ui->hitsLabel->setEnabled(false); ui->step1TickLabel->setPixmap(tick); ui->step2TickLabel->setPixmap(tick); ui->step3TickLabel->setPixmap(QPixmap()); ui->buildBlastDatabaseInfoText->setEnabled(true); ui->loadQueriesFromFastaInfoText->setEnabled(true); ui->enterQueryManuallyInfoText->setEnabled(true); ui->clearAllQueriesInfoText->setEnabled(true); ui->clearSelectedQueriesInfoText->setEnabled(true); ui->blastHitsTableWidget->setEnabled(false); ui->blastSearchWidget->setEnabled(true); ui->blastHitsTableInfoText->setEnabled(false); break; case BLAST_SEARCH_IN_PROGRESS: ui->step1Label->setEnabled(true); ui->buildBlastDatabaseButton->setEnabled(false); ui->step2Label->setEnabled(true); ui->loadQueriesFromFastaButton->setEnabled(true); ui->enterQueryManuallyButton->setEnabled(true); ui->blastQueriesTableWidget->setEnabled(true); ui->blastQueriesTableInfoText->setEnabled(true); ui->step3Label->setEnabled(true); ui->parametersLabel->setEnabled(true); ui->parametersLineEdit->setEnabled(true); ui->runBlastSearchButton->setEnabled(false); ui->clearAllQueriesButton->setEnabled(true); queryTableSelectionChanged(); ui->hitsLabel->setEnabled(false); ui->step1TickLabel->setPixmap(tick); ui->step2TickLabel->setPixmap(tick); ui->step3TickLabel->setPixmap(QPixmap()); ui->buildBlastDatabaseInfoText->setEnabled(true); ui->loadQueriesFromFastaInfoText->setEnabled(true); ui->enterQueryManuallyInfoText->setEnabled(true); ui->clearAllQueriesInfoText->setEnabled(true); ui->clearSelectedQueriesInfoText->setEnabled(true); ui->blastHitsTableWidget->setEnabled(false); ui->blastSearchWidget->setEnabled(true); ui->blastHitsTableInfoText->setEnabled(false); break; case BLAST_SEARCH_COMPLETE: ui->step1Label->setEnabled(true); ui->buildBlastDatabaseButton->setEnabled(false); ui->step2Label->setEnabled(true); ui->loadQueriesFromFastaButton->setEnabled(true); ui->enterQueryManuallyButton->setEnabled(true); ui->blastQueriesTableWidget->setEnabled(true); ui->blastQueriesTableInfoText->setEnabled(true); ui->step3Label->setEnabled(true); ui->parametersLabel->setEnabled(true); ui->parametersLineEdit->setEnabled(true); ui->runBlastSearchButton->setEnabled(true); ui->clearAllQueriesButton->setEnabled(true); queryTableSelectionChanged(); ui->hitsLabel->setEnabled(true); ui->step1TickLabel->setPixmap(tick); ui->step2TickLabel->setPixmap(tick); ui->step3TickLabel->setPixmap(tick); ui->buildBlastDatabaseInfoText->setEnabled(true); ui->loadQueriesFromFastaInfoText->setEnabled(true); ui->enterQueryManuallyInfoText->setEnabled(true); ui->clearAllQueriesInfoText->setEnabled(true); ui->clearSelectedQueriesInfoText->setEnabled(true); ui->blastHitsTableWidget->setEnabled(true); ui->blastSearchWidget->setEnabled(true); ui->blastHitsTableInfoText->setEnabled(true); break; } } void BlastSearchDialog::setInfoTexts() { QString settingsDialogTitle = "settings"; #ifdef Q_OS_MAC settingsDialogTitle = "preferences"; #endif ui->buildBlastDatabaseInfoText->setInfoText("This step runs makeblastdb on the contig sequences, " "preparing them for a BLAST search.

" "The database files generated are temporary and will " "be deleted when Bandage is closed."); ui->loadQueriesFromFastaInfoText->setInfoText("Click this button to load a FASTA file. Each " "sequence in the FASTA file will be a separate " "query."); ui->enterQueryManuallyInfoText->setInfoText("Click this button to type or paste a single query sequence."); ui->parametersInfoText->setInfoText("You may add additional blastn/tblastn parameters here, exactly as they " "would be typed at the command line."); ui->startBlastSearchInfoText->setInfoText("Click this to conduct search for the above " "queries on the graph nodes.

" "If no parameters were added above, this will run:
" "blastn -query queries.fasta -db all_nodes.fasta -outfmt 6

" "If, for example, '-evalue 0.01' was entered in the above " "parameters field, then this will run:
" "blastn -query queries.fasta -db all_nodes.fasta -outfmt 6 -evalue 0.01

" "For protein queries, tblastn will be used instead of blastn."); ui->clearSelectedQueriesInfoText->setInfoText("Click this button to remove any selected queries in the below list."); ui->clearAllQueriesInfoText->setInfoText("Click this button to remove all queries in the below list."); ui->blastQueriesTableInfoText->setInfoText("The BLAST queries are displayed in this table. Before a BLAST search " "is run, some information about the queries is not yet available and " "will show a dash.

" "Colour: Each query is automatically assigned a colour which is used for the " "'Blast hits (solid)' graph colour scheme. This colour can be changed by " "clicking in the table cell.

" "Show: if this box is ticked, the query's hits will be able to be viewed " "on the graph. If this box is not ticked, the query's hits will be hidden " "on the graph.

" "Query name: If a query is loaded from a FASTA file, its name is the sequence " "ID (the text between the '>' and the first space in the description line). " "Query names are editable by double clicking in their table cell.

" "Type: This is either 'nucl' for nucleotide sequences or 'prot' for protein " "sequences. Nucleotide sequences will be searched for using blastn, while " "protein sequences will be search for with tblastn. Both types can be used " "simultaneously.

" "Length: This is the length of the query, in bases (for nucleotide queries) " "or amino acids (for protein queries).

" "Hits: This is the number of BLAST hits acquired for the query.

" "Query cover: This is the total fraction of the query captured by all of " "the BLAST hits. However, the hits may not be proximal to each other. For " "example, if the first half a query was found in one part of the graph and " "the second half in a different part, this value would be 100%. This value " "is equivalent to the 'qcovs' output option in BLAST.

" "Paths: These are the number of possible paths through the graph which " "represent the entire query. If there is at least one path, you can click on " "this button to view a table of the paths' properties."); ui->blastHitsTableInfoText->setInfoText("The BLAST hits are displayed in this table after a BLAST search is run.

" "Query name: This is the name of the BLAST query for the hit.

" "Node name: This is the name of the graph node that the query matched.

" "Percent identity: This is the sequence similarity over the length of the hit. " "100% identity means that the query and node are identical over the length of the hit.

" "Alignment length: This is the length of the alignment as measured against the " "node. It is equal to node end minus node start plus one.

" "Query cover: This is the fraction of the query covered by the hit. It is " "equivalent to the 'qcovhsp' output option in BLAST.

" "Mismatches: This is the number of locations (bases for nucleotide sequence, " "amino acids for protein sequences) where the query and node sequences differ.

" "Gap opens: This is the number of gaps in the alignment due to either insertions " "or deletions.

" "Query start: This is the position where the alignment starts relative to the query.

" "Query end: This is the position where the alignment ends relative to the query.

" "Node start: This is the position where the alignment starts relative to the node sequence.

" "Node end: This is the position where the alignment ends relative to the node sequence.

" "E-value: This is the BLAST-calculated expect value.

" "Bit score: This is the BLAST-calculated bit score."); ui->blastHitFiltersInfoText->setInfoText("Click the 'Set filters' button to open a dialog where you can set one or more BLAST hit " "filters.

" "These let you exclude BLAST hits using a threshold for alignment length, query coverage, " "identity, e-value or bit score."); } //This function is called whenever a user changed the 'Show' tick box for a //query. It does three things: // 1) Updates the 'shown' status of the TableWidgetItem so the table can be // sorted by that column. // 2) Colours the QTableWidgetItems in the query table to match the query's // 'shown' status. // 3) Colours the QTableWidgetItems in the hits table to match the hit's query's // 'shown' status. void BlastSearchDialog::queryShownChanged() { ui->blastQueriesTableWidget->blockSignals(true); QTableWidgetItem tempItem; QColor shownColour = tempItem.foreground().color(); QColor hiddenColour = QColor(150, 150, 150); for (int i = 0; i < ui->blastQueriesTableWidget->rowCount(); ++i) { QString queryName = ui->blastQueriesTableWidget->item(i, 2)->text(); BlastQuery * query = g_blastSearch->m_blastQueries.getQueryFromName(queryName); if (query == 0) continue; QTableWidgetItem * item = ui->blastQueriesTableWidget->item(i, 1); TableWidgetItemShown * shownItem = dynamic_cast(item); if (shownItem == 0) continue; shownItem->m_shown = query->isShown(); QColor colour = shownColour; if (query->isHidden()) colour = hiddenColour; ui->blastQueriesTableWidget->item(i, 2)->setForeground(colour); ui->blastQueriesTableWidget->item(i, 3)->setForeground(colour); ui->blastQueriesTableWidget->item(i, 4)->setForeground(colour); ui->blastQueriesTableWidget->item(i, 5)->setForeground(colour); ui->blastQueriesTableWidget->item(i, 6)->setForeground(colour); ui->blastQueriesTableWidget->item(i, 7)->setForeground(colour); } for (int i = 0; i < ui->blastHitsTableWidget->rowCount(); ++i) { QString queryName = ui->blastHitsTableWidget->item(i, 1)->text(); BlastQuery * query = g_blastSearch->m_blastQueries.getQueryFromName(queryName); if (query == 0) continue; QColor colour = shownColour; if (query->isHidden()) colour = hiddenColour; ui->blastHitsTableWidget->item(i, 1)->setForeground(colour); ui->blastHitsTableWidget->item(i, 2)->setForeground(colour); ui->blastHitsTableWidget->item(i, 3)->setForeground(colour); ui->blastHitsTableWidget->item(i, 4)->setForeground(colour); ui->blastHitsTableWidget->item(i, 5)->setForeground(colour); ui->blastHitsTableWidget->item(i, 6)->setForeground(colour); ui->blastHitsTableWidget->item(i, 7)->setForeground(colour); ui->blastHitsTableWidget->item(i, 8)->setForeground(colour); ui->blastHitsTableWidget->item(i, 9)->setForeground(colour); ui->blastHitsTableWidget->item(i, 10)->setForeground(colour); ui->blastHitsTableWidget->item(i, 11)->setForeground(colour); ui->blastHitsTableWidget->item(i, 12)->setForeground(colour); } ui->blastQueriesTableWidget->blockSignals(false); emit blastChanged(); } void BlastSearchDialog::showPathsDialog(BlastQuery * query) { deleteQueryPathsDialog(); m_queryPathsDialog = new QueryPathsDialog(this, query); connect(m_queryPathsDialog, SIGNAL(selectionChanged()), this, SLOT(queryPathSelectionChangedSlot())); m_queryPathsDialog->show(); } void BlastSearchDialog::deleteQueryPathsDialog() { if (m_queryPathsDialog != 0) delete m_queryPathsDialog; m_queryPathsDialog = 0; } void BlastSearchDialog::queryPathSelectionChangedSlot() { emit queryPathSelectionChanged(); } void BlastSearchDialog::openFiltersDialog() { BlastHitFiltersDialog filtersDialog(this); filtersDialog.setWidgetsFromSettings(); if (filtersDialog.exec()) //The user clicked OK { filtersDialog.setSettingsFromWidgets(); setFilterText(); } } void BlastSearchDialog::setFilterText() { ui->blastHitFiltersLabel->setText("Current filters: " + BlastHitFiltersDialog::getFilterText()); }