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