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 "MultipleDocumentsReadingModeSelectorController.h"
23 
24 #include <QDir>
25 
26 #include <U2Core/AppContext.h>
27 #include <U2Core/BaseDocumentFormats.h>
28 #include <U2Core/DocumentModel.h>
29 #include <U2Core/DocumentUtils.h>
30 #include <U2Core/GUrlUtils.h>
31 #include <U2Core/IOAdapter.h>
32 #include <U2Core/LocalFileAdapter.h>
33 #include <U2Core/ProjectModel.h>
34 #include <U2Core/U2SafePoints.h>
35 
36 #include <U2Gui/HelpButton.h>
37 #include <U2Gui/SaveDocumentController.h>
38 
39 namespace U2 {
40 
41 struct GUrlLess {
operator ()U2::GUrlLess42     bool operator()(GUrl a, GUrl b) const {
43         return a.getURLString() < b.getURLString();
44     }
45 };
46 
adjustReadingMode(QVariantMap & props,QList<GUrl> & urls,const QMap<QString,qint64> & headerSequenceLengths)47 bool MultipleDocumentsReadingModeSelectorController::adjustReadingMode(QVariantMap &props, QList<GUrl> &urls, const QMap<QString, qint64> &headerSequenceLengths) {
48     std::sort(urls.begin(), urls.end(), GUrlLess());
49 
50     MultipleDocumentsReadingModeDialog d(urls, QApplication::activeWindow());
51     return d.setupGUI(urls, props, headerSequenceLengths);
52 }
53 
mergeDocumentOption(const FormatDetectionResult & formatResult,QMap<QString,qint64> * headerSequenceLengths)54 bool MultipleDocumentsReadingModeSelectorController::mergeDocumentOption(const FormatDetectionResult &formatResult, QMap<QString, qint64> *headerSequenceLengths) {
55     QVariantMap docHints = formatResult.rawDataCheckResult.properties;
56     if (formatResult.format == nullptr) {
57         return false;
58     }
59     if (formatResult.format->getFormatId() == BaseDocumentFormats::PLAIN_GENBANK) {
60         if (docHints.value(RawDataCheckResult_Sequence) == false) {
61             static const int MAX_LINE = 8192;
62             char buff[MAX_LINE + 1] = {0};
63 
64             QScopedPointer<LocalFileAdapterFactory> factory(new LocalFileAdapterFactory());
65             QScopedPointer<IOAdapter> io(factory->createIOAdapter());
66             if (!io->open(formatResult.url, IOAdapterMode_Read)) {
67                 return false;
68             }
69             bool terminatorFound = false;
70             io->readLine(buff, MAX_LINE, &terminatorFound);
71             if (!terminatorFound) {
72                 return false;
73             }
74             QString line = QString(QByteArray(buff));
75             QStringList words = line.split(QRegExp("\\s"), QString::SkipEmptyParts);
76             if (words.size() < 3) {  // origin len not defined
77                 return false;
78             }
79             bool isLenDefined = false;
80             qint64 seqLen = words[2].toLongLong(&isLenDefined);
81 
82             if (!isLenDefined || seqLen <= 0) {
83                 return false;
84             }
85             (*headerSequenceLengths)[formatResult.url.getURLString()] = seqLen;
86             return true;
87         }
88     }
89     return docHints.value(RawDataCheckResult_Sequence).toBool();
90 }
91 
MultipleDocumentsReadingModeDialog(const QList<GUrl> & _urls,QWidget * parent)92 MultipleDocumentsReadingModeDialog::MultipleDocumentsReadingModeDialog(const QList<GUrl> &_urls, QWidget *parent)
93     : QDialog(parent),
94       saveController(nullptr),
95       urls(_urls) {
96 }
97 
setupNewUrl()98 QString MultipleDocumentsReadingModeDialog::setupNewUrl() {
99     QString urlStr = newDocUrl->text();
100     if (urlStr.isEmpty()) {
101         GUrl url = urls.at(0);
102         urlStr = url.getURLString();
103     }
104     QFileInfo fi(urlStr);
105     urlStr = fi.dir().path();
106 
107     QString extension4MergedDocument;
108     if (mergeMode->isChecked()) {
109         extension4MergedDocument = AppContext::getDocumentFormatRegistry()->getFormatById(BaseDocumentFormats::PLAIN_GENBANK)->getSupportedDocumentFileExtensions().first();
110     } else if (join2alignmentMode->isChecked()) {
111         extension4MergedDocument = AppContext::getDocumentFormatRegistry()->getFormatById(BaseDocumentFormats::CLUSTAL_ALN)->getSupportedDocumentFileExtensions().first();
112     }
113 
114     return urlStr + "/merged_document." + extension4MergedDocument;
115 }
116 
setupGUI(QList<GUrl> & _urls,QVariantMap & props,const QMap<QString,qint64> & headerSequenceLengths)117 bool MultipleDocumentsReadingModeDialog::setupGUI(QList<GUrl> &_urls, QVariantMap &props, const QMap<QString, qint64> &headerSequenceLengths) {
118     setModal(true);
119     setupUi(this);
120     new HelpButton(this, buttonBox, "65929449");
121     buttonBox->button(QDialogButtonBox::Ok)->setText(tr("OK"));
122     buttonBox->button(QDialogButtonBox::Cancel)->setText(tr("Cancel"));
123 
124     // doesn't matter from what position, because excluded fileName all path of documents are the same
125     CHECK(!urls.isEmpty(), false);
126 
127     connect(separateMode, SIGNAL(toggled(bool)), SLOT(sl_optionChanged()));
128     connect(mergeMode, SIGNAL(toggled(bool)), SLOT(sl_optionChanged()));
129     connect(join2alignmentMode, SIGNAL(toggled(bool)), SLOT(sl_optionChanged()));
130     connect(upperButton, SIGNAL(clicked()), SLOT(sl_onMoveUp()));
131     connect(bottomButton, SIGNAL(clicked()), SLOT(sl_onMoveDown()));
132 
133     upperButton->setIcon(QIcon(":ugene/images/up.png"));
134     bottomButton->setIcon(QIcon(":ugene/images/down.png"));
135 
136     for (int i = 0; i < urls.size(); ++i) {
137         listDocuments->addItem(new QListWidgetItem(QString("%1. ").arg(i + 1) + urls.at(i).fileName(), listDocuments));
138     }
139 
140     int rc = exec();
141 
142     if (rc == QDialog::Rejected) {
143         return false;
144     }
145 
146     if (separateMode->isChecked() || saveController->getSaveFileName().isEmpty()) {
147         return true;
148     }
149 
150     deleteAllNumPrefix();
151     _urls.clear();
152 
153     for (int i = 0; i < listDocuments->count(); ++i) {
154         _urls << findUrlByFileName(listDocuments->item(i)->text());
155     }
156 
157     listDocuments->clear();
158 
159     if (mergeMode->isChecked()) {
160         props[DocumentReadingMode_SequenceMergeGapSize] = fileGap->value();
161         props[ProjectLoaderHint_MultipleFilesMode_RealDocumentFormat] = BaseDocumentFormats::PLAIN_GENBANK;
162 
163         foreach (const GUrl &url, _urls) {
164             props[RawDataCheckResult_HeaderSequenceLength + url.getURLString()] = headerSequenceLengths.value(url.getURLString(), -1);
165         }
166     } else if (join2alignmentMode->isChecked()) {
167         props[ProjectLoaderHint_MultipleFilesMode_RealDocumentFormat] = BaseDocumentFormats::CLUSTAL_ALN;
168         props[DocumentReadingMode_SequenceAsAlignmentHint] = true;
169     }
170     props[ProjectLoaderHint_MultipleFilesMode_URLDocument] = saveController->getSaveFileName();
171     props[ProjectLoaderHint_MultipleFilesMode_SaveDocumentFlag] = saveBox->isChecked();
172     props[ProjectLoaderHint_MultipleFilesMode_Flag] = true;
173 
174     QStringList urlsStr;
175     foreach (GUrl url, _urls) {
176         urlsStr << url.getURLString();
177     }
178 
179     props[ProjectLoaderHint_MultipleFilesMode_URLsDocumentConsistOf] = urlsStr;
180 
181     _urls.clear();
182     _urls << GUrl(saveController->getSaveFileName());
183 
184     return true;
185 }
186 
findUrlByFileName(const QString & fileName)187 QString MultipleDocumentsReadingModeDialog::findUrlByFileName(const QString &fileName) {
188     foreach (GUrl url, urls) {
189         QString _fileName = url.fileName();
190         if (_fileName == fileName) {
191             return url.getURLString();
192         }
193     }
194     assert(false && "Error finding url by file name");
195     return "";
196 }
197 
sl_optionChanged()198 void MultipleDocumentsReadingModeDialog::sl_optionChanged() {
199     bool isNewDocInfoAvailable = !separateMode->isChecked();
200 
201     saveBox->setEnabled(isNewDocInfoAvailable);
202     listDocuments->setEnabled(isNewDocInfoAvailable);
203     upperButton->setEnabled(isNewDocInfoAvailable);
204     bottomButton->setEnabled(isNewDocInfoAvailable);
205     newDocLabel->setEnabled(isNewDocInfoAvailable);
206     newDocUrl->setEnabled(isNewDocInfoAvailable);
207 
208     bool mergeInfoAvailable = mergeMode->isChecked();
209     fileGap->setEnabled(mergeInfoAvailable);
210     mergeModeLabel->setEnabled(mergeInfoAvailable);
211 
212     if (mergeMode->isChecked()) {
213         delete saveController;
214         initSequenceSaveController();
215     } else if (join2alignmentMode->isChecked()) {
216         delete saveController;
217         initMsaSaveController();
218     } else {
219         return;
220     }
221 }
222 
initSequenceSaveController()223 void MultipleDocumentsReadingModeDialog::initSequenceSaveController() {
224     SaveDocumentControllerConfig config;
225     config.defaultDomain = "SaveMergeDocumentsAsOneDocument";
226     config.defaultFileName = setupNewUrl();
227     config.defaultFormatId = BaseDocumentFormats::PLAIN_GENBANK;
228     config.fileDialogButton = toolButton;
229     config.fileNameEdit = newDocUrl;
230     config.parentWidget = this;
231     config.saveTitle = tr("Select file to save new document");
232     config.rollOutProjectUrls = true;
233 
234     const QList<DocumentFormatId> formats = QList<DocumentFormatId>() << BaseDocumentFormats::PLAIN_GENBANK;
235 
236     saveController = new SaveDocumentController(config, formats, this);
237 }
238 
initMsaSaveController()239 void MultipleDocumentsReadingModeDialog::initMsaSaveController() {
240     SaveDocumentControllerConfig config;
241     config.defaultDomain = "SaveMergeDocumentsAsOneDocument";
242     config.defaultFileName = setupNewUrl();
243     config.defaultFormatId = BaseDocumentFormats::CLUSTAL_ALN;
244     config.fileDialogButton = toolButton;
245     config.fileNameEdit = newDocUrl;
246     config.parentWidget = this;
247     config.saveTitle = tr("Select file to save new document");
248     config.rollOutProjectUrls = true;
249 
250     const QList<DocumentFormatId> formats = QList<DocumentFormatId>() << BaseDocumentFormats::CLUSTAL_ALN;
251 
252     saveController = new SaveDocumentController(config, formats, this);
253 }
254 
sl_onMoveUp()255 void MultipleDocumentsReadingModeDialog::sl_onMoveUp() {
256     QListWidgetItem *item = listDocuments->currentItem();
257     if (item == nullptr) {
258         return;
259     }
260     int row = listDocuments->row(item);
261     if (row == 0) {
262         return;
263     }
264 
265     QListWidgetItem *newCurrent = new QListWidgetItem(item->text());
266     listDocuments->insertItem(row - 1, newCurrent);
267     listDocuments->removeItemWidget(item);
268     delete item;
269 
270     changeNumPrefix();
271     listDocuments->setCurrentItem(newCurrent);
272 }
273 
sl_onMoveDown()274 void MultipleDocumentsReadingModeDialog::sl_onMoveDown() {
275     QListWidgetItem *item = listDocuments->currentItem();
276     if (item == nullptr) {
277         return;
278     }
279     int row = listDocuments->row(item);
280     if (row == listDocuments->count() - 1) {
281         return;
282     }
283 
284     QListWidgetItem *newCurrent = new QListWidgetItem(item->text());
285     listDocuments->insertItem(row + 2, newCurrent);
286     listDocuments->removeItemWidget(item);
287     delete item;
288 
289     changeNumPrefix();
290     listDocuments->setCurrentItem(newCurrent);
291 }
292 
changeNumPrefix()293 void MultipleDocumentsReadingModeDialog::changeNumPrefix() {
294     for (int i = 0; i < listDocuments->count(); ++i) {
295         listDocuments->item(i)->setText(QString("%1. ").arg(i + 1) + deleteNumPrefix(listDocuments->item(i)->text()));
296     }
297 }
298 
deleteNumPrefix(QString prefixString)299 QString MultipleDocumentsReadingModeDialog::deleteNumPrefix(QString prefixString) {
300     QString toDelete = prefixString.split(" ")[0];  // number with space
301     prefixString.remove(0, toDelete.size() + 1);
302     return prefixString;
303 }
304 
deleteAllNumPrefix()305 void MultipleDocumentsReadingModeDialog::deleteAllNumPrefix() {
306     for (int i = 0; i < listDocuments->count(); ++i) {
307         listDocuments->item(i)->setText(deleteNumPrefix(listDocuments->item(i)->text()));
308     }
309 }
310 
311 }  // namespace U2
312