Gui: Migration UI take 2

This commit is contained in:
Chris Hennes
2025-09-09 14:56:53 -05:00
parent 3dd12e5da1
commit 26ba6969f6
5 changed files with 226 additions and 116 deletions

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
@@ -1386,7 +1390,6 @@ SET(FreeCADGui_CPP_SRCS
StartupProcess.cpp
TransactionObject.cpp
ToolHandler.cpp
VersionMigrator.cpp
StyleParameters/Parser.cpp
StyleParameters/ParameterManager.cpp
)
@@ -1430,7 +1433,6 @@ SET(FreeCADGui_SRCS
StartupProcess.h
TransactionObject.h
ToolHandler.h
VersionMigrator.h
StyleParameters/Parser.h
StyleParameters/ParameterManager.h
)

View File

@@ -23,8 +23,7 @@
#include "PreCompiled.h"
#ifndef _PreComp_
#include <QApplication>
#include <QLabel>
#include <QDialog>
#include <QLocale>
#include <QMessageBox>
#include <QOpenGLFunctions>
@@ -33,32 +32,26 @@
#include <QTimer>
#include <QWindow>
#include <memory>
#include <set>
#include <string>
#include <ranges>
#endif
#include "VersionMigrator.h"
#include "DlgVersionMigrator.h"
#include "ui_DlgVersionMigrator.h"
#include "MainWindow.h"
#include "../MainWindow.h"
#include <App/Application.h>
#include <App/ApplicationDirectories.h>
#include "QTextBrowser"
using namespace Gui;
using namespace Gui::Dialog;
namespace fs = std::filesystem;
uintmax_t calculateDirectorySize(const fs::path &dir) {
uintmax_t size = 0;
for (auto &entry: fs::recursive_directory_iterator(dir)) {
if (fs::is_regular_file(entry.status())) {
size += fs::file_size(entry.path());
}
}
return size;
}
std::set<std::string> getKnownVersions() {
auto splitCommas = [](const std::string &input) {
std::set<std::string> result;
@@ -110,46 +103,63 @@ void markCurrentVersionAsKnown() {
setKnownVersions(knownVersions);
}
VersionMigrator::VersionMigrator(MainWindow *mw) : QObject(mw), mainWindow(mw) {
}
void VersionMigrator::execute() {
// If the user is running a custom directory set, there is no migration to versioned directories
if (App::Application::directories()->usingCustomDirectories()) {
return;
}
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");
// Split our comma-separated list of already-migrated-to version directories into a set for easy
// searching
std::string offeredToMigrateToVersionedConfig =
prefGroup->GetASCII("OfferedToMigrateToVersionedConfig", "");
std::set<std::string> knownVersions = getKnownVersions();
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 welcomeString = QStringLiteral("<b>") + QObject::tr("Welcome to %1 %2.%3\n\n").arg(
programName, QString::number(major), QString::number(minor)) + QStringLiteral("</b>");
auto calculatingSizeString = QStringLiteral("<b>") + QObject::tr("Calculating size…") + QStringLiteral("</b>");
auto shareConfigurationString = QStringLiteral("<a href='#shareConfiguration'>") +
QObject::tr("Share configuration between versions") + QStringLiteral("</a>");
ui->welcomeLabel->setText(welcomeString);
ui->sizeLabel->setText(calculatingSizeString);
ui->shareLinkLabel->setText(shareConfigurationString);
ui->shareLinkLabel->setTextInteractionFlags(Qt::TextBrowserInteraction);
connect(ui->copyButton, &QPushButton::clicked, this, &DlgVersionMigrator::migrateToCurrentVersion);
connect(ui->shareLinkLabel, &QLabel::linkActivated, this, &DlgVersionMigrator::doNotMigrate);
}
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;
}
std::set<std::string> knownVersions = getKnownVersions();
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 configuration to a new directory for this version? Answering 'No' will "
"continue to use the old directory. 'Yes' will copy it."),
QMessageBox::Yes | QMessageBox::No);
if (result == QMessageBox::Yes) {
confirmMigration();
} else {
// Don't ask again for this version
markCurrentVersionAsKnown();
if (!knownVersions.contains(currentVersionedDirName) && !App::Application::directories()->usingCurrentVersionConfig(
App::Application::directories()->getUserAppDataDir())) {
calculateMigrationSize();
auto result = QDialog::exec();
if (sizeCalculationWorkerThread && sizeCalculationWorkerThread->isRunning()) {
sizeCalculationWorkerThread->quit();
}
}
return 0;
}
class DirectorySizeCalculationWorker : public QObject {
@@ -216,58 +226,41 @@ Q_SIGNALS:
void failed();
};
void VersionMigrator::confirmMigration() {
auto *workerThread = new QThread(mainWindow);
void DlgVersionMigrator::calculateMigrationSize() {
sizeCalculationWorkerThread = new QThread(mainWindow);
auto *worker = new DirectorySizeCalculationWorker();
worker->moveToThread(workerThread);
connect(workerThread, &QThread::started, worker, &DirectorySizeCalculationWorker::run);
worker->moveToThread(sizeCalculationWorkerThread);
connect(sizeCalculationWorkerThread, &QThread::started, worker, &DirectorySizeCalculationWorker::run);
connect(worker, &DirectorySizeCalculationWorker::sizeFound, this, &VersionMigrator::showSizeOfMigration);
connect(worker, &DirectorySizeCalculationWorker::finished, workerThread, &QThread::quit);
connect(worker, &DirectorySizeCalculationWorker::sizeFound, this, &DlgVersionMigrator::showSizeOfMigration);
connect(worker, &DirectorySizeCalculationWorker::finished, sizeCalculationWorkerThread, &QThread::quit);
connect(worker, &DirectorySizeCalculationWorker::finished, worker, &QObject::deleteLater);
connect(workerThread, &QThread::finished, workerThread, &QObject::deleteLater);
connect(sizeCalculationWorkerThread, &QThread::finished, sizeCalculationWorkerThread, &QObject::deleteLater);
auto calculatingSize = new QMessageBox(mainWindow);
calculatingSize->setWindowTitle(QObject::tr("Calculating size"));
calculatingSize->setText(QObject::tr("Calculating directory size…"));
calculatingSize->setStandardButtons(QMessageBox::Cancel);
connect(worker, &DirectorySizeCalculationWorker::sizeFound, calculatingSize, &QMessageBox::accept);
connect(calculatingSize, &QMessageBox::rejected, workerThread, &QThread::requestInterruption);
connect(calculatingSize, &QMessageBox::rejected, this, &VersionMigrator::migrationCancelled);
workerThread->start();
calculatingSize->exec();
sizeCalculationWorkerThread->start();
}
void VersionMigrator::migrationCancelled() const {
auto message = QObject::tr(
"Migration cancelled. Ask again on next restart?");
auto result = QMessageBox::question(
mainWindow,
QObject::tr("Migration cancelled"),
message,
QMessageBox::Yes | QMessageBox::No);
if (result == QMessageBox::No) {
markCurrentVersionAsKnown();
void DlgVersionMigrator::doNotMigrate(const QString &linkText)
{
Q_UNUSED(linkText);
markCurrentVersionAsKnown();
if (sizeCalculationWorkerThread && sizeCalculationWorkerThread->isRunning()) {
sizeCalculationWorkerThread->quit();
}
close();
}
void VersionMigrator::showSizeOfMigration(uintmax_t size) {
void DlgVersionMigrator::showSizeOfMigration(uintmax_t size) {
auto sizeString = QLocale().formattedDataSize(static_cast<qint64>(size));
auto result = QMessageBox::question(
mainWindow,
QObject::tr("Migration Size"),
QObject::tr("Migrating will copy %1 into a versioned subdirectory. Continue?").arg(sizeString),
QMessageBox::Yes | QMessageBox::No
);
if (result == QMessageBox::Yes) {
migrateToCurrentVersion();
} else {
migrationCancelled();
}
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 VersionMigrator::migrateToCurrentVersion() {
void DlgVersionMigrator::migrateToCurrentVersion() {
hide();
auto oldKnownVersions = getKnownVersions();
markCurrentVersionAsKnown(); // This MUST be done before the migration, or it won't get remembered
auto *workerThread = new QThread(mainWindow);
@@ -278,9 +271,9 @@ void VersionMigrator::migrateToCurrentVersion() {
connect(worker, &PathMigrationWorker::finished, worker, &QObject::deleteLater);
connect(workerThread, &QThread::finished, workerThread, &QObject::deleteLater);
auto migrationRunning = new QMessageBox(mainWindow);
auto migrationRunning = new QMessageBox(this);
migrationRunning->setWindowTitle(QObject::tr("Migrating"));
migrationRunning->setText(QObject::tr("Migrating configuration data and addons..."));
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);
@@ -290,16 +283,16 @@ void VersionMigrator::migrateToCurrentVersion() {
if (migrationRunning->result() == QDialog::Accepted) {
App::GetApplication().GetUserParameter().SaveDocument(); // Flush to disk before restarting
auto *restarting = new QMessageBox(mainWindow);
auto *restarting = new QMessageBox(this);
restarting->setText(
QObject::tr("Migration complete. Restarting..."));
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
// 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);
@@ -323,4 +316,4 @@ void VersionMigrator::migrateToCurrentVersion() {
}
}
#include "VersionMigrator.moc"
#include "DlgVersionMigrator.moc"

View File

@@ -21,38 +21,46 @@
* *
**************************************************************************/
#ifndef GUI_VERSIONMIGRATOR_H
#define GUI_VERSIONMIGRATOR_H
#ifndef GUI_DIALOG_VERSIONMIGRATOR_H
#define GUI_DIALOG_VERSIONMIGRATOR_H
#include <FCGlobal.h>
#include <cstdint>
#include <QObject>
#include <memory>
#include <QDialog>
namespace Gui {
class MainWindow;
namespace Dialog {
class GuiExport VersionMigrator : public QObject
{
Q_OBJECT
class GuiExport DlgVersionMigrator final : public QDialog
{
Q_OBJECT
public:
explicit VersionMigrator(MainWindow *mw);
void execute();
public:
explicit DlgVersionMigrator(MainWindow *mw);
~DlgVersionMigrator() override;
Q_DISABLE_COPY_MOVE(DlgVersionMigrator)
protected Q_SLOTS:
int exec() override;
void confirmMigration();
void migrationCancelled() const;
void showSizeOfMigration(uintmax_t size);
void migrateToCurrentVersion();
protected Q_SLOTS:
private:
MainWindow* mainWindow;
};
void calculateMigrationSize(); // Async -> this starts the process and immediately returns
void showSizeOfMigration(uintmax_t size);
void migrateToCurrentVersion();
void doNotMigrate(const QString &linkText);
private:
MainWindow* mainWindow;
QThread* sizeCalculationWorkerThread;
std::unique_ptr<class Ui_DlgVersionMigrator> ui;
};
}
}
#endif // GUI_VERSIONMIGRATOR_H
#endif // GUI_DIALOG_VERSIONMIGRATOR_H

View File

@@ -0,0 +1,109 @@
<?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>527</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>
<layout class="QHBoxLayout" name="horizontalLayout">
<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="QLabel" name="shareLinkLabel">
<property name="text">
<!--Don't translate this here, it's replaced in code-->
<string notr="true">Share configuration between versions</string>
</property>
<property name="textFormat">
<enum>Qt::AutoText</enum>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="copyButton">
<property name="text">
<string>Copy Configuration</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@@ -30,8 +30,6 @@
#include <QApplication>
#include <QImageReader>
#include <QLabel>
#include <QLocale>
#include <QMessageBox>
#include <QOpenGLContext>
#include <QOpenGLFunctions>
#include <QProcess>
@@ -53,13 +51,12 @@
#include "GuiApplication.h"
#include "MainWindow.h"
#include "Language/Translator.h"
#include "VersionMigrator.h"
#include "Dialogs/DlgVersionMigrator.h"
#include <App/Application.h>
#include <Base/Console.h>
using namespace Gui;
namespace fs = std::filesystem;
StartupProcess::StartupProcess() = default;
@@ -560,6 +557,7 @@ void StartupPostProcess::checkParameters()
}
void StartupPostProcess::checkVersionMigration() const {
VersionMigrator migrator(mainWindow);
migrator.execute();
auto migrator = new Dialog::DlgVersionMigrator (mainWindow);
migrator->exec();
migrator->deleteLater();
}