Merge pull request #23585 from chennes/extractVersionMigrator

Gui: Improve version migration UX
This commit is contained in:
sliptonic
2025-09-20 10:01:40 -05:00
committed by GitHub
10 changed files with 618 additions and 154 deletions

View File

@@ -41,6 +41,8 @@
#include <Python.h>
#include <QString>
#include "Base/Console.h"
using namespace App;
namespace fs = std::filesystem;
@@ -576,6 +578,7 @@ void ApplicationDirectories::migrateAllPaths(const std::vector<fs::path> &paths)
} else {
newPath = path / versionStringForPath(major, minor);
}
Base::Console().message("Migrating config from %s to %s\n", Base::FileInfo::pathToString(path), Base::FileInfo::pathToString(newPath));
if (fs::exists(newPath)) {
continue; // Ignore an existing path: not an error, just a migration that was already done
}

View File

@@ -448,6 +448,7 @@ SET(Gui_UIC_SRCS
Dialogs/DlgPropertyLink.ui
Dialogs/DlgRevertToBackupConfig.ui
Dialogs/DlgThemeEditor.ui
Dialogs/DlgVersionMigrator.ui
PreferencePages/DlgSettings3DView.ui
PreferencePages/DlgSettingsCacheDirectory.ui
Dialogs/DlgSettingsColorGradient.ui
@@ -564,6 +565,7 @@ SET(Dialog_CPP_SRCS
Dialogs/DlgRevertToBackupConfigImp.cpp
Dialogs/DlgExpressionInput.cpp
Dialogs/DlgThemeEditor.cpp
Dialogs/DlgVersionMigrator.cpp
TaskDlgRelocation.cpp
Dialogs/DlgCheckableMessageBox.cpp
TaskTransform.cpp
@@ -606,6 +608,7 @@ SET(Dialog_HPP_SRCS
Dialogs/DlgCheckableMessageBox.h
Dialogs/DlgExpressionInput.h
Dialogs/DlgThemeEditor.h
Dialogs/DlgVersionMigrator.h
TaskDlgRelocation.h
TaskTransform.h
Dialogs/DlgUndoRedo.h
@@ -652,6 +655,7 @@ SET(Dialog_SRCS
Dialogs/DlgTreeWidget.ui
Dialogs/DlgExpressionInput.ui
Dialogs/DlgCreateNewPreferencePack.ui
Dialogs/DlgVersionMigrator.ui
DownloadManager.ui
DownloadItem.ui
DocumentRecovery.ui

View File

@@ -0,0 +1,371 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
/***************************************************************************
* Copyright (c) 2025 The FreeCAD project association AISBL *
* *
* This file is part of FreeCAD. *
* *
* FreeCAD is free software: you can redistribute it and/or modify it *
* under the terms of the GNU Lesser General Public License as *
* published by the Free Software Foundation, either version 2.1 of the *
* License, or (at your option) any later version. *
* *
* FreeCAD is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with FreeCAD. If not, see *
* <https://www.gnu.org/licenses/>. *
* *
**************************************************************************/
#include <QDesktopServices>
#include <QDialog>
#include <QMenu>
#include <QMessageBox>
#include <QOpenGLFunctions>
#include <QProcess>
#include <QThread>
#include <QTimer>
#include <QToolButton>
#include <QWindow>
#include <fstream>
#include <memory>
#include <set>
#include <string>
#include <ranges>
#include "DlgVersionMigrator.h"
#include "ui_DlgVersionMigrator.h"
#include "../MainWindow.h"
#include <App/Application.h>
#include <App/ApplicationDirectories.h>
#include "QTextBrowser"
using namespace Gui::Dialog;
namespace fs = std::filesystem;
bool isCurrentVersionKnown()
{
std::set<fs::path> paths = {
App::Application::directories()->getUserAppDataDir(),
App::Application::directories()->getUserConfigPath()
};
int major = std::stoi(App::Application::Config()["BuildVersionMajor"]);
int minor = std::stoi(App::Application::Config()["BuildVersionMinor"]);
std::string currentVersionedDirName = App::ApplicationDirectories::versionStringForPath(major, minor);
for (auto &path : paths) {
if (App::Application::directories()->usingCurrentVersionConfig(path)) {
return true;
}
fs::path markerPath = path;
if (App::Application::directories()->isVersionedPath(path)) {
markerPath = path.parent_path();
}
markerPath /= currentVersionedDirName + ".do_not_migrate";
if (fs::exists(markerPath)) {
return true;
}
}
return false;
}
void markCurrentVersionAsDoNotMigrate() {
std::set<fs::path> paths = {
App::Application::directories()->getUserAppDataDir(),
App::Application::directories()->getUserConfigPath()
};
int major = std::stoi(App::Application::Config()["BuildVersionMajor"]);
int minor = std::stoi(App::Application::Config()["BuildVersionMinor"]);
std::string currentVersionedDirName = App::ApplicationDirectories::versionStringForPath(major, minor);
for (auto &path : paths) {
if (App::Application::directories()->usingCurrentVersionConfig(path)) {
// No action to take: the migration is done, so this call doesn't need to do anything
continue;
}
fs::path markerPath = path;
if (App::Application::directories()->isVersionedPath(path)) {
markerPath = path.parent_path();
}
markerPath /= currentVersionedDirName + ".do_not_migrate";
std::ofstream markerFile(markerPath);
if (!markerFile.is_open()) {
Base::Console().error("Unable to open marker file %s\n", Base::FileInfo::pathToString(markerPath).c_str());
continue;
}
markerFile << "Migration to version " << currentVersionedDirName << " was declined. "
<< "To request migration again, delete this file.";
markerFile.close();
}
}
DlgVersionMigrator::DlgVersionMigrator(MainWindow *mw) :
QDialog(mw)
, mainWindow(mw)
, sizeCalculationWorkerThread(nullptr)
, ui(std::make_unique<Ui_DlgVersionMigrator>())
{
ui->setupUi(this);
auto prefGroup = App::GetApplication().GetParameterGroupByPath(
"User parameter:BaseApp/Preferences/Migration");
int major = std::stoi(App::Application::Config()["BuildVersionMajor"]);
int minor = std::stoi(App::Application::Config()["BuildVersionMinor"]);
auto programName = QString::fromStdString(App::Application::getExecutableName());
// NOTE: All rich-text strings are generated programmatically so that translators don't have to deal with the
// markup. The two strings in the middle of the dialog are set in the UI file.
auto programNameString = tr("Welcome to %1 %2.%3\n\n").arg(
programName, QString::number(major), QString::number(minor));
auto welcomeString = QStringLiteral("<b>") + programNameString + QStringLiteral("</b>");
auto calculatingSizeString = QStringLiteral("<b>") + tr("Calculating size…") + QStringLiteral("</b>");
auto shareConfigurationString = QStringLiteral("<a href='#shareConfiguration'>") +
tr("Share configuration between versions") + QStringLiteral("</a>");
setWindowTitle(programNameString);
#ifdef Q_OS_MACOS
// macOS does not show the window title on modals, so add the extra label
ui->welcomeLabel->setText(welcomeString);
#else
ui->welcomeLabel->hide();
#endif
ui->sizeLabel->setText(calculatingSizeString);
connect(ui->copyButton, &QPushButton::clicked, this, &DlgVersionMigrator::migrate);
connect(ui->helpButton, &QPushButton::clicked, this, &DlgVersionMigrator::help);
// Set up the menu actions for the two hidden options
auto *menu = new QMenu(ui->menuButton);
QAction *share = menu->addAction(tr("Share configuration with previous version"));
QAction *reset = menu->addAction(tr("Use a new default configuration"));
ui->menuButton->setMenu(menu);
ui->menuButton->setPopupMode(QToolButton::InstantPopup);
ui->menuButton->setStyleSheet(QStringLiteral("QToolButton::menu-indicator { image: none; width: 0px; }"));
ui->menuButton->setProperty("flat", true);
connect(share, &QAction::triggered, this, &DlgVersionMigrator::share);
connect(reset, &QAction::triggered, this, &DlgVersionMigrator::freshStart);
}
DlgVersionMigrator::~DlgVersionMigrator() = default;
int DlgVersionMigrator::exec() {
// If the user is running a custom directory set, there is no migration to versioned directories
if (App::Application::directories()->usingCustomDirectories()) {
return 0;
}
if (!isCurrentVersionKnown()) {
calculateMigrationSize();
QDialog::exec();
if (sizeCalculationWorkerThread && sizeCalculationWorkerThread->isRunning()) {
sizeCalculationWorkerThread->quit();
}
}
return 0;
}
class DirectorySizeCalculationWorker : public QObject {
Q_OBJECT
public:
void run() {
auto dir = App::Application::directories()->getUserAppDataDir();
uintmax_t size = 0;
auto thisThread = QThread::currentThread();
for (auto &entry: fs::recursive_directory_iterator(dir)) {
if (thisThread->isInterruptionRequested()) {
Q_EMIT(cancelled());
Q_EMIT(finished());
return;
}
if (fs::is_regular_file(entry.status())) {
size += fs::file_size(entry.path());
}
}
Q_EMIT(sizeFound(size));
Q_EMIT(finished());
}
Q_SIGNALS:
void finished();
void sizeFound(uintmax_t _t1);
void cancelled();
};
class PathMigrationWorker : public QObject {
Q_OBJECT
public:
void run() {
try {
App::GetApplication().GetUserParameter().SaveDocument();
App::Application::directories()->migrateAllPaths(
{
App::Application::directories()->getUserAppDataDir(),
App::Application::directories()->getUserConfigPath()
});
Q_EMIT(complete());
} catch (const Base::Exception &e) {
Base::Console().error("Error migrating configuration data: %s\n", e.what());
Q_EMIT(failed());
} catch (const std::exception &e) {
Base::Console().error("Unrecognized error migrating configuration data: %s\n", e.what());
Q_EMIT(failed());
} catch (...) {
Base::Console().error("Error migrating configuration data\n");
Q_EMIT(failed());
}
Q_EMIT(finished());
}
Q_SIGNALS:
void finished();
void complete();
void failed();
};
void DlgVersionMigrator::calculateMigrationSize() {
sizeCalculationWorkerThread = new QThread(mainWindow);
auto *worker = new DirectorySizeCalculationWorker();
worker->moveToThread(sizeCalculationWorkerThread);
connect(sizeCalculationWorkerThread, &QThread::started, worker, &DirectorySizeCalculationWorker::run);
connect(worker, &DirectorySizeCalculationWorker::sizeFound, this, &DlgVersionMigrator::showSizeOfMigration);
connect(worker, &DirectorySizeCalculationWorker::finished, sizeCalculationWorkerThread, &QThread::quit);
connect(worker, &DirectorySizeCalculationWorker::finished, worker, &QObject::deleteLater);
connect(sizeCalculationWorkerThread, &QThread::finished, sizeCalculationWorkerThread, &QObject::deleteLater);
sizeCalculationWorkerThread->start();
}
void DlgVersionMigrator::share()
{
markCurrentVersionAsDoNotMigrate();
if (sizeCalculationWorkerThread && sizeCalculationWorkerThread->isRunning()) {
sizeCalculationWorkerThread->quit();
}
close();
}
void DlgVersionMigrator::showSizeOfMigration(uintmax_t size) {
auto sizeString = QLocale().formattedDataSize(static_cast<qint64>(size));
auto sizeMessage = QStringLiteral("<b>") +
QObject::tr("Estimated size of data to copy: %1").arg(sizeString) +
QStringLiteral("</b>");
ui->sizeLabel->setText(sizeMessage);
sizeCalculationWorkerThread = nullptr; // Deleted via a previously-configured deleteLater()
}
void DlgVersionMigrator::migrate() {
hide();
auto *workerThread = new QThread(mainWindow);
auto *worker = new PathMigrationWorker();
worker->moveToThread(workerThread);
connect(workerThread, &QThread::started, worker, &PathMigrationWorker::run);
connect(worker, &PathMigrationWorker::finished, workerThread, &QThread::quit);
connect(worker, &PathMigrationWorker::finished, worker, &QObject::deleteLater);
connect(workerThread, &QThread::finished, workerThread, &QObject::deleteLater);
auto migrationRunning = new QMessageBox(this);
migrationRunning->setWindowTitle(QObject::tr("Migrating"));
migrationRunning->setText(QObject::tr("Migrating configuration data and addons…"));
migrationRunning->setStandardButtons(QMessageBox::NoButton);
connect(worker, &PathMigrationWorker::complete, migrationRunning, &QMessageBox::accept);
connect(worker, &PathMigrationWorker::failed, migrationRunning, &QMessageBox::reject);
workerThread->start();
migrationRunning->exec();
if (migrationRunning->result() == QDialog::Accepted) {
restart(tr("Migration complete"));
} else {
QMessageBox::critical(mainWindow, QObject::tr("Migration failed"),
QObject::tr("Migration failed. See the Report View for details."));
}
}
void DlgVersionMigrator::freshStart()
{
// Create the versioned directories, but don't put anything in them
std::set<fs::path> paths = {
App::Application::directories()->getUserAppDataDir(),
App::Application::directories()->getUserConfigPath()
};
int major = std::stoi(App::Application::Config()["BuildVersionMajor"]);
int minor = std::stoi(App::Application::Config()["BuildVersionMinor"]);
std::string currentVersionedDirName = App::ApplicationDirectories::versionStringForPath(major, minor);
for (auto &path : paths) {
if (App::Application::directories()->usingCurrentVersionConfig(path)) {
continue;
}
fs::path versionDir = path;
if (App::Application::directories()->isVersionedPath(path)) {
versionDir = path.parent_path();
}
versionDir /= currentVersionedDirName;
if (fs::exists(versionDir)) {
continue;
}
fs::create_directory(versionDir);
}
restart(tr("New default configuration created"));
}
void DlgVersionMigrator::help()
{
auto helpPage = QStringLiteral("https://wiki.freecad.org/Version_migration");
QDesktopServices::openUrl(QUrl(helpPage));
}
void DlgVersionMigrator::restart(const QString &message)
{
App::GetApplication().GetUserParameter().SaveDocument(); // Flush to disk before restarting
auto *restarting = new QMessageBox(this);
restarting->setText(
message + QObject::tr(" → Restarting…"));
restarting->setWindowTitle(QObject::tr("Restarting"));
restarting->setStandardButtons(QMessageBox::NoButton);
auto closeNotice = [restarting]() {
restarting->reject();
};
// Insert a short delay before restart so the user can see the success message and
// knows it's a restart and not a crash...
constexpr int delayRestartMillis{2000};
QTimer::singleShot(delayRestartMillis, closeNotice);
restarting->exec();
connect(qApp, &QCoreApplication::aboutToQuit, [=] {
if (getMainWindow()->close()) {
auto args = QApplication::arguments();
args.removeFirst();
QProcess::startDetached(QApplication::applicationFilePath(),
args,
QApplication::applicationDirPath());
}
});
QCoreApplication::exit(0);
_exit(0); // No really. Die.
}
#include "DlgVersionMigrator.moc"

View File

@@ -0,0 +1,70 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
/***************************************************************************
* Copyright (c) 2025 The FreeCAD project association AISBL *
* *
* This file is part of FreeCAD. *
* *
* FreeCAD is free software: you can redistribute it and/or modify it *
* under the terms of the GNU Lesser General Public License as *
* published by the Free Software Foundation, either version 2.1 of the *
* License, or (at your option) any later version. *
* *
* FreeCAD is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with FreeCAD. If not, see *
* <https://www.gnu.org/licenses/>. *
* *
**************************************************************************/
#ifndef GUI_DIALOG_VERSIONMIGRATOR_H
#define GUI_DIALOG_VERSIONMIGRATOR_H
#include <FCGlobal.h>
#include <cstdint>
#include <memory>
#include <QDialog>
namespace Gui {
class MainWindow;
namespace Dialog {
class GuiExport DlgVersionMigrator final : public QDialog
{
Q_OBJECT
public:
explicit DlgVersionMigrator(MainWindow *mw);
~DlgVersionMigrator() override;
Q_DISABLE_COPY_MOVE(DlgVersionMigrator)
int exec() override;
protected Q_SLOTS:
void calculateMigrationSize(); // Async -> this starts the process and immediately returns
void showSizeOfMigration(uintmax_t size);
void migrate();
void share();
void freshStart();
void help();
private:
MainWindow* mainWindow;
QThread* sizeCalculationWorkerThread;
std::unique_ptr<class Ui_DlgVersionMigrator> ui;
void restart(const QString &message);
};
}
}
#endif // GUI_DIALOG_VERSIONMIGRATOR_H

View File

@@ -0,0 +1,133 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Gui::Dialog::DlgVersionMigrator</class>
<widget class="QDialog" name="Gui::Dialog::DlgVersionMigrator">
<property name="windowModality">
<enum>Qt::WindowModal</enum>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>556</width>
<height>233</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<property name="modal">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="welcomeLabel">
<property name="text">
<string>TextLabel</string>
</property>
<property name="textFormat">
<enum>Qt::AutoText</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="questionLabel">
<property name="text">
<string>Configuration data and addons from a previous program version were found. Migrate the configuration to a new directory for this version?</string>
</property>
<property name="textFormat">
<enum>Qt::PlainText</enum>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="explanationLabel">
<property name="text">
<string>Copying the configuration will ensure that any changes from the new version will not affect the previous installation. Sharing configuration between versions can cause problems and is not recommended.</string>
</property>
<property name="textFormat">
<enum>Qt::PlainText</enum>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="sizeLabel">
<property name="text">
<string>TextLabel</string>
</property>
<property name="textFormat">
<enum>Qt::AutoText</enum>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="helpButton">
<property name="text">
<string>Help</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="copyButton">
<property name="text">
<string>Copy Configuration (Recommended)</string>
</property>
<property name="default">
<bool>true</bool>
</property>
<property name="flat">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="menuButton">
<property name="text">
<string notr="true">⋮</string>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextOnly</enum>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@@ -30,7 +30,6 @@
#include <QApplication>
#include <QImageReader>
#include <QLabel>
#include <QMessageBox>
#include <QOpenGLContext>
#include <QOpenGLFunctions>
#include <QProcess>
@@ -52,8 +51,8 @@
#include "GuiApplication.h"
#include "MainWindow.h"
#include "Language/Translator.h"
#include "Dialogs/DlgVersionMigrator.h"
#include <App/Application.h>
#include <App/ApplicationDirectories.h>
#include <Base/Console.h>
@@ -231,7 +230,7 @@ void StartupPostProcess::execute()
showMainWindow();
activateWorkbench();
checkParameters();
runWelcomeScreen();
checkVersionMigration();
}
void StartupPostProcess::setWindowTitle()
@@ -557,145 +556,8 @@ void StartupPostProcess::checkParameters()
}
}
void StartupPostProcess::runWelcomeScreen()
{
// If the user is running a custom directory set, there is no migration to versioned directories
if (App::Application::directories()->usingCustomDirectories()) {
return;
}
auto prefGroup = App::GetApplication().GetParameterGroupByPath(
"User parameter:BaseApp/Preferences/Migration");
// Split our comma-separated list of already-migrated-to version directories into a set for easy
// searching
auto splitCommas = [](const std::string &input) {
std::set<std::string> result;
std::stringstream ss(input);
std::string token;
while (std::getline(ss, token, ',')) {
result.insert(token);
}
return result;
};
std::string offeredToMigrateToVersionedConfig =
prefGroup->GetASCII("OfferedToMigrateToVersionedConfig", "");
std::set<std::string> knownVersions;
if (!offeredToMigrateToVersionedConfig.empty()) {
knownVersions = splitCommas(offeredToMigrateToVersionedConfig);
}
auto joinCommas = [](const std::set<std::string>& s) {
std::ostringstream oss;
for (auto it = s.begin(); it != s.end(); ++it) {
if (it != s.begin()) {
oss << ',';
}
oss << *it;
}
return oss.str();
};
int major = std::stoi(App::Application::Config()["BuildVersionMajor"]);
int minor = std::stoi(App::Application::Config()["BuildVersionMinor"]);
std::string currentVersionedDirName = App::ApplicationDirectories::versionStringForPath(major, minor);
if (!knownVersions.contains(currentVersionedDirName)
&& !App::Application::directories()->usingCurrentVersionConfig(
App::Application::directories()->getUserAppDataDir())) {
auto programName = QString::fromStdString(App::Application::getExecutableName());
auto result = QMessageBox::question(
mainWindow,
QObject::tr("Welcome to %1 v%2.%3").arg(programName, QString::number(major), QString::number(minor)),
QObject::tr("Welcome to %1 v%2.%3\n\n").arg(programName, QString::number(major), QString::number(minor))
+ QObject::tr("Configuration data and addons from previous program version found. "
"Migrate the old configuration to this version?"),
QMessageBox::Yes | QMessageBox::No);
knownVersions.insert(currentVersionedDirName);
prefGroup->SetASCII("OfferedToMigrateToVersionedConfig", joinCommas(knownVersions));
if (result == QMessageBox::Yes) {
migrateToCurrentVersion();
}
}
void StartupPostProcess::checkVersionMigration() const {
auto migrator = new Dialog::DlgVersionMigrator (mainWindow);
migrator->exec();
migrator->deleteLater();
}
class PathMigrationWorker : public QObject
{
Q_OBJECT
public:
void run () {
try {
App::GetApplication().GetUserParameter().SaveDocument();
App::Application::directories()->migrateAllPaths(
{App::Application::getUserAppDataDir(), App::Application::getUserConfigPath()});
Q_EMIT(complete());
} catch (const Base::Exception& e) {
Base::Console().error("Error migrating configuration data: %s\n", e.what());
Q_EMIT(failed());
} catch (const std::exception& e) {
Base::Console().error("Unrecognized error migrating configuration data: %s\n", e.what());
Q_EMIT(failed());
} catch (...) {
Base::Console().error("Error migrating configuration data\n");
Q_EMIT(failed());
}
}
Q_SIGNALS:
void complete();
void failed();
};
void StartupPostProcess::migrateToCurrentVersion()
{
auto *workerThread = new QThread(mainWindow);
auto *worker = new PathMigrationWorker();
worker->moveToThread(workerThread);
QObject::connect(workerThread, &QThread::started, worker, &PathMigrationWorker::run);
auto migrationRunning = new QMessageBox(mainWindow);
migrationRunning->setWindowTitle(QObject::tr("Migrating"));
migrationRunning->setText(QObject::tr("Migrating configuration data and addons..."));
migrationRunning->setStandardButtons(QMessageBox::NoButton);
QObject::connect(worker, &PathMigrationWorker::complete, migrationRunning, &QMessageBox::accept);
QObject::connect(worker, &PathMigrationWorker::failed, migrationRunning, &QMessageBox::reject);
workerThread->start();
migrationRunning->exec();
if (migrationRunning->result() == QDialog::Accepted) {
auto* restarting = new QMessageBox(mainWindow);
restarting->setText(
QObject::tr("Migration complete. Restarting..."));
restarting->setWindowTitle(QObject::tr("Restarting"));
restarting->setStandardButtons(QMessageBox::NoButton);
auto closeNotice = [restarting]() {
restarting->reject();
};
// Insert a short delay before restart so the user can see the success message, and
// knows it's a restart and not a crash...
const int delayRestartMillis {2000};
QTimer::singleShot(delayRestartMillis, closeNotice);
restarting->exec();
QObject::connect(qApp, &QCoreApplication::aboutToQuit, [=] {
if (getMainWindow()->close()) {
auto args = QApplication::arguments();
args.removeFirst();
QProcess::startDetached(QApplication::applicationFilePath(),
args,
QApplication::applicationDirPath());
}
});
QCoreApplication::exit(0);
_exit(0); // No really. Die.
} else {
QMessageBox::critical(mainWindow, QObject::tr("Migration failed"),QObject::tr("Migration failed. See the Report View for details."));
}
}
#include "StartupProcess.moc"

View File

@@ -76,8 +76,7 @@ private:
void showMainWindow();
void activateWorkbench();
void checkParameters();
void runWelcomeScreen();
void migrateToCurrentVersion();
void checkVersionMigration() const;
private:
bool loadFromPythonModule = false;

View File

@@ -115,7 +115,9 @@ Main window
==================================================================================================*/
QMainWindow,
QDockWidget {
background-color: @PrimaryColor;/* main background color */}
background-color: @PrimaryColor;/* main background color */
}
QDialog {
background-color: @DialogBackgroundColor;
}
@@ -1174,13 +1176,23 @@ https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qpushbutton
--------------------------------------------------------------------------- */
QPushButton {
background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 @ButtonTopBackgroundColor,stop:1 @ButtonBottomBackgroundColor);
background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 @ButtonTopBackgroundColor, stop:1 @ButtonBottomBackgroundColor);
border: 1px solid @ButtonBorderColor;
border-radius: @InputFieldBorderRadius;
padding: 3px 12px;
min-width: 64px;
}
QPushButton:default {
background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 @DefaultButtonTopBackgroundColor, stop:1 @DefaultButtonBottomBackgroundColor);
border: 1px solid @DefaultButtonBorderColor;
}
QAbstractButton[flat="true"] {
background-color: transparent;
border: 1px solid transparent;
}
#CreateNewRow > QPushButton {
border-radius: @InputFieldBorderRadius;
}
@@ -1199,12 +1211,12 @@ QPushButton:checked {
background-color: qlineargradient(x1:0, y1:1, x2:0, y2:0, stop:0 @ButtonTopBackgroundColor, stop:0.6 @ButtonBottomBackgroundColor);
}
QPushButton:focus {
border: 1px solid @AccentColor;
QPushButton:focus, QAbstractButton[flat="true"]:focus {
border: 1px solid @AccentColor;
}
QPushButton:checked:disabled {
border: 1px solid @AccentColor;
border: 1px solid @AccentColor;
color: @TextDisabledColor;
outline: none;
}
@@ -1218,11 +1230,15 @@ QPushButton:hover {
background-color: @GeneralBackgroundHoverColor;
}
QPushButton:pressed {
QAbstractButton[flat="true"]:hover {
border: 1px solid @ButtonBorderColor;
}
QPushButton:pressed, QAbstractButton[flat="true"]:pressed {
border: 1px solid @GeneralBorderHoverColor;
}
QPushButton:selected {
QPushButton:selected, QAbstractButton[flat="true"]:selected {
border: 1px solid @GeneralBorderHoverColor;
}

View File

@@ -4,6 +4,9 @@ ButtonBorderColor: "@GeneralBorderColor"
ButtonBorderHooverColor: "@GeneralBorderColor"
ButtonBottomBackgroundColor: "@PrimaryColorLighten2"
ButtonTopBackgroundColor: "@PrimaryColorLighten3"
DefaultButtonBottomBackgroundColor: "blend(@ButtonBottomBackgroundColor, @AccentColor, 10)"
DefaultButtonTopBackgroundColor: "blend(@ButtonTopBackgroundColor, @AccentColor, 10)"
DefaultButtonBorderColor: "blend(@ButtonBorderColor, @AccentColor, 20)"
CheckBoxBackgroundColor: "@TextEditFieldBackgroundColor"
CheckBoxBorderColor: "@GeneralBorderColor"
DialogBackgroundColor: "@PrimaryColorLighten3"

View File

@@ -4,9 +4,12 @@ ButtonBorderColor: "@GeneralBorderColor"
ButtonBorderHooverColor: "@GeneralBorderColor"
ButtonBottomBackgroundColor: "@PrimaryColorDarken1"
ButtonTopBackgroundColor: "@PrimaryColorLighten3"
DefaultButtonBottomBackgroundColor: "blend(@ButtonBottomBackgroundColor, @AccentColor, 5)"
DefaultButtonTopBackgroundColor: "blend(@ButtonTopBackgroundColor, @AccentColor, 5)"
DefaultButtonBorderColor: "blend(@ButtonBorderColor, @AccentColor, 40)"
CheckBoxBackgroundColor: "@TextEditFieldBackgroundColor"
CheckBoxBorderColor: "@GeneralBorderColor"
DialogBackgroundColor: "@PrimaryColorLighten4"
DialogBackgroundColor: "@PrimaryColorLighten3"
GeneralAlternateBackgroundColor: "@PrimaryColor"
GeneralBackgroundColor: "@PrimaryColor"
GeneralBackgroundHoverColor: "@PrimaryColorLighten5"