From acf25d8a33ef67b79d8e8a8859f5559e011373a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Thu, 26 Dec 2013 05:14:32 +0100 Subject: [PATCH] Disable/enable mods with checkboxes. Needs testing. A lot of testing! --- gui/widgets/MCModInfoFrame.cpp | 2 +- gui/widgets/ModListView.cpp | 5 +- logic/LegacyInstance.cpp | 1 + logic/Mod.cpp | 89 ++++++++++++-- logic/Mod.h | 30 +++-- logic/ModList.cpp | 218 ++++++++++++++++++++++++--------- logic/ModList.h | 18 ++- 7 files changed, 277 insertions(+), 86 deletions(-) diff --git a/gui/widgets/MCModInfoFrame.cpp b/gui/widgets/MCModInfoFrame.cpp index ad167bc9..abcea6c6 100644 --- a/gui/widgets/MCModInfoFrame.cpp +++ b/gui/widgets/MCModInfoFrame.cpp @@ -30,7 +30,7 @@ void MCModInfoFrame::updateWithMod(Mod &m) QString text = ""; QString name = ""; - if(m.name().isEmpty()) name = m.id(); + if(m.name().isEmpty()) name = m.mmc_id(); else name = m.name(); if(m.homeurl().isEmpty()) text = name; diff --git a/gui/widgets/ModListView.cpp b/gui/widgets/ModListView.cpp index 838af75e..9d5950c3 100644 --- a/gui/widgets/ModListView.cpp +++ b/gui/widgets/ModListView.cpp @@ -44,8 +44,9 @@ void ModListView::setModel ( QAbstractItemModel* model ) QTreeView::setModel ( model ); auto head = header(); head->setStretchLastSection(false); - head->setSectionResizeMode(0, QHeaderView::Stretch); - for(int i = 1; i < head->count(); i++) + head->setSectionResizeMode(0, QHeaderView::ResizeToContents); + head->setSectionResizeMode(1, QHeaderView::Stretch); + for(int i = 2; i < head->count(); i++) head->setSectionResizeMode(i, QHeaderView::ResizeToContents); dropIndicatorPosition(); } diff --git a/logic/LegacyInstance.cpp b/logic/LegacyInstance.cpp index fef27bcd..5c82b837 100644 --- a/logic/LegacyInstance.cpp +++ b/logic/LegacyInstance.cpp @@ -150,6 +150,7 @@ std::shared_ptr LegacyInstance::jarModList() void LegacyInstance::jarModsChanged() { + QLOG_INFO() << "Jar mods of instance " << name() << " have changed. Jar will be rebuilt."; setShouldRebuild(true); } diff --git a/logic/Mod.cpp b/logic/Mod.cpp index cff9467e..8e880be1 100644 --- a/logic/Mod.cpp +++ b/logic/Mod.cpp @@ -35,20 +35,40 @@ Mod::Mod(const QFileInfo &file) void Mod::repath(const QFileInfo &file) { m_file = file; - m_name = file.completeBaseName(); - m_id = file.fileName(); + QString name_base = file.fileName(); m_type = Mod::MOD_UNKNOWN; + if (m_file.isDir()) + { m_type = MOD_FOLDER; + m_name = name_base; + m_mmc_id = name_base; + } else if (m_file.isFile()) { - QString ext = m_file.suffix().toLower(); - if (ext == "zip" || ext == "jar") - m_type = MOD_ZIPFILE; + if(name_base.endsWith(".disabled")) + { + m_enabled = false; + name_base.chop(9); + } else + { + m_enabled = true; + } + m_mmc_id = name_base; + if (name_base.endsWith(".zip") || name_base.endsWith(".jar")) + { + m_type = MOD_ZIPFILE; + name_base.chop(4); + } + else + { m_type = MOD_SINGLEFILE; + } + m_name = name_base; } + if (m_type == MOD_ZIPFILE) { QuaZip zip(m_file.filePath()); @@ -114,7 +134,7 @@ void Mod::ReadMCModInfo(QByteArray contents) if (!arr.at(0).isObject()) return; auto firstObj = arr.at(0).toObject(); - m_id = firstObj.value("modid").toString(); + m_mod_id = firstObj.value("modid").toString(); m_name = firstObj.value("name").toString(); m_version = firstObj.value("version").toString(); m_homeurl = firstObj.value("url").toString(); @@ -163,7 +183,7 @@ void Mod::ReadForgeInfo(QByteArray contents) { // Read the data m_name = "Minecraft Forge"; - m_id = "Forge"; + m_mod_id = "Forge"; m_homeurl = "http://www.minecraftforge.net/forum/"; INIFile ini; if (!ini.loadFile(contents)) @@ -183,9 +203,11 @@ bool Mod::replace(Mod &with) return false; bool success = false; auto t = with.type(); + if (t == MOD_ZIPFILE || t == MOD_SINGLEFILE) { - success = QFile::copy(with.m_file.filePath(), m_file.path()); + QLOG_DEBUG() << "Copy: " << with.m_file.filePath() << " to " << m_file.filePath(); + success = QFile::copy(with.m_file.filePath(), m_file.filePath()); } if (t == MOD_FOLDER) { @@ -193,11 +215,17 @@ bool Mod::replace(Mod &with) } if (success) { - m_id = with.m_id; - m_mcversion = with.m_mcversion; - m_type = with.m_type; m_name = with.m_name; + m_mmc_id = with.m_mmc_id; + m_mod_id = with.m_mod_id; m_version = with.m_version; + m_mcversion = with.m_mcversion; + m_description = with.m_description; + m_authors = with.m_authors; + m_credits = with.m_credits; + m_homeurl = with.m_homeurl; + m_type = with.m_type; + m_file.refresh(); } return success; } @@ -241,3 +269,42 @@ QString Mod::version() const return "VOID"; } } + +bool Mod::enable(bool value) +{ + if(m_type == Mod::MOD_UNKNOWN || m_type == Mod::MOD_FOLDER) + return false; + + if(m_enabled == value) + return false; + + QString path = m_file.absoluteFilePath(); + if(value) + { + QFile foo(path); + if(!path.endsWith(".disabled")) + return false; + path.chop(9); + if(!foo.rename(path)) + return false; + } + else + { + QFile foo(path); + path += ".disabled"; + if(!foo.rename(path)) + return false; + } + m_file = QFileInfo(path); + m_enabled = value; + return true; +} +bool Mod::operator==(const Mod &other) const +{ + return mmc_id() == other.mmc_id(); +} +bool Mod::strongCompare(const Mod &other) const +{ + return mmc_id() == other.mmc_id() && + version() == other.version() && type() == other.type(); +} diff --git a/logic/Mod.h b/logic/Mod.h index ca362a9d..05d3cea2 100644 --- a/logic/Mod.h +++ b/logic/Mod.h @@ -33,9 +33,13 @@ public: { return m_file; } - QString id() const + QString mmc_id() const { - return m_id; + return m_mmc_id; + } + QString mod_id() const + { + return m_mod_id; } ModType type() const { @@ -77,6 +81,13 @@ public: return m_credits; } + bool enabled() const + { + return m_enabled; + } + + bool enable(bool value); + // delete all the files of this mod bool destroy(); // replace this mod with a copy of the other @@ -85,15 +96,8 @@ public: void repath(const QFileInfo &file); // WEAK compare operator - used for replacing mods - bool operator==(const Mod &other) const - { - return filename() == other.filename(); - } - bool strongCompare(const Mod &other) const - { - return filename() == other.filename() && id() == other.id() && - version() == other.version() && type() == other.type(); - } + bool operator==(const Mod &other) const; + bool strongCompare(const Mod &other) const; private: void ReadMCModInfo(QByteArray contents); @@ -108,7 +112,9 @@ protected: */ QFileInfo m_file; - QString m_id; + QString m_mmc_id; + QString m_mod_id; + bool m_enabled = true; QString m_name; QString m_version; QString m_mcversion; diff --git a/logic/ModList.cpp b/logic/ModList.cpp index d5235fe9..dbc85320 100644 --- a/logic/ModList.cpp +++ b/logic/ModList.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include "logger/QsLog.h" @@ -27,7 +28,7 @@ ModList::ModList(const QString &dir, const QString &list_file) { m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs | QDir::NoSymLinks); - m_dir.setSorting(QDir::Name); + m_dir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware); m_list_id = QUuid::createUuid().toString(); m_watcher = new QFileSystemWatcher(this); is_watching = false; @@ -66,52 +67,79 @@ bool ModList::update() if (!isValid()) return false; + QList orderedMods; QList newMods; m_dir.refresh(); auto folderContents = m_dir.entryInfoList(); - bool orderWasInvalid = false; + bool orderOrStateChanged = false; // first, process the ordered items (if any) - QStringList listOrder = readListFile(); + OrderList listOrder = readListFile(); for (auto item : listOrder) { - QFileInfo info(m_dir.filePath(item)); - int idx = folderContents.indexOf(info); + QFileInfo infoEnabled(m_dir.filePath(item.id)); + QFileInfo infoDisabled(m_dir.filePath(item.id + ".disabled")); + int idxEnabled = folderContents.indexOf(infoEnabled); + int idxDisabled = folderContents.indexOf(infoDisabled); + // if both enabled and disabled versions are present, PANIC! + if (idxEnabled >= 0 && idxDisabled >= 0) + { + return false; + } + bool isEnabled = idxEnabled >= 0; + int idx = isEnabled ? idxEnabled : idxDisabled; + QFileInfo info = isEnabled ? infoEnabled : infoDisabled; // if the file from the index file exists if (idx != -1) { // remove from the actual folder contents list folderContents.takeAt(idx); // append the new mod - newMods.append(Mod(info)); + orderedMods.append(Mod(info)); + if (isEnabled != item.enabled) + orderOrStateChanged = true; } else { - orderWasInvalid = true; + orderOrStateChanged = true; } } - for (auto entry : folderContents) + // if there are any untracked files... + if (folderContents.size()) { - newMods.append(Mod(entry)); - } - if (mods.size() != newMods.size()) - { - orderWasInvalid = true; - } - else - for (int i = 0; i < mods.size(); i++) + // the order surely changed! + for (auto entry : folderContents) { - if (!mods[i].strongCompare(newMods[i])) - { - orderWasInvalid = true; - break; - } + newMods.append(Mod(entry)); } - beginResetModel(); - mods.swap(newMods); - endResetModel(); - if (orderWasInvalid) + std::sort(newMods.begin(), newMods.end(), [](const Mod & left, const Mod & right) + { return left.name().localeAwareCompare(right.name()) <= 0; }); + orderedMods.append(newMods); + orderOrStateChanged = true; + } + // otherwise, if we were already tracking some mods + else if (mods.size()) { + // if the number doesn't match, order changed. + if (mods.size() != orderedMods.size()) + orderOrStateChanged = true; + // if it does match, compare the mods themselves + else + for (int i = 0; i < mods.size(); i++) + { + if (!mods[i].strongCompare(orderedMods[i])) + { + orderOrStateChanged = true; + break; + } + } + } + beginResetModel(); + mods.swap(orderedMods); + endResetModel(); + if (orderOrStateChanged && !m_list_file.isEmpty()) + { + QLOG_INFO() << "Mod list " << m_list_file << " changed!"; saveListFile(); emit changed(); } @@ -123,17 +151,19 @@ void ModList::directoryChanged(QString path) update(); } -QStringList ModList::readListFile() +ModList::OrderList ModList::readListFile() { - QStringList stringList; + OrderList itemList; if (m_list_file.isNull() || m_list_file.isEmpty()) - return stringList; + return itemList; QFile textFile(m_list_file); if (!textFile.open(QIODevice::ReadOnly | QIODevice::Text)) - return QStringList(); + return OrderList(); - QTextStream textStream(&textFile); + QTextStream textStream; + textStream.setAutoDetectUnicode(true); + textStream.setDevice(&textFile); while (true) { QString line = textStream.readLine(); @@ -141,11 +171,18 @@ QStringList ModList::readListFile() break; else { - stringList.append(line); + OrderItem it; + it.enabled = !line.endsWith(".disabled"); + if (!it.enabled) + { + line.chop(9); + } + it.id = line; + itemList.append(it); } } textFile.close(); - return stringList; + return itemList; } bool ModList::saveListFile() @@ -155,12 +192,16 @@ bool ModList::saveListFile() QFile textFile(m_list_file); if (!textFile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) return false; - QTextStream textStream(&textFile); + QTextStream textStream; + textStream.setGenerateByteOrderMark(true); + textStream.setCodec("UTF-8"); + textStream.setDevice(&textFile); for (auto mod : mods) { - auto pathname = mod.filename(); - QString filename = pathname.fileName(); - textStream << filename << endl; + textStream << mod.mmc_id(); + if (!mod.enabled()) + textStream << ".disabled"; + textStream << endl; } textFile.close(); return false; @@ -327,7 +368,7 @@ bool ModList::moveModsDown(int first, int last) int ModList::columnCount(const QModelIndex &parent) const { - return 2; + return 3; } QVariant ModList::data(const QModelIndex &index, int role) const @@ -341,43 +382,96 @@ QVariant ModList::data(const QModelIndex &index, int role) const if (row < 0 || row >= mods.size()) return QVariant(); - if (role != Qt::DisplayRole) - return QVariant(); - - switch (column) + switch (role) { - case 0: - return mods[row].name(); - case 1: - return mods[row].version(); - case 2: - return mods[row].mcversion(); + case Qt::DisplayRole: + switch (index.column()) + { + case NameColumn: + return mods[row].name(); + case VersionColumn: + return mods[row].version(); + + default: + return QVariant(); + } + + case Qt::ToolTipRole: + return mods[row].mmc_id(); + + case Qt::CheckStateRole: + switch (index.column()) + { + case ActiveColumn: + return mods[row].enabled(); + default: + return QVariant(); + } default: return QVariant(); } } +bool ModList::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (index.row() < 0 || index.row() >= rowCount(index) || !index.isValid()) + { + return false; + } + + if (role == Qt::CheckStateRole) + { + auto &mod = mods[index.row()]; + if (mod.enable(!mod.enabled())) + { + emit dataChanged(index, index); + return true; + } + } + return false; +} + QVariant ModList::headerData(int section, Qt::Orientation orientation, int role) const { - if (role != Qt::DisplayRole || orientation != Qt::Horizontal) - return QVariant(); - switch (section) + switch (role) { - case 0: - return QString("Name"); - case 1: - return QString("Version"); - case 2: - return QString("Minecraft"); + case Qt::DisplayRole: + switch (section) + { + case ActiveColumn: + return QString(); + case NameColumn: + return QString("Name"); + case VersionColumn: + return QString("Version"); + default: + return QVariant(); + } + + case Qt::ToolTipRole: + switch (section) + { + case ActiveColumn: + return "Is the mod enabled?"; + case NameColumn: + return "The name of the mod."; + case VersionColumn: + return "The version of the mod."; + default: + return QVariant(); + } + default: + return QVariant(); } - return QString(); + return QVariant(); } Qt::ItemFlags ModList::flags(const QModelIndex &index) const { Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index); if (index.isValid()) - return Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | defaultFlags; + return Qt::ItemIsUserCheckable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | + defaultFlags; else return Qt::ItemIsDropEnabled | defaultFlags; } @@ -456,6 +550,14 @@ bool ModList::dropMimeData(const QMimeData *data, Qt::DropAction action, int row QString filename = url.toLocalFile(); installMod(filename, row); QLOG_INFO() << "installing: " << filename; + // if there is no ordering, re-sort the list + if (m_list_file.isEmpty()) + { + beginResetModel(); + std::sort(mods.begin(), mods.end(), [](const Mod & left, const Mod & right) + { return left.name().localeAwareCompare(right.name()) <= 0; }); + endResetModel(); + } } if (was_watching) startWatching(); diff --git a/logic/ModList.h b/logic/ModList.h index 803a5429..0d6507fb 100644 --- a/logic/ModList.h +++ b/logic/ModList.h @@ -34,9 +34,18 @@ class ModList : public QAbstractListModel { Q_OBJECT public: + enum Columns + { + ActiveColumn = 0, + NameColumn, + VersionColumn + }; ModList(const QString &dir, const QString &list_file = QString()); virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + virtual bool setData(const QModelIndex &index, const QVariant &value, + int role = Qt::EditRole); + virtual int rowCount(const QModelIndex &parent = QModelIndex()) const { return size(); @@ -59,7 +68,6 @@ public: { return mods[index]; } - ; /// Reloads the mod list and returns true if the list changed. virtual bool update(); @@ -119,7 +127,13 @@ public: } private: - QStringList readListFile(); + struct OrderItem + { + QString id; + bool enabled = false; + }; + typedef QList OrderList; + OrderList readListFile(); bool saveListFile(); private slots: