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