This commit is contained in:
Andrey Kurlin 2026-06-28 21:50:31 +00:00 committed by GitHub
commit afdb2dc5bb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 309 additions and 25 deletions

View file

@ -321,6 +321,8 @@ set(MINECRAFT_SOURCES
minecraft/World.cpp
minecraft/WorldList.h
minecraft/WorldList.cpp
minecraft/WorldTasks.h
minecraft/WorldTasks.cpp
minecraft/mod/MetadataHandler.h
minecraft/mod/Mod.h

View file

@ -34,6 +34,7 @@
*/
#include "WorldList.h"
#include "WorldTasks.h"
#include <FileSystem.h>
#include <QDebug>
@ -123,18 +124,23 @@ QString WorldList::instDirPath() const
return QFileInfo(m_instance->instanceRoot()).absoluteFilePath();
}
bool WorldList::deleteWorld(int index)
bool WorldList::removeWorldFromModel(const QFileInfo& sourceFile)
{
if (index >= m_worlds.size() || index < 0)
return false;
World& m = m_worlds[index];
if (m.destroy()) {
beginRemoveRows(QModelIndex(), index, index);
m_worlds.removeAt(index);
const auto sourcePath = sourceFile.absoluteFilePath();
for (int row = 0; row < m_worlds.size(); ++row) {
if (m_worlds.at(row).container().absoluteFilePath() != sourcePath) {
continue;
}
beginRemoveRows(QModelIndex(), row, row);
m_worlds.removeAt(row);
endRemoveRows();
emit changed();
return true;
}
return false;
}
@ -360,6 +366,46 @@ void WorldList::installWorld(QFileInfo filename)
w.install(m_dir.absolutePath());
}
std::unique_ptr<Task> WorldList::createInstallWorldTask(QFileInfo filename)
{
return std::make_unique<InstallWorldTask>(InstallWorldTask::Args{
.worlds = this,
.sourceFile = filename,
.targetDir = m_dir.absolutePath(),
});
}
std::unique_ptr<Task> WorldList::createCopyWorldTask(int index, const QString& name)
{
if (index >= m_worlds.size() || index < 0) {
return nullptr;
}
const auto& world = m_worlds.at(index);
return std::make_unique<CopyWorldTask>(CopyWorldTask::Args{
.worlds = this,
.sourceFile = world.container(),
.targetDir = m_dir.absolutePath(),
.targetName = name,
});
}
std::unique_ptr<Task> WorldList::createDeleteWorldTask(int index)
{
if (index >= m_worlds.size() || index < 0) {
return nullptr;
}
const auto& world = m_worlds.at(index);
return std::make_unique<DeleteWorldTask>(DeleteWorldTask::Args{
.worlds = this,
.sourceFile = world.container(),
.displayName = world.name(),
});
}
bool WorldList::dropMimeData(const QMimeData* data,
Qt::DropAction action,
[[maybe_unused]] int row,

View file

@ -20,10 +20,12 @@
#include <QList>
#include <QMimeData>
#include <QString>
#include <memory>
#include "BaseInstance.h"
#include "minecraft/World.h"
class QFileSystemWatcher;
class Task;
class WorldList : public QAbstractListModel {
Q_OBJECT
@ -50,8 +52,17 @@ class WorldList : public QAbstractListModel {
/// Install a world from location
void installWorld(QFileInfo filename);
/// Deletes the mod at the given index.
virtual bool deleteWorld(int index);
/// Create a task to install a world from location
std::unique_ptr<Task> createInstallWorldTask(QFileInfo filename);
/// Create a task to copy the world at the given index.
std::unique_ptr<Task> createCopyWorldTask(int index, const QString& name);
/// Create a task to delete the world at the given index.
std::unique_ptr<Task> createDeleteWorldTask(int index);
/// Remove an already deleted world from the model.
bool removeWorldFromModel(const QFileInfo& sourceFile);
/// Removes the world icon, if any
virtual bool resetIcon(int index);

View file

@ -0,0 +1,130 @@
#include "WorldTasks.h"
#include "World.h"
#include "WorldList.h"
#include <QCoreApplication>
#include <QMetaObject>
#include <QThreadPool>
#include <utility>
namespace {
template <typename Func>
void invokeOnMainThread(Func&& func)
{
auto app = QCoreApplication::instance();
if (!app) {
return;
}
QMetaObject::invokeMethod(app, std::forward<Func>(func), Qt::QueuedConnection);
}
} // namespace
InstallWorldTask::InstallWorldTask(Args args) : m_args(std::move(args)) {}
void InstallWorldTask::executeTask()
{
setStatus(tr("Importing world..."));
setDetails(m_args.sourceFile.fileName());
setProgress(0, 0);
QPointer<InstallWorldTask> self(this);
auto args = m_args;
QThreadPool::globalInstance()->start([self, args]() mutable {
World world(args.sourceFile);
const bool ok = world.isValid() && world.install(args.targetDir);
invokeOnMainThread([self, worlds = args.worlds, ok]() {
if (!self) {
return;
}
if (!ok) {
self->emitFailed(self->tr("Failed to import world."));
return;
}
if (worlds) {
worlds->update();
}
self->setProgress(1, 1);
self->emitSucceeded();
});
});
}
CopyWorldTask::CopyWorldTask(Args args) : m_args(std::move(args)) {}
void CopyWorldTask::executeTask()
{
setStatus(tr("Copying world..."));
setDetails(m_args.targetName);
setProgress(0, 0);
QPointer<CopyWorldTask> self(this);
auto args = m_args;
QThreadPool::globalInstance()->start([self, args]() mutable {
World world(args.sourceFile);
const bool ok = world.isValid() && world.install(args.targetDir, args.targetName);
invokeOnMainThread([self, worlds = args.worlds, ok]() {
if (!self) {
return;
}
if (!ok) {
self->emitFailed(self->tr("Failed to copy world."));
return;
}
if (worlds) {
worlds->update();
}
self->setProgress(1, 1);
self->emitSucceeded();
});
});
}
DeleteWorldTask::DeleteWorldTask(Args args) : m_args(std::move(args)) {}
void DeleteWorldTask::executeTask()
{
setStatus(tr("Deleting world..."));
setDetails(m_args.displayName);
setProgress(0, 0);
QPointer<DeleteWorldTask> self(this);
auto args = m_args;
QThreadPool::globalInstance()->start([self, args]() mutable {
World world(args.sourceFile);
const bool ok = world.destroy();
invokeOnMainThread([self, worlds = args.worlds, sourceFile = args.sourceFile, ok]() {
if (!self) {
return;
}
if (!ok) {
self->emitFailed(self->tr("Failed to delete world."));
return;
}
if (worlds) {
worlds->removeWorldFromModel(sourceFile);
}
self->setProgress(1, 1);
self->emitSucceeded();
});
});
}

View file

@ -0,0 +1,63 @@
#pragma once
#include <memory>
#include <QFileInfo>
#include <QPointer>
#include <QString>
#include "tasks/Task.h"
class WorldList;
class InstallWorldTask : public Task {
public:
struct Args {
QPointer<WorldList> worlds;
QFileInfo sourceFile;
QString targetDir;
};
explicit InstallWorldTask(Args args);
protected:
void executeTask() override;
private:
Args m_args;
};
class CopyWorldTask : public Task {
public:
struct Args {
QPointer<WorldList> worlds;
QFileInfo sourceFile;
QString targetDir;
QString targetName;
};
explicit CopyWorldTask(Args args);
protected:
void executeTask() override;
private:
Args m_args;
};
class DeleteWorldTask : public Task {
public:
struct Args {
QPointer<WorldList> worlds;
QFileInfo sourceFile;
QString displayName;
};
explicit DeleteWorldTask(Args args);
protected:
void executeTask() override;
private:
Args m_args;
};

View file

@ -38,6 +38,7 @@
#include "WorldListPage.h"
#include "minecraft/WorldList.h"
#include "ui/dialogs/CustomMessageBox.h"
#include "ui/dialogs/ProgressDialog.h"
#include "ui_WorldListPage.h"
#include <ui/widgets/PageContainer.h>
@ -187,23 +188,34 @@ bool WorldListPage::eventFilter(QObject* obj, QEvent* ev)
void WorldListPage::on_actionRemove_triggered()
{
auto proxiedIndex = getSelectedWorld();
if (!proxiedIndex.isValid())
if (!proxiedIndex.isValid()) {
return;
}
const auto& world = m_worlds->allWorlds().at(proxiedIndex.row());
auto result = CustomMessageBox::selectable(this, tr("Confirm Deletion"),
tr("You are about to delete \"%1\".\n"
"The world may be gone forever (A LONG TIME).\n\n"
"Are you sure?")
.arg(m_worlds->allWorlds().at(proxiedIndex.row()).name()),
.arg(world.name()),
QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
->exec();
if (result != QMessageBox::Yes) {
return;
}
auto task = m_worlds->createDeleteWorldTask(proxiedIndex.row());
if (!task) {
return;
}
m_worlds->stopWatching();
m_worlds->deleteWorld(proxiedIndex.row());
ProgressDialog dialog(this);
dialog.execWithTask(std::move(task));
m_worlds->startWatching();
}
@ -393,13 +405,20 @@ void WorldListPage::on_actionAdd_triggered()
{
auto list = GuiUtil::BrowseForFiles(displayName(), tr("Select a Minecraft world zip"), tr("Minecraft World Zip File") + " (*.zip)",
QString(), this->parentWidget());
if (!list.empty()) {
m_worlds->stopWatching();
for (auto filename : list) {
m_worlds->installWorld(QFileInfo(filename));
}
m_worlds->startWatching();
if (list.empty()) {
return;
}
m_worlds->stopWatching();
for (auto filename : list) {
auto task = m_worlds->createInstallWorldTask(QFileInfo(filename));
if (!task) {
continue;
}
ProgressDialog dialog(this);
dialog.execWithTask(std::move(task));
}
m_worlds->startWatching();
}
bool WorldListPage::isWorldSafe(QModelIndex)
@ -426,18 +445,31 @@ void WorldListPage::on_actionCopy_triggered()
return;
}
if (!worldSafetyNagQuestion(tr("Copy World")))
if (!worldSafetyNagQuestion(tr("Copy World"))) {
return;
}
const auto world = m_worlds->allWorlds().at(index.row());
auto worldVariant = m_worlds->data(index, WorldList::ObjectRole);
auto world = (World*)worldVariant.value<void*>();
bool ok = false;
QString name =
QInputDialog::getText(this, tr("World name"), tr("Enter a new name for the copy."), QLineEdit::Normal, world->name(), &ok);
QInputDialog::getText(this, tr("World name"), tr("Enter a new name for the copy."), QLineEdit::Normal, world.name(), &ok);
if (ok && name.length() > 0) {
world->install(m_worlds->dir().absolutePath(), name);
if (!ok || name.isEmpty()) {
return;
}
auto task = m_worlds->createCopyWorldTask(index.row(), name);
if (!task) {
return;
}
m_worlds->stopWatching();
ProgressDialog dialog(this);
dialog.execWithTask(std::move(task));
m_worlds->startWatching();
}
void WorldListPage::on_actionRename_triggered()