From 0a19f0433992be5f9791c8869380d09cd06b66b8 Mon Sep 17 00:00:00 2001 From: Tannin Date: Fri, 22 Mar 2013 18:46:40 +0100 Subject: - 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 --- src/ModOrganizer.pro | 2 +- src/categories.cpp | 12 + src/categories.h | 330 ++++++++++---------- src/icondelegate.cpp | 120 ++++---- src/installationmanager.cpp | 526 +++----------------------------- src/installationmanager.h | 16 +- src/mainwindow.cpp | 101 ++++-- src/mainwindow.h | 6 +- src/mainwindow.ui | 34 ++- src/modinfo.cpp | 37 +-- src/modinfo.h | 9 +- src/modlist.cpp | 6 +- src/modlistgroupcategoriesproxy.cpp | 123 ++++++++ src/modlistgroupcategoriesproxy.h | 40 +++ src/modlistgroupnexusidproxy.cpp | 130 ++++++++ src/modlistgroupnexusidproxy.h | 49 +++ src/modlistsortproxy.cpp | 14 +- src/modlistsortproxy.h | 2 + src/organizer.pro | 16 +- src/pluginlist.cpp | 14 +- src/profile.cpp | 75 +++-- src/profile.h | 593 ++++++++++++++++++------------------ src/profilesdialog.cpp | 82 +++-- src/profilesdialog.h | 2 + src/profilesdialog.ui | 10 + 25 files changed, 1219 insertions(+), 1130 deletions(-) create mode 100644 src/modlistgroupcategoriesproxy.cpp create mode 100644 src/modlistgroupcategoriesproxy.h create mode 100644 src/modlistgroupnexusidproxy.cpp create mode 100644 src/modlistgroupnexusidproxy.h (limited to 'src') 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 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 &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 . */ -#ifndef CATEGORIES_H -#define CATEGORIES_H - - -#include -#include -#include - - -/** - * @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 &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 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 &nexusID, int parentID); - - void setParents(); - -private: - - static CategoryFactory *s_Instance; - - std::vector m_Categories; - std::map m_IDMap; - std::map m_NexusMap; - -}; - - -#endif // CATEGORIES_H +#ifndef CATEGORIES_H +#define CATEGORIES_H + + +#include +#include +#include +#include + + +/** + * @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 &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 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 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 &nexusID, int parentID); + + void setParents(); + +private: + + static CategoryFactory *s_Instance; + + std::vector m_Categories; + std::map m_IDMap; + std::map 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 . */ -#include "icondelegate.h" -#include -#include -#include - - -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 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 +#include +#include + + +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 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 . #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 &modName, const QString &archiveName) { - QString temp = modName; + GuessedValue 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 &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 &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 &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(this, &InstallationManager::updateProgress), - new MethodCallback(this, &InstallationManager::dummyProgressFile), - new MethodCallback(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 &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::iterator iter = m_FilesToDelete.begin(); - iter != m_FilesToDelete.end(); ++iter) { - QFile(*iter).remove(); - } - m_FilesToDelete.clear(); - - for (std::set::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 . #ifndef INSTALLATIONMANAGER_H #define INSTALLATIONMANAGER_H -#include "installdialog.h" #include #include +#include #include #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 &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 &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 &modName); - bool doInstall(const QString &modsDirectory, QString &modName, + bool doInstall(const QString &modsDirectory, MOBase::GuessedValue &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 &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 . #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 . #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 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(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 += "
  • " + name + "
  • "; 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("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 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 flags = modInfo->getFlags(); + std::vector modInfo = ModInfo::getByModID(modID); + for (auto iter = modInfo.begin(); iter != modInfo.end(); ++iter) { + std::vector 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(modName)); if (posList.count() == 1) { ui->modList->scrollTo(posList.at(0)); } @@ -3597,13 +3609,13 @@ void MainWindow::nxmUpdatesAvailable(const std::vector &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 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 . #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; } QAbstractItemView::SelectRows - 0 + 20 + + + true - false + true true + + true + false @@ -328,7 +334,7 @@ p, li { white-space: pre-wrap; } - + @@ -377,6 +383,25 @@ p, li { white-space: pre-wrap; } + + + + + No groups + + + + + Categories + + + + + Nexus IDs + + + + @@ -709,6 +734,9 @@ p, li { white-space: pre-wrap; } 0 + + true + false 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::s_Collection; std::map ModInfo::s_ModsByName; -std::map ModInfo::s_ModsByModID; +std::map > 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::getByModID(int modID) { QMutexLocker locker(&s_Mutex); - std::map::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(); + } + + std::vector 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::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 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 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 &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 s_ModsByModID; + static std::map > 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(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 +#include + +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 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 &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 &subMods = m_GroupMap.at(parentMod->getNexusID()); + if ((row < 0) || (row >= static_cast(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 +#include +#include +#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 > m_GroupMap; + + std::map m_RowIdxMap; // maps row to mod id + std::map m_IdxRowMap; // maps mod id to row + + mutable std::map 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 lockedLoadOrder; std::for_each(m_LockedOrder.begin(), m_LockedOrder.end(), @@ -550,10 +548,16 @@ void PluginList::refreshLoadOrder() ++targetPrio; } } + + if (static_cast(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 . #include "modinfo.h" #include #include +#include #include #include #include @@ -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 . */ -#ifndef PROFILE_H -#define PROFILE_H - - -#include "modinfo.h" - -#include -#include -#include -#include -#include -#include - - -/** - * @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 > 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 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 m_ModStatus; - std::vector 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 +#include +#include +#include +#include +#include + + +/** + * @brief represents a profile + **/ +class Profile : public QObject +{ + + Q_OBJECT + +public: + + typedef boost::shared_ptr 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 > 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 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 m_ModStatus; + std::vector 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 . 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("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("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("profilesList"); try { - const Profile ¤tProfile = profilesList->currentItem()->data(Qt::UserRole).value(); - createProfile(name, currentProfile); + const Profile::Ptr currentProfile = profilesList->currentItem()->data(Qt::UserRole).value(); + 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("profilesList"); - const Profile ¤tProfile = profilesList->currentItem()->data(Qt::UserRole).value(); + Profile::Ptr currentProfile = profilesList->currentItem()->data(Qt::UserRole).value(); // 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(); + + 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("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(); + const Profile::Ptr currentProfile = currentItem->data(Qt::UserRole).value(); 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("copyProfileButton"); QPushButton *removeButton = findChild("removeProfileButton"); QPushButton *transferButton = findChild("transferButton"); + QPushButton *renameButton = findChild("renameButton"); if (current != NULL) { - const Profile ¤tProfile = current->data(Qt::UserRole).value(); + const Profile::Ptr currentProfile = current->data(Qt::UserRole).value(); 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(); - if (currentProfile.enableLocalSaves(state == Qt::Checked)) { + Profile::Ptr currentProfile = m_ProfilesList->currentItem()->data(Qt::UserRole).value(); + + 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(); - TransferSavesDialog transferDialog(currentProfile, this); + const Profile::Ptr currentProfile = m_ProfilesList->currentItem()->data(Qt::UserRole).value(); + 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 @@ -114,6 +114,16 @@ p, li { white-space: pre-wrap; } + + + + false + + + Rename + + + -- cgit v1.3.1