mirror of
https://github.com/PrismLauncher/PrismLauncher.git
synced 2025-09-18 08:29:07 -04:00
Add file conflict dialog
Signed-off-by: Naomi <103967@gmail.com>
This commit is contained in:
parent
7673364841
commit
7f35195686
@ -999,6 +999,8 @@ SET(LAUNCHER_SOURCES
|
||||
ui/dialogs/ExportPackDialog.h
|
||||
ui/dialogs/ExportToModListDialog.cpp
|
||||
ui/dialogs/ExportToModListDialog.h
|
||||
ui/dialogs/FileConflictDialog.cpp
|
||||
ui/dialogs/FileConflictDialog.h
|
||||
ui/dialogs/IconPickerDialog.cpp
|
||||
ui/dialogs/IconPickerDialog.h
|
||||
ui/dialogs/ImportResourceDialog.cpp
|
||||
@ -1174,6 +1176,7 @@ qt_wrap_ui(LAUNCHER_UI
|
||||
ui/dialogs/ExportInstanceDialog.ui
|
||||
ui/dialogs/ExportPackDialog.ui
|
||||
ui/dialogs/ExportToModListDialog.ui
|
||||
ui/dialogs/FileConflictDialog.ui
|
||||
ui/dialogs/IconPickerDialog.ui
|
||||
ui/dialogs/ImportResourceDialog.ui
|
||||
ui/dialogs/MSALoginDialog.ui
|
||||
|
@ -1737,4 +1737,5 @@ QString getSymLinkTarget(const QString& path)
|
||||
{
|
||||
return QFileInfo(path).symLinkTarget();
|
||||
}
|
||||
|
||||
} // namespace FS
|
||||
|
@ -1,15 +1,67 @@
|
||||
#include "UpdateGlobalDirectoriesTask.h"
|
||||
|
||||
#include <QDirIterator>
|
||||
|
||||
#include "Application.h"
|
||||
#include "FileSystem.h"
|
||||
#include "minecraft/MinecraftInstance.h"
|
||||
#include "tasks/ConcurrentTask.h"
|
||||
#include "ui/dialogs/CustomMessageBox.h"
|
||||
#include "ui/dialogs/FileConflictDialog.h"
|
||||
|
||||
/**
|
||||
* @brief Move a file or folder, and ask the user what to do in case of a conflict.
|
||||
* @param source What to move.
|
||||
* @param destination Where to move it to.
|
||||
* @param recursive If true, all direct children will be moved 1 by 1.
|
||||
* If false, the source will be directly moved to the destination.
|
||||
* @param parent The parent of the dialog.
|
||||
* @return True if everything could be moved.
|
||||
*/
|
||||
bool interactiveMove(const QString& source, const QString& destination, bool recursive = false, QWidget* parent = nullptr)
|
||||
{
|
||||
const QFileInfo sourceInfo(source);
|
||||
|
||||
// Make sure the source exists.
|
||||
if (!sourceInfo.exists())
|
||||
return false;
|
||||
|
||||
if (recursive) {
|
||||
// Recursive doesn't make sense if the source isn't a directory.
|
||||
if (!sourceInfo.isDir())
|
||||
return false;
|
||||
|
||||
QDirIterator sourceIt(source, QDir::Filter::Files | QDir::Filter::Dirs | QDir::Filter::Hidden | QDir::Filter::NoDotAndDotDot);
|
||||
|
||||
while (sourceIt.hasNext()) {
|
||||
if (!interactiveMove(sourceIt.next(), FS::PathCombine(destination, sourceIt.fileName()), false))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (QFile(destination).exists()) {
|
||||
FileConflictDialog dialog(source, destination, true, parent);
|
||||
FileConflictDialog::Result result = dialog.execWithResult();
|
||||
|
||||
if (result == FileConflictDialog::Cancel)
|
||||
return false;
|
||||
else if (result == FileConflictDialog::ChooseDestination)
|
||||
return FS::deletePath(source);
|
||||
}
|
||||
|
||||
return FS::move(source, destination);
|
||||
}
|
||||
|
||||
class TryCreateSymlinkTask : public Task {
|
||||
public:
|
||||
explicit TryCreateSymlinkTask(const QString& source, const QString& destination, MinecraftInstance* instance, const QString& setting)
|
||||
: m_source(source), m_destination(destination), m_inst(instance), m_setting(setting)
|
||||
explicit TryCreateSymlinkTask(const QString& source,
|
||||
const QString& destination,
|
||||
MinecraftInstance* instance,
|
||||
const QString& setting,
|
||||
QWidget* parent)
|
||||
: m_source(source), m_destination(destination), m_inst(instance), m_setting(setting), m_parent(parent)
|
||||
{
|
||||
setObjectName("TryCreateSymlinkTask");
|
||||
}
|
||||
@ -49,9 +101,11 @@ class TryCreateSymlinkTask : public Task {
|
||||
FS::deletePath(m_destination);
|
||||
} else if (FS::checkFolderPathExists(m_destination)) {
|
||||
if (!FS::checkFolderPathEmpty(m_destination)) {
|
||||
if (!interactiveMove(m_destination, m_source, true, m_parent)) {
|
||||
fail(tr("Failed to create global folder.\nEnsure that \"%1\" is empty.").arg(m_destination));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
FS::deletePath(m_destination);
|
||||
}
|
||||
@ -81,6 +135,7 @@ class TryCreateSymlinkTask : public Task {
|
||||
QString m_destination;
|
||||
MinecraftInstance* m_inst;
|
||||
QString m_setting;
|
||||
QWidget* m_parent;
|
||||
};
|
||||
|
||||
UpdateGlobalDirectoriesTask::UpdateGlobalDirectoriesTask(MinecraftInstance* inst, QWidget* parent)
|
||||
@ -94,22 +149,22 @@ void UpdateGlobalDirectoriesTask::executeTask()
|
||||
auto tasks = makeShared<ConcurrentTask>(this, "UpdateGlobalDirectoriesTask");
|
||||
|
||||
auto screenshotsTask = makeShared<TryCreateSymlinkTask>(m_inst->settings()->get("GlobalScreenshotsPath").toString(),
|
||||
m_inst->screenshotsDir(), m_inst, "UseGlobalScreenshotsFolder");
|
||||
m_inst->screenshotsDir(), m_inst, "UseGlobalScreenshotsFolder", m_parent);
|
||||
connect(screenshotsTask.get(), &Task::failed, this, &UpdateGlobalDirectoriesTask::notifyFailed);
|
||||
tasks->addTask(screenshotsTask);
|
||||
|
||||
auto savesTask = makeShared<TryCreateSymlinkTask>(m_inst->settings()->get("GlobalSavesPath").toString(), m_inst->worldDir(), m_inst,
|
||||
"UseGlobalSavesFolder");
|
||||
"UseGlobalSavesFolder", m_parent);
|
||||
connect(savesTask.get(), &Task::failed, this, &UpdateGlobalDirectoriesTask::notifyFailed);
|
||||
tasks->addTask(savesTask);
|
||||
|
||||
auto resoucePacksTask = makeShared<TryCreateSymlinkTask>(m_inst->settings()->get("GlobalResourcePacksPath").toString(),
|
||||
m_inst->resourcePacksDir(), m_inst, "UseGlobalResourcePacksFolder");
|
||||
m_inst->resourcePacksDir(), m_inst, "UseGlobalResourcePacksFolder", m_parent);
|
||||
connect(resoucePacksTask.get(), &Task::failed, this, &UpdateGlobalDirectoriesTask::notifyFailed);
|
||||
tasks->addTask(resoucePacksTask);
|
||||
|
||||
auto texturePacksTask = makeShared<TryCreateSymlinkTask>(m_inst->settings()->get("GlobalResourcePacksPath").toString(),
|
||||
m_inst->texturePacksDir(), m_inst, "UseGlobalResourcePacksFolder");
|
||||
m_inst->texturePacksDir(), m_inst, "UseGlobalResourcePacksFolder", m_parent);
|
||||
connect(texturePacksTask.get(), &Task::failed, this, &UpdateGlobalDirectoriesTask::notifyFailed);
|
||||
tasks->addTask(texturePacksTask);
|
||||
|
||||
|
85
launcher/ui/dialogs/FileConflictDialog.cpp
Normal file
85
launcher/ui/dialogs/FileConflictDialog.cpp
Normal file
@ -0,0 +1,85 @@
|
||||
#include "FileConflictDialog.h"
|
||||
#include "ui_FileConflictDialog.h"
|
||||
|
||||
#include <QDialogButtonBox>
|
||||
#include <QFileInfo>
|
||||
#include <QPushButton>
|
||||
|
||||
#include "Application.h"
|
||||
|
||||
FileConflictDialog::FileConflictDialog(QString source, QString destination, bool move, QWidget* parent)
|
||||
: QDialog(parent), ui(new Ui::FileConflictDialog), m_result(Result::Cancel)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
QLocale locale;
|
||||
|
||||
// Setup buttons
|
||||
connect(ui->buttonBox->button(QDialogButtonBox::Cancel), &QPushButton::clicked, this, &FileConflictDialog::cancel);
|
||||
if (move) {
|
||||
setWindowTitle("File conflict while moving files");
|
||||
|
||||
auto chooseSourceButton = ui->buttonBox->addButton(tr("Keep source"), QDialogButtonBox::DestructiveRole);
|
||||
chooseSourceButton->setIcon(APPLICATION->getThemedIcon("delete"));
|
||||
connect(chooseSourceButton, &QPushButton::clicked, this, &FileConflictDialog::chooseSource);
|
||||
|
||||
auto chooseDestinationButton = ui->buttonBox->addButton(tr("Keep destination"), QDialogButtonBox::DestructiveRole);
|
||||
chooseDestinationButton->setIcon(APPLICATION->getThemedIcon("delete"));
|
||||
connect(chooseDestinationButton, &QPushButton::clicked, this, &FileConflictDialog::chooseDestination);
|
||||
} else {
|
||||
setWindowTitle("File conflict while copying files");
|
||||
|
||||
auto chooseSourceButton = ui->buttonBox->addButton(tr("Overwrite destination"), QDialogButtonBox::DestructiveRole);
|
||||
chooseSourceButton->setIcon(APPLICATION->getThemedIcon("delete"));
|
||||
connect(chooseSourceButton, &QPushButton::clicked, this, &FileConflictDialog::chooseSource);
|
||||
|
||||
auto chooseDestinationButton = ui->buttonBox->addButton(tr("Skip"), QDialogButtonBox::DestructiveRole);
|
||||
connect(chooseDestinationButton, &QPushButton::clicked, this, &FileConflictDialog::chooseDestination);
|
||||
}
|
||||
|
||||
// Setup info
|
||||
QFileInfo sourceInfo(source);
|
||||
ui->sourceInfoLabel->setText(tr("<b>Name:</b> %1<br/><b>Size:</b> %2<br/><b>Last modified:</b> %3")
|
||||
.arg(source, sourceInfo.isDir() ? "-" : locale.formattedDataSize(sourceInfo.size()),
|
||||
sourceInfo.lastModified().toString(locale.dateTimeFormat())));
|
||||
|
||||
QFileInfo destinationInfo(destination);
|
||||
ui->destinationInfoLabel->setText(tr("<b>Name:</b> %1<br/><b>Size:</b> %2<br/><b>Last modified:</b> %3")
|
||||
.arg(destination,
|
||||
destinationInfo.isDir() ? "-" : locale.formattedDataSize(destinationInfo.size()),
|
||||
destinationInfo.lastModified().toString(locale.dateTimeFormat())));
|
||||
}
|
||||
|
||||
FileConflictDialog::~FileConflictDialog()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
FileConflictDialog::Result FileConflictDialog::execWithResult()
|
||||
{
|
||||
exec();
|
||||
return m_result;
|
||||
}
|
||||
|
||||
FileConflictDialog::Result FileConflictDialog::getResult() const
|
||||
{
|
||||
return m_result;
|
||||
}
|
||||
|
||||
void FileConflictDialog::chooseSource()
|
||||
{
|
||||
m_result = Result::ChooseSource;
|
||||
accept();
|
||||
}
|
||||
|
||||
void FileConflictDialog::chooseDestination()
|
||||
{
|
||||
m_result = Result::ChooseDestination;
|
||||
accept();
|
||||
}
|
||||
|
||||
void FileConflictDialog::cancel()
|
||||
{
|
||||
m_result = Result::Cancel;
|
||||
reject();
|
||||
}
|
34
launcher/ui/dialogs/FileConflictDialog.h
Normal file
34
launcher/ui/dialogs/FileConflictDialog.h
Normal file
@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
namespace Ui {
|
||||
class FileConflictDialog;
|
||||
}
|
||||
|
||||
class FileConflictDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum Result { Cancel, ChooseSource, ChooseDestination };
|
||||
|
||||
/// @brief Create a new file conflict dialog
|
||||
/// @param source The source path. What to copy/move.
|
||||
/// @param destination The destination path. Where to copy/move.
|
||||
/// @param move Whether the conflict is for a move or copy action
|
||||
/// @param parent The parent of the dialog
|
||||
explicit FileConflictDialog(QString source, QString destination, bool move = false, QWidget* parent = nullptr);
|
||||
~FileConflictDialog() override;
|
||||
|
||||
Result execWithResult();
|
||||
Result getResult() const;
|
||||
|
||||
private slots:
|
||||
void chooseSource();
|
||||
void chooseDestination();
|
||||
void cancel();
|
||||
|
||||
private:
|
||||
Ui::FileConflictDialog* ui;
|
||||
Result m_result;
|
||||
};
|
114
launcher/ui/dialogs/FileConflictDialog.ui
Normal file
114
launcher/ui/dialogs/FileConflictDialog.ui
Normal file
@ -0,0 +1,114 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>FileConflictDialog</class>
|
||||
<widget class="QDialog" name="FileConflictDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>411</width>
|
||||
<height>219</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string/>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item alignment="Qt::AlignmentFlag::AlignTop">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Would you like to overwrite the destination?</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignmentFlag::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<property name="sizeConstraint">
|
||||
<enum>QLayout::SizeConstraint::SetDefaultConstraint</enum>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="sourceLayout" stretch="0,1">
|
||||
<item>
|
||||
<widget class="QLabel" name="sourceLabel">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string><html><head/><body><p><span style=" font-weight:700;">Source</span></p></body></html></string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignmentFlag::AlignHCenter|Qt::AlignmentFlag::AlignTop</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="sourceInfoLabel">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string><html><head/><body><p><b>Name:</b></p><p>Size:</p><p>Date:</p></body></html></string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="destinationLayout" stretch="0,1">
|
||||
<item>
|
||||
<widget class="QLabel" name="destinationLabel">
|
||||
<property name="text">
|
||||
<string><html><head/><body><p><span style=" font-weight:700;">Destination</span></p></body></html></string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignmentFlag::AlignHCenter|Qt::AlignmentFlag::AlignTop</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="destinationInfoLabel">
|
||||
<property name="text">
|
||||
<string><html><head/><body><p>Name:</p><p>Size:</p><p>Date:</p></body></html></string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::StandardButton::Cancel</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
Loading…
x
Reference in New Issue
Block a user