diff options
| author | Tannin <devnull@localhost> | 2013-03-22 18:46:40 +0100 |
|---|---|---|
| committer | Tannin <devnull@localhost> | 2013-03-22 18:46:40 +0100 |
| commit | 0a19f0433992be5f9791c8869380d09cd06b66b8 (patch) | |
| tree | 12399954f7c1156ed1cacb24dac3a8574a513e7f /src | |
| parent | 8b4e11060b37ad70754aa665ad59744eadd2dd1e (diff) | |
- support for grouping filters for mod list (incomplete)
- offering multiple options for mod names during installation
- support for renaming profiles
- updated installer plugins
- minor bugfixes
Diffstat (limited to 'src')
| -rw-r--r-- | src/ModOrganizer.pro | 2 | ||||
| -rw-r--r-- | src/categories.cpp | 12 | ||||
| -rw-r--r-- | src/categories.h | 330 | ||||
| -rw-r--r-- | src/icondelegate.cpp | 120 | ||||
| -rw-r--r-- | src/installationmanager.cpp | 526 | ||||
| -rw-r--r-- | src/installationmanager.h | 16 | ||||
| -rw-r--r-- | src/mainwindow.cpp | 101 | ||||
| -rw-r--r-- | src/mainwindow.h | 6 | ||||
| -rw-r--r-- | src/mainwindow.ui | 34 | ||||
| -rw-r--r-- | src/modinfo.cpp | 37 | ||||
| -rw-r--r-- | src/modinfo.h | 9 | ||||
| -rw-r--r-- | src/modlist.cpp | 6 | ||||
| -rw-r--r-- | src/modlistgroupcategoriesproxy.cpp | 123 | ||||
| -rw-r--r-- | src/modlistgroupcategoriesproxy.h | 40 | ||||
| -rw-r--r-- | src/modlistgroupnexusidproxy.cpp | 130 | ||||
| -rw-r--r-- | src/modlistgroupnexusidproxy.h | 49 | ||||
| -rw-r--r-- | src/modlistsortproxy.cpp | 14 | ||||
| -rw-r--r-- | src/modlistsortproxy.h | 2 | ||||
| -rw-r--r-- | src/organizer.pro | 16 | ||||
| -rw-r--r-- | src/pluginlist.cpp | 14 | ||||
| -rw-r--r-- | src/profile.cpp | 75 | ||||
| -rw-r--r-- | src/profile.h | 593 | ||||
| -rw-r--r-- | src/profilesdialog.cpp | 82 | ||||
| -rw-r--r-- | src/profilesdialog.h | 2 | ||||
| -rw-r--r-- | src/profilesdialog.ui | 10 |
25 files changed, 1219 insertions, 1130 deletions
diff --git a/src/ModOrganizer.pro b/src/ModOrganizer.pro index 8b6db27a..01c9c146 100644 --- a/src/ModOrganizer.pro +++ b/src/ModOrganizer.pro @@ -13,7 +13,7 @@ SUBDIRS = bsatk \ proxydll
hookdll.depends = shared
-organizer.depends = shared, uibase
+organizer.depends = shared, uibase, plugins
CONFIG(debug, debug|release) {
DESTDIR = outputd
diff --git a/src/categories.cpp b/src/categories.cpp index f8bc3530..7b765afe 100644 --- a/src/categories.cpp +++ b/src/categories.cpp @@ -144,6 +144,18 @@ void CategoryFactory::saveCategories() } +unsigned int CategoryFactory::countCategories(std::tr1::function<bool (const Category &category)> filter) +{ + unsigned int result = 0; + for (auto iter = m_Categories.begin(); iter != m_Categories.end(); ++iter) { + if (filter(*iter)) { + ++result; + } + } + return result; +} + + void CategoryFactory::addCategory(int id, const QString &name, const std::vector<int> &nexusIDs, int parentID) { int index = m_Categories.size(); diff --git a/src/categories.h b/src/categories.h index e6978259..47b15711 100644 --- a/src/categories.h +++ b/src/categories.h @@ -17,164 +17,172 @@ You should have received a copy of the GNU General Public License along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef CATEGORIES_H
-#define CATEGORIES_H
-
-
-#include <QString>
-#include <vector>
-#include <map>
-
-
-/**
- * @brief Manage the available mod categories
- * @warning member functions of this class currently use a wild mix of ids and indexes to look up categories,
- * optimized to where the request comes from. Therefore be very careful which of the two you have available
- **/
-class CategoryFactory {
-
- friend class CategoriesDialog;
-
-public:
-
- static const int CATEGORY_NONE = 0;
-
- static const int CATEGORY_SPECIAL_FIRST = 10000;
- static const int CATEGORY_SPECIAL_CHECKED = CATEGORY_SPECIAL_FIRST;
- static const int CATEGORY_SPECIAL_UNCHECKED = 10001;
- static const int CATEGORY_SPECIAL_UPDATEAVAILABLE = 10002;
- static const int CATEGORY_SPECIAL_NOCATEGORY = 10003;
- static const int CATEGORY_SPECIAL_CONFLICT = 10004;
-
-public:
-
- /**
- * @brief reset the list of categories
- **/
- void reset();
-
- /**
- * @brief save the categories to the categories.dat file
- **/
- void saveCategories();
-
- /**
- * @brief retrieve the number of available categories
- *
- * @return unsigned int number of categories
- **/
- unsigned numCategories() const { return m_Categories.size(); }
-
- /**
- * @brief get the id of the parent category
- *
- * @param index the index to look up
- * @return int id of the parent category
- **/
- int getParentID(unsigned int index) const;
-
- /**
- * @brief determine if a category exists (by id)
- *
- * @param id the id to check for existance
- * @return true if the category exists, false otherwise
- **/
- bool categoryExists(int id) const;
-
- /**
- * @brief test if a category is child of a second one
- * @param id the presumed child id
- * @param parentID the parent id to test for
- * @return true if id is a child of parentID
- **/
- bool isDecendantOf(int id, int parentID) const;
-
- /**
- * @brief test if the specified category has child categories
- *
- * @param index index of the category to look up
- * @return bool true if the category has child categories
- **/
- bool hasChildren(unsigned int index) const;
-
- /**
- * @brief retrieve the name of a category
- *
- * @param index index of the category to look up
- * @return QString name of the category
- **/
- QString getCategoryName(unsigned int index) const;
-
- /**
- * @brief look up the id of a category by its index
- *
- * @param index index of the category to look up
- * @return int id of the category
- **/
- int getCategoryID(unsigned int index) const;
-
- /**
- * @brief look up the index of a category by its id
- *
- * @param id index of the category to look up
- * @return unsigned int index of the category
- **/
- int getCategoryIndex(int ID) const;
-
- /**
- * @brief retrieve the index of a category by its nexus id
- *
- * @param nexusID nexus id of the category to look up
- * @return unsigned int index of the category or 0 if no category matches
- **/
- unsigned int resolveNexusID(int nexusID) const;
-
-public:
-
- /**
- * @brief retrieve a reference to the singleton instance
- *
- * @return the reference to the singleton
- **/
- static CategoryFactory &instance();
-
-private:
-
- struct Category {
- Category(int sortValue, int id, const QString &name, const std::vector<int> &nexusIDs, int parentID)
- : m_SortValue(sortValue), m_ID(id), m_Name(name), m_HasChildren(false),
- m_NexusIDs(nexusIDs), m_ParentID(parentID) {}
- int m_SortValue;
- int m_ID;
- int m_ParentID;
- bool m_HasChildren;
- QString m_Name;
- std::vector<int> m_NexusIDs;
-
- friend bool operator<(const Category &LHS, const Category &RHS) {
- return LHS.m_SortValue < RHS.m_SortValue;
- }
- };
-
-private:
-
- CategoryFactory();
-
- void loadDefaultCategories();
-
- void addCategory(int id, const QString &name, const std::vector<int> &nexusID, int parentID);
-
- void setParents();
-
-private:
-
- static CategoryFactory *s_Instance;
-
- std::vector<Category> m_Categories;
- std::map<int, unsigned int> m_IDMap;
- std::map<int, unsigned int> m_NexusMap;
-
-};
-
-
-#endif // CATEGORIES_H
+#ifndef CATEGORIES_H +#define CATEGORIES_H + + +#include <QString> +#include <vector> +#include <map> +#include <functional> + + +/** + * @brief Manage the available mod categories + * @warning member functions of this class currently use a wild mix of ids and indexes to look up categories, + * optimized to where the request comes from. Therefore be very careful which of the two you have available + **/ +class CategoryFactory { + + friend class CategoriesDialog; + +public: + + static const int CATEGORY_NONE = 0; + + static const int CATEGORY_SPECIAL_FIRST = 10000; + static const int CATEGORY_SPECIAL_CHECKED = CATEGORY_SPECIAL_FIRST; + static const int CATEGORY_SPECIAL_UNCHECKED = 10001; + static const int CATEGORY_SPECIAL_UPDATEAVAILABLE = 10002; + static const int CATEGORY_SPECIAL_NOCATEGORY = 10003; + static const int CATEGORY_SPECIAL_CONFLICT = 10004; + +public: + + struct Category { + Category(int sortValue, int id, const QString &name, const std::vector<int> &nexusIDs, int parentID) + : m_SortValue(sortValue), m_ID(id), m_Name(name), m_HasChildren(false), + m_NexusIDs(nexusIDs), m_ParentID(parentID) {} + int m_SortValue; + int m_ID; + int m_ParentID; + bool m_HasChildren; + QString m_Name; + std::vector<int> m_NexusIDs; + + friend bool operator<(const Category &LHS, const Category &RHS) { + return LHS.m_SortValue < RHS.m_SortValue; + } + }; + +public: + + /** + * @brief reset the list of categories + **/ + void reset(); + + /** + * @brief save the categories to the categories.dat file + **/ + void saveCategories(); + + /** + * @brief retrieve the number of available categories + * + * @return unsigned int number of categories + **/ + unsigned numCategories() const { return m_Categories.size(); } + + /** + * @brief count all categories that match a specified filter + * @param filter the filter to test + * @return number of matching categories + */ + unsigned int countCategories(std::tr1::function<bool (const Category &category)> filter); + + /** + * @brief get the id of the parent category + * + * @param index the index to look up + * @return int id of the parent category + **/ + int getParentID(unsigned int index) const; + + /** + * @brief determine if a category exists (by id) + * + * @param id the id to check for existance + * @return true if the category exists, false otherwise + **/ + bool categoryExists(int id) const; + + /** + * @brief test if a category is child of a second one + * @param id the presumed child id + * @param parentID the parent id to test for + * @return true if id is a child of parentID + **/ + bool isDecendantOf(int id, int parentID) const; + + /** + * @brief test if the specified category has child categories + * + * @param index index of the category to look up + * @return bool true if the category has child categories + **/ + bool hasChildren(unsigned int index) const; + + /** + * @brief retrieve the name of a category + * + * @param index index of the category to look up + * @return QString name of the category + **/ + QString getCategoryName(unsigned int index) const; + + /** + * @brief look up the id of a category by its index + * + * @param index index of the category to look up + * @return int id of the category + **/ + int getCategoryID(unsigned int index) const; + + /** + * @brief look up the index of a category by its id + * + * @param id index of the category to look up + * @return unsigned int index of the category + **/ + int getCategoryIndex(int ID) const; + + /** + * @brief retrieve the index of a category by its nexus id + * + * @param nexusID nexus id of the category to look up + * @return unsigned int index of the category or 0 if no category matches + **/ + unsigned int resolveNexusID(int nexusID) const; + +public: + + /** + * @brief retrieve a reference to the singleton instance + * + * @return the reference to the singleton + **/ + static CategoryFactory &instance(); + +private: + + CategoryFactory(); + + void loadDefaultCategories(); + + void addCategory(int id, const QString &name, const std::vector<int> &nexusID, int parentID); + + void setParents(); + +private: + + static CategoryFactory *s_Instance; + + std::vector<Category> m_Categories; + std::map<int, unsigned int> m_IDMap; + std::map<int, unsigned int> m_NexusMap; + +}; + + +#endif // CATEGORIES_H diff --git a/src/icondelegate.cpp b/src/icondelegate.cpp index 859311fc..05a5c0c3 100644 --- a/src/icondelegate.cpp +++ b/src/icondelegate.cpp @@ -17,61 +17,65 @@ You should have received a copy of the GNU General Public License along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. */ -#include "icondelegate.h"
-#include <QHBoxLayout>
-#include <QLabel>
-#include <QPainter>
-
-
-IconDelegate::IconDelegate(QAbstractProxyModel *proxyModel, QObject *parent)
- : QStyledItemDelegate(parent), m_ProxyModel(proxyModel)
-{
-}
-
-
-QIcon IconDelegate::getFlagIcon(ModInfo::EFlag flag) const
-{
- switch (flag) {
- case ModInfo::FLAG_BACKUP: return QIcon(":/MO/gui/emblem_backup");
- case ModInfo::FLAG_INVALID: return QIcon(":/MO/gui/emblem_problem");
- case ModInfo::FLAG_NOTENDORSED: return QIcon(":/MO/gui/emblem_notendorsed");
- case ModInfo::FLAG_NOTES: return QIcon(":/MO/gui/emblem_notes");
- case ModInfo::FLAG_CONFLICT_OVERWRITE: return QIcon(":/MO/gui/emblem_conflict_overwrite");
- case ModInfo::FLAG_CONFLICT_OVERWRITTEN: return QIcon(":/MO/gui/emblem_conflict_overwritten");
- case ModInfo::FLAG_CONFLICT_MIXED: return QIcon(":/MO/gui/emblem_conflict_mixed");
- case ModInfo::FLAG_CONFLICT_REDUNDANT: return QIcon(":MO/gui/emblem_conflict_redundant");
- default: return QIcon();
- }
-}
-
-
-void IconDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
-{
- QStyledItemDelegate::paint(painter, option, index);
- ModInfo::Ptr info = ModInfo::getByIndex(m_ProxyModel->mapToSource(index).row());
- std::vector<ModInfo::EFlag> flags = info->getFlags();
-
- int x = 4;
- painter->save();
- painter->translate(option.rect.topLeft());
- for (auto iter = flags.begin(); iter != flags.end(); ++iter) {
- QIcon temp = getFlagIcon(*iter);
- painter->drawPixmap(x, 2, 16, 16, temp.pixmap(QSize(16, 16)));
- x += 20;
- }
-
- painter->restore();
-}
-
-
-QSize IconDelegate::sizeHint(const QStyleOptionViewItem&, const QModelIndex &modelIndex) const
-{
- unsigned int index = m_ProxyModel->mapToSource(modelIndex).row();
- if (index < ModInfo::getNumMods()) {
- ModInfo::Ptr info = ModInfo::getByIndex(index);
- return QSize(info->getFlags().size() * 20, 20);
- } else {
- return QSize(1, 20);
- }
-}
-
+#include "icondelegate.h" +#include <QHBoxLayout> +#include <QLabel> +#include <QPainter> + + +IconDelegate::IconDelegate(QAbstractProxyModel *proxyModel, QObject *parent) + : QStyledItemDelegate(parent), m_ProxyModel(proxyModel) +{ +} + + +QIcon IconDelegate::getFlagIcon(ModInfo::EFlag flag) const +{ + switch (flag) { + case ModInfo::FLAG_BACKUP: return QIcon(":/MO/gui/emblem_backup"); + case ModInfo::FLAG_INVALID: return QIcon(":/MO/gui/emblem_problem"); + case ModInfo::FLAG_NOTENDORSED: return QIcon(":/MO/gui/emblem_notendorsed"); + case ModInfo::FLAG_NOTES: return QIcon(":/MO/gui/emblem_notes"); + case ModInfo::FLAG_CONFLICT_OVERWRITE: return QIcon(":/MO/gui/emblem_conflict_overwrite"); + case ModInfo::FLAG_CONFLICT_OVERWRITTEN: return QIcon(":/MO/gui/emblem_conflict_overwritten"); + case ModInfo::FLAG_CONFLICT_MIXED: return QIcon(":/MO/gui/emblem_conflict_mixed"); + case ModInfo::FLAG_CONFLICT_REDUNDANT: return QIcon(":MO/gui/emblem_conflict_redundant"); + default: return QIcon(); + } +} + + +void IconDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + QStyledItemDelegate::paint(painter, option, index); + QModelIndex sourceIndex = m_ProxyModel->mapToSource(index); + if (!sourceIndex.isValid()) { + return; + } + ModInfo::Ptr info = ModInfo::getByIndex(sourceIndex.row()); + std::vector<ModInfo::EFlag> flags = info->getFlags(); + + int x = 4; + painter->save(); + painter->translate(option.rect.topLeft()); + for (auto iter = flags.begin(); iter != flags.end(); ++iter) { + QIcon temp = getFlagIcon(*iter); + painter->drawPixmap(x, 2, 16, 16, temp.pixmap(QSize(16, 16))); + x += 20; + } + + painter->restore(); +} + + +QSize IconDelegate::sizeHint(const QStyleOptionViewItem&, const QModelIndex &modelIndex) const +{ + unsigned int index = m_ProxyModel->mapToSource(modelIndex).row(); + if (index < ModInfo::getNumMods()) { + ModInfo::Ptr info = ModInfo::getByIndex(index); + return QSize(info->getFlags().size() * 20, 20); + } else { + return QSize(1, 20); + } +} + diff --git a/src/installationmanager.cpp b/src/installationmanager.cpp index 27dfea6e..0c47bf49 100644 --- a/src/installationmanager.cpp +++ b/src/installationmanager.cpp @@ -20,10 +20,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include "installationmanager.h" #include "utility.h" -#include "installdialog.h" -#include "simpleinstalldialog.h" -#include "baincomplexinstallerdialog.h" -#include "fomodinstallerdialog.h" + #include "report.h" #include "categories.h" #include "questionboxmemory.h" @@ -268,11 +265,11 @@ QStringList InstallationManager::extractFiles(const QStringList &filesOrig) return result; } -IPluginInstaller::EInstallResult InstallationManager::installArchive(const QString &modName, const QString &archiveName) +IPluginInstaller::EInstallResult InstallationManager::installArchive(GuessedValue<QString> &modName, const QString &archiveName) { - QString temp = modName; + GuessedValue<QString> temp(modName); bool iniTweaks; - if (install(archiveName, "", "modsdir", false, true, temp, iniTweaks)) { + if (install(archiveName, "modsdir", temp, iniTweaks)) { return IPluginInstaller::RESULT_SUCCESS; } else { return IPluginInstaller::RESULT_FAILED; @@ -484,7 +481,7 @@ QString InstallationManager::generateBackupName(const QString &directoryName) } -bool InstallationManager::testOverwrite(const QString &modsDirectory, QString &modName) +bool InstallationManager::testOverwrite(const QString &modsDirectory, GuessedValue<QString> &modName) { QString targetDirectory = QDir::fromNativeSeparators(modsDirectory.mid(0).append("\\").append(modName)); @@ -503,7 +500,7 @@ bool InstallationManager::testOverwrite(const QString &modsDirectory, QString &m QString name = QInputDialog::getText(m_ParentWidget, tr("Mod Name"), tr("Name"), QLineEdit::Normal, modName, &ok); if (ok && !name.isEmpty()) { - modName = name; + modName.update(name, GUESS_USER); if (!ensureValidModName(modName)) { return false; } @@ -547,45 +544,48 @@ bool InstallationManager::testOverwrite(const QString &modsDirectory, QString &m return true; } - -void InstallationManager::fixModName(QString &name) +/* +bool InstallationManager::fixModName(QString &name) { -// name = name.remove("^[ ]*").trimmed(); - name = name.simplified(); - while (name.endsWith('.')) name.chop(1); - - name.replace(QRegExp("[<>:\"/\\|?*]"), ""); + QString temp = name.simplified(); + while (temp.endsWith('.')) temp.chop(1); + temp.replace(QRegExp("[<>:\"/\\|?*]"), ""); static QString invalidNames[] = { "CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9" }; for (int i = 0; i < sizeof(invalidNames) / sizeof(QString); ++i) { - if (name == invalidNames[i]) { - name = ""; + if (temp == invalidNames[i]) { + temp = ""; break; } } -} + if (temp.length() > 1) { + name = temp; + return true; + } else { + return false; + } +} +*/ -bool InstallationManager::ensureValidModName(QString &name) +bool InstallationManager::ensureValidModName(GuessedValue<QString> &name) { - fixModName(name); - - while (name.isEmpty()) { + while (name->isEmpty()) { bool ok; - name = QInputDialog::getText(m_ParentWidget, tr("Invalid name"), - tr("The name you entered is invalid, please enter a different one."), - QLineEdit::Normal, "", &ok); + name.update(QInputDialog::getText(m_ParentWidget, tr("Invalid name"), + tr("The name you entered is invalid, please enter a different one."), + QLineEdit::Normal, "", &ok), + GUESS_USER); if (!ok) { return false; } - fixModName(name); } return true; } -bool InstallationManager::doInstall(const QString &modsDirectory, QString &modName, int modID, +bool InstallationManager::doInstall(const QString &modsDirectory, GuessedValue<QString> &modName, int modID, const QString &version, const QString &newestVersion, int categoryID) { if (!ensureValidModName(modName)) { @@ -684,270 +684,36 @@ bool EndsWith(LPCWSTR string, LPCWSTR subString) } -bool InstallationManager::installFomodInternal(DirectoryTree *&baseNode, const QString &fomodPath, const QString &modsDirectory, - int modID, const QString &version, const QString &newestVersion, int categoryID, - QString &modName, bool nameGuessed, bool &manualRequest) -{ - qDebug("treating as fomod archive"); - - FileData* const *data; - size_t size; - m_CurrentArchive->getFileList(data, size); - wchar_t *installerFiles[] = { L"fomod\\info.xml", L"fomod\\ModuleConfig.xml", - L"fomod\\script.cs", L"fomod\\screenshot.png", NULL }; - for (size_t i = 0; i < size; ++i) { - data[i]->setSkip(true); - if (data[i]->getFileName() == NULL) { - qCritical("invalid archive file name"); - } - for (int fileIdx = 0; installerFiles[fileIdx] != NULL; ++fileIdx) { - if (EndsWith(data[i]->getFileName(), installerFiles[fileIdx])) { - wchar_t *baseName = wcsrchr(installerFiles[fileIdx], '\\'); - if (baseName != NULL) { - data[i]->setSkip(false); - data[i]->setOutputFileName(baseName); - m_TempFilesToDelete.insert(ToQString(baseName)); - } else { - qCritical("failed to find backslash in %ls", installerFiles[fileIdx]); - } - break; - } - } - if (EndsWith(data[i]->getFileName(), L".png") || - EndsWith(data[i]->getFileName(), L".jpg") || - EndsWith(data[i]->getFileName(), L".gif") || - EndsWith(data[i]->getFileName(), L".bmp")) { - const wchar_t *baseName = wcsrchr(data[i]->getFileName(), '\\'); - if (baseName == NULL) { - baseName = data[i]->getFileName(); - } else { - ++baseName; - } - data[i]->setSkip(false); - data[i]->setOutputFileName(baseName); - m_TempFilesToDelete.insert(ToQString(baseName)); - } - } - - m_InstallationProgress.setWindowTitle(tr("Preparing installer")); - m_InstallationProgress.setLabelText(QString()); - m_InstallationProgress.setValue(0); - m_InstallationProgress.setWindowModality(Qt::WindowModal); - m_InstallationProgress.show(); - - // unpack only the files we need for the installer - if (!m_CurrentArchive->extract(ToWString(QDir::toNativeSeparators(QDir::tempPath())).c_str(), - new MethodCallback<InstallationManager, void, float>(this, &InstallationManager::updateProgress), - new MethodCallback<InstallationManager, void, LPCWSTR>(this, &InstallationManager::dummyProgressFile), - new MethodCallback<InstallationManager, void, LPCWSTR>(this, &InstallationManager::report7ZipError))) { - throw std::runtime_error("extracting failed"); - } - - m_InstallationProgress.hide(); - - bool success = false; - try { - FomodInstallerDialog dialog(modName, nameGuessed, fomodPath); - - FileData* const *data; - size_t size; - m_CurrentArchive->getFileList(data, size); - - // the installer will want to unpack screenshots... - for (size_t i = 0; i < size; ++i) { - data[i]->setSkip(true); - } - - dialog.initData(); - if (dialog.exec() == QDialog::Accepted) { - modName = dialog.getName(); - baseNode = dialog.updateTree(baseNode); - mapToArchive(baseNode); - - if (doInstall(modsDirectory, modName, modID, version, newestVersion, categoryID)) { - success = true; - } - } else { - if (dialog.manualRequested()) { - manualRequest = true; - modName = dialog.getName(); - } - } - } catch (const std::exception &e) { - reportError(tr("Installation as fomod failed: %1").arg(e.what())); - manualRequest = true; - } - return success; -} - - -bool InstallationManager::installFomodExternal(const QString &fileName, const QString &pluginsFileName, const QString &modDirectory) -{ - wchar_t binary[MAX_PATH]; - wchar_t parameters[1024]; // maximum: 2xMAX_PATH + approx 20 characters - wchar_t currentDirectory[MAX_PATH]; - - _snwprintf(binary, MAX_PATH, L"%ls", ToWString(QDir::toNativeSeparators(m_NCCPath)).c_str()); - _snwprintf(parameters, 1024, L"-g %ls -p \"%ls\" -i \"%ls\" \"%ls\"", - GameInfo::instance().getGameShortName().c_str(), - ToWString(QDir::toNativeSeparators(pluginsFileName)).c_str(), - ToWString(QDir::toNativeSeparators(fileName)).c_str(), - ToWString(QDir::toNativeSeparators(modDirectory)).c_str()); - _snwprintf(currentDirectory, MAX_PATH, L"%ls", ToWString(QFileInfo(m_NCCPath).absolutePath()).c_str()); - - GameInfo &gameInfo = GameInfo::instance(); - - QString binaryDestination = modDirectory.mid(0).append("/").append(ToQString(gameInfo.getBinaryName())); - - // NCC assumes the installation directory is the game directory and may try to access the binary to determine version information - QFile::copy(QDir::fromNativeSeparators(ToQString(gameInfo.getGameDirectory().append(L"\\").append(gameInfo.getBinaryName()))), - binaryDestination); - - ON_BLOCK_EXIT([&binaryDestination] { if (!QFile::remove(binaryDestination)) qCritical("failed to remove %s", qPrintable(binaryDestination)); } ); - - SHELLEXECUTEINFOW execInfo = {0}; - execInfo.cbSize = sizeof(SHELLEXECUTEINFOW); - execInfo.fMask = SEE_MASK_NOCLOSEPROCESS; - execInfo.hwnd = NULL; - execInfo.lpVerb = L"open"; - execInfo.lpFile = binary; - execInfo.lpParameters = parameters; - execInfo.lpDirectory = currentDirectory; - execInfo.nShow = SW_SHOW; - - if (!::ShellExecuteExW(&execInfo)) { - reportError(tr("failed to start %1").arg(m_NCCPath)); - return false; - } - - QProgressDialog busyDialog(tr("Running external installer.\nNote: This installer will not be aware of other installed mods!"), tr("Force Close"), 0, 0, m_ParentWidget); - busyDialog.setWindowModality(Qt::WindowModal); - bool confirmCancel = false; - busyDialog.show(); - bool finished = false; - while (true) { - QCoreApplication::processEvents(); - DWORD res = ::WaitForSingleObject(execInfo.hProcess, 100); - if (res == WAIT_OBJECT_0) { - finished = true; - break; - } else if ((busyDialog.wasCanceled()) || (res != WAIT_TIMEOUT)) { - if (!confirmCancel) { - confirmCancel = true; - busyDialog.hide(); - busyDialog.reset(); - busyDialog.show(); - busyDialog.setCancelButtonText(tr("Confirm")); - } else { - break; - } - } - } - - if (!finished) { - ::TerminateProcess(execInfo.hProcess, 1); - return false; - } - - DWORD exitCode = 128; - ::GetExitCodeProcess(execInfo.hProcess, &exitCode); - - ::CloseHandle(execInfo.hProcess); - - if ((exitCode == 0) || (exitCode == 10)) { // 0 = success, 10 = incomplete installation - bool errorOccured = false; - { // move all installed files from the data directory one directory up - QDir targetDir(modDirectory); - - QDirIterator dirIter(targetDir.absoluteFilePath("Data"), QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot); - bool hasFiles = false; - - while (dirIter.hasNext()) { - dirIter.next(); - QFileInfo fileInfo = dirIter.fileInfo(); - QString newName = targetDir.absoluteFilePath(fileInfo.fileName()); - if (fileInfo.isFile() && QFile::exists(newName)) { - if (!QFile::remove(newName)) { - qCritical("failed to overwrite %s", qPrintable(newName)); - errorOccured = true; - } - } // if it's a directory and the target exists that isn't really a problem - - if (!QFile::rename(fileInfo.absoluteFilePath(), newName)) { - // moving doesn't work when merging - if (!copyDir(fileInfo.absoluteFilePath(), newName, true)) { - qCritical("failed to move %s to %s", qPrintable(fileInfo.absoluteFilePath()), qPrintable(newName)); - errorOccured = true; - } - } - hasFiles = true; - } - // recognition of canceled installation in the external installer is broken so we assume the installation was - // canceled if no files were installed - if (!hasFiles) { - exitCode = 11; - } - } - - QString dataDir = modDirectory.mid(0).append("/Data"); - if (!removeDir(dataDir)) { - qCritical("failed to remove data directory from %s", dataDir.toUtf8().constData()); - errorOccured = true; - } - if (errorOccured) { - reportError(tr("Finalization of the installation failed. The mod may or may not work correctly. See mo_interface.log for details")); - } - } else if (exitCode != 11) { // 11 = manually canceled - reportError(tr("installation failed (errorcode %1)").arg(exitCode)); - } - - if ((exitCode == 0) || (exitCode == 10)) { - return true; - } else { - // after cancelation or error the installer may leave the empty mod directory - if (!removeDir(modDirectory)) { - qCritical ("failed to remove empty mod directory %s", modDirectory.toUtf8().constData()); - } - return false; - } -} - - bool InstallationManager::wasCancelled() { return m_CurrentArchive->getLastError() == Archive::ERROR_EXTRACT_CANCELLED; } -bool InstallationManager::install(const QString &fileName, const QString &pluginsFileName, const QString &modsDirectory, - bool preferIntegrated, bool enableQuickInstall, QString &modName, bool &hasIniTweaks) +bool InstallationManager::install(const QString &fileName, const QString &modsDirectory, + GuessedValue<QString> &modName, bool &hasIniTweaks) { QFileInfo fileInfo(fileName); - bool success = false; if (m_SupportedExtensions.find(fileInfo.suffix()) == m_SupportedExtensions.end()) { reportError(tr("File format \"%1\" not supported").arg(fileInfo.completeSuffix())); return false; } + modName.setFilter(&fixDirectoryName); + // read out meta information from the download if available int modID = 0; QString version = ""; QString newestVersion = ""; int categoryID = 0; - bool nameGuessed = false; QString metaName = fileName.mid(0).append(".meta"); if (QFile(metaName).exists()) { QSettings metaFile(metaName, QSettings::IniFormat); modID = metaFile.value("modID", 0).toInt(); - if (modName.isEmpty()) { - modName = metaFile.value("modName", "").toString(); - // it is possible we have a file-name but not the correct mod name. in this case, - // the stored mod name may be "\0" - if (modName.isEmpty() || (modName.length() < 2)) { - modName = metaFile.value("name", "").toString(); - } - } + modName.update(metaFile.value("name", "").toString(), GUESS_FALLBACK); + modName.update(metaFile.value("modName", "").toString(), GUESS_META); + version = metaFile.value("version", "").toString(); newestVersion = metaFile.value("newestVersion", "").toString(); unsigned int categoryIndex = CategoryFactory::instance().resolveNexusID(metaFile.value("category", 0).toInt()); @@ -963,15 +729,10 @@ bool InstallationManager::install(const QString &fileName, const QString &plugin } else if (modID != guessedModID) { qDebug("passed mod id: %d, guessed id: %d", modID, guessedModID); } - - if (modName.isEmpty()) { - modName = guessedModName; - nameGuessed = true; - } + modName.update(guessedModName, GUESS_GOOD); } - fixModName(modName); - qDebug("using mod name \"%s\" (id %d)", modName.toUtf8().constData(), modID); + qDebug("using mod name \"%s\" (id %d)", modName->toUtf8().constData(), modID); m_CurrentFile = fileInfo.fileName(); // open the archive and construct the directory tree the installers work on @@ -980,7 +741,7 @@ bool InstallationManager::install(const QString &fileName, const QString &plugin DirectoryTree *filesTree = archiveOpen ? createFilesTree() : NULL; -/* IPluginInstaller::EInstallResult installResult = IPluginInstaller::RESULT_NOTATTEMPTED; + IPluginInstaller::EInstallResult installResult = IPluginInstaller::RESULT_NOTATTEMPTED; std::sort(m_Installers.begin(), m_Installers.end(), [] (IPluginInstaller *LHS, IPluginInstaller *RHS) { return LHS->priority() > RHS->priority(); @@ -1046,216 +807,7 @@ bool InstallationManager::install(const QString &fileName, const QString &plugin } reportError(tr("None of the available installer plugins were able to handle that archive")); - return false;*/ - - - - hasIniTweaks = false; - - - DirectoryTree::Node *baseNode = NULL; - bool manualRequest = false; - - if (!archiveOpen) { - reportError(tr("Failed to open \"%1\": %2").arg(QDir::toNativeSeparators(fileName)).arg(getErrorString(m_CurrentArchive->getLastError()))); - return false; - } - - // bundled fomod? - if ((baseNode == NULL) && !manualRequest) { - QStringList bundledFomods; - for (DirectoryTree::const_leaf_iterator fileIter = filesTree->leafsBegin(); fileIter != filesTree->leafsEnd(); ++fileIter) { - if (fileIter->getName().endsWith(".fomod", Qt::CaseInsensitive)) { - bundledFomods.append(fileIter->getName()); - } - } - QString bundledFomodInst; - if (bundledFomods.count() > 1) { - SelectionDialog selection(tr("This seems like a bundle of fomods, which one do you want to install?"), m_ParentWidget); - foreach (const QString &fomod, bundledFomods) { - selection.addChoice(fomod, fomod, QVariant()); - } - if (selection.exec() == QDialog::Accepted) { - bundledFomodInst = selection.getChoiceString(); - } else { - return false; - } - } else if (bundledFomods.count() == 1) { - bundledFomodInst = bundledFomods.at(0); - qDebug("archive contains fomod: %s", qPrintable(bundledFomodInst)); - } - if (!bundledFomodInst.isEmpty()) { - unpackSingleFile(bundledFomodInst); - m_CurrentArchive->close(); - return install(QDir::tempPath().append("/").append(bundledFomodInst), pluginsFileName, modsDirectory, preferIntegrated, - enableQuickInstall, modName, hasIniTweaks); - } - } - - // fomod installer? - if ((baseNode == NULL) && !manualRequest) { - QString fomodPath; - bool xmlInstaller = false; - if (checkFomodPackage(filesTree, fomodPath, xmlInstaller)) { - baseNode = filesTree; - bool nmmInstaller = checkNMMInstaller(); - - if (xmlInstaller || nmmInstaller) { - if (!xmlInstaller || (nmmInstaller && !preferIntegrated)) { - if (!ensureValidModName(modName) || - !testOverwrite(modsDirectory, modName)) { - return false; - } - - QString targetDirectory = QDir::fromNativeSeparators(modsDirectory.mid(0).append("\\").append(modName)); - - if (installFomodExternal(fileName, pluginsFileName, targetDirectory)) { - QSettings settingsFile(targetDirectory.mid(0).append("/meta.ini"), QSettings::IniFormat); - - // overwrite settings only if they are actually are available or haven't been set before - if ((modID != 0) || !settingsFile.contains("modid")) { - settingsFile.setValue("modid", modID); - } - if (!settingsFile.contains("version") || - (!version.isEmpty() && - (VersionInfo(version) >= VersionInfo(settingsFile.value("version").toString())))) { - settingsFile.setValue("version", version); - } - if (!newestVersion.isEmpty() || !settingsFile.contains("newestVersion")) { - settingsFile.setValue("newestVersion", newestVersion); - } - if (!settingsFile.contains("category")) { - settingsFile.setValue("category", QString::number(categoryID)); - } - settingsFile.setValue("installationFile", m_CurrentFile); - - success = true; - } - } else { - if (installFomodInternal(baseNode, fomodPath, modsDirectory, - modID, version, newestVersion, categoryID, - modName, nameGuessed, manualRequest)) { - success = true; - } - } - if (success) { - DirectoryTree::node_iterator iniTweakNode = baseNode->nodeFind(DirectoryTreeInformation("INI Tweaks")); - hasIniTweaks = (iniTweakNode != baseNode->nodesEnd()) && - ((*iniTweakNode)->numLeafs() != 0); - } - if (baseNode != filesTree) { - delete baseNode; - } - } else { - if (QuestionBoxMemory::query(m_ParentWidget, Settings::instance().directInterface(), "missingNCC", - tr("Installer missing"), - tr("This package contains a scripted installer. To use this installer " - "you need the optional \"NCC\"-package and the .net runtime. " - "Do you want to continue, treating this as a manual installer?"), - QDialogButtonBox::Yes | QDialogButtonBox::Cancel) == QMessageBox::Yes) { - manualRequest = true; - } else { - MessageDialog::showMessage(tr("Please install NCC"), m_ParentWidget); - } - } - } - } - - // simple installer? - if ((baseNode == NULL) && enableQuickInstall && !manualRequest) { - baseNode = getSimpleArchiveBase(filesTree); - if (baseNode != NULL) { - qDebug("treating as simple archive (%d)", baseNode->numLeafs()); - SimpleInstallDialog dialog(modName, m_ParentWidget); - if (dialog.exec() == QDialog::Accepted) { - mapToArchive(baseNode); - modName = dialog.getName(); - if (doInstall(modsDirectory, modName, modID, version, newestVersion, categoryID)) { - success = true; - - DirectoryTree::node_iterator iniTweakNode = baseNode->nodeFind(DirectoryTreeInformation("INI Tweaks")); - hasIniTweaks = (iniTweakNode != baseNode->nodesEnd()) && - ((*iniTweakNode)->numLeafs() != 0); - } - } else { - if (dialog.manualRequested()) { - manualRequest = true; - modName = dialog.getName(); - } - } - } - } - - // bain complex package? - if ((baseNode == NULL) && !manualRequest) { - if (checkBainPackage(filesTree)) { - bool hasPackageTXT = unpackPackageTXT(); - - baseNode = filesTree; - qDebug("treating as complex archive (%d)", filesTree->numNodes()); - BainComplexInstallerDialog dialog(filesTree, modName, hasPackageTXT, m_ParentWidget); - if (dialog.exec() == QDialog::Accepted) { - modName = dialog.getName(); - // create a new tree with the selected directories mapped to the - // base directory. This is destructive on the original tree - baseNode = dialog.updateTree(baseNode); - mapToArchive(baseNode); - - if (doInstall(modsDirectory, modName, modID, version, newestVersion, categoryID)) { - success = true; - - DirectoryTree::node_iterator iniTweakNode = baseNode->nodeFind(DirectoryTreeInformation("INI Tweaks")); - hasIniTweaks = (iniTweakNode != baseNode->nodesEnd()) && - ((*iniTweakNode)->numLeafs() != 0); - } - delete baseNode; - } else { - if (dialog.manualRequested()) { - manualRequest = true; - modName = dialog.getName(); - } - } - QFile::remove(QDir::tempPath().append("/package.txt")); - } - } - - // final option: manual installer - if ((baseNode == NULL) || manualRequest) { - qDebug("offering installation dialog"); - InstallDialog dialog(filesTree, modName, m_ParentWidget); - connect(&dialog, SIGNAL(openFile(QString)), this, SLOT(openFile(QString))); - if (dialog.exec() == QDialog::Accepted) { - modName = dialog.getModName(); - baseNode = dialog.getDataTree(); - mapToArchive(baseNode); - if (doInstall(modsDirectory, modName, modID, version, newestVersion, categoryID)) { - success = true; - - DirectoryTree::node_iterator iniTweakNode = baseNode->nodeFind(DirectoryTreeInformation("INI Tweaks")); - hasIniTweaks = (iniTweakNode != baseNode->nodesEnd()) && - ((*iniTweakNode)->numLeafs() != 0); - } - delete baseNode; // baseNode is a new tree, independent of filesTree - } - } - - delete filesTree; - m_CurrentArchive->close(); - - for (std::set<QString>::iterator iter = m_FilesToDelete.begin(); - iter != m_FilesToDelete.end(); ++iter) { - QFile(*iter).remove(); - } - m_FilesToDelete.clear(); - - for (std::set<QString>::iterator iter = m_TempFilesToDelete.begin(); - iter != m_TempFilesToDelete.end(); ++iter) { - QFile(QDir::tempPath().append("/").append(*iter)).remove(); - } - - m_TempFilesToDelete.clear(); - - return success; + return false; } diff --git a/src/installationmanager.h b/src/installationmanager.h index 7d864647..5bf64e94 100644 --- a/src/installationmanager.h +++ b/src/installationmanager.h @@ -20,10 +20,10 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #ifndef INSTALLATIONMANAGER_H #define INSTALLATIONMANAGER_H -#include "installdialog.h" #include <iinstallationmanager.h> #include <iplugininstaller.h> +#include <guessedvalue.h> #include <QObject> #define WIN32_LEAN_AND_MEAN @@ -61,12 +61,12 @@ public: * @brief install a mod from an archive * * @param fileName absolute file name of the archive to install + * @param modsDirectory directory to install mods to * @param modName suggested name of the mod. If this is empty (the default), a name will be guessed based on the filename. The user will always have a chance to rename the mod - * @param preferIntegrated if true, integrated installers are chosen over external installers * @return true if the archive was installed, false if installation failed or was refused * @exception std::exception an exception may be thrown if the archive can't be opened (maybe the format is invalid or the file is damaged) **/ - bool install(const QString &fileName, const QString &pluginsFileName, const QString &modsDirectory, bool preferIntegrated, bool enableQuickInstall, QString &modName, bool &hasIniTweaks); + bool install(const QString &fileName, const QString &modsDirectory, MOBase::GuessedValue<QString> &modName, bool &hasIniTweaks); /** * @return true if the installation was canceled @@ -123,7 +123,7 @@ public: * @param archiveFile path to the archive to install * @return the installation result */ - virtual MOBase::IPluginInstaller::EInstallResult installArchive(const QString &modName, const QString &archiveName); + virtual MOBase::IPluginInstaller::EInstallResult installArchive(MOBase::GuessedValue<QString> &modName, const QString &archiveName); private: @@ -152,11 +152,11 @@ private: bool checkFomodPackage(MOBase::DirectoryTree *dataTree, QString &offset, bool &xmlInstaller); bool checkNMMInstaller(); - void fixModName(QString &name); +// static bool fixModName(QString &name); - bool testOverwrite(const QString &modsDirectory, QString &modName); + bool testOverwrite(const QString &modsDirectory, MOBase::GuessedValue<QString> &modName); - bool doInstall(const QString &modsDirectory, QString &modName, + bool doInstall(const QString &modsDirectory, MOBase::GuessedValue<QString> &modName, int modID, const QString &version, const QString &newestVersion, int categoryID); bool installFomodExternal(const QString &fileName, const QString &pluginsFileName, const QString &modDirectory); @@ -165,7 +165,7 @@ private: int categoryID, QString &modName, bool nameGuessed, bool &manualRequest); QString generateBackupName(const QString &directoryName); - bool ensureValidModName(QString &name); + bool ensureValidModName(MOBase::GuessedValue<QString> &name); private slots: diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index ac368719..6c8871ce 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -23,9 +23,11 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include "spawn.h" #include "report.h" #include "modlist.h" +#include "modlistsortproxy.h" +#include "modlistgroupcategoriesproxy.h" +#include "modlistgroupnexusidproxy.h" #include "profile.h" #include "pluginlist.h" -#include "installdialog.h" #include "profilesdialog.h" #include "editexecutablesdialog.h" #include "categories.h" @@ -43,7 +45,6 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include "syncoverwritedialog.h" #include "logbuffer.h" #include "downloadlistsortproxy.h" -#include "modlistsortproxy.h" #include "motddialog.h" #include "filedialogmemory.h" #include "questionboxmemory.h" @@ -99,7 +100,7 @@ MainWindow::MainWindow(const QString &exeName, QSettings &initSettings, QWidget : QMainWindow(parent), ui(new Ui::MainWindow), m_Tutorial(this, "MainWindow"), m_ExeName(exeName), m_OldProfileIndex(-1), m_DirectoryStructure(new DirectoryEntry(L"data", NULL, 0)), - m_ModList(NexusInterface::instance()), + m_ModList(NexusInterface::instance()), m_ModListSortProxy(NULL), m_ModListGroupProxy(NULL), m_OldExecutableIndex(-1), m_GamePath(ToQString(GameInfo::instance().getGameDirectory())), m_NexusDialog(NexusInterface::instance()->getAccessManager(), NULL), m_DownloadManager(NexusInterface::instance(), this), @@ -129,13 +130,19 @@ MainWindow::MainWindow(const QString &exeName, QSettings &initSettings, QWidget ModInfo::updateFromDisc(m_Settings.getModDirectory(), &m_DirectoryStructure); + // set up mod list m_ModListSortProxy = new ModListSortProxy(m_CurrentProfile, this); m_ModListSortProxy->setSourceModel(&m_ModList); - ui->modList->setModel(m_ModListSortProxy); + +// m_ModListGroupProxy = new ModListGroupProxy(this); + m_ModListGroupProxy = new QIdentityProxyModel(this); + m_ModListGroupProxy->setSourceModel(m_ModListSortProxy); + + ui->modList->setModel(m_ModListGroupProxy); ui->modList->sortByColumn(ModList::COL_PRIORITY, Qt::AscendingOrder); - ui->modList->setItemDelegateForColumn(ModList::COL_FLAGS, new IconDelegate(m_ModListSortProxy)); + ui->modList->setItemDelegateForColumn(ModList::COL_FLAGS, new IconDelegate(m_ModListGroupProxy, ui->modList)); ui->modList->header()->installEventFilter(&m_ModList); ui->modList->header()->restoreState(initSettings.value("mod_list_state").toByteArray()); ui->modList->installEventFilter(&m_ModList); @@ -1129,11 +1136,11 @@ void MainWindow::on_profileBox_currentIndexChanged(int index) } if (ui->profileBox->currentIndex() == 0) { - ui->profileBox->setCurrentIndex(previousIndex); ProfilesDialog(m_GamePath).exec(); while (!refreshProfiles()) { ProfilesDialog(m_GamePath).exec(); } + ui->profileBox->setCurrentIndex(previousIndex); } else { activateSelectedProfile(); } @@ -1657,14 +1664,13 @@ static QString guessModName(const QString &fileName) void MainWindow::installMod(const QString &fileName) { bool hasIniTweaks = false; - QString modName; + GuessedValue<QString> modName; m_CurrentProfile->writeModlistNow(); - if (m_InstallationManager.install(fileName, m_CurrentProfile->getPluginsFileName(), m_Settings.getModDirectory(), m_Settings.preferIntegratedInstallers(), - m_Settings.enableQuickInstaller(), modName, hasIniTweaks)) { + if (m_InstallationManager.install(fileName, m_Settings.getModDirectory(), modName, hasIniTweaks)) { MessageDialog::showMessage(tr("Installation successful"), this); refreshModList(); - QModelIndexList posList = m_ModList.match(m_ModList.index(0, 0), Qt::DisplayRole, modName); + QModelIndexList posList = m_ModList.match(m_ModList.index(0, 0), Qt::DisplayRole, static_cast<const QString&>(modName)); if (posList.count() == 1) { ui->modList->scrollTo(posList.at(0)); } @@ -2252,7 +2258,7 @@ void MainWindow::refreshFilters() void MainWindow::renameMod_clicked() { try { - QModelIndex treeIdx = m_ModListSortProxy->mapFromSource(m_ModList.index(m_ContextRow, 0)); + QModelIndex treeIdx = m_ModListGroupProxy->mapFromSource(m_ModList.index(m_ContextRow, 0)); ui->modList->setCurrentIndex(treeIdx); ui->modList->edit(treeIdx); } catch (const std::exception &e) { @@ -2294,7 +2300,7 @@ void MainWindow::removeMod_clicked() QString mods; QStringList modNames; foreach (QModelIndex idx, selection->selectedRows()) { - QString name = ModInfo::getByIndex(m_ModListSortProxy->mapToSource(idx).row())->name(); + QString name = ModInfo::getByIndex(m_ModListGroupProxy->mapToSource(idx).row())->name(); mods += "<li>" + name + "</li>"; modNames.append(name); } @@ -2559,8 +2565,16 @@ void MainWindow::cancelModListEditor() void MainWindow::on_modList_doubleClicked(const QModelIndex &index) { + if (!index.isValid()) { + return; + } + QModelIndex sourceIdx = m_ModListGroupProxy->mapToSource(index); + if (!sourceIdx.isValid()) { + return; + } + try { - displayModInformation(m_ModListSortProxy->mapToSource(index).row()); + displayModInformation(sourceIdx.row()); // workaround to cancel the editor that might have opened because of // selection-click ui->modList->closePersistentEditor(index); @@ -2794,7 +2808,7 @@ void MainWindow::on_modList_customContextMenuRequested(const QPoint &pos) try { QTreeView *modList = findChild<QTreeView*>("modList"); - m_ContextRow = m_ModListSortProxy->mapToSource(modList->indexAt(pos)).row(); + m_ContextRow = m_ModListGroupProxy->mapToSource(modList->indexAt(pos)).row(); QMenu menu; menu.addAction(tr("Install Mod..."), this, SLOT(installMod_clicked())); @@ -2865,6 +2879,7 @@ void MainWindow::on_categoriesList_currentItemChanged(QTreeWidgetItem *current, if (current != NULL) { m_ModListSortProxy->setCategoryFilter(current->data(0, Qt::UserRole).toInt()); ui->currentCategoryLabel->setText(QString("(%1)").arg(current->text(0))); + ui->modList->reset(); } } @@ -3173,32 +3188,29 @@ void MainWindow::installDownload(int index) try { QString fileName = m_DownloadManager.getFilePath(index); int modID = m_DownloadManager.getModID(index); - QString modName; + GuessedValue<QString> modName; // see if there already are mods with the specified mod id if (modID != 0) { - ModInfo::Ptr modInfo = ModInfo::getByModID(modID, true); - if (!modInfo.isNull()) { - std::vector<ModInfo::EFlag> flags = modInfo->getFlags(); + std::vector<ModInfo::Ptr> modInfo = ModInfo::getByModID(modID); + for (auto iter = modInfo.begin(); iter != modInfo.end(); ++iter) { + std::vector<ModInfo::EFlag> flags = (*iter)->getFlags(); if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_BACKUP) == flags.end()) { - modName = modInfo->name(); - modInfo->saveMeta(); + modName.update((*iter)->name(), GUESS_PRESET); + (*iter)->saveMeta(); } } - // TODO there may be multiple mods with the same id! -// modName = m_ModList.getModByModID(modID); } m_CurrentProfile->writeModlistNow(); bool hasIniTweaks = false; - if (m_InstallationManager.install(fileName, m_CurrentProfile->getPluginsFileName(), m_Settings.getModDirectory(), m_Settings.preferIntegratedInstallers(), - m_Settings.enableQuickInstaller(), modName, hasIniTweaks)) { + if (m_InstallationManager.install(fileName, m_Settings.getModDirectory(), modName, hasIniTweaks)) { MessageDialog::showMessage(tr("Installation successful"), this); refreshModList(); - QModelIndexList posList = m_ModList.match(m_ModList.index(0, 0), Qt::DisplayRole, modName); + QModelIndexList posList = m_ModList.match(m_ModList.index(0, 0), Qt::DisplayRole, static_cast<const QString&>(modName)); if (posList.count() == 1) { ui->modList->scrollTo(posList.at(0)); } @@ -3597,13 +3609,13 @@ void MainWindow::nxmUpdatesAvailable(const std::vector<int> &modIDs, QVariant us ui->actionEndorseMO->setVisible(true); } } else { - ModInfo::Ptr info = ModInfo::getByModID(result["id"].toInt(), true); - if (!info.isNull()) { - info->setNewestVersion(VersionInfo(result["version"].toString())); - info->setNexusDescription(result["description"].toString()); + std::vector<ModInfo::Ptr> info = ModInfo::getByModID(result["id"].toInt()); + for (auto iter = info.begin(); iter != info.end(); ++iter) { + (*iter)->setNewestVersion(VersionInfo(result["version"].toString())); + (*iter)->setNexusDescription(result["description"].toString()); if (NexusInterface::instance()->getAccessManager()->loggedIn()) { // don't use endorsement info if we're not logged in - info->setIsEndorsed(result["voted_by_user"].toBool()); + (*iter)->setIsEndorsed(result["voted_by_user"].toBool()); } } } @@ -3903,3 +3915,32 @@ void MainWindow::on_espList_customContextMenuRequested(const QPoint &pos) reportError(tr("Unknown exception")); } } + +void MainWindow::on_groupCombo_currentIndexChanged(int index) +{ + if (m_ModListGroupProxy == NULL) return; + + QAbstractProxyModel *newModel = NULL; + switch (index) { + case 0: { + newModel = new QIdentityProxyModel(this); + } break; + case 1: { + newModel = new ModListGroupCategoriesProxy(this); + } break; + case 2: { + newModel = new ModListGroupNexusIDProxy(m_CurrentProfile, this); + } break; + default: { + qDebug("invalid modlist grouping %d", index); + return; + } break; + } + + newModel->setSourceModel(m_ModListSortProxy); + ui->modList->setModel(newModel); + delete ui->modList->itemDelegateForColumn(ModList::COL_FLAGS); + ui->modList->setItemDelegateForColumn(ModList::COL_FLAGS, new IconDelegate(newModel, ui->modList)); + delete m_ModListGroupProxy; + m_ModListGroupProxy = newModel; +} diff --git a/src/mainwindow.h b/src/mainwindow.h index 26e4c9dc..1484a1bf 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -46,7 +46,6 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include "installationmanager.h" #include "selfupdater.h" #include "savegamegamebyro.h" -#include "modlistsortproxy.h" #include "pluginlistsortproxy.h" #include "tutorialcontrol.h" #include "savegameinfowidgetgamebryo.h" @@ -56,6 +55,8 @@ namespace Ui { } class QToolButton; +class ModListSortProxy; +class ModListGroupCategoriesProxy; class MainWindow : public QMainWindow, public MOBase::IOrganizer { @@ -239,6 +240,8 @@ private: ModList m_ModList; ModListSortProxy *m_ModListSortProxy; + QAbstractProxyModel *m_ModListGroupProxy; + PluginList m_PluginList; PluginListSortProxy *m_PluginListSortProxy; @@ -425,6 +428,7 @@ private slots: // ui slots void on_espList_customContextMenuRequested(const QPoint &pos); void on_displayCategoriesBtn_toggled(bool checked); + void on_groupCombo_currentIndexChanged(int index); }; #endif // MAINWINDOW_H diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 3493b710..370265da 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -302,14 +302,20 @@ p, li { white-space: pre-wrap; } <enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="indentation">
- <number>0</number>
+ <number>20</number>
+ </property>
+ <property name="uniformRowHeights">
+ <bool>true</bool>
</property>
<property name="itemsExpandable">
- <bool>false</bool>
+ <bool>true</bool>
</property>
<property name="sortingEnabled">
<bool>true</bool>
</property>
+ <property name="animated">
+ <bool>true</bool>
+ </property>
<property name="expandsOnDoubleClick">
<bool>false</bool>
</property>
@@ -328,7 +334,7 @@ p, li { white-space: pre-wrap; } </widget>
</item>
<item>
- <layout class="QHBoxLayout" name="horizontalLayout_4" stretch="0,0,1,1">
+ <layout class="QHBoxLayout" name="horizontalLayout_4" stretch="0,0,1,1,1">
<item>
<widget class="QPushButton" name="displayCategoriesBtn">
<property name="maximumSize">
@@ -378,6 +384,25 @@ p, li { white-space: pre-wrap; } </widget>
</item>
<item>
+ <widget class="QComboBox" name="groupCombo">
+ <item>
+ <property name="text">
+ <string>No groups</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Categories</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Nexus IDs</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item>
<widget class="MOBase::LineEditClear" name="modFilterEdit">
<property name="placeholderText">
<string>Namefilter</string>
@@ -709,6 +734,9 @@ p, li { white-space: pre-wrap; } <property name="indentation">
<number>0</number>
</property>
+ <property name="uniformRowHeights">
+ <bool>true</bool>
+ </property>
<property name="itemsExpandable">
<bool>false</bool>
</property>
diff --git a/src/modinfo.cpp b/src/modinfo.cpp index b8ef834f..28bdf789 100644 --- a/src/modinfo.cpp +++ b/src/modinfo.cpp @@ -44,7 +44,7 @@ using namespace MOShared; std::vector<ModInfo::Ptr> ModInfo::s_Collection; std::map<QString, unsigned int> ModInfo::s_ModsByName; -std::map<int, unsigned int> ModInfo::s_ModsByModID; +std::map<int, std::vector<unsigned int> > ModInfo::s_ModsByModID; int ModInfo::s_NextID; QMutex ModInfo::s_Mutex(QMutex::Recursive); @@ -128,20 +128,21 @@ ModInfo::Ptr ModInfo::getByIndex(unsigned int index) } -ModInfo::Ptr ModInfo::getByModID(int modID, bool missingAcceptable) +std::vector<ModInfo::Ptr> ModInfo::getByModID(int modID) { QMutexLocker locker(&s_Mutex); - std::map<int, unsigned int>::iterator iter = s_ModsByModID.find(modID); + auto iter = s_ModsByModID.find(modID); if (iter == s_ModsByModID.end()) { - if (missingAcceptable) { - return ModInfo::Ptr(); - } else { - throw MyException(tr("invalid mod id %1").arg(modID)); - } + return std::vector<ModInfo::Ptr>(); + } + + std::vector<ModInfo::Ptr> result; + for (auto idxIter = iter->second.begin(); idxIter != iter->second.end(); ++idxIter) { + result.push_back(getByIndex(*idxIter)); } - return getByIndex(iter->second); + return result; } @@ -157,12 +158,11 @@ bool ModInfo::removeMod(unsigned int index) ModInfo::Ptr modInfo = s_Collection[index]; s_ModsByName.erase(s_ModsByName.find(modInfo->name())); - //TODO this is a bit more complicated since multiple mods may have the - // same mod id but only one appears in the index - std::map<int, unsigned int>::iterator iter = s_ModsByModID.find(modInfo->getNexusID()) ; - if ((iter != s_ModsByModID.end()) && - (iter->second == index)) { - s_ModsByModID.erase(iter); + auto iter = s_ModsByModID.find(modInfo->getNexusID()); + if (iter != s_ModsByModID.end()) { + std::vector<unsigned int> indices = iter->second; + std::remove(indices.begin(), indices.end(), index); + s_ModsByModID[modInfo->getNexusID()] = indices; } // physically remove the mod directory @@ -234,12 +234,7 @@ void ModInfo::updateIndices() QString modName = s_Collection[i]->name(); int modID = s_Collection[i]->getNexusID(); s_ModsByName[modName] = i; - - // don't overwrite a modid-entry with a backup entry. This is a bit of a workaround - if ((s_ModsByModID.find(modID) == s_ModsByModID.end()) || - !backupRegEx.exactMatch(modName)) { - s_ModsByModID[modID] = i; - } + s_ModsByModID[modID].push_back(i); } } diff --git a/src/modinfo.h b/src/modinfo.h index a944a6f6..fba6fc36 100644 --- a/src/modinfo.h +++ b/src/modinfo.h @@ -125,12 +125,11 @@ public: * @brief retrieve a ModInfo object based on its nexus mod id * * @param modID the nexus mod id to look up - * @param missingAcceptable if true, this function will return a null-pointer if no mod has the specified mod id, otherwise an exception is thrown * @return a reference counting pointer to the mod info * @todo in its current form, this function is broken! There may be multiple mods with the same nexus id, * this function will return only one of them **/ - static ModInfo::Ptr getByModID(int modID, bool missingAcceptable); + static std::vector<ModInfo::Ptr> getByModID(int modID); /** * @brief remove a mod by index @@ -390,14 +389,14 @@ public: bool categorySet(int categoryID) const; /** - * @brief retrive the whole list of categories this mod belongs to + * @brief retrive the whole list of categories (as ids) this mod belongs to * * @return list of categories **/ const std::set<int> &getCategories() const { return m_Categories; } /** - * @return the primary category of this mod + * @return id of the primary category of this mod */ int getPrimaryCategory() const { return m_PrimaryCategory; } @@ -459,7 +458,7 @@ protected: private: static QMutex s_Mutex; - static std::map<int, unsigned int> s_ModsByModID; + static std::map<int, std::vector<unsigned int> > s_ModsByModID; static int s_NextID; bool m_Valid; diff --git a/src/modlist.cpp b/src/modlist.cpp index e70e3c31..dad77d68 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -122,6 +122,7 @@ QString ModList::getFlagText(ModInfo::EFlag flag, ModInfo::Ptr modInfo) const QVariant ModList::data(const QModelIndex &modelIndex, int role) const { if (m_Profile == NULL) return QVariant(); + if (!modelIndex.isValid()) return QVariant(); unsigned int modIndex = modelIndex.row(); int column = modelIndex.column(); @@ -197,6 +198,8 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const } } else if (role == Qt::UserRole) { return modInfo->getNexusID(); + } else if (role == Qt::UserRole + 1) { + return modIndex; } else if (role == Qt::FontRole) { QFont result; if (column == COL_NAME) { @@ -578,7 +581,7 @@ void ModList::notifyChange(int row) QModelIndex ModList::index(int row, int column, const QModelIndex&) const { - return createIndex(row, column, row); + return createIndex(row, column, -1); } @@ -634,6 +637,7 @@ bool ModList::eventFilter(QObject *obj, QEvent *event) (keyEvent->modifiers() == Qt::ControlModifier) && ((keyEvent->key() == Qt::Key_Up) || (keyEvent->key() == Qt::Key_Down))) { QItemSelectionModel *selectionModel = itemView->selectionModel(); +#pragma message("no longer a sortfilter model") const QSortFilterProxyModel *proxyModel = qobject_cast<const QSortFilterProxyModel*>(selectionModel->model()); int diff = -1; if (((keyEvent->key() == Qt::Key_Up) && (proxyModel->sortOrder() == Qt::DescendingOrder)) || diff --git a/src/modlistgroupcategoriesproxy.cpp b/src/modlistgroupcategoriesproxy.cpp new file mode 100644 index 00000000..4320be97 --- /dev/null +++ b/src/modlistgroupcategoriesproxy.cpp @@ -0,0 +1,123 @@ +#include "modlistgroupcategoriesproxy.h"
+#include "categories.h"
+#include "modinfo.h"
+
+
+ModListGroupCategoriesProxy::ModListGroupCategoriesProxy(QObject *parent)
+ : QAbstractProxyModel(parent)
+{
+}
+
+int ModListGroupCategoriesProxy::rowCount(const QModelIndex &parent) const
+{
+ if (!parent.isValid()) {
+ return CategoryFactory::instance().numCategories();
+ } else {
+ unsigned int count = 0;
+ for (int i = 0; i < sourceModel()->rowCount(); ++i) {
+ if (ModInfo::getByIndex(i)->getPrimaryCategory() ==
+ CategoryFactory::instance().getCategoryID(parent.row())) {
+ ++count;
+ }
+ }
+ return count;
+ }
+}
+
+int ModListGroupCategoriesProxy::columnCount(const QModelIndex &parent) const
+{
+ return sourceModel()->columnCount(mapToSource(parent));
+}
+
+QModelIndex ModListGroupCategoriesProxy::mapToSource(const QModelIndex &proxyIndex) const
+{
+ auto iter = m_IndexMap.find(proxyIndex);
+ if (iter != m_IndexMap.end()) {
+ return iter->second;
+ } else {
+ return QModelIndex();
+ }
+}
+
+QModelIndex ModListGroupCategoriesProxy::mapFromSource(const QModelIndex &sourceIndex) const
+{
+ if (!sourceIndex.isValid()) {
+ return QModelIndex();
+ } else {
+ ModInfo::Ptr mod = ModInfo::getByIndex(sourceIndex.data(Qt::UserRole + 1).toInt());
+ return index(sourceIndex.row(), sourceIndex.column(), index(mod->getPrimaryCategory(), 0, QModelIndex()));
+ }
+}
+
+QModelIndex ModListGroupCategoriesProxy::index(int row, int column, const QModelIndex &parent) const
+{
+ if (parent.isValid()) {
+ // mod
+ int categoryId = CategoryFactory::instance().getCategoryID(parent.row());
+
+ int srcRow = 0;
+ int offset = row;
+ for (; srcRow < sourceModel()->rowCount(); ++srcRow) {
+ int modId = sourceModel()->index(srcRow, column).data(Qt::UserRole + 1).toInt();
+ if (ModInfo::getByIndex(modId)->getPrimaryCategory() == categoryId) {
+
+ if (--offset < 0) {
+ break;
+ }
+ }
+ }
+ QPersistentModelIndex idx = createIndex(row, column, categoryId);
+ m_IndexMap[idx] = QPersistentModelIndex(sourceModel()->index(srcRow, column));
+ return idx;
+ } else {
+ // category
+ return createIndex(row, column, -1);
+ }
+}
+
+QModelIndex ModListGroupCategoriesProxy::parent(const QModelIndex &child) const
+{
+ if (!child.isValid()) {
+ return QModelIndex();
+ } else if (child.internalId() == -1) {
+ return QModelIndex(); // top level
+ } else {
+ return index(CategoryFactory::instance().getCategoryIndex(child.internalId()), 0, QModelIndex());
+ }
+}
+
+bool ModListGroupCategoriesProxy::hasChildren(const QModelIndex &parent) const
+{
+ // root item always has children (the categories), mods don't
+ if (!parent.isValid()) return true;
+ else if (parent.internalId() != -1) return false;
+
+ for (int i = 0; i < sourceModel()->rowCount(); ++i) {
+ ModInfo::Ptr mod = ModInfo::getByIndex(i);
+ if (mod->getPrimaryCategory() == CategoryFactory::instance().getCategoryID(parent.row())) {
+ return true;
+ }
+ }
+ return false;
+}
+
+QVariant ModListGroupCategoriesProxy::data(const QModelIndex &proxyIndex, int role) const
+{
+ auto iter = m_IndexMap.find(proxyIndex);
+ if (iter != m_IndexMap.end()) {
+ // mod
+ return sourceModel()->data(iter->second, role);
+ } else {
+ // category
+ if ((role == Qt::DisplayRole) && (proxyIndex.column() == 0)) {
+ return CategoryFactory::instance().getCategoryName(proxyIndex.row());
+ } else {
+ return QVariant();
+ }
+ }
+}
+
+QVariant ModListGroupCategoriesProxy::headerData(int section, Qt::Orientation orientation, int role) const
+{
+ return sourceModel()->headerData(section, orientation, role);
+}
diff --git a/src/modlistgroupcategoriesproxy.h b/src/modlistgroupcategoriesproxy.h new file mode 100644 index 00000000..23fd36df --- /dev/null +++ b/src/modlistgroupcategoriesproxy.h @@ -0,0 +1,40 @@ +#ifndef MODLISTGROUPPROXY_H
+#define MODLISTGROUPPROXY_H
+
+#include <QIdentityProxyModel>
+#include <tuple>
+
+class ModListGroupCategoriesProxy : public QAbstractProxyModel
+{
+
+ Q_OBJECT
+
+public:
+
+ explicit ModListGroupCategoriesProxy(QObject *parent = 0);
+
+ virtual int rowCount(const QModelIndex &parent) const;
+ virtual int columnCount(const QModelIndex &parent) const;
+
+ virtual QModelIndex mapToSource(const QModelIndex &proxyIndex) const;
+ virtual QModelIndex mapFromSource(const QModelIndex &sourceIndex) const;
+
+ virtual QModelIndex index(int row, int column, const QModelIndex &parent) const;
+ virtual QModelIndex parent(const QModelIndex &child) const;
+
+ virtual bool hasChildren(const QModelIndex &parent) const;
+
+ virtual QVariant data(const QModelIndex &proxyIndex, int role) const;
+ virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const;
+
+signals:
+
+public slots:
+
+private:
+
+ mutable std::map<QPersistentModelIndex, QPersistentModelIndex> m_IndexMap;
+
+};
+
+#endif // MODLISTGROUPPROXY_H
diff --git a/src/modlistgroupnexusidproxy.cpp b/src/modlistgroupnexusidproxy.cpp new file mode 100644 index 00000000..be072829 --- /dev/null +++ b/src/modlistgroupnexusidproxy.cpp @@ -0,0 +1,130 @@ +#include "modlistgroupnexusidproxy.h"
+#include "modinfo.h"
+
+ModListGroupNexusIDProxy::ModListGroupNexusIDProxy(Profile *profile, QObject *parent)
+ : QAbstractProxyModel(parent)
+{
+ refreshMap(profile);
+}
+
+void ModListGroupNexusIDProxy::refreshMap(Profile *profile)
+{
+ int row = 0;
+ for (unsigned int i = 0; i < ModInfo::getNumMods(); ++i) {
+ int nexusID = ModInfo::getByIndex(i)->getNexusID();
+ if (m_GroupMap.find(nexusID) == m_GroupMap.end()) {
+ m_RowIdxMap[row] = i;
+ m_IdxRowMap[i] = row;
+ ++row;
+ }
+ m_GroupMap[nexusID].push_back(i);
+ }
+
+ for (auto iter = m_GroupMap.begin(); iter != m_GroupMap.end(); ++iter) {
+ std::sort(iter->second.begin(), iter->second.end(),
+ [profile] (unsigned int LHS, unsigned int RHS) {
+ return profile->getModPriority(LHS) < profile->getModPriority(RHS);
+ });
+ }
+}
+
+int ModListGroupNexusIDProxy::rowCount(const QModelIndex &parent) const
+{
+ int res = 0;
+ if (!parent.isValid()) {
+ res = m_GroupMap.size();
+ } else {
+ ModInfo::Ptr info = ModInfo::getByIndex(parent.internalId());
+ res = m_GroupMap.at(info->getNexusID()).size();
+ }
+ return res;
+}
+
+int ModListGroupNexusIDProxy::columnCount(const QModelIndex &parent) const
+{
+ return sourceModel()->columnCount(mapToSource(parent));
+}
+
+QModelIndex ModListGroupNexusIDProxy::mapToSource(const QModelIndex &proxyIndex) const
+{
+ auto iter = m_IndexMap.find(proxyIndex);
+ if (iter != m_IndexMap.end()) {
+ return iter->second;
+ } else {
+ return QModelIndex();
+ }
+}
+
+QModelIndex ModListGroupNexusIDProxy::mapFromSource(const QModelIndex &sourceIndex) const
+{
+ unsigned int modID = sourceIndex.data(Qt::UserRole + 1).toInt();
+ ModInfo::Ptr mod = ModInfo::getByIndex(modID);
+ const std::vector<unsigned int> &subMods = m_GroupMap.at(mod->getNexusID());
+
+ if (subMods.size() == 0) return QModelIndex();
+
+ QModelIndex parent = QModelIndex();
+ if (modID != subMods[0]) {
+ parent = index(m_IdxRowMap.at(subMods[0]), 0, QModelIndex());
+ }
+ return index(sourceIndex.row(), sourceIndex.column(), parent);
+}
+
+QModelIndex ModListGroupNexusIDProxy::index(int row, int column, const QModelIndex &parent) const
+{
+ if (parent.isValid()) {
+ // sub-mod
+ ModInfo::Ptr parentMod = ModInfo::getByIndex(parent.internalId());
+ const std::vector<unsigned int> &subMods = m_GroupMap.at(parentMod->getNexusID());
+ if ((row < 0) || (row >= static_cast<int>(subMods.size()))) {
+ qCritical("invalid index: %dx%d", row, column);
+ return QModelIndex();
+ }
+
+ QPersistentModelIndex idx = createIndex(row, column, subMods[row]);
+ m_IndexMap[idx] = QPersistentModelIndex(sourceModel()->index(subMods[row], column));
+
+ return idx;
+ } else {
+ // top-level mod
+ return createIndex(row, column, m_RowIdxMap.at(row));
+ }
+}
+
+QModelIndex ModListGroupNexusIDProxy::parent(const QModelIndex &child) const
+{
+ if (!child.isValid()) return QModelIndex();
+
+ ModInfo::Ptr mod = ModInfo::getByIndex(child.internalId());
+ if (m_GroupMap.at(mod->getNexusID()).size() < 1) {
+ return QModelIndex();
+ }
+ if (m_GroupMap.at(mod->getNexusID())[0] == child.internalId()) {
+ return QModelIndex();
+ } else {
+ return index(m_IdxRowMap.at(m_GroupMap.at(mod->getNexusID())[0]), 0, QModelIndex());
+ }
+}
+
+bool ModListGroupNexusIDProxy::hasChildren(const QModelIndex &parent) const
+{
+ if (!parent.isValid()) return true;
+ ModInfo::Ptr mod = ModInfo::getByIndex(parent.internalId());
+ return m_GroupMap.at(mod->getNexusID()).size() > 1;
+}
+
+QVariant ModListGroupNexusIDProxy::data(const QModelIndex &proxyIndex, int role) const
+{
+ auto iter = m_IndexMap.find(proxyIndex);
+ if (iter != m_IndexMap.end()) {
+ // mod
+ return sourceModel()->data(iter->second, role);
+ } else {
+ return QVariant();
+ }
+}
+
+QVariant ModListGroupNexusIDProxy::headerData(int section, Qt::Orientation orientation, int role) const
+{
+ return sourceModel()->headerData(section, orientation, role);
+}
diff --git a/src/modlistgroupnexusidproxy.h b/src/modlistgroupnexusidproxy.h new file mode 100644 index 00000000..01d8a97d --- /dev/null +++ b/src/modlistgroupnexusidproxy.h @@ -0,0 +1,49 @@ +#ifndef MODLISTGROUPNEXUSIDPROXY_H
+#define MODLISTGROUPNEXUSIDPROXY_H
+
+
+#include <QAbstractProxyModel>
+#include <vector>
+#include <map>
+#include "profile.h"
+
+
+class ModListGroupNexusIDProxy : public QAbstractProxyModel
+{
+ Q_OBJECT
+public:
+ explicit ModListGroupNexusIDProxy(Profile *profile, QObject *parent = 0);
+
+ virtual int rowCount(const QModelIndex &parent) const;
+ virtual int columnCount(const QModelIndex &parent) const;
+
+ virtual QModelIndex mapToSource(const QModelIndex &proxyIndex) const;
+ virtual QModelIndex mapFromSource(const QModelIndex &sourceIndex) const;
+
+ virtual QModelIndex index(int row, int column, const QModelIndex &parent) const;
+ virtual QModelIndex parent(const QModelIndex &child) const;
+
+ virtual bool hasChildren(const QModelIndex &parent) const;
+
+ virtual QVariant data(const QModelIndex &proxyIndex, int role) const;
+ virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const;
+signals:
+
+public slots:
+
+private:
+
+ void refreshMap(Profile *profile);
+
+private:
+
+ std::map<int, std::vector<unsigned int> > m_GroupMap;
+
+ std::map<unsigned int, unsigned int> m_RowIdxMap; // maps row to mod id
+ std::map<unsigned int, unsigned int> m_IdxRowMap; // maps mod id to row
+
+ mutable std::map<QPersistentModelIndex, QPersistentModelIndex> m_IndexMap;
+
+};
+
+#endif // MODLISTGROUPNEXUSIDPROXY_H
diff --git a/src/modlistsortproxy.cpp b/src/modlistsortproxy.cpp index bf70f238..a5f2ee82 100644 --- a/src/modlistsortproxy.cpp +++ b/src/modlistsortproxy.cpp @@ -119,8 +119,8 @@ void ModListSortProxy::disableAllVisible() bool ModListSortProxy::lessThan(const QModelIndex &left, const QModelIndex &right) const { - int leftIndex = left.internalId(); - int rightIndex = right.internalId(); + int leftIndex = left.row(); + int rightIndex = right.row(); ModInfo::Ptr leftMod = ModInfo::getByIndex(leftIndex); ModInfo::Ptr rightMod = ModInfo::getByIndex(rightIndex); @@ -196,9 +196,17 @@ bool ModListSortProxy::filterMatches(ModInfo::Ptr info, bool enabled) const ((m_CategoryFilter == CategoryFactory::CATEGORY_SPECIAL_UNCHECKED) && !enabled) || ((m_CategoryFilter == CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE) && info->updateAvailable()) || ((m_CategoryFilter == CategoryFactory::CATEGORY_SPECIAL_NOCATEGORY) && (info->getCategories().size() == 0)) || - ((m_CategoryFilter == CategoryFactory::CATEGORY_SPECIAL_CONFLICT) && (hasConflictFlag(info->getFlags())))); + ((m_CategoryFilter == CategoryFactory::CATEGORY_SPECIAL_CONFLICT) && (hasConflictFlag(info->getFlags())))); } +/*QModelIndex ModListSortProxy::mapToSource(const QModelIndex &proxyIndex) const +{ +if (proxyIndex.model() && (proxyIndex.model() != this)) { + qDebug("%p - %p - %p - %d - %d - %d", proxyIndex.model(), this, this->sourceModel(), proxyIndex.row(), proxyIndex.column(), proxyIndex.parent().isValid()); +} + return QSortFilterProxyModel::mapToSource(proxyIndex); +}*/ + bool ModListSortProxy::filterAcceptsRow(int row, const QModelIndex&) const { diff --git a/src/modlistsortproxy.h b/src/modlistsortproxy.h index f7a9b587..cc3bf6ad 100644 --- a/src/modlistsortproxy.h +++ b/src/modlistsortproxy.h @@ -54,6 +54,8 @@ public: bool filterMatches(ModInfo::Ptr info, bool enabled) const; +//virtual QModelIndex mapToSource(const QModelIndex &proxyIndex) const; + public slots: void displayColumnSelection(const QPoint &pos); diff --git a/src/organizer.pro b/src/organizer.pro index 723c1e04..633b2a21 100644 --- a/src/organizer.pro +++ b/src/organizer.pro @@ -18,7 +18,6 @@ SOURCES += \ syncoverwritedialog.cpp \
spawn.cpp \
singleinstance.cpp \
- simpleinstalldialog.cpp \
settingsdialog.cpp \
settings.cpp \
selfupdater.cpp \
@@ -53,10 +52,8 @@ SOURCES += \ lockeddialog.cpp \
loadmechanism.cpp \
json.cpp \
- installdialog.cpp \
installationmanager.cpp \
helper.cpp \
- fomodinstallerdialog.cpp \
finddialog.cpp \
filedialogmemory.cpp \
executableslist.cpp \
@@ -72,7 +69,6 @@ SOURCES += \ categoriesdialog.cpp \
categories.cpp \
bbcode.cpp \
- baincomplexinstallerdialog.cpp \
archivetree.cpp \
activatemodsdialog.cpp \
moapplication.cpp \
@@ -80,14 +76,15 @@ SOURCES += \ icondelegate.cpp \
gameinfoimpl.cpp \
csvbuilder.cpp \
- savetextasdialog.cpp
+ savetextasdialog.cpp \
+ modlistgroupcategoriesproxy.cpp \
+ modlistgroupnexusidproxy.cpp
HEADERS += \
transfersavesdialog.h \
syncoverwritedialog.h \
spawn.h \
singleinstance.h \
- simpleinstalldialog.h \
settingsdialog.h \
settings.h \
selfupdater.h \
@@ -121,10 +118,8 @@ HEADERS += \ lockeddialog.h \
loadmechanism.h \
json.h \
- installdialog.h \
installationmanager.h \
helper.h \
- fomodinstallerdialog.h \
finddialog.h \
filedialogmemory.h \
executableslist.h \
@@ -140,7 +135,6 @@ HEADERS += \ categoriesdialog.h \
categories.h \
bbcode.h \
- baincomplexinstallerdialog.h \
archivetree.h \
activatemodsdialog.h \
moapplication.h \
@@ -148,7 +142,9 @@ HEADERS += \ icondelegate.h \
gameinfoimpl.h \
csvbuilder.h \
- savetextasdialog.h
+ savetextasdialog.h \
+ modlistgroupcategoriesproxy.h \
+ modlistgroupnexusidproxy.h
FORMS += \
transfersavesdialog.ui \
diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp index 486d6f42..da31d11c 100644 --- a/src/pluginlist.cpp +++ b/src/pluginlist.cpp @@ -340,7 +340,6 @@ void PluginList::readLockedOrderFrom(const QString &fileName) } } } - file.close(); } @@ -530,7 +529,6 @@ void PluginList::syncLoadOrder() void PluginList::refreshLoadOrder() { syncLoadOrder(); - // set priorities according to locked load order std::map<int, QString> lockedLoadOrder; std::for_each(m_LockedOrder.begin(), m_LockedOrder.end(), @@ -550,10 +548,16 @@ void PluginList::refreshLoadOrder() ++targetPrio; } } + + if (static_cast<size_t>(targetPrio) >= m_ESPs.size()) { + continue; + } + int temp = targetPrio; - if (m_ESPs[nameIter->second].m_Priority != temp) { - setPluginPriority(nameIter->second, temp); - m_ESPs[nameIter->second].m_LoadOrder = iter->first; + int index = nameIter->second; + if (m_ESPs[index].m_Priority != temp) { + setPluginPriority(index, temp); + m_ESPs[index].m_LoadOrder = iter->first; syncLoadOrder(); m_Modified = true; } diff --git a/src/profile.cpp b/src/profile.cpp index 002c93c0..62ac943e 100644 --- a/src/profile.cpp +++ b/src/profile.cpp @@ -25,6 +25,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include "modinfo.h" #include <utility.h> #include <util.h> +#include <error_report.h> #include <appconfig.h> #include <QMessageBox> #include <QApplication> @@ -47,14 +48,19 @@ Profile::Profile(const QString &name, bool useDefaultSettings) QString profilesDir = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getProfilesDir())); QDir profileBase(profilesDir); - if (!profileBase.exists() || !profileBase.mkdir(name)) { - throw std::runtime_error(QObject::tr("failed to create %1").arg(name).toUtf8().constData()); + QString fixedName = name; + if (!fixDirectoryName(fixedName)) { + throw MyException(tr("invalid profile name %1").arg(name)); } - QString fullPath = profilesDir + "/" + name; + + if (!profileBase.exists() || !profileBase.mkdir(fixedName)) { + throw MyException(tr("failed to create %1").arg(fixedName).toUtf8().constData()); + } + QString fullPath = profilesDir + "/" + fixedName; m_Directory = QDir(fullPath); QFile modList(m_Directory.filePath("modlist.txt")); if (!modList.open(QIODevice::ReadWrite)) { - profileBase.rmdir(name); + profileBase.rmdir(fixedName); throw std::runtime_error(QObject::tr("failed to create %1").arg(m_Directory.filePath("modlist.txt")).toUtf8().constData()); } modList.close(); @@ -63,7 +69,7 @@ Profile::Profile(const QString &name, bool useDefaultSettings) GameInfo::instance().createProfile(ToWString(fullPath), useDefaultSettings); } catch (...) { // clean up in case of an error - removeDir(profileBase.absoluteFilePath(name)); + removeDir(profileBase.absoluteFilePath(fixedName)); throw; } initTimer(); @@ -133,11 +139,16 @@ void Profile::cancelWriteModlist() const void Profile::writeModlistNow() const { m_SaveTimer->stop(); + if (!m_Directory.exists()) return; #pragma message("right now, this is doing unnecessary saves. Need a flag that says that mod priority, enabled-state or name of a mod has changed") QString fileName = getModlistFileName(); QFile file(fileName); - file.open(QIODevice::WriteOnly); + if (!file.open(QIODevice::WriteOnly)) { + reportError(tr("failed to open \"%1\" for writing").arg(fileName)); + return; + } + file.resize(0); file.write(QString("# This file was automatically generated by Mod Organizer.\r\n").toUtf8()); if (m_ModStatus.empty()) { @@ -168,9 +179,7 @@ void Profile::writeModlistNow() const void Profile::createTweakedIniFile() { - QFileInfo iniInfo(getIniFileName()); - - QString tweakedIni = iniInfo.absolutePath() + "/initweaks.ini"; + QString tweakedIni = m_Directory.absoluteFilePath("initweaks.ini"); // QFile iniFile(tweakedIni); // workaround: the fallout nv launcher seems to mark the file read-only. crazy... @@ -189,6 +198,28 @@ void Profile::createTweakedIniFile() mergeTweaks(modInfo, tweakedIni); } } + + + bool error = false; + if (!::WritePrivateProfileStringW(L"Archive", L"bInvalidateOlderFiles", L"1", ToWString(tweakedIni).c_str())) { + error = true; + } + + if (localSavesEnabled()) { + if (!::WritePrivateProfileStringW(L"General", L"bUseMyGamesDirectory", L"1", ToWString(tweakedIni).c_str())) { + error = true; + } + + if (!::WritePrivateProfileStringW(L"General", L"SLocalSavePath", + AppConfig::localSavePlaceholder(), + ToWString(tweakedIni).c_str())) { + error = true; + } + } + + if (error) { + reportError(tr("failed to create tweaked ini: %1").arg(getCurrentErrorStringA().c_str())); + } } @@ -427,6 +458,14 @@ Profile Profile::createFrom(const QString &name, const Profile &reference) } +Profile *Profile::createPtrFrom(const QString &name, const Profile &reference) +{ + QString profileDirectory = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getProfilesDir())).append("/").append(name); + reference.copyFilesTo(profileDirectory); + return new Profile(QDir(profileDirectory)); +} + + void Profile::copyFilesTo(QString &target) const { copyDir(m_Directory.absolutePath(), target, false); @@ -499,16 +538,6 @@ void Profile::mergeTweaks(ModInfo::Ptr modInfo, const QString &tweakedIni) const iter != iniTweaks.end(); ++iter) { mergeTweak(*iter, tweakedIni); } - - ::WritePrivateProfileStringW(L"Archive", L"bInvalidateOlderFiles", L"1", ToWString(tweakedIni).c_str()); - - if (localSavesEnabled()) { - ::WritePrivateProfileStringW(L"General", L"bUseMyGamesDirectory", L"1", ToWString(tweakedIni).c_str()); - - ::WritePrivateProfileStringW(L"General", L"SLocalSavePath", - AppConfig::localSavePlaceholder(), - ToWString(tweakedIni).c_str()); - } } @@ -659,7 +688,6 @@ QString Profile::getPluginsFileName() const return QDir::cleanPath(m_Directory.absoluteFilePath("plugins.txt")); } - QString Profile::getLoadOrderFileName() const { return QDir::cleanPath(m_Directory.absoluteFilePath("loadorder.txt")); @@ -693,3 +721,10 @@ QString Profile::getPath() const { return QDir::cleanPath(m_Directory.absolutePath()); } + +void Profile::rename(const QString &newName) +{ + QDir profileDir(QDir::fromNativeSeparators(ToQString(GameInfo::instance().getProfilesDir()))); + profileDir.rename(getName(), newName); + m_Directory = profileDir.absoluteFilePath(newName); +} diff --git a/src/profile.h b/src/profile.h index 0c25b8f1..52b6a5eb 100644 --- a/src/profile.h +++ b/src/profile.h @@ -17,295 +17,304 @@ You should have received a copy of the GNU General Public License along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef PROFILE_H
-#define PROFILE_H
-
-
-#include "modinfo.h"
-
-#include <QString>
-#include <QDir>
-#include <QMetaType>
-#include <QSettings>
-#include <vector>
-#include <tuple>
-
-
-/**
- * @brief represents a profile
- **/
-class Profile : public QObject
-{
-
- Q_OBJECT
-
-public:
-
- /**
- * @brief default constructor
- * @todo This constructor initialised nothing, the resulting object is not usable
- **/
- Profile();
-
- /**
- * @brief constructor
- *
- * This constructor is used to create a new profile so it is to be assumed a profile
- * by this name does not yet exist
- * @param name name of the new profile
- * @param filter save game filter. Defaults to <no filter>.
- **/
- Profile(const QString &name, bool useDefaultSettings);
- /**
- * @brief constructor
- *
- * This constructor is used to open an existing profile though it will also try to repair
- * the profile if important files are missing (including the directory itself) so technically,
- * invoking this should always produce a working profile
- * @param directory directory to read the profile from
- **/
- Profile(const QDir &directory);
-
- Profile(const Profile &reference);
-
- ~Profile();
-
- /**
- * @return true if this profile (still) exists on disc
- */
- bool exists() const;
-
- /**
- * @brief copy constructor
- *
- * @param name of the new profile
- * @param reference profile to copy from
- **/
- static Profile createFrom(const QString &name, const Profile &reference);
-
- /**
- * @brief write out the modlist.txt
- **/
- void writeModlist() const;
-
- /**
- * cancel any potential request to write modlist.txt. This is used to ensure no invalid modlist is
- * saved while it's being modified
- */
- void cancelWriteModlist() const;
-
- /**
- * @brief test if this profile uses archive invalidation
- *
- * @param supported if this is not null, the parameter will be set to false if invalidation is not supported in this profile
- * @return true if archive invalidation is active
- * @note currently, invalidation is not supported if the relevant entry in the ini file does not exist
- **/
- bool invalidationActive(bool *supported) const;
-
- /**
- * @brief deactivate archive invalidation if it was active
- **/
- void deactivateInvalidation() const;
-
- /**
- * @brief activate archive invalidation
- *
- * @param dataDirectory data directory of the game
- * @todo passing the data directory as a parameter is useless, the function should
- * be able to query it from GameInfo
- **/
- void activateInvalidation(const QString &dataDirectory) const;
-
- /**
- * @return true if this profile uses local save games
- */
- bool localSavesEnabled() const;
-
- /**
- * @brief enables or disables the use of local save games for this profile
- * disabling this does not delete exising local saves but they will not be visible
- * in the game
- * @param enable if true, local saves are enabled, otherewise they are disabled
- */
- bool enableLocalSaves(bool enable);
-
- /**
- * @return name of the profile (this is identical to its directory name)
- **/
- QString getName() const { return m_Directory.dirName(); }
-
- /**
- * @return the path of the plugins file in this profile
- * @todo is this required? can the functionality using this function be moved to the Profile-class?
- **/
- QString getPluginsFileName() const;
-
- /**
- * @return the path of the loadorder file in this profile
- **/
- QString getLoadOrderFileName() const;
-
- /**
- * @return the path of the file containing locked mod indices
- */
- QString getLockedOrderFileName() const;
-
- /**
- * @return path of the archives file in this profile
- */
- QString getArchivesFileName() const;
-
- /**
- * @return the path of the delete file in this profile
- * @note the deleter file lists plugins that should be hidden from the game and other tools
- **/
- QString getDeleterFileName() const;
-
- /**
- * @return the path of the ini file in this profile
- * @todo since the game can contain multiple ini files (i.e. skyrim.ini skyrimprefs.ini)
- * the concept of this function is somewhat broken
- **/
- QString getIniFileName() const;
-
- /**
- * @return path to this profile
- **/
- QString getPath() const;
-
- /**
- * @brief create the ini file to be used by the game
- *
- * the tweaked ini file constructed by this file is a merger
- * of the game-ini of this profile with ini tweaks applied */
- void createTweakedIniFile();
-
- /**
- * @brief re-read the modlist.txt and update the mod status from it
- **/
- void refreshModStatus();
-
- /**
- * @brief retrieve a list of mods that are enabled in this profile
- *
- * @return list of active mods sorted by priority (ascending). "first" is the mod name, "second" is its path
- **/
- std::vector<std::tuple<QString, QString, int> > getActiveMods();
-
- /**
- * retrieve the number of mods for which this object has status information.
- * This is usually the same as ModInfo::getNumMods() except between
- * calls to ModInfo::updateFromDisc() and the Profile::refreshModStatus()
- *
- * @return number of mods for which the profile has status information
- **/
- unsigned int numMods() const { return m_ModStatus.size(); }
-
- /**
- * @return the number of mods that can be enabled and where the priority can be modified
- */
- unsigned int numRegularMods() const { return m_NumRegularMods; }
-
- /**
- * @brief retrieve the mod index based on the priority
- *
- * @param priority priority to look up
- * @return the index of the mod
- * @throw std::exception an exception is thrown if there is no mod with the specified priority
- **/
- unsigned int modIndexByPriority(unsigned int priority) const;
-
- /**
- * @brief enable or disable a mod
- *
- * @param index index of the mod to enable/disable
- * @param enabled true if the mod is to be enabled, false if it is to be disabled
- **/
- void setModEnabled(unsigned int index, bool enabled);
-
- /**
- * change the priority of a mod. Of course this also changes the priority of other mods.
- * The priority of the mods in the range ]old, new priority] are shifted so that no gaps
- * are possible.
- *
- * @param index index of the mod to change
- * @param newPriority the new priority value
- *
- * @todo what happens if the new priority is outside the range?
- **/
- void setModPriority(unsigned int index, int &newPriority);
-
- /**
- * @brief determine if a mod is enabled
- *
- * @param index index of the mod to look up
- * @return true if the mod is enabled, false otherwise
- **/
- bool modEnabled(unsigned int index) const;
-
- /**
- * @brief query the priority of a mod
- *
- * @param index index of the mod to look up
- * @return priority of the specified mod
- **/
- int getModPriority(unsigned int index) const;
-
- void dumpModStatus() const;
-
-signals:
-
- /**
- * @brief emitted whenever the status (enabled/disabled) of a mod changed
- *
- * @param index index of the mod that changed
- **/
- void modStatusChanged(unsigned int index);
-
-public slots:
-
- void writeModlistNow() const;
-
-private:
-
- class ModStatus {
- friend class Profile;
- public:
- ModStatus() : m_Overwrite(false), m_Enabled(false), m_Priority(-1) {}
- private:
- bool m_Overwrite;
- bool m_Enabled;
- int m_Priority;
- };
-
-private:
- Profile& operator=(const Profile &reference); // not implemented
-
- void initTimer();
-
- void updateIndices();
-
- QString getModlistFileName() const;
- void copyFilesTo(QString &target) const;
-
- std::vector<std::wstring> splitDZString(const wchar_t *buffer) const;
- void mergeTweak(const QString &tweakName, const QString &tweakedIni) const;
- void mergeTweaks(ModInfo::Ptr modInfo, const QString &tweakedIni) const;
-
-private:
-
- QDir m_Directory;
-
- std::vector<ModStatus> m_ModStatus;
- std::vector<unsigned int> m_ModIndexByPriority;
- unsigned int m_NumRegularMods;
-
- QTimer *m_SaveTimer;
-
-};
-
-Q_DECLARE_METATYPE(Profile)
-
-
-#endif // PROFILE_H
+#ifndef PROFILE_H +#define PROFILE_H + + +#include "modinfo.h" + +#include <QString> +#include <QDir> +#include <QMetaType> +#include <QSettings> +#include <vector> +#include <tuple> + + +/** + * @brief represents a profile + **/ +class Profile : public QObject +{ + + Q_OBJECT + +public: + + typedef boost::shared_ptr<Profile> Ptr; + +public: + + /** + * @brief default constructor + * @todo This constructor initialised nothing, the resulting object is not usable + **/ + Profile(); + + /** + * @brief constructor + * + * This constructor is used to create a new profile so it is to be assumed a profile + * by this name does not yet exist + * @param name name of the new profile + * @param filter save game filter. Defaults to <no filter>. + **/ + Profile(const QString &name, bool useDefaultSettings); + /** + * @brief constructor + * + * This constructor is used to open an existing profile though it will also try to repair + * the profile if important files are missing (including the directory itself) so technically, + * invoking this should always produce a working profile + * @param directory directory to read the profile from + **/ + Profile(const QDir &directory); + + Profile(const Profile &reference); + + ~Profile(); + + /** + * @return true if this profile (still) exists on disc + */ + bool exists() const; + + /** + * @param name of the new profile + * @param reference profile to copy from + **/ + static Profile createFrom(const QString &name, const Profile &reference); + + /** + * @param name of the new profile + * @param reference profile to copy from + **/ + static Profile *createPtrFrom(const QString &name, const Profile &reference); + + /** + * @brief write out the modlist.txt + **/ + void writeModlist() const; + + /** + * cancel any potential request to write modlist.txt. This is used to ensure no invalid modlist is + * saved while it's being modified + */ + void cancelWriteModlist() const; + + /** + * @brief test if this profile uses archive invalidation + * + * @param supported if this is not null, the parameter will be set to false if invalidation is not supported in this profile + * @return true if archive invalidation is active + * @note currently, invalidation is not supported if the relevant entry in the ini file does not exist + **/ + bool invalidationActive(bool *supported) const; + + /** + * @brief deactivate archive invalidation if it was active + **/ + void deactivateInvalidation() const; + + /** + * @brief activate archive invalidation + * + * @param dataDirectory data directory of the game + * @todo passing the data directory as a parameter is useless, the function should + * be able to query it from GameInfo + **/ + void activateInvalidation(const QString &dataDirectory) const; + + /** + * @return true if this profile uses local save games + */ + bool localSavesEnabled() const; + + /** + * @brief enables or disables the use of local save games for this profile + * disabling this does not delete exising local saves but they will not be visible + * in the game + * @param enable if true, local saves are enabled, otherewise they are disabled + */ + bool enableLocalSaves(bool enable); + + /** + * @return name of the profile (this is identical to its directory name) + **/ + QString getName() const { return m_Directory.dirName(); } + + /** + * @return the path of the plugins file in this profile + * @todo is this required? can the functionality using this function be moved to the Profile-class? + **/ + QString getPluginsFileName() const; + + /** + * @return the path of the loadorder file in this profile + **/ + QString getLoadOrderFileName() const; + + /** + * @return the path of the file containing locked mod indices + */ + QString getLockedOrderFileName() const; + + /** + * @return path of the archives file in this profile + */ + QString getArchivesFileName() const; + + /** + * @return the path of the delete file in this profile + * @note the deleter file lists plugins that should be hidden from the game and other tools + **/ + QString getDeleterFileName() const; + + /** + * @return the path of the ini file in this profile + * @todo since the game can contain multiple ini files (i.e. skyrim.ini skyrimprefs.ini) + * the concept of this function is somewhat broken + **/ + QString getIniFileName() const; + + /** + * @return path to this profile + **/ + QString getPath() const; + + void rename(const QString &newName); + + /** + * @brief create the ini file to be used by the game + * + * the tweaked ini file constructed by this file is a merger + * of the game-ini of this profile with ini tweaks applied */ + void createTweakedIniFile(); + + /** + * @brief re-read the modlist.txt and update the mod status from it + **/ + void refreshModStatus(); + + /** + * @brief retrieve a list of mods that are enabled in this profile + * + * @return list of active mods sorted by priority (ascending). "first" is the mod name, "second" is its path + **/ + std::vector<std::tuple<QString, QString, int> > getActiveMods(); + + /** + * retrieve the number of mods for which this object has status information. + * This is usually the same as ModInfo::getNumMods() except between + * calls to ModInfo::updateFromDisc() and the Profile::refreshModStatus() + * + * @return number of mods for which the profile has status information + **/ + unsigned int numMods() const { return m_ModStatus.size(); } + + /** + * @return the number of mods that can be enabled and where the priority can be modified + */ + unsigned int numRegularMods() const { return m_NumRegularMods; } + + /** + * @brief retrieve the mod index based on the priority + * + * @param priority priority to look up + * @return the index of the mod + * @throw std::exception an exception is thrown if there is no mod with the specified priority + **/ + unsigned int modIndexByPriority(unsigned int priority) const; + + /** + * @brief enable or disable a mod + * + * @param index index of the mod to enable/disable + * @param enabled true if the mod is to be enabled, false if it is to be disabled + **/ + void setModEnabled(unsigned int index, bool enabled); + + /** + * change the priority of a mod. Of course this also changes the priority of other mods. + * The priority of the mods in the range ]old, new priority] are shifted so that no gaps + * are possible. + * + * @param index index of the mod to change + * @param newPriority the new priority value + * + * @todo what happens if the new priority is outside the range? + **/ + void setModPriority(unsigned int index, int &newPriority); + + /** + * @brief determine if a mod is enabled + * + * @param index index of the mod to look up + * @return true if the mod is enabled, false otherwise + **/ + bool modEnabled(unsigned int index) const; + + /** + * @brief query the priority of a mod + * + * @param index index of the mod to look up + * @return priority of the specified mod + **/ + int getModPriority(unsigned int index) const; + + void dumpModStatus() const; + +signals: + + /** + * @brief emitted whenever the status (enabled/disabled) of a mod changed + * + * @param index index of the mod that changed + **/ + void modStatusChanged(unsigned int index); + +public slots: + + void writeModlistNow() const; + +private: + + class ModStatus { + friend class Profile; + public: + ModStatus() : m_Overwrite(false), m_Enabled(false), m_Priority(-1) {} + private: + bool m_Overwrite; + bool m_Enabled; + int m_Priority; + }; + +private: + Profile& operator=(const Profile &reference); // not implemented + + void initTimer(); + + void updateIndices(); + + QString getModlistFileName() const; + void copyFilesTo(QString &target) const; + + std::vector<std::wstring> splitDZString(const wchar_t *buffer) const; + void mergeTweak(const QString &tweakName, const QString &tweakedIni) const; + void mergeTweaks(ModInfo::Ptr modInfo, const QString &tweakedIni) const; + +private: + + QDir m_Directory; + + std::vector<ModStatus> m_ModStatus; + std::vector<unsigned int> m_ModIndexByPriority; + unsigned int m_NumRegularMods; + + QTimer *m_SaveTimer; +}; + +Q_DECLARE_METATYPE(Profile) + + +#endif // PROFILE_H diff --git a/src/profilesdialog.cpp b/src/profilesdialog.cpp index cbf50dfc..733412b0 100644 --- a/src/profilesdialog.cpp +++ b/src/profilesdialog.cpp @@ -37,6 +37,8 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. using namespace MOBase; using namespace MOShared; +Q_DECLARE_METATYPE(Profile::Ptr) + ProfilesDialog::ProfilesDialog(const QString &gamePath, QWidget *parent) : TutorableDialog("Profiles", parent), ui(new Ui::ProfilesDialog), m_GamePath(gamePath), m_FailState(false) @@ -89,11 +91,11 @@ void ProfilesDialog::on_closeButton_clicked() void ProfilesDialog::addItem(const QString &name) { try { - QVariant temp; +// QVariant temp; QDir profileDir(name); - temp.setValue(Profile(profileDir)); +// temp.setValue(Profile(profileDir)); QListWidgetItem *newItem = new QListWidgetItem(profileDir.dirName(), m_ProfilesList); - newItem->setData(Qt::UserRole, temp); + newItem->setData(Qt::UserRole, QVariant::fromValue(Profile::Ptr(new Profile(profileDir)))); m_FailState = false; } catch (const std::exception& e) { reportError(tr("failed to create profile: %1").arg(e.what())); @@ -104,11 +106,11 @@ void ProfilesDialog::addItem(const QString &name) void ProfilesDialog::createProfile(const QString &name, bool useDefaultSettings) { try { - QVariant temp; - temp.setValue(Profile(name, useDefaultSettings)); +// QVariant temp; +// temp.setValue(Profile(name, useDefaultSettings)); QListWidget *profilesList = findChild<QListWidget*>("profilesList"); QListWidgetItem *newItem = new QListWidgetItem(name, profilesList); - newItem->setData(Qt::UserRole, temp); + newItem->setData(Qt::UserRole, QVariant::fromValue(Profile::Ptr(new Profile(name, useDefaultSettings)))); profilesList->addItem(newItem); m_FailState = false; } catch (const std::exception&) { @@ -121,13 +123,13 @@ void ProfilesDialog::createProfile(const QString &name, bool useDefaultSettings) void ProfilesDialog::createProfile(const QString &name, const Profile &reference) { try { - Profile newProfile = Profile::createFrom(name, reference); +// Profile newProfile = Profile::createFrom(name, reference); - QVariant temp; - temp.setValue(newProfile); +// QVariant temp; +// temp.setValue(newProfile); QListWidget *profilesList = findChild<QListWidget*>("profilesList"); QListWidgetItem *newItem = new QListWidgetItem(name, profilesList); - newItem->setData(Qt::UserRole, temp); + newItem->setData(Qt::UserRole, QVariant::fromValue(Profile::Ptr(Profile::createPtrFrom(name, reference)))); profilesList->addItem(newItem); m_FailState = false; } catch (const std::exception&) { @@ -160,8 +162,8 @@ void ProfilesDialog::on_copyProfileButton_clicked() QListWidget *profilesList = findChild<QListWidget*>("profilesList"); try { - const Profile ¤tProfile = profilesList->currentItem()->data(Qt::UserRole).value<Profile>(); - createProfile(name, currentProfile); + const Profile::Ptr currentProfile = profilesList->currentItem()->data(Qt::UserRole).value<Profile::Ptr>(); + createProfile(name, *currentProfile); } catch (const std::exception &e) { reportError(tr("failed to copy profile: %1").arg(e.what())); } @@ -176,11 +178,11 @@ void ProfilesDialog::on_removeProfileButton_clicked() if (confirmBox.exec() == QMessageBox::Yes) { QListWidget *profilesList = findChild<QListWidget*>("profilesList"); - const Profile ¤tProfile = profilesList->currentItem()->data(Qt::UserRole).value<Profile>(); + Profile::Ptr currentProfile = profilesList->currentItem()->data(Qt::UserRole).value<Profile::Ptr>(); // on destruction, the profile object would write the profile.ini file again, so // we have to get rid of the it before deleting the directory - QString profilePath = currentProfile.getPath(); + QString profilePath = currentProfile->getPath(); QListWidgetItem* item = profilesList->takeItem(profilesList->currentRow()); if (item != NULL) { delete item; @@ -190,6 +192,33 @@ void ProfilesDialog::on_removeProfileButton_clicked() } +void ProfilesDialog::on_renameButton_clicked() +{ + Profile::Ptr currentProfile = ui->profilesList->currentItem()->data(Qt::UserRole).value<Profile::Ptr>(); + + bool valid = false; + QString name; + + while (!valid) { + bool ok = false; + name = QInputDialog::getText(this, tr("Rename Profile"), tr("New Name"), + QLineEdit::Normal, currentProfile->getName(), + &ok); + valid = fixDirectoryName(name); + if (!ok) { + return; + } + } + + ui->profilesList->currentItem()->setText(name); + currentProfile->rename(name); + +// QVariant temp; +// temp.setValue(currentProfile); +// ui->profilesList->currentItem()->setData(Qt::UserRole, temp); +} + + void ProfilesDialog::on_invalidationBox_stateChanged(int state) { QListWidget *profilesList = findChild<QListWidget*>("profilesList"); @@ -206,11 +235,11 @@ void ProfilesDialog::on_invalidationBox_stateChanged(int state) if (!currentProfileVariant.isValid() || currentProfileVariant.isNull()) { return; } - const Profile ¤tProfile = currentItem->data(Qt::UserRole).value<Profile>(); + const Profile::Ptr currentProfile = currentItem->data(Qt::UserRole).value<Profile::Ptr>(); if (state == Qt::Unchecked) { - currentProfile.deactivateInvalidation(); + currentProfile->deactivateInvalidation(); } else { - currentProfile.activateInvalidation(m_GamePath + "/data"); + currentProfile->activateInvalidation(m_GamePath + "/data"); } } catch (const std::exception &e) { reportError(tr("failed to change archive invalidation state: %1").arg(e.what())); @@ -225,18 +254,19 @@ void ProfilesDialog::on_profilesList_currentItemChanged(QListWidgetItem *current QPushButton *copyButton = findChild<QPushButton*>("copyProfileButton"); QPushButton *removeButton = findChild<QPushButton*>("removeProfileButton"); QPushButton *transferButton = findChild<QPushButton*>("transferButton"); + QPushButton *renameButton = findChild<QPushButton*>("renameButton"); if (current != NULL) { - const Profile ¤tProfile = current->data(Qt::UserRole).value<Profile>(); + const Profile::Ptr currentProfile = current->data(Qt::UserRole).value<Profile::Ptr>(); try { bool invalidationSupported = false; invalidationBox->blockSignals(true); - invalidationBox->setChecked(currentProfile.invalidationActive(&invalidationSupported)); + invalidationBox->setChecked(currentProfile->invalidationActive(&invalidationSupported)); invalidationBox->setEnabled(invalidationSupported); invalidationBox->blockSignals(false); - bool localSaves = currentProfile.localSavesEnabled(); + bool localSaves = currentProfile->localSavesEnabled(); transferButton->setEnabled(localSaves); // prevent the stateChanged-event for the saves-box from triggering, otherwise it may think local saves // were disabled and delete the files/rename the dir @@ -246,23 +276,27 @@ void ProfilesDialog::on_profilesList_currentItemChanged(QListWidgetItem *current copyButton->setEnabled(true); removeButton->setEnabled(true); + renameButton->setEnabled(true); } catch (const std::exception& E) { reportError(tr("failed to determine if invalidation is active: %1").arg(E.what())); copyButton->setEnabled(false); removeButton->setEnabled(false); + renameButton->setEnabled(false); invalidationBox->setChecked(false); } } else { invalidationBox->setChecked(false); copyButton->setEnabled(false); removeButton->setEnabled(false); + renameButton->setEnabled(false); } } void ProfilesDialog::on_localSavesBox_stateChanged(int state) { - Profile ¤tProfile = m_ProfilesList->currentItem()->data(Qt::UserRole).value<Profile>(); - if (currentProfile.enableLocalSaves(state == Qt::Checked)) { + Profile::Ptr currentProfile = m_ProfilesList->currentItem()->data(Qt::UserRole).value<Profile::Ptr>(); + + if (currentProfile->enableLocalSaves(state == Qt::Checked)) { ui->transferButton->setEnabled(state == Qt::Checked); } else { // revert checkbox-state @@ -272,7 +306,7 @@ void ProfilesDialog::on_localSavesBox_stateChanged(int state) void ProfilesDialog::on_transferButton_clicked() { - const Profile ¤tProfile = m_ProfilesList->currentItem()->data(Qt::UserRole).value<Profile>(); - TransferSavesDialog transferDialog(currentProfile, this); + const Profile::Ptr currentProfile = m_ProfilesList->currentItem()->data(Qt::UserRole).value<Profile::Ptr>(); + TransferSavesDialog transferDialog(*currentProfile, this); transferDialog.exec(); } diff --git a/src/profilesdialog.h b/src/profilesdialog.h index df08aa45..f864a7e0 100644 --- a/src/profilesdialog.h +++ b/src/profilesdialog.h @@ -83,6 +83,8 @@ private slots: void on_transferButton_clicked(); + void on_renameButton_clicked(); + private: Ui::ProfilesDialog *ui; QString m_GamePath; diff --git a/src/profilesdialog.ui b/src/profilesdialog.ui index a2983436..0c952877 100644 --- a/src/profilesdialog.ui +++ b/src/profilesdialog.ui @@ -115,6 +115,16 @@ p, li { white-space: pre-wrap; } </widget>
</item>
<item>
+ <widget class="QPushButton" name="renameButton">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Rename</string>
+ </property>
+ </widget>
+ </item>
+ <item>
<widget class="QPushButton" name="transferButton">
<property name="enabled">
<bool>false</bool>
|
