Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 0 additions & 10 deletions .claude/settings.local.json

This file was deleted.

110 changes: 79 additions & 31 deletions src/core/managers/ModManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,49 @@ bool ModManager::disableMod(const QString &modId) {
return true;
}

bool ModManager::setAllModsEnabled(bool enabled) {
QList<ModInfo> modsToProcess;
{
QMutexLocker locker(&m_modsMutex);
for (const ModInfo &mod : m_mods) {
if (mod.enabled != enabled) {
modsToProcess.append(mod);
}
}
}

if (modsToProcess.isEmpty()) {
return true;
}

bool anyFailed = false;
for (const ModInfo &mod : modsToProcess) {
bool ok = enabled ? copyModToPaks(mod) : removeModFromPaks(mod);
if (!ok) {
emit errorOccurred(tr("Failed to %1 mod: %2")
.arg(enabled ? tr("enable") : tr("disable"), mod.name));
anyFailed = true;
continue;
}

QMutexLocker locker(&m_modsMutex);
auto it = std::find_if(m_mods.begin(), m_mods.end(),
[&mod](const ModInfo &item) { return item.id == mod.id; });
if (it != m_mods.end()) {
it->enabled = enabled;
}
}

if (enabled) {
renumberEnabledMods();
}

saveMods();
emit modsChanged();

return !anyFailed;
}

bool ModManager::setModPriority(const QString &modId, int priority) {
QMutexLocker locker(&m_modsMutex);
auto it = std::find_if(m_mods.begin(), m_mods.end(),
Expand Down Expand Up @@ -626,53 +669,58 @@ void ModManager::renumberEnabledMods() {
return;
}

QMutexLocker locker(&m_modsMutex);
for (auto &mod : m_mods) {
if (!mod.enabled) {
continue;
}
struct EnabledModSnapshot {
QString id;
QString fileName;
QString numberedFileName;
int priority;
};

QString oldNumberedName = mod.numberedFileName;
QString newNumberedName = generateNumberedFileName(mod.priority, mod.fileName);

if (oldNumberedName == newNumberedName) {
QString expectedPath = paksPath + "/" + newNumberedName;
if (QFile::exists(expectedPath)) {
QList<EnabledModSnapshot> enabledMods;
{
QMutexLocker locker(&m_modsMutex);
enabledMods.reserve(m_mods.size());
for (const ModInfo &mod : m_mods) {
if (!mod.enabled) {
continue;
}
enabledMods.append({mod.id, mod.fileName, mod.numberedFileName, mod.priority});
}
}

QString sourcePath = m_modsStoragePath + "/" + mod.fileName;
locker.unlock();
for (const auto &mod : enabledMods) {
QString newNumberedName = generateNumberedFileName(mod.priority, mod.fileName);
QString expectedPath = paksPath + "/" + newNumberedName;
QString sourcePath = m_modsStoragePath + "/" + mod.fileName;

if (QFile::exists(sourcePath)) {
if (mod.numberedFileName == newNumberedName) {
if (!QFile::exists(expectedPath) && QFile::exists(sourcePath)) {
QFile::copy(sourcePath, expectedPath);
locker.relock();
mod.numberedFileName = newNumberedName;
QMutexLocker locker(&m_modsMutex);
auto it = std::find_if(m_mods.begin(), m_mods.end(),
[&mod](const ModInfo &item) { return item.id == mod.id; });
if (it != m_mods.end()) {
it->numberedFileName = newNumberedName;
}
qDebug() << "Restored mod to paks:" << newNumberedName;
locker.unlock();
} else {
locker.relock();
}
continue;
}

QString oldPath = paksPath + "/" + oldNumberedName;
QString newPath = paksPath + "/" + newNumberedName;
locker.unlock();

QString oldPath = paksPath + "/" + mod.numberedFileName;
if (QFile::exists(oldPath)) {
QFile::remove(oldPath);
}

QString sourcePath = m_modsStoragePath + "/" + mod.fileName;
if (QFile::exists(sourcePath)) {
QFile::copy(sourcePath, newPath);
locker.relock();
mod.numberedFileName = newNumberedName;
qDebug() << "Renumbered mod:" << oldNumberedName << "->" << newNumberedName;
locker.unlock();
} else {
locker.relock();
QFile::copy(sourcePath, expectedPath);
QMutexLocker locker(&m_modsMutex);
auto it = std::find_if(m_mods.begin(), m_mods.end(),
[&mod](const ModInfo &item) { return item.id == mod.id; });
if (it != m_mods.end()) {
it->numberedFileName = newNumberedName;
}
qDebug() << "Renumbered mod:" << mod.numberedFileName << "->" << newNumberedName;
}
}
}
Expand Down
1 change: 1 addition & 0 deletions src/core/managers/ModManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class ModManager : public QObject {
const QDateTime &uploadDate = QDateTime());
bool enableMod(const QString &modId);
bool disableMod(const QString &modId);
bool setAllModsEnabled(bool enabled);
bool setModPriority(const QString &modId, int priority);
bool batchSetModPriorities(const QMap<QString, int> &priorityMap);
bool updateModMetadata(const ModInfo &updatedMod);
Expand Down
63 changes: 62 additions & 1 deletion src/views/mod_manager/ModListWidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
#include <QScrollBar>
#include <QLineEdit>
#include <QKeyEvent>
#include <QCheckBox>
#include <QSignalBlocker>

ModListWidget::ModListWidget(QWidget *parent)
: QWidget(parent)
Expand Down Expand Up @@ -84,7 +86,13 @@ void ModListWidget::setupUi() {

auto *titleLayout = new QHBoxLayout();
titleLayout->setSpacing(Theme::Spacing::MOD_LIST_TITLE_SPACING);
titleLayout->setContentsMargins(0, 0, 0, 0);
titleLayout->setContentsMargins(Theme::Spacing::MOD_ROW_PADDING_HORIZONTAL, 0, 0, 0);

m_enableAllCheckBox = new QCheckBox(this);
m_enableAllCheckBox->setObjectName("modEnableAllCheckBox");
m_enableAllCheckBox->setTristate(true);
m_enableAllCheckBox->setCursor(Qt::PointingHandCursor);
m_enableAllCheckBox->setToolTip(tr("Enable or disable all mods"));

m_titleLabel = new QLabel(tr("Installed Mods:"), this);
m_titleLabel->setObjectName("modListTitle");
Expand All @@ -96,13 +104,16 @@ void ModListWidget::setupUi() {
m_checkUpdatesButton->setObjectName("checkUpdatesButton");
m_checkUpdatesButton->setCursor(Qt::PointingHandCursor);

titleLayout->addWidget(m_enableAllCheckBox);
titleLayout->addWidget(m_titleLabel);
titleLayout->addWidget(m_modCountLabel);
titleLayout->addStretch();
titleLayout->addWidget(m_checkUpdatesButton);

connect(m_checkUpdatesButton, &QPushButton::clicked,
this, &ModListWidget::onCheckUpdatesClicked);
connect(m_enableAllCheckBox, &QCheckBox::clicked,
this, &ModListWidget::onEnableAllClicked);

m_searchContainer = new QWidget(this);
auto *searchLayout = new QHBoxLayout(m_searchContainer);
Expand Down Expand Up @@ -174,6 +185,12 @@ void ModListWidget::refreshModList() {
m_modList->clear();

QList<ModInfo> mods = m_modManager->getMods();
int enabledCount = 0;
for (const ModInfo &mod : mods) {
if (mod.enabled) {
enabledCount++;
}
}
QList<ModInfo> filteredMods;
filteredMods.reserve(mods.size());
for (const ModInfo &mod : mods) {
Expand All @@ -198,6 +215,23 @@ void ModListWidget::refreshModList() {
m_modCountLabel->setText(QString::number(mods.size()) + " Total");
}

if (m_enableAllCheckBox) {
QSignalBlocker blocker(m_enableAllCheckBox);
if (mods.isEmpty()) {
m_enableAllCheckBox->setEnabled(false);
m_enableAllCheckBox->setCheckState(Qt::Unchecked);
} else {
m_enableAllCheckBox->setEnabled(true);
if (enabledCount == 0) {
m_enableAllCheckBox->setCheckState(Qt::Unchecked);
} else if (enabledCount == mods.size()) {
m_enableAllCheckBox->setCheckState(Qt::Checked);
} else {
m_enableAllCheckBox->setCheckState(Qt::PartiallyChecked);
}
}
}

for (int i = 0; i < filteredMods.size(); ++i) {
const ModInfo &mod = filteredMods[i];
auto *modRow = new ModRowWidget(mod, this);
Expand Down Expand Up @@ -275,6 +309,10 @@ void ModListWidget::changeEvent(QEvent *event) {
void ModListWidget::retranslateUi() {
if (m_titleLabel) m_titleLabel->setText(tr("Installed Mods:"));
if (m_checkUpdatesButton) m_checkUpdatesButton->setText(tr("Check for Updates"));
if (m_enableAllCheckBox) {
m_enableAllCheckBox->setText(QString());
m_enableAllCheckBox->setToolTip(tr("Enable or disable all mods"));
}
}

void ModListWidget::resizeEvent(QResizeEvent *event) {
Expand Down Expand Up @@ -318,6 +356,29 @@ void ModListWidget::onModEnabledChanged(const QString &modId, bool enabled) {
}
}

void ModListWidget::onEnableAllClicked() {
if (m_updating || !m_modManager || !m_enableAllCheckBox) {
return;
}

QList<ModInfo> mods = m_modManager->getMods();
if (mods.isEmpty()) {
return;
}

int enabledCount = 0;
for (const ModInfo &mod : mods) {
if (mod.enabled) {
enabledCount++;
}
}

bool enableAll = enabledCount < mods.size();
m_enableAllCheckBox->setEnabled(false);
m_modManager->setAllModsEnabled(enableAll);
m_enableAllCheckBox->setEnabled(true);
}

void ModListWidget::onAddModClicked() {
if (!m_modManager || !m_modalManager) {
return;
Expand Down
3 changes: 3 additions & 0 deletions src/views/mod_manager/ModListWidget.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <QLabel>
#include <QTimer>
#include <QRegularExpression>
#include <QCheckBox>

class NexusModsClient;
class NexusModsAuth;
Expand Down Expand Up @@ -63,6 +64,7 @@ public slots:
private slots:
void onModsChanged();
void onModEnabledChanged(const QString &modId, bool enabled);
void onEnableAllClicked();
void onItemsReordered();
void updateLoadingAnimation();
void onRenameRequested(const QString &modId);
Expand Down Expand Up @@ -107,6 +109,7 @@ private slots:
QLabel *m_loadingLabel;
QLabel *m_modCountLabel;
QPushButton *m_checkUpdatesButton;
QCheckBox *m_enableAllCheckBox = nullptr;
QTimer *m_loadingTimer;
QWidget *m_searchContainer = nullptr;
QLineEdit *m_searchEdit = nullptr;
Expand Down
Loading