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 "ConvertToSQLiteDialog.h"
23 
24 #include <QDesktopWidget>
25 #include <QMessageBox>
26 #include <QTextEdit>
27 
28 #include <U2Core/AppContext.h>
29 #include <U2Core/BaseDocumentFormats.h>
30 #include <U2Core/DocumentUtils.h>
31 #include <U2Core/FileAndDirectoryUtils.h>
32 #include <U2Core/FormatUtils.h>
33 #include <U2Core/GUrlUtils.h>
34 #include <U2Core/ProjectModel.h>
35 #include <U2Core/QObjectScopedPointer.h>
36 #include <U2Core/Task.h>
37 #include <U2Core/Theme.h>
38 #include <U2Core/U2SafePoints.h>
39 
40 #include <U2Gui/DialogUtils.h>
41 #include <U2Gui/HelpButton.h>
42 #include <U2Gui/ObjectViewModel.h>
43 #include <U2Gui/SaveDocumentController.h>
44 #include <U2Gui/U2FileDialog.h>
45 
46 #include "BAMDbiPlugin.h"
47 #include "BaiReader.h"
48 #include "LoadBamInfoTask.h"
49 
50 namespace U2 {
51 namespace BAM {
52 
53 static const QString DIR_HELPER_DOMAIN("ConvertToSQLiteDialog");
54 
ConvertToSQLiteDialog(const GUrl & _sourceUrl,BAMInfo & _bamInfo,bool sam)55 ConvertToSQLiteDialog::ConvertToSQLiteDialog(const GUrl &_sourceUrl, BAMInfo &_bamInfo, bool sam)
56     : QDialog(QApplication::activeWindow()),
57       saveController(nullptr),
58       sourceUrl(_sourceUrl),
59       bamInfo(_bamInfo) {
60     ui.setupUi(this);
61     new HelpButton(this, ui.buttonBox, "65929794");
62     ui.buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Import"));
63     ui.buttonBox->button(QDialogButtonBox::Cancel)->setText(tr("Cancel"));
64 
65     if (sam) {
66         setWindowTitle(tr("Import SAM File"));
67     } else {
68         setWindowTitle(tr("Import BAM File"));
69     }
70     this->setObjectName("Import BAM File");
71 
72     const QString warningMessageStyleSheet("color: " + Theme::successColorLabelStr() + "; font: bold;");
73     ui.indexNotAvailableLabel->setStyleSheet(warningMessageStyleSheet);
74     ui.referenceWarningLabel->setStyleSheet(warningMessageStyleSheet);
75 
76     initSaveController();
77 
78     connect(ui.bamInfoButton, SIGNAL(clicked()), SLOT(sl_bamInfoButtonClicked()));
79     connect(ui.refUrlButton, SIGNAL(clicked()), SLOT(sl_refUrlButtonClicked()));
80     connect(ui.selectAllToolButton, SIGNAL(clicked()), SLOT(sl_selectAll()));
81     connect(ui.selectNoneToolButton, SIGNAL(clicked()), SLOT(sl_unselectAll()));
82     connect(ui.inverseSelectionToolButton, SIGNAL(clicked()), SLOT(sl_inverseSelection()));
83     ui.indexNotAvailableLabel->setVisible(sam ? false : !bamInfo.hasIndex());
84 
85     if (sam && bamInfo.getHeader().getReferences().isEmpty()) {
86         hideReferencesTable();
87     } else {
88         hideReferenceUrl();
89         hideReferenceMessage();
90         ui.tableWidget->setColumnCount(3);
91         ui.tableWidget->setRowCount(bamInfo.getHeader().getReferences().count());
92         QStringList header;
93         header << BAMDbiPlugin::tr("Assembly name") << BAMDbiPlugin::tr("Length") << BAMDbiPlugin::tr("URI");
94         ui.tableWidget->setHorizontalHeaderLabels(header);
95         ui.tableWidget->horizontalHeader()->setStretchLastSection(true);
96         {
97             int i = 0;
98             foreach (const Header::Reference &ref, bamInfo.getHeader().getReferences()) {
99                 QTableWidgetItem *checkbox = new QTableWidgetItem();
100                 checkbox->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled);
101                 checkbox->setText(ref.getName());
102                 ui.tableWidget->setItem(i, 0, checkbox);
103                 QTableWidgetItem *item = new QTableWidgetItem(FormatUtils::formatNumberWithSeparators(ref.getLength()));
104                 item->setFlags(Qt::ItemIsEnabled);
105                 ui.tableWidget->setItem(i, 1, item);
106                 ui.tableWidget->setCellWidget(i, 2, new QLabel("<a href=\"" + ref.getUri() + "\">" + ref.getUri() + "</a>"));
107                 checkbox->setCheckState(Qt::Checked);
108                 i++;
109             }
110         }
111         ui.tableWidget->verticalHeader()->setDefaultSectionSize(QFontMetrics(QFont()).height() + 5);
112     }
113     QPushButton *okButton = ui.buttonBox->button(QDialogButtonBox::Ok);
114     ui.importUnmappedBox->setCheckState(bamInfo.isUnmappedSelected() ? Qt::Checked : Qt::Unchecked);
115     ui.sourceUrlView->setText(QDir::cleanPath(sourceUrl.getURLString()));
116     okButton->setFocus();
117     connect(ui.tableWidget, SIGNAL(itemChanged(QTableWidgetItem *)), SLOT(sl_assemblyCheckChanged(QTableWidgetItem *)));
118 
119     setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
120     adjustSize();
121     if (ui.tableWidget->isHidden()) {
122         setFixedHeight(height());
123     }
124     setMinimumWidth(600);
125 }
126 
hideReferenceUrl()127 void ConvertToSQLiteDialog::hideReferenceUrl() {
128     ui.refUrlLabel->hide();
129     ui.refUrlEdit->hide();
130     ui.refUrlButton->hide();
131 }
132 
hideReferencesTable()133 void ConvertToSQLiteDialog::hideReferencesTable() {
134     ui.selectionButtons->hide();
135     ui.tableWidget->hide();
136 }
137 
hideReferenceMessage()138 void ConvertToSQLiteDialog::hideReferenceMessage() {
139     ui.referenceWarningLabel->setVisible(false);
140 }
141 
sl_selectAll()142 void ConvertToSQLiteDialog::sl_selectAll() {
143     for (int i = 0; i < bamInfo.getSelected().count(); i++) {
144         ui.tableWidget->item(i, 0)->setCheckState(Qt::Checked);
145     }
146 }
147 
sl_unselectAll()148 void ConvertToSQLiteDialog::sl_unselectAll() {
149     for (int i = 0; i < bamInfo.getSelected().count(); i++) {
150         ui.tableWidget->item(i, 0)->setCheckState(Qt::Unchecked);
151     }
152 }
sl_inverseSelection()153 void ConvertToSQLiteDialog::sl_inverseSelection() {
154     for (int i = 0; i < bamInfo.getSelected().count(); i++) {
155         QTableWidgetItem *item = ui.tableWidget->item(i, 0);
156         item->setCheckState(item->checkState() == Qt::Checked ? Qt::Unchecked : Qt::Checked);
157     }
158 }
159 
sl_bamInfoButtonClicked()160 void ConvertToSQLiteDialog::sl_bamInfoButtonClicked() {
161     const Header &header = bamInfo.getHeader();
162     QObjectScopedPointer<QDialog> dialog = new QDialog(this);
163     dialog->setWindowTitle(BAMDbiPlugin::tr("%1 file info").arg(sourceUrl.getURLString()));
164     dialog->setLayout(new QVBoxLayout());
165 
166     {
167         QTableWidget *table = new QTableWidget();
168         table->setColumnCount(2);
169         table->setHorizontalHeaderLabels(QStringList() << BAMDbiPlugin::tr("Property name") << BAMDbiPlugin::tr("Value"));
170         table->horizontalHeader()->setStretchLastSection(true);
171         table->verticalHeader()->setVisible(false);
172 
173         QList<QPair<QString, QString>> list;
174         QString sort;
175         switch (header.getSortingOrder()) {
176             case Header::Unsorted:
177                 sort = BAMDbiPlugin::tr("Unsorted");
178                 break;
179             case Header::Unknown:
180                 sort = BAMDbiPlugin::tr("Unknown");
181                 break;
182             case Header::Coordinate:
183                 sort = BAMDbiPlugin::tr("Coordinate");
184                 break;
185             case Header::QueryName:
186                 sort = BAMDbiPlugin::tr("Query name");
187                 break;
188         }
189 
190         list << QPair<QString, QString>(BAMDbiPlugin::tr("URL"), sourceUrl.getURLString())
191              << QPair<QString, QString>(BAMDbiPlugin::tr("Format version"), header.getFormatVersion().text)
192              << QPair<QString, QString>(BAMDbiPlugin::tr("Sorting order"), sort);
193 
194         table->setRowCount(list.count());
195         {
196             for (int i = 0; i < list.count(); i++) {
197                 const QPair<QString, QString> &pair = list.at(i);
198                 QTableWidgetItem *item = new QTableWidgetItem(pair.first);
199                 item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
200                 table->setItem(i, 0, item);
201                 item = new QTableWidgetItem(pair.second);
202                 item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
203                 table->setItem(i, 1, item);
204             }
205         }
206         dialog->layout()->addWidget(table);
207     }
208 
209     {
210         QTableWidget *table = new QTableWidget();
211         table->setColumnCount(9);
212         table->setHorizontalHeaderLabels(QStringList() << BAMDbiPlugin::tr("Sequencing center") << BAMDbiPlugin::tr("Description") << BAMDbiPlugin::tr("Date")
213                                                        << BAMDbiPlugin::tr("Library") << BAMDbiPlugin::tr("Programs") << BAMDbiPlugin::tr("Predicted median insert size") << BAMDbiPlugin::tr("Platform/technology")
214                                                        << BAMDbiPlugin::tr("Platform unit") << BAMDbiPlugin::tr("Sample"));
215         table->horizontalHeader()->setStretchLastSection(true);
216 
217         int i = 0;
218         foreach (const Header::ReadGroup &rg, header.getReadGroups()) {
219             QStringList rgList;
220             rgList << QString(rg.getSequencingCenter()) << QString(rg.getDescription()) << QString(rg.getDate().toString()) << QString(rg.getLibrary())
221                    << QString(rg.getPlatform()) << QString(rg.getPredictedInsertSize()) << QString(rg.getPlatform()) << QString(rg.getPlatformUnit()) << QString(rg.getSample());
222             int j = 0;
223             foreach (const QString &s, rgList) {
224                 QTableWidgetItem *item = new QTableWidgetItem(s);
225                 item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
226                 table->setItem(i, j, item);
227                 j++;
228             }
229             i++;
230         }
231         dialog->layout()->addWidget(new QLabel(BAMDbiPlugin::tr("Read groups:")));
232         dialog->layout()->addWidget(table);
233     }
234 
235     {
236         QTableWidget *table = new QTableWidget();
237         table->setColumnCount(4);
238         table->setHorizontalHeaderLabels(QStringList() << BAMDbiPlugin::tr("Name") << BAMDbiPlugin::tr("Version") << BAMDbiPlugin::tr("Command") << BAMDbiPlugin::tr("Previous ID"));
239         table->horizontalHeader()->setStretchLastSection(true);
240 
241         int i = 0;
242         foreach (const Header::Program &pg, header.getPrograms()) {
243             QStringList pgList;
244             pgList << QString(pg.getName()) << QString(pg.getVersion()) << QString(pg.getCommandLine()) << QString(pg.getPreviousId());
245             int j = 0;
246             foreach (const QString &s, pgList) {
247                 QTableWidgetItem *item = new QTableWidgetItem(s);
248                 item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
249                 table->setItem(i, j, item);
250                 j++;
251             }
252             i++;
253         }
254         dialog->layout()->addWidget(new QLabel(BAMDbiPlugin::tr("Programs:")));
255         dialog->layout()->addWidget(table);
256     }
257     dialog->resize(qMin(600, QApplication::desktop()->screenGeometry().width()), dialog->sizeHint().height());
258     dialog->exec();
259 }
260 
sl_refUrlButtonClicked()261 void ConvertToSQLiteDialog::sl_refUrlButtonClicked() {
262     GUrl currentUrl = ui.refUrlEdit->text();
263     if (ui.refUrlEdit->text().isEmpty()) {
264         currentUrl = sourceUrl;
265     }
266     QString dir = currentUrl.dirPath() + "/" + currentUrl.baseFileName();
267     QString value;
268 #ifdef Q_OS_DARWIN
269     if (qgetenv(ENV_GUI_TEST).toInt() == 1 && qgetenv(ENV_USE_NATIVE_DIALOGS).toInt() == 0) {
270         value = U2FileDialog::getOpenFileName(this, QObject::tr("Reference File"), dir, "", 0, QFileDialog::DontUseNativeDialog);
271     } else
272 #endif
273         value = U2FileDialog::getOpenFileName(this, QObject::tr("Reference File"), dir);
274     if (!value.isEmpty()) {
275         ui.refUrlEdit->setText(value);
276         hideReferenceMessage();
277     }
278 }
279 
sl_assemblyCheckChanged(QTableWidgetItem * item)280 void ConvertToSQLiteDialog::sl_assemblyCheckChanged(QTableWidgetItem *item) {
281     bamInfo.getSelected()[item->row()] = (item->checkState() == Qt::Checked);
282 }
283 
getDestinationUrl() const284 const GUrl &ConvertToSQLiteDialog::getDestinationUrl() const {
285     return destinationUrl;
286 }
287 
getReferenceUrl() const288 QString ConvertToSQLiteDialog::getReferenceUrl() const {
289     return ui.refUrlEdit->text();
290 }
291 
addToProject() const292 bool ConvertToSQLiteDialog::addToProject() const {
293     return ui.addToProjectBox->isChecked();
294 }
295 
hideAddToProjectOption()296 void ConvertToSQLiteDialog::hideAddToProjectOption() {
297     ui.addToProjectBox->hide();
298 }
299 
referenceFromFile()300 bool ConvertToSQLiteDialog::referenceFromFile() {
301     return ui.refUrlEdit->isVisible();
302 }
303 
checkReferencesState()304 bool ConvertToSQLiteDialog::checkReferencesState() {
305     if (referenceFromFile()) {
306         return true;
307     } else {
308         bool selected = false;
309         foreach (const bool &i, bamInfo.getSelected()) {
310             if (i) {
311                 selected = true;
312                 break;
313             }
314         }
315         if (!selected && !bamInfo.isUnmappedSelected()) {
316             QMessageBox::critical(this, windowTitle(), BAMDbiPlugin::tr("Please select assemblies to be imported"));
317             return false;
318         }
319     }
320     return true;
321 }
322 
initSaveController()323 void ConvertToSQLiteDialog::initSaveController() {
324     SaveDocumentControllerConfig config;
325     config.defaultDomain = DIR_HELPER_DOMAIN;
326     config.defaultFileName = sourceUrl.dirPath() + "/" + QFileInfo(sourceUrl.fileName()).completeBaseName() + ".ugenedb";
327     config.defaultFormatId = BaseDocumentFormats::UGENEDB;
328     config.fileDialogButton = ui.destinationUrlButton;
329     config.fileNameEdit = ui.destinationUrlEdit;
330     config.parentWidget = this;
331     config.saveTitle = BAMDbiPlugin::tr("Destination UGENEDB File");
332 
333     const QList<DocumentFormatId> formats = QList<DocumentFormatId>() << BaseDocumentFormats::UGENEDB;
334 
335     saveController = new SaveDocumentController(config, formats, this);
336 }
337 
338 namespace {
checkWritePermissions(const QString & fileUrl)339 bool checkWritePermissions(const QString &fileUrl) {
340     QDir dir = QFileInfo(fileUrl).dir();
341     if (!dir.exists()) {
342         bool created = dir.mkpath(dir.absolutePath());
343         CHECK(created, false);
344     }
345     return FileAndDirectoryUtils::isDirectoryWritable(dir.absolutePath());
346 }
347 }  // namespace
348 
accept()349 void ConvertToSQLiteDialog::accept() {
350     destinationUrl = GUrl(saveController->getSaveFileName());
351     bamInfo.setUnmappedSelected(ui.importUnmappedBox->checkState() == Qt::Checked);
352     if (destinationUrl.isEmpty()) {
353         ui.destinationUrlEdit->setFocus(Qt::OtherFocusReason);
354         QMessageBox::critical(this, windowTitle(), BAMDbiPlugin::tr("Destination URL is not specified"));
355     } else if (!destinationUrl.isLocalFile()) {
356         ui.destinationUrlEdit->setFocus(Qt::OtherFocusReason);
357         QMessageBox::critical(this, windowTitle(), BAMDbiPlugin::tr("Destination URL must point to a local file"));
358     } else if (!checkWritePermissions(destinationUrl.getURLString())) {
359         ui.destinationUrlEdit->setFocus(Qt::OtherFocusReason);
360         QMessageBox::critical(this, windowTitle(), BAMDbiPlugin::tr("Destination URL folder has not write permissions"));
361     } else {
362         if (!checkReferencesState()) {
363             return;
364         }
365 
366         Project *prj = AppContext::getProject();
367         if (prj != nullptr) {
368             Document *destDoc = prj->findDocumentByURL(destinationUrl);
369             if (destDoc != nullptr && destDoc->isLoaded() && !GObjectViewUtils::findViewsWithAnyOfObjects(destDoc->getObjects()).isEmpty()) {
370                 QMessageBox::critical(this, windowTitle(), BAMDbiPlugin::tr("There is opened view with destination file.\n"
371                                                                             "Close it or choose different file"));
372                 ui.destinationUrlEdit->setFocus(Qt::OtherFocusReason);
373                 return;
374             }
375         }
376         QFileInfo destinationDir(QFileInfo(destinationUrl.getURLString()).path());
377         if (!destinationDir.isWritable()) {
378             ui.destinationUrlEdit->setFocus(Qt::OtherFocusReason);
379             QMessageBox::critical(this, windowTitle(), BAMDbiPlugin::tr("Destination folder '%1' is not writable, please choose different destination URL").arg(destinationDir.absoluteFilePath()));
380             return;
381         }
382 
383         if (QFile::exists(destinationUrl.getURLString())) {
384             int result = QMessageBox::question(this, windowTitle(), BAMDbiPlugin::tr("Destination file already exists.\n"
385                                                                                      "To overwrite the file, press 'Replace'.\n"
386                                                                                      "To append data to existing file press 'Append'."),
387                                                BAMDbiPlugin::tr("Replace"),
388                                                BAMDbiPlugin::tr("Append"),
389                                                BAMDbiPlugin::tr("Cancel"),
390                                                2);
391             switch (result) {
392                 case 0: {
393                     bool ok = QFile::remove(destinationUrl.getURLString());
394                     if (!ok) {
395                         QMessageBox::critical(this, windowTitle(), BAMDbiPlugin::tr("Destination file '%1' cannot be removed").arg(destinationUrl.getURLString()));
396                         return;
397                     }
398                 }
399                     QDialog::accept();
400                     break;
401                 case 1:
402                     QDialog::accept();
403                     break;
404             }
405         } else {
406             QDialog::accept();
407         }
408     }
409 }
410 
411 }  // namespace BAM
412 }  // namespace U2
413