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 "ProjectLoaderImpl.h"
23 
24 #include <QAction>
25 #include <QDesktopServices>
26 #include <QMainWindow>
27 #include <QMessageBox>
28 #include <QPushButton>
29 #include <QToolBar>
30 
31 #include <U2Core/AddDocumentTask.h>
32 #include <U2Core/AppContext.h>
33 #include <U2Core/CMDLineCoreOptions.h>
34 #include <U2Core/CMDLineUtils.h>
35 #include <U2Core/DocumentImport.h>
36 #include <U2Core/DocumentUtils.h>
37 #include <U2Core/GHints.h>
38 #include <U2Core/GUrlUtils.h>
39 #include <U2Core/IOAdapter.h>
40 #include <U2Core/IOAdapterUtils.h>
41 #include <U2Core/IdRegistry.h>
42 #include <U2Core/L10n.h>
43 #include <U2Core/LoadDocumentTask.h>
44 #include <U2Core/ProjectModel.h>
45 #include <U2Core/QObjectScopedPointer.h>
46 #include <U2Core/ServiceTypes.h>
47 #include <U2Core/Settings.h>
48 #include <U2Core/U2OpStatusUtils.h>
49 #include <U2Core/U2SafePoints.h>
50 
51 #include <U2Gui/CreateDocumentFromTextDialogController.h>
52 #include <U2Gui/DownloadRemoteFileDialog.h>
53 #include <U2Gui/HelpButton.h>
54 #include <U2Gui/LastUsedDirHelper.h>
55 #include <U2Gui/MainWindow.h>
56 #include <U2Gui/ObjectViewModel.h>
57 #include <U2Gui/OpenViewTask.h>
58 #include <U2Gui/PasteController.h>
59 #include <U2Gui/ProjectUtils.h>
60 #include <U2Gui/SearchGenbankSequenceDialogController.h>
61 #include <U2Gui/SharedConnectionsDialog.h>
62 #include <U2Gui/U2FileDialog.h>
63 
64 #include <U2View/DnaAssemblyGUIExtension.h>
65 
66 #include "DocumentFormatSelectorController.h"
67 #include "DocumentProviderSelectorController.h"
68 #include "DocumentReadingModeSelectorController.h"
69 #include "MultipleDocumentsReadingModeSelectorController.h"
70 #include "ProjectImpl.h"
71 #include "ProjectTasksGui.h"
72 #include "project_view/ProjectViewImpl.h"
73 
74 namespace U2 {
75 
getProjectFilePathFromPathEdit(const QLineEdit * projectFilePathEdit)76 static QString getProjectFilePathFromPathEdit(const QLineEdit *projectFilePathEdit) {
77     QString path = projectFilePathEdit->text();
78     if (path.isEmpty()) {
79         return path;
80     }
81     if (!path.endsWith(PROJECTFILE_EXT)) {
82         path.append(PROJECTFILE_EXT);
83     }
84     return path;
85 }
86 
87 //////////////////////////////////////////////////////////////////////////
88 /// ProjectLoaderImpl
89 //////////////////////////////////////////////////////////////////////////
90 
ProjectLoaderImpl()91 ProjectLoaderImpl::ProjectLoaderImpl() {
92     pasteAction = openProjectAction = newProjectAction = nullptr;
93     recentProjectsMenu = nullptr;
94 
95     assert(AppContext::getProject() == nullptr);
96     assert(AppContext::getProjectLoader() == nullptr);
97 
98     ServiceRegistry *sr = AppContext::getServiceRegistry();
99     connect(sr, SIGNAL(si_serviceStateChanged(Service *, ServiceState)), SLOT(sl_serviceStateChanged(Service *, ServiceState)));
100 
101     newProjectAction = new QAction(QIcon(":ugene/images/project_new.png"), tr("&New project..."), this);
102     newProjectAction->setObjectName(ACTION_PROJECTSUPPORT__NEW_PROJECT);
103     //    newProjectAction->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_N));
104     newProjectAction->setShortcutContext(Qt::WindowShortcut);
105     connect(newProjectAction, SIGNAL(triggered()), SLOT(sl_newProject()));
106 
107     addExistingDocumentAction = new QAction(QIcon(":ugene/images/advanced_open.png"), tr("Open as..."), this);
108     addExistingDocumentAction->setObjectName(ACTION_PROJECTSUPPORT__OPEN_AS);
109     addExistingDocumentAction->setShortcut(QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_O));
110     addExistingDocumentAction->setShortcutContext(Qt::ApplicationShortcut);
111     connect(addExistingDocumentAction, SIGNAL(triggered()), SLOT(sl_onAddExistingDocument()));
112 
113     newDocumentFromTextAction = new QAction(QIcon(), tr("New document from text..."), this);
114     newDocumentFromTextAction->setObjectName("NewDocumentFromText");
115     newDocumentFromTextAction->setShortcutContext(Qt::WindowShortcut);
116     connect(newDocumentFromTextAction, SIGNAL(triggered()), SLOT(sl_newDocumentFromText()));
117 
118     pasteAction = new QAction(QIcon(":ugene/images/paste.png"), tr("Open from clipboard..."), this);
119     pasteAction->setObjectName(ACTION_PROJECTSUPPORT__PASTE);
120     pasteAction->setShortcut(QKeySequence::Paste);
121     pasteAction->setShortcutContext(Qt::WidgetShortcut);
122     connect(pasteAction, SIGNAL(triggered()), SLOT(sl_paste()));
123 
124     openProjectAction = new QAction(QIcon(":ugene/images/project_open.png"), tr("Open..."), this);
125     openProjectAction->setObjectName(ACTION_PROJECTSUPPORT__OPEN_PROJECT);
126     openProjectAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_O));
127     openProjectAction->setShortcutContext(Qt::WindowShortcut);
128     connect(openProjectAction, SIGNAL(triggered()), SLOT(sl_openProject()));
129 
130     downloadRemoteFileAction = new QAction(tr("Access remote database..."), this);
131     downloadRemoteFileAction->setObjectName(ACTION_PROJECTSUPPORT__ACCESS_REMOTE_DB);
132     downloadRemoteFileAction->setIcon(QIcon(":ugene/images/world_go.png"));
133     connect(downloadRemoteFileAction, SIGNAL(triggered()), SLOT(sl_downloadRemoteFile()));
134 
135     accessSharedDatabaseAction = new QAction(tr("Connect to UGENE shared database..."), this);
136     accessSharedDatabaseAction->setObjectName(ACTION_PROJECTSUPPORT__ACCESS_SHARED_DB);
137     accessSharedDatabaseAction->setIcon(QIcon(":core/images/db/database_go.png"));
138     connect(accessSharedDatabaseAction, SIGNAL(triggered()), SLOT(sl_accessSharedDatabase()));
139 
140     searchGenbankEntryAction = new QAction(tr("Search NCBI GenBank..."), this);
141     searchGenbankEntryAction->setObjectName(ACTION_PROJECTSUPPORT__SEARCH_GENBANK);
142     searchGenbankEntryAction->setIcon(QIcon(":ugene/images/world_go.png"));
143     connect(searchGenbankEntryAction, SIGNAL(triggered()), SLOT(sl_searchGenbankEntry()));
144 
145     // add load/close actions to menu and toolbar
146     MainWindow *mw = AppContext::getMainWindow();
147     QMenu *fileMenu = mw->getTopLevelMenu(MWMENU_FILE);
148 
149     recentProjectsMenu = new QMenu(tr("Recent projects"));
150     recentProjectsMenu->menuAction()->setObjectName(ACTION_PROJECTSUPPORT__RECENT_PROJECTS_MENU);
151     updateRecentProjectsMenu();
152 
153     recentItemsMenu = new QMenu(tr("Recent files"));
154     recentItemsMenu->menuAction()->setObjectName("recent_docs_menu_action");
155     updateRecentItemsMenu();
156 
157     QAction *newSectionSeparator = new QAction("", this);
158     newSectionSeparator->setSeparator(true);
159     newSectionSeparator->setObjectName(ACTION_PROJECTSUPPORT__NEW_SECTION_SEPARATOR);
160 
161     QAction *openSectionSeparator = new QAction("", this);
162     openSectionSeparator->setSeparator(true);
163 
164     QAction *remoteSectionSeparator = new QAction("", this);
165     remoteSectionSeparator->setSeparator(true);
166 
167     QAction *recentSectionSeparator = new QAction("", this);
168     recentSectionSeparator->setSeparator(true);
169 
170     QList<QAction *> actions;
171     actions << newProjectAction
172             << newDocumentFromTextAction
173             << newSectionSeparator
174             << openProjectAction
175             << addExistingDocumentAction
176             << pasteAction
177             << openSectionSeparator
178             << downloadRemoteFileAction
179             << searchGenbankEntryAction
180             << accessSharedDatabaseAction
181             << remoteSectionSeparator
182             << recentItemsMenu->menuAction()
183             << recentProjectsMenu->menuAction()
184             << recentSectionSeparator;
185 
186     fileMenu->insertActions(fileMenu->actions().first(), actions);
187 
188     QToolBar *tb = mw->getToolbar(MWTOOLBAR_MAIN);
189     tb->addAction(newProjectAction);
190     tb->addAction(openProjectAction);
191 
192     updateState();
193 
194     IdRegistry<WelcomePageAction> *welcomePageActions = AppContext::getWelcomePageActionRegistry();
195     CHECK(nullptr != welcomePageActions, );
196     welcomePageActions->registerEntry(new LoadDataWelcomePageAction(this));
197     welcomePageActions->registerEntry(new CreateSequenceWelcomePageAction(this));
198 }
199 
updateState()200 void ProjectLoaderImpl::updateState() {
201     recentProjectsMenu->setDisabled(recentProjectsMenu->isEmpty());
202 }
203 
204 #define MAX_RECENT_FILES 7
205 
sl_newProject()206 void ProjectLoaderImpl::sl_newProject() {
207     QWidget *p = (QWidget *)AppContext::getMainWindow()->getQMainWindow();
208     QObjectScopedPointer<ProjectDialogController> d = new ProjectDialogController(ProjectDialogController::New_Project, p);
209     int rc = d->exec();
210     CHECK(!d.isNull(), );
211     QFileInfo projectPathFileInfo(getProjectFilePathFromPathEdit(d->projectFilePathEdit));
212     AppContext::getSettings()->setValue(SETTINGS_DIR + "last_dir", projectPathFileInfo.absoluteDir().absolutePath(), true);
213 
214     if (rc == QDialog::Rejected) {
215         updateState();
216         return;
217     }
218 
219     QString projectPath = projectPathFileInfo.absoluteFilePath();
220     if (projectPathFileInfo.exists()) {
221         QFile::remove(projectPath);
222     }
223 
224     QString projectName = d->projectNameEdit->text();
225     AppContext::getTaskScheduler()->registerTopLevelTask(new OpenProjectTask(projectPath, projectName));
226 }
227 
sl_openProject()228 void ProjectLoaderImpl::sl_openProject() {
229     LastUsedDirHelper h;
230     QString filter = DialogUtils::prepareDocumentsFileFilter(true);
231 
232     filter.append("\n" + tr("UGENE project file") + " (*" + PROJECTFILE_EXT + ")");
233 
234     QStringList files;
235 
236     if (qgetenv(ENV_GUI_TEST).toInt() == 1 && qgetenv(ENV_USE_NATIVE_DIALOGS).toInt() == 0) {
237         files = U2FileDialog::getOpenFileNames(QApplication::activeWindow(), tr("Select files to open"), h.dir, filter, 0, QFileDialog::DontUseNativeDialog);
238     } else {
239         files = U2FileDialog::getOpenFileNames(QApplication::activeWindow(), tr("Select files to open"), h.dir, filter);
240     }
241 
242     if (files.isEmpty()) {
243         return;
244     }
245 
246     if (QFileInfo(files.first()).exists()) {
247         h.url = files.first();
248     }
249     QList<GUrl> urls;
250     foreach (QString file, files) {
251         urls << GUrl(file, GUrl_File);
252     }
253     // updateRecentItemsMenu();
254     Task *openTask = openWithProjectTask(urls);
255     if (openTask != nullptr) {
256         AppContext::getTaskScheduler()->registerTopLevelTask(openTask);
257     }
258 }
259 
sl_openRecentProject()260 void ProjectLoaderImpl::sl_openRecentProject() {
261     QAction *action = qobject_cast<QAction *>(sender());
262     SAFE_POINT(action != nullptr, "sl_openRecentProject action is null!", );
263     QString url = action->data().toString();
264     runOpenRecentFileOrProjectTask(url);
265 }
266 
sl_openRecentFile()267 void ProjectLoaderImpl::sl_openRecentFile() {
268     QAction *action = qobject_cast<QAction *>(sender());
269     SAFE_POINT(action != nullptr, "sl_openRecentFile action is null!", );
270     GUrl url = action->data().toString();
271     runOpenRecentFileOrProjectTask(url);
272 }
273 
prependToRecentProjects(const QString & url)274 void ProjectLoaderImpl::prependToRecentProjects(const QString &url) {
275     assert(!url.isEmpty());
276     CHECK(GUrl(url).isLocalFile(), );
277     QStringList recentFiles = AppContext::getSettings()->getValue(SETTINGS_DIR + RECENT_PROJECTS_SETTINGS_NAME).toStringList();
278     recentFiles.removeAll(QString());  // remove all empty tokens if fount (a kind of cleanup)
279     recentFiles.removeAll(url);  // remove URL from the old position
280     recentFiles.prepend(url);  // make URL first
281     while (recentFiles.size() > MAX_RECENT_FILES) {
282         recentFiles.pop_back();
283     }
284     AppContext::getSettings()->setValue(SETTINGS_DIR + RECENT_PROJECTS_SETTINGS_NAME, recentFiles);
285     emit si_recentListChanged();
286 }
287 
removeUrlFromRecentItems(const GUrl & url)288 void ProjectLoaderImpl::removeUrlFromRecentItems(const GUrl &url) {
289     SAFE_POINT(!url.isEmpty(), "URL is empty", );
290     bool isRecentProjectsChanged = false;
291     Settings *appSettings = AppContext::getSettings();
292     QStringList recentProjects = appSettings->getValue(SETTINGS_DIR + RECENT_PROJECTS_SETTINGS_NAME).toStringList();
293     for (int i = recentProjects.size(); --i >= 0;) {
294         if (GUrl(recentProjects[i]) == url) {
295             recentProjects.removeAt(i);
296             isRecentProjectsChanged = true;
297         }
298     }
299     if (isRecentProjectsChanged) {
300         appSettings->setValue(SETTINGS_DIR + RECENT_PROJECTS_SETTINGS_NAME, recentProjects);
301     }
302     bool isRecentFilesChanged = false;
303     QStringList recentFiles = appSettings->getValue(SETTINGS_DIR + RECENT_ITEMS_SETTINGS_NAME).toStringList();
304     for (int i = recentFiles.size(); --i >= 0;) {
305         if (GUrl(recentFiles[i]) == url) {
306             recentFiles.removeAt(i);
307             isRecentFilesChanged = true;
308         }
309     }
310     if (isRecentFilesChanged) {
311         appSettings->setValue(SETTINGS_DIR + RECENT_ITEMS_SETTINGS_NAME, recentFiles);
312     }
313     if (isRecentProjectsChanged || isRecentFilesChanged) {
314         updateRecentItemsMenu();
315         emit si_recentListChanged();
316     }
317 }
318 
updateRecentProjectsMenu()319 void ProjectLoaderImpl::updateRecentProjectsMenu() {
320     SAFE_POINT(recentProjectsMenu != nullptr, "recentProjectsMenu is null", );
321     recentProjectsMenu->clear();
322     QStringList recentFiles = AppContext::getSettings()->getValue(SETTINGS_DIR + RECENT_PROJECTS_SETTINGS_NAME).toStringList();
323     Project *p = AppContext::getProject();
324     for (const QString &url : recentFiles) {
325         if ((p == nullptr || url != p->getProjectURL()) && !url.isEmpty()) {
326             QAction *a = recentProjectsMenu->addAction(url, this, SLOT(sl_openRecentProject()));
327             a->setData(url);
328         }
329     }
330 }
331 
332 namespace {
333 /**
334  * If there are only unsupported documents which are needed to load
335  * then it is not needed to show the project because it will be empty
336  */
prepareDocTab(const QList<AD2P_DocumentInfo> & docsInfo,const QList<AD2P_ProviderInfo> & docProviders)337 void prepareDocTab(const QList<AD2P_DocumentInfo> &docsInfo, const QList<AD2P_ProviderInfo> &docProviders) {
338     CHECK(docProviders.isEmpty(), );
339     foreach (const AD2P_DocumentInfo &info, docsInfo) {
340         const DocumentFormat *df = AppContext::getDocumentFormatRegistry()->getFormatById(info.formatId);
341         if (nullptr == df) {
342             continue;
343         }
344         const GObjectType t = df->getSupportedObjectTypes().toList().first();
345         if (GObjectTypes::getTypeInfo(t).type != GObjectTypes::UNKNOWN) {
346             // the project will not be empty
347             return;
348         }
349     }
350 
351     const MainWindow *mw = AppContext::getMainWindow();
352     CHECK(nullptr != mw, );
353     MWDockManager *dm = mw->getDockManager();
354     CHECK(nullptr != dm, );
355 
356     {  // do not activate the tab
357         dm->dontActivateNextTime(MWDockArea_Left);
358         AppContext::getSettings()->setValue(ProjectViewImpl::SETTINGS_ROOT + "firstShow", false);
359     }
360 }
361 
haveFormatsRelations(const FormatDetectionResult & firstFormat,const FormatDetectionResult & secondFormat)362 bool haveFormatsRelations(const FormatDetectionResult &firstFormat, const FormatDetectionResult &secondFormat) {
363     if (nullptr != firstFormat.format && nullptr != secondFormat.format) {
364         return false;
365     }
366     if (nullptr != firstFormat.format && nullptr != secondFormat.importer) {
367         return secondFormat.importer->getFormatIds().contains(firstFormat.format->getFormatId());
368     }
369     if (nullptr != firstFormat.importer && nullptr != secondFormat.format) {
370         return firstFormat.importer->getFormatIds().contains(secondFormat.format->getFormatId());
371     }
372     if (nullptr != firstFormat.importer && nullptr != secondFormat.importer) {
373         return !firstFormat.importer->getFormatIds().toSet().intersect(secondFormat.importer->getFormatIds().toSet()).isEmpty();
374     }
375     return false;
376 }
377 
getFirstUnrelatedFormat(const QList<FormatDetectionResult> & formats)378 FormatDetectionResult getFirstUnrelatedFormat(const QList<FormatDetectionResult> &formats) {
379     CHECK(formats.size() > 1, FormatDetectionResult());
380     const FormatDetectionResult firstFormat = formats[0];
381 
382     for (int i = 1; i < formats.size(); i++) {
383         if (!haveFormatsRelations(firstFormat, formats[i])) {
384             return formats[i];
385         }
386     }
387     return FormatDetectionResult();
388 }
389 
getRelatedFormats(const QList<FormatDetectionResult> & formats,int idx)390 QList<FormatDetectionResult> getRelatedFormats(const QList<FormatDetectionResult> &formats, int idx) {
391     SAFE_POINT(0 <= idx && idx < formats.size(), "Format index is out of range", QList<FormatDetectionResult>());
392     QList<FormatDetectionResult> result;
393     result << formats[idx];
394     for (int i = 0; i < formats.size(); i++) {
395         if (Q_LIKELY(idx != i) && haveFormatsRelations(formats[idx], formats[i])) {
396             result << formats[i];
397         }
398     }
399     return result;
400 }
401 }  // namespace
402 
shouldFormatBeSelected(const QList<FormatDetectionResult> & formats,bool forceSelectFormat)403 bool ProjectLoaderImpl::shouldFormatBeSelected(const QList<FormatDetectionResult> &formats, bool forceSelectFormat) {
404     CHECK(formats.size() > 1, false);
405 
406     const FormatDetectionResult firstFormat = formats[0];
407     const FormatDetectionResult firstUnrelatedFormat = getFirstUnrelatedFormat(formats);
408     CHECK(FormatDetection_NotMatched != firstUnrelatedFormat.score(), false);
409 
410     int firstFormatScore = firstFormat.score();
411     int firstUnrelatedFormatScore = firstUnrelatedFormat.score();
412     bool isFirstFormatEqualFirstUnrelatedFormat = firstFormatScore == firstUnrelatedFormatScore;
413     bool isFirstUnrelatedFormatMoreThenFormatDetectionAverageSimilarity = firstUnrelatedFormatScore > FormatDetection_AverageSimilarity;
414     bool isFirstFormatLessThenFormatDetectionMatched = firstFormatScore < FormatDetection_Matched;
415     bool isFirstFormatLessOrEqualThenFormatDetectionAverageSimilarity = firstFormatScore <= FormatDetection_AverageSimilarity;
416     return isFirstFormatEqualFirstUnrelatedFormat || (isFirstUnrelatedFormatMoreThenFormatDetectionAverageSimilarity && isFirstFormatLessThenFormatDetectionMatched) || isFirstFormatLessOrEqualThenFormatDetectionAverageSimilarity || forceSelectFormat;
417 }
418 
getMaxObjectsInSingleDocument()419 int ProjectLoaderImpl::getMaxObjectsInSingleDocument() {
420     int maxObjects = qgetenv("UGENE_MAX_OBJECTS_PER_DOCUMENT").toInt();
421     return maxObjects < 10 ? 50000 : maxObjects;
422 }
423 
detectFormat(const GUrl & url,QList<FormatDetectionResult> & formats,const QVariantMap & hints,FormatDetectionResult & selectedResult)424 bool ProjectLoaderImpl::detectFormat(const GUrl &url, QList<FormatDetectionResult> &formats, const QVariantMap &hints, FormatDetectionResult &selectedResult) {
425     CHECK(!formats.isEmpty(), false);
426     int idx = 0;
427     if (shouldFormatBeSelected(formats, hints.value(ProjectLoaderHint_ForceFormatOptions, false).toBool())) {
428         const FormatDetectionResult &result = formats.first();
429         idx = DocumentFormatSelectorController::selectResult(url, result.getRawDataPreviewText(), formats);
430         if (idx >= 0) {
431             selectedResult = formats[idx];
432             return true;
433         } else {
434             return false;
435         }
436     } else {
437         QList<FormatDetectionResult> relatedFormats = getRelatedFormats(formats, idx);
438         if (relatedFormats.size() > 1) {
439             int indexInRelatedList = DocumentProviderSelectorController::selectResult(url, relatedFormats);
440             if (indexInRelatedList >= 0) {
441                 selectedResult = relatedFormats[indexInRelatedList];
442                 return true;
443             } else {
444                 return false;
445             }
446         }
447     }
448     selectedResult = formats[0];
449     return true;
450 }
451 
openWithProjectTask(const QList<GUrl> & _urls,const QVariantMap & hints)452 Task *ProjectLoaderImpl::openWithProjectTask(const QList<GUrl> &_urls, const QVariantMap &hints) {
453     QList<GUrl> urls = _urls;
454     // detect if we open real UGENE project file
455     bool projectsOnly = true;
456     foreach (const GUrl &url, urls) {
457         projectsOnly = projectsOnly && isProjectFileUrl(url);
458         if (!projectsOnly) {
459             break;
460         }
461     }
462     if (projectsOnly) {
463         GUrl projectUrl = urls.isEmpty() ? QString() : urls.last();
464         QVariantMap h2 = hints;
465         h2[ProjectLoaderHint_CloseActiveProject] = true;
466         return createProjectLoadingTask(projectUrl, h2);
467     }
468     bool abilityUniteDocuments = true;
469 
470     QVariantMap hintsOverDocuments;
471     QMap<QString, qint64> headerSequenceLengths;
472 
473     if (urls.size() >= 2) {
474         foreach (const GUrl &url, urls) {
475             FormatDetectionResult dr;
476             FormatDetectionConfig conf;
477             conf.useImporters = hints.value(ProjectLoaderHint_UseImporters, true).toBool();
478             conf.bestMatchesOnly = false;
479             QList<FormatDetectionResult> formats = DocumentUtils::detectFormat(url, conf);
480             if (formats.isEmpty()) {
481                 abilityUniteDocuments = false;
482                 break;
483             }
484             dr = formats[0];
485             bool matchCurrentDocument = MultipleDocumentsReadingModeSelectorController::mergeDocumentOption(dr, &headerSequenceLengths);
486 
487             if (!matchCurrentDocument) {
488                 abilityUniteDocuments = false;
489                 break;
490             }
491         }
492     } else {
493         abilityUniteDocuments = false;
494     }
495 
496     if (abilityUniteDocuments) {
497         bool ok = MultipleDocumentsReadingModeSelectorController::adjustReadingMode(hintsOverDocuments, urls, headerSequenceLengths);
498         if (!ok) {
499             return nullptr;
500         }
501     }
502 
503     // detect all formats from urls list and add files to project
504     QList<AD2P_DocumentInfo> docsInfo;
505     QList<AD2P_ProviderInfo> docProviders;
506     int nViews = 0;
507     foreach (const GUrl &url, urls) {
508         if (isProjectFileUrl(url.lastFileSuffix())) {
509             // skip extra project files
510             coreLog.info(tr("Project file '%1' ignored").arg(url.getURLString()));
511             continue;
512         }
513         Project *project = AppContext::getProject();
514         Document *doc = project == nullptr ? nullptr : project->findDocumentByURL(url);
515         if (doc != nullptr) {
516             coreLog.details(tr("The document with the same URL is already added to the project"));
517             if (doc->isLoaded()) {
518                 const QList<GObject *> &docObjects = doc->getObjects();
519                 QList<GObjectViewWindow *> viewsList = GObjectViewUtils::findViewsWithAnyOfObjects(docObjects);
520                 if (viewsList.isEmpty()) {
521                     AppContext::getTaskScheduler()->registerTopLevelTask(new OpenViewTask(doc));
522                 } else {
523                     AppContext::getMainWindow()->getMDIManager()->activateWindow(viewsList.first());
524                 }
525                 coreLog.info(tr("The document is already loaded and added to project: %1").arg(url.fileName()));
526             } else if (!doc->isLoaded() && AppContext::getProjectView()) {
527                 if (nullptr == ProjectUtils::findLoadTask(url.getURLString())) {
528                     AppContext::getTaskScheduler()->registerTopLevelTask(new LoadUnloadedDocumentAndOpenViewTask(doc));
529                 } else {
530                     coreLog.details(tr("The document with the same URL is already loading"));
531                 }
532             }
533         } else {
534             QList<FormatDetectionResult> formats;
535             if (hintsOverDocuments.value(ProjectLoaderHint_MultipleFilesMode_Flag, false).toBool() == false) {
536                 FormatDetectionConfig conf;
537                 conf.useImporters = hints.value(ProjectLoaderHint_UseImporters, true).toBool();
538                 conf.bestMatchesOnly = false;
539                 QString forcedFormatId = hints.value(ProjectLoaderHint_DocumentFormat).toString();
540                 DocumentFormat *documentFormat = forcedFormatId.isEmpty() ? nullptr : AppContext::getDocumentFormatRegistry()->getFormatById(forcedFormatId);
541                 if (documentFormat != nullptr) {
542                     FormatDetectionResult formatDetectionResult;
543                     formatDetectionResult.format = documentFormat;
544                     formats << formatDetectionResult;
545                 } else {
546                     formats = DocumentUtils::detectFormat(url, conf);
547                 }
548             } else {
549                 FormatDetectionResult result;
550                 result.format = AppContext::getDocumentFormatRegistry()->getFormatById(hintsOverDocuments[ProjectLoaderHint_MultipleFilesMode_RealDocumentFormat].toString());
551                 formats << result;
552             }
553 
554             if (!formats.isEmpty()) {
555                 FormatDetectionResult dr;
556                 const bool accepted = detectFormat(url, formats, hints, dr);
557                 if (accepted) {
558                     dr.rawDataCheckResult.properties.unite(hints);
559                     dr.rawDataCheckResult.properties.unite(hintsOverDocuments);
560                     if (dr.format != nullptr) {
561                         bool forceReadingOptions = hints.value(ProjectLoaderHint_ForceFormatOptions, false).toBool();
562                         bool optionsAlreadyChosen = hints.value((ProjectLoaderHint_MultipleFilesMode_Flag), false).toBool() || hints.value((DocumentReadingMode_SequenceMergeGapSize), false).toBool() || hints.value((DocumentReadingMode_SequenceAsAlignmentHint), false).toBool() || hints.value((DocumentReadingMode_SequenceAsShortReadsHint), false).toBool() || hints.value((DocumentReadingMode_SequenceAsSeparateHint), false).toBool();
563                         bool ok = DocumentReadingModeSelectorController::adjustReadingMode(dr, forceReadingOptions, optionsAlreadyChosen);
564                         if (!ok) {
565                             continue;
566                         }
567                         bool documentProcessingFinished = processHints(dr);
568                         if (documentProcessingFinished) {
569                             continue;
570                         }
571                         AD2P_DocumentInfo info;
572                         if (hints.value(ProjectLoaderHint_LoadWithoutView, false).toBool() == true) {
573                             info.openView = false;
574                         } else {
575                             info.openView = nViews++ < OpenViewTask::MAX_DOC_NUMBER_TO_OPEN_VIEWS;
576                         }
577                         info.loadDocuments = hints.value(ProjectLoaderHint_LoadUnloadedDocument, true).toBool();
578                         info.url = url;
579                         info.hints = dr.rawDataCheckResult.properties;
580                         if (!info.hints.contains(DocumentReadingMode_MaxObjectsInDoc)) {
581                             info.hints[DocumentReadingMode_MaxObjectsInDoc] = getMaxObjectsInSingleDocument();
582                         }
583                         info.formatId = dr.format->getFormatId();
584                         info.iof = AppContext::getIOAdapterRegistry()->getIOAdapterFactoryById(IOAdapterUtils::url2io(url));
585                         docsInfo << info;
586                     } else {
587                         assert(dr.importer != nullptr);
588                         AD2P_ProviderInfo info;
589                         if (hints.value(ProjectLoaderHint_LoadWithoutView, false).toBool() == true) {
590                             info.openView = false;
591                         } else {
592                             info.openView = nViews++ < OpenViewTask::MAX_DOC_NUMBER_TO_OPEN_VIEWS;
593                         }
594                         QVariantMap _hints = dr.rawDataCheckResult.properties;
595                         info.dp = dr.importer->createImportTask(dr, true, _hints);
596                         docProviders << info;
597                     }
598                 }
599             } else {
600                 if (hints.value(ProjectLoaderHint_OpenBySystemIfFormatDetectionFailed, false).toBool()) {
601                     QDesktopServices::openUrl(QUrl(url.getURLString(), QUrl::TolerantMode));
602                 } else {
603                     QString message = tr("Failed to detect file format: %1").arg(url.getURLString());
604                     QFileInfo finfo(url.getURLString());
605                     if (!finfo.exists()) {
606                         message = tr("File doesn't exist: %1").arg(url.getURLString());
607                     } else if (finfo.size() == 0) {
608                         message = tr("File is empty: %1").arg(url.getURLString());
609                     }
610                     coreLog.error(message);
611                     QMessageBox::critical(AppContext::getMainWindow()->getQMainWindow(), L10N::errorTitle(), message);
612                 }
613             }
614         }
615     }
616     if (docsInfo.isEmpty() && docProviders.isEmpty()) {
617         return nullptr;
618     }
619 
620     prepareDocTab(docsInfo, docProviders);
621     return new AddDocumentsToProjectTask(docsInfo, docProviders);
622 }
623 
processHints(FormatDetectionResult & dr)624 bool ProjectLoaderImpl::processHints(FormatDetectionResult &dr) {
625     bool alignAsShortReads = dr.rawDataCheckResult.properties.value(DocumentReadingMode_SequenceAsShortReadsHint).toBool();
626     if (alignAsShortReads) {
627         DnaAssemblyGUIUtils::runAssembly2ReferenceDialog(QStringList() << dr.url.getURLString());
628         return true;
629     }
630     return false;
631 }
632 
createNewProjectTask(const GUrl & url)633 Task *ProjectLoaderImpl::createNewProjectTask(const GUrl &url) {
634     return createProjectLoadingTask(url);
635 }
636 
createProjectLoadingTask(const GUrl & url,const QVariantMap & hints)637 Task *ProjectLoaderImpl::createProjectLoadingTask(const GUrl &url, const QVariantMap &hints) {
638     Project *p = AppContext::getProject();
639     if (p == nullptr) {
640         return new OpenProjectTask(url.getURLString());
641     }
642     if (url == p->getProjectURL()) {
643         QString message = tr("Project is already opened");
644         QMessageBox::critical(AppContext::getMainWindow()->getQMainWindow(), "UGENE", message);
645         return nullptr;
646     }
647     QObjectScopedPointer<QMessageBox> msgBox = new QMessageBox(AppContext::getMainWindow()->getQMainWindow());
648     msgBox->setWindowTitle(U2_APP_TITLE);
649     msgBox->setText(tr("New project can either be opened in a new window or replace the project in the existing. How would you like to open the project?"));
650     QPushButton *newWindow = msgBox->addButton(tr("New Window"), QMessageBox::ActionRole);
651     newWindow->setObjectName("New Window");
652     QPushButton *oldWindow = msgBox->addButton(tr("This Window"), QMessageBox::ActionRole);
653     oldWindow->setObjectName("This Window");
654     msgBox->addButton(QMessageBox::Abort);
655     msgBox->exec();
656     CHECK(!msgBox.isNull(), nullptr);
657 
658     if (msgBox->clickedButton() == newWindow) {
659         QStringList params = CMDLineRegistryUtils::getPureValues(0);
660         params.append("--" + CMDLineCoreOptions::INI_FILE + "=" + AppContext::getSettings()->fileName());
661         bool b = QProcess::startDetached(params.first(), QStringList() << url.getURLString() << params[1]);
662         if (!b) {
663             coreLog.error(tr("Failed to open new instance of UGENE"));
664         }
665         return nullptr;
666     } else if (msgBox->clickedButton() == oldWindow) {
667         bool closeActiveProject = hints.value(ProjectLoaderHint_CloseActiveProject, QVariant::fromValue(false)).toBool();
668         if (!closeActiveProject) {
669             coreLog.error(tr("Stopped loading project: %1. Reason: active project found").arg(url.getURLString()));
670             return nullptr;
671         }
672     } else {
673         return nullptr;
674     }
675     return new OpenProjectTask(url.getURLString());
676 }
677 
sl_projectURLChanged(const QString & oldURL)678 void ProjectLoaderImpl::sl_projectURLChanged(const QString &oldURL) {
679     if (!oldURL.isEmpty()) {
680         prependToRecentProjects(oldURL);
681     }
682     rememberProjectURL();
683 }
684 
rememberProjectURL()685 void ProjectLoaderImpl::rememberProjectURL() {
686     Project *p = AppContext::getProject();
687     QString url = p == nullptr ? QString() : p->getProjectURL();
688     if (!url.isEmpty()) {
689         prependToRecentProjects(url);
690     }
691     updateRecentProjectsMenu();
692 }
693 
isProjectFileUrl(const GUrl & url)694 bool ProjectLoaderImpl::isProjectFileUrl(const GUrl &url) {
695     return url.lastFileSuffix() == PROJECT_FILE_PURE_EXT;
696 }
697 
sl_serviceStateChanged(Service * s,ServiceState prevState)698 void ProjectLoaderImpl::sl_serviceStateChanged(Service *s, ServiceState prevState) {
699     Q_UNUSED(prevState);
700 
701     if (s->getType() != Service_Project) {
702         return;
703     }
704     if (s->isEnabled()) {
705         Project *p = AppContext::getProject();
706         connect(p, SIGNAL(si_projectURLChanged(const QString &)), SLOT(sl_projectURLChanged(const QString &)));
707         connect(p, SIGNAL(si_documentAdded(Document *)), SLOT(sl_documentAdded(Document *)));
708     }
709     rememberProjectURL();
710     updateState();
711 }
712 
getLastProjectURL()713 QString ProjectLoaderImpl::getLastProjectURL() {
714     QStringList recentFiles = AppContext::getSettings()->getValue(SETTINGS_DIR + RECENT_PROJECTS_SETTINGS_NAME).toStringList();
715     if (!recentFiles.isEmpty()) {
716         return recentFiles.first();
717     }
718     return QString();
719 }
720 
prependToRecentItems(const QString & url)721 void ProjectLoaderImpl::prependToRecentItems(const QString &url) {
722     SAFE_POINT(!url.isEmpty(), "Invalid URL string!", );
723     CHECK(GUrl(url).isLocalFile(), );
724     QStringList recentFiles = AppContext::getSettings()->getValue(SETTINGS_DIR + RECENT_ITEMS_SETTINGS_NAME).toStringList();
725     recentFiles.removeAll(url);
726     recentFiles.prepend(url);
727     while (recentFiles.size() > MAX_RECENT_FILES) {
728         recentFiles.pop_back();
729     }
730     AppContext::getSettings()->setValue(SETTINGS_DIR + RECENT_ITEMS_SETTINGS_NAME, recentFiles);
731     emit si_recentListChanged();
732 }
733 
updateRecentItemsMenu()734 void ProjectLoaderImpl::updateRecentItemsMenu() {
735     SAFE_POINT(recentItemsMenu != nullptr, "updateRecentItemsMenu is nullptr", );
736     recentItemsMenu->clear();
737     QStringList recentFiles = AppContext::getSettings()->getValue(SETTINGS_DIR + RECENT_ITEMS_SETTINGS_NAME).toStringList();
738     recentItemsMenu->menuAction()->setEnabled(!recentFiles.isEmpty());
739     Project *p = AppContext::getProject();
740     for (const QString &url : qAsConst(recentFiles)) {
741         if ((p == nullptr || url != p->getProjectURL()) && !url.isEmpty()) {
742             QAction *a = recentItemsMenu->addAction(url, this, SLOT(sl_openRecentFile()));
743             a->setData(url);
744         }
745     }
746 }
747 
sl_paste()748 void ProjectLoaderImpl::sl_paste() {
749     PasteFactory *pasteFactory = AppContext::getPasteFactory();
750     SAFE_POINT(pasteFactory != nullptr, "PasteFactory is null", );
751 
752     PasteTask *task = pasteFactory->createPasteTask(true);
753     CHECK(task != nullptr, );
754     AppContext::getTaskScheduler()->registerTopLevelTask(task);
755 }
756 
sl_documentAdded(Document * doc)757 void ProjectLoaderImpl::sl_documentAdded(Document *doc) {
758     bool doNotAddToRecent = doc->getGHints()->get(ProjectLoaderHint_DoNotAddToRecentDocuments, false).toBool();
759     if (!doc->isModified()) {
760         if (!doNotAddToRecent) {
761             prependToRecentItems(doc->getURLString());
762             updateRecentItemsMenu();
763         }
764     } else {
765         connect(doc, SIGNAL(si_modifiedStateChanged()), SLOT(sl_documentStateChanged()));
766     }
767     doc->getGHints()->remove(ProjectLoaderHint_DoNotAddToRecentDocuments);
768 }
769 
sl_documentStateChanged()770 void ProjectLoaderImpl::sl_documentStateChanged() {
771     Document *doc = qobject_cast<Document *>(QObject::sender());
772     if (doc != nullptr) {
773         if (!doc->isModified()) {
774             prependToRecentItems(doc->getURLString());
775             updateRecentItemsMenu();
776         }
777     }
778 }
779 
sl_newDocumentFromText()780 void ProjectLoaderImpl::sl_newDocumentFromText() {
781     QWidget *p = (QWidget *)AppContext::getMainWindow()->getQMainWindow();
782     QObjectScopedPointer<CreateDocumentFromTextDialogController> dialog = new CreateDocumentFromTextDialogController(p);
783     dialog->exec();
784 }
785 
sl_downloadRemoteFile()786 void ProjectLoaderImpl::sl_downloadRemoteFile() {
787     QWidget *p = (QWidget *)(AppContext::getMainWindow()->getQMainWindow());
788     QObjectScopedPointer<DownloadRemoteFileDialog> dlg = new DownloadRemoteFileDialog(p);
789     dlg->exec();
790 }
791 
sl_accessSharedDatabase()792 void ProjectLoaderImpl::sl_accessSharedDatabase() {
793     QWidget *p = (QWidget *)(AppContext::getMainWindow()->getQMainWindow());
794     QObjectScopedPointer<SharedConnectionsDialog> dlg = new SharedConnectionsDialog(p);
795     dlg->exec();
796 }
797 
sl_searchGenbankEntry()798 void ProjectLoaderImpl::sl_searchGenbankEntry() {
799     QWidget *p = (QWidget *)(AppContext::getMainWindow()->getQMainWindow());
800     QObjectScopedPointer<SearchGenbankSequenceDialogController> dlg = new SearchGenbankSequenceDialogController(p);
801     dlg->exec();
802 }
803 
getAddExistingDocumentAction()804 QAction *ProjectLoaderImpl::getAddExistingDocumentAction() {
805     return addExistingDocumentAction;
806 }
807 
runOpenRecentFileOrProjectTask(const GUrl & url)808 void ProjectLoaderImpl::runOpenRecentFileOrProjectTask(const GUrl &url) {
809     const QString &urlString = url.getURLString();
810     if (url.isLocalFile()) {
811         QFileInfo fileInfo(urlString);
812         QString question = !fileInfo.exists() ? tr("The path %1 does not exist.").arg(fileInfo.absoluteFilePath())
813                                               : (!fileInfo.isReadable() ? tr("The path %1 is not readable.").arg(fileInfo.absoluteFilePath()) : "");
814         if (!question.isEmpty()) {
815             int rc = QMessageBox::question(nullptr, L10N::errorTitle(), question, tr("OK"), tr("Remove From List"));
816             if (rc == 1) {  // The second button (Remove From List).
817                 removeUrlFromRecentItems(url);
818             }
819             return;
820         }
821     }
822     auto task = openWithProjectTask({url});
823     if (task != nullptr) {
824         AppContext::getTaskScheduler()->registerTopLevelTask(task);
825     }
826     if (isProjectFileUrl(url)) {
827         prependToRecentProjects(urlString);
828     } else {
829         prependToRecentItems(urlString);
830     }
831     updateRecentItemsMenu();
832 }
833 
834 //////////////////////////////////////////////////////////////////////////
835 // WelcomePageActions
836 //////////////////////////////////////////////////////////////////////////
LoadDataWelcomePageAction(ProjectLoaderImpl * loader)837 LoadDataWelcomePageAction::LoadDataWelcomePageAction(ProjectLoaderImpl *loader)
838     : WelcomePageAction(BaseWelcomePageActions::LOAD_DATA), loader(loader) {
839 }
840 
perform()841 void LoadDataWelcomePageAction::perform() {
842     SAFE_POINT(!loader.isNull(), L10N::nullPointerError("Project Loader"), );
843     loader->sl_openProject();
844 }
845 
CreateSequenceWelcomePageAction(ProjectLoaderImpl * loader)846 CreateSequenceWelcomePageAction::CreateSequenceWelcomePageAction(ProjectLoaderImpl *loader)
847     : WelcomePageAction(BaseWelcomePageActions::CREATE_SEQUENCE), loader(loader) {
848 }
849 
perform()850 void CreateSequenceWelcomePageAction::perform() {
851     SAFE_POINT(!loader.isNull(), L10N::nullPointerError("Project Loader"), );
852     loader->sl_newDocumentFromText();
853 }
854 
855 //////////////////////////////////////////////////////////////////////////
856 // SaveProjectDialogController
857 //////////////////////////////////////////////////////////////////////////
858 
SaveProjectDialogController(QWidget * w)859 SaveProjectDialogController::SaveProjectDialogController(QWidget *w)
860     : QDialog(w), Ui_SaveProjectDialog() {
861     setupUi(this);
862     setModal(true);
863     buttonBox->button(QDialogButtonBox::Yes)->setText(tr("Yes"));
864     buttonBox->button(QDialogButtonBox::No)->setText(tr("No"));
865     buttonBox->button(QDialogButtonBox::Cancel)->setText(tr("Cancel"));
866 
867     connect(buttonBox, SIGNAL(clicked(QAbstractButton *)), this, SLOT(sl_clicked(QAbstractButton *)));
868 }
869 
sl_clicked(QAbstractButton * button)870 void SaveProjectDialogController::sl_clicked(QAbstractButton *button) {
871     done(buttonBox->standardButton(button));
872 }
873 
874 //////////////////////////////////////////////////////////////////////////
875 // ProjectDialogController
876 //////////////////////////////////////////////////////////////////////////
ProjectDialogController(ProjectDialogController::Mode m,QWidget * p)877 ProjectDialogController::ProjectDialogController(ProjectDialogController::Mode m, QWidget *p)
878     : QDialog(p), Ui_CreateNewProjectDialog() {
879     setupUi(this);
880     new HelpButton(this, buttonBox, "65929273");
881     buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Create"));
882     buttonBox->button(QDialogButtonBox::Cancel)->setText(tr("Cancel"));
883 
884     createButton = buttonBox->button(QDialogButtonBox::Ok);
885     setModal(true);
886     fileEditIsEmpty = false;
887 
888     if (m == Save_Project) {
889         setWindowTitle(ProjectLoaderImpl::tr("Save project as"));
890         createButton->setText(ProjectLoaderImpl::tr("Save"));
891         projectNameEdit->setText(AppContext::getProject()->getProjectName());
892         QString url = AppContext::getProject()->getProjectURL();
893         if (!url.isEmpty()) {
894             QFileInfo fi(url);
895             projectFilePathEdit->setText(fi.absoluteFilePath());
896         } else {
897             setupDefaults();
898         }
899     } else {
900         setupDefaults();
901     }
902     // projectFolderEdit->setReadOnly(true);
903     if (projectFilePathEdit->text().isEmpty()) {
904         fileEditIsEmpty = true;
905     }
906     connect(fileSelectButton, SIGNAL(clicked()), SLOT(sl_fileSelectClicked()));
907     connect(projectFilePathEdit, SIGNAL(textEdited(const QString &)), SLOT(sl_fileNameEdited(const QString &)));
908     connect(projectNameEdit, SIGNAL(textEdited(const QString &)), SLOT(sl_projectNameEdited(const QString &)));
909     updateState();
910 }
911 
updateState()912 void ProjectDialogController::updateState() {
913     bool ready = true;
914 
915     const QString &file = projectFilePathEdit->text();
916     const QString &name = projectNameEdit->text();
917 
918     // todo: improve check
919     if (file.isEmpty() || name.isEmpty()) {
920         ready = false;
921     }
922 
923     createButton->setEnabled(ready);
924 }
925 
keyPressEvent(QKeyEvent * event)926 void ProjectDialogController::keyPressEvent(QKeyEvent *event) {
927     int key = event->key();
928     if (event->modifiers() == Qt::NoModifier && (key == Qt::Key_Enter || key == Qt::Key_Return)) {
929         createButton->animateClick();
930     } else {
931         QDialog::keyPressEvent(event);
932     }
933 }
934 
sl_fileSelectClicked()935 void ProjectDialogController::sl_fileSelectClicked() {
936     QString filepath = U2FileDialog::getSaveFileName(this, tr("Save file"), AppContext::getSettings()->getValue(SETTINGS_DIR + "last_dir").toString(), tr("Project files") + DIALOG_FILTER_PROJECT_EXT);
937     if (filepath.isEmpty())
938         return;
939     projectFilePathEdit->setText(filepath);
940     updateState();
941 }
942 
sl_fileNameEdited(const QString &)943 void ProjectDialogController::sl_fileNameEdited(const QString &) {
944     // TODO: warn about overwrite
945     fileEditIsEmpty = false;
946     updateState();
947 }
948 
sl_projectNameEdited(const QString & text)949 void ProjectDialogController::sl_projectNameEdited(const QString &text) {
950     if (fileEditIsEmpty) {
951         projectFilePathEdit->setText(text);
952     }
953     updateState();
954 }
955 
setupDefaults()956 void ProjectDialogController::setupDefaults() {
957     const QString defaultPath = QFileInfo(GUrlUtils::getDefaultDataPath(), "project" + PROJECTFILE_EXT).absoluteFilePath();
958     projectNameEdit->setText(ProjectLoaderImpl::tr("New Project"));
959     projectFilePathEdit->setText(defaultPath);
960 }
961 
accept()962 void ProjectDialogController::accept() {
963     QString projectPath = getProjectFilePathFromPathEdit(projectFilePathEdit);
964 
965     // Check that dir path is valid
966     U2OpStatus2Log os;
967     QString projectDir = GUrlUtils::prepareDirLocation(QFileInfo(projectPath).absoluteDir().absolutePath(), os);
968     if (projectDir.isEmpty()) {
969         assert(os.hasError());
970         QMessageBox::critical(this, this->windowTitle(), os.getError());
971         return;
972     }
973 
974     if (QFileInfo(projectPath).exists()) {
975         int rc = QMessageBox::question(this, windowTitle(), tr("<html><body align=\"center\"><br>Project file already exists.<br>Are you sure you want to overwrite it?<body></html>"), QMessageBox::Yes, QMessageBox::No);
976         if (rc != QMessageBox::Yes) {
977             return;
978         }
979     }
980     QDialog::accept();
981 }
982 
createProject(const QString & name,const QString & url,QList<Document * > & documents,QList<GObjectViewState * > & states)983 Project *ProjectLoaderImpl::createProject(const QString &name, const QString &url, QList<Document *> &documents, QList<GObjectViewState *> &states) {
984     ProjectImpl *pi = new ProjectImpl(name, url, documents, states);
985     return pi;
986 }
987 
sl_onAddExistingDocument()988 void ProjectLoaderImpl::sl_onAddExistingDocument() {
989     LastUsedDirHelper h;
990     QString filter = DialogUtils::prepareDocumentsFileFilter(true);
991     QString file = U2FileDialog::getOpenFileName(nullptr, tr("Select files to open"), h.dir, filter);
992     if (file.isEmpty()) {
993         return;
994     }
995     if (QFileInfo(file).exists()) {
996         h.url = file;
997     }
998     QList<GUrl> urls;
999     urls << GUrl(file, GUrl_File);
1000     QVariantMap hints;
1001     hints[ProjectLoaderHint_ForceFormatOptions] = true;
1002     Task *openTask = AppContext::getProjectLoader()->openWithProjectTask(urls, hints);
1003     if (openTask != nullptr) {
1004         AppContext::getTaskScheduler()->registerTopLevelTask(openTask);
1005     }
1006 }
1007 
1008 //////////////////////////////////////////////////////////////////////////
1009 // Add documents to project task
1010 
AddDocumentsToProjectTask(const QList<AD2P_DocumentInfo> & _docsInfo,const QList<AD2P_ProviderInfo> & _provInfo)1011 AddDocumentsToProjectTask::AddDocumentsToProjectTask(const QList<AD2P_DocumentInfo> &_docsInfo, const QList<AD2P_ProviderInfo> &_provInfo)
1012     : Task(tr("Loading documents"), TaskFlags_NR_FOSE_COSC | TaskFlag_CollectChildrenWarnings), docsInfo(_docsInfo), providersInfo(_provInfo), loadTasksAdded(false) {
1013     setMaxParallelSubtasks(MAX_PARALLEL_SUBTASKS_AUTO);
1014 
1015     Project *p = AppContext::getProject();
1016     if (!p) {
1017         // create anonymous project
1018         Task *rpt = AppContext::getProjectLoader()->createNewProjectTask();
1019         rpt->setSubtaskProgressWeight(0);
1020         addSubTask(rpt);
1021     } else {
1022         QList<Task *> tasks = prepareLoadTasks();
1023         foreach (Task *t, tasks) {
1024             addSubTask(t);
1025         }
1026         loadTasksAdded = true;
1027     }
1028 }
1029 
~AddDocumentsToProjectTask()1030 AddDocumentsToProjectTask::~AddDocumentsToProjectTask() {
1031     if (!loadTasksAdded) {
1032         foreach (const AD2P_ProviderInfo &info, providersInfo) {
1033             delete info.dp;
1034         }
1035     }
1036 }
1037 
onSubTaskFinished(Task * t)1038 QList<Task *> AddDocumentsToProjectTask::onSubTaskFinished(Task *t) {
1039     QList<Task *> res;
1040     if (!loadTasksAdded) {
1041         res = prepareLoadTasks();
1042         loadTasksAdded = true;
1043     } else if (t->hasError()) {
1044         coreLog.error(t->getError());
1045     } else if (t->hasWarning()) {
1046         setReportingSupported(true);
1047         setReportingEnabled(true);
1048     }
1049     foreach (Document *d, docsToMarkAsModified) {
1050         if (d->isLoaded() && !d->isModified()) {
1051             d->setModified(true);
1052             docsToMarkAsModified.removeOne(d);
1053         }
1054     }
1055     return res;
1056 }
1057 
generateReport() const1058 QString AddDocumentsToProjectTask::generateReport() const {
1059     SAFE_POINT(stateInfo.hasWarnings(), L10N::internalError("No warnings to show"), "");
1060     QString warnings = stateInfo.getWarnings().join("<br>");
1061     warnings.replace("\n", "<br>");
1062     return warnings;
1063 }
1064 
prepareLoadTasks()1065 QList<Task *> AddDocumentsToProjectTask::prepareLoadTasks() {
1066     QList<Task *> res;
1067 
1068     Project *p = AppContext::getProject();
1069     SAFE_POINT(p != nullptr, tr("No active project found!"), res);
1070 
1071     foreach (const AD2P_DocumentInfo &info, docsInfo) {
1072         Document *doc = p->findDocumentByURL(info.url);
1073         bool unsupportedObjectType = false;
1074         if (doc == nullptr) {
1075             DocumentFormat *df = AppContext::getDocumentFormatRegistry()->getFormatById(info.formatId);
1076             GObjectType t = df->getSupportedObjectTypes().toList().first();
1077             if (GObjectTypes::getTypeInfo(t).type == GObjectTypes::UNKNOWN) {
1078                 unsupportedObjectType = true;
1079             }
1080             U2OpStatus2Log os;
1081             doc = df->createNewUnloadedDocument(info.iof, info.url, os, info.hints);
1082             if (doc == nullptr) {
1083                 continue;
1084             }
1085             if (info.markLoadedAsModified) {
1086                 docsToMarkAsModified << doc;
1087             }
1088         }
1089         if (unsupportedObjectType) {
1090             if (info.openView) {
1091                 res << new LoadUnloadedDocumentAndOpenViewTask(doc);
1092             } else {
1093                 coreLog.trace(QString("View limit exceed for the document: %1").arg(info.url.getURLString()));
1094                 delete doc;
1095             }
1096         } else {
1097             if (info.openView) {
1098                 res << new AddDocumentAndOpenViewTask(doc);
1099             } else {
1100                 Task *addDocTask = new AddDocumentTask(doc);
1101                 if (info.loadDocuments) {
1102                     QList<Task *> tasks;
1103                     tasks << addDocTask;
1104                     tasks << new LoadUnloadedDocumentTask(doc);
1105                     SequentialMultiTask *multiTask = new SequentialMultiTask(tr("Load document and add to project: %1").arg(doc->getName()), tasks);
1106                     res << multiTask;
1107                 } else {
1108                     res << addDocTask;
1109                 }
1110             }
1111         }
1112     }
1113 
1114     AddDocumentTaskConfig conf;
1115     conf.unloadExistingDocument = true;  // -> re-import kills old document version
1116     foreach (const AD2P_ProviderInfo &info, providersInfo) {
1117         if (info.openView) {
1118             res << new AddDocumentAndOpenViewTask(info.dp, conf);
1119         } else {
1120             res << new AddDocumentTask(info.dp, conf);
1121         }
1122     }
1123 
1124     return res;
1125 }
1126 
OpenWithProjectTask(const QStringList & _urls)1127 OpenWithProjectTask::OpenWithProjectTask(const QStringList &_urls)
1128     : Task(tr(""), TaskFlags_NR_FOSCOE) {
1129     foreach (const QString &u, _urls) {
1130         urls << GUrl(u);
1131     }
1132 
1133     if (urls.size() == 1) {
1134         setTaskName(tr("Opening document: %1").arg(urls.first().getURLString()));
1135     } else {
1136         setTaskName(tr("Opening %1 documents").arg(urls.size()));
1137     }
1138 }
1139 
prepare()1140 void OpenWithProjectTask::prepare() {
1141     Task *t = AppContext::getProjectLoader()->openWithProjectTask(urls);
1142     if (t != nullptr) {
1143         addSubTask(t);
1144     }
1145 }
1146 
1147 }  // namespace U2
1148