summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSilarn <jrim@rimpo.org>2019-01-29 20:10:53 -0600
committerSilarn <jrim@rimpo.org>2019-02-18 21:29:23 -0600
commitf07f6b8904f69a92cd24e3c52eaf1fc6db8e60dd (patch)
tree8433556283b516a4dda97bbae86e6c1855e20272
parentef286a938d79f74947a392d049068e4818ccf7fd (diff)
Update Process Improvements
* 5 minute batch auto-update of up to 10 mods - Still able to force an update of all 'unchecked' mods - Prioritizes mods with oldest update 'age' * Implemented 1-hour wait between update checks per mod * Fixed issues with update progress display * Only enable update filter automatically if 'force update' * Improved display of version update status in mod list - Italic text when ready to perform update check - Tooltip indicates when next update is available * Fixed remaining issues with update fallback check * Only trigger one update API request for duplicate sources
-rw-r--r--src/mainwindow.cpp80
-rw-r--r--src/mainwindow.h3
-rw-r--r--src/modinfo.cpp49
-rw-r--r--src/modinfo.h1433
-rw-r--r--src/modinfodialog.cpp2
-rw-r--r--src/modinfoforeign.h3
-rw-r--r--src/modinfooverwrite.h3
-rw-r--r--src/modinforegular.cpp42
-rw-r--r--src/modinforegular.h18
-rw-r--r--src/modinfoseparator.h4
-rw-r--r--src/modlist.cpp2808
11 files changed, 2288 insertions, 2157 deletions
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index 094a44af..b57c62f8 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -400,7 +400,7 @@ MainWindow::MainWindow(QSettings &initSettings
connect(NexusInterface::instance(&pluginContainer), SIGNAL(nxmDownloadURLsAvailable(QString,int,int,QVariant,QVariant,int)), this, SLOT(nxmDownloadURLs(QString,int,int,QVariant,QVariant,int)));
connect(NexusInterface::instance(&pluginContainer), SIGNAL(needLogin()), &m_OrganizerCore, SLOT(nexusApi()));
connect(NexusInterface::instance(&pluginContainer)->getAccessManager(), SIGNAL(validateFailed(QString)), this, SLOT(validationFailed(QString)));
- connect(NexusInterface::instance(&pluginContainer)->getAccessManager(), SIGNAL(credentialsReceived(const QString&, bool)),
+ connect(NexusInterface::instance(&pluginContainer)->getAccessManager(), SIGNAL(credentialsReceived(const QString&, bool, std::tuple<int, int, int, int>)),
this, SLOT(updateWindowTitle(const QString&, bool)));
connect(NexusInterface::instance(&pluginContainer)->getAccessManager(), SIGNAL(credentialsReceived(const QString&, bool, std::tuple<int, int, int, int>)),
NexusInterface::instance(&m_PluginContainer), SLOT(setRateMax(const QString&, bool, std::tuple<int, int, int, int>)));
@@ -439,6 +439,10 @@ MainWindow::MainWindow(QSettings &initSettings
connect(&m_SaveMetaTimer, SIGNAL(timeout()), this, SLOT(saveModMetas()));
m_SaveMetaTimer.start(5000);
+ m_ModUpdateTimer.setSingleShot(false);
+ connect(&m_ModUpdateTimer, SIGNAL(timeout()), this, SLOT(modUpdateCheck()));
+ m_ModUpdateTimer.start(300 * 1000);
+
setCategoryListVisible(initSettings.value("categorylist_visible", true).toBool());
FileDialogMemory::restore(initSettings);
@@ -491,6 +495,8 @@ MainWindow::MainWindow(QSettings &initSettings
updatePluginCount();
updateModCount();
+
+ modUpdateCheck();
}
@@ -3996,6 +4002,14 @@ void MainWindow::checkModsForUpdates()
m_ModsToUpdate = ModInfo::checkAllForUpdate(&m_PluginContainer, this);
}
}
+
+ m_ModListSortProxy->setCategoryFilter(boost::assign::list_of(CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE));
+ for (int i = 0; i < ui->categoriesList->topLevelItemCount(); ++i) {
+ if (ui->categoriesList->topLevelItem(i)->data(0, Qt::UserRole) == CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE) {
+ ui->categoriesList->setCurrentItem(ui->categoriesList->topLevelItem(i));
+ break;
+ }
+ }
}
void MainWindow::changeVersioningScheme() {
@@ -4399,7 +4413,7 @@ void MainWindow::initModListContextMenu(QMenu *menu)
menu->addAction(tr("Enable all visible"), this, SLOT(enableVisibleMods()));
menu->addAction(tr("Disable all visible"), this, SLOT(disableVisibleMods()));
- menu->addAction(tr("Check all for update"), this, SLOT(checkModsForUpdates()));
+ menu->addAction(tr("Force update check"), this, SLOT(checkModsForUpdates()));
menu->addAction(tr("Refresh"), &m_OrganizerCore, SLOT(profileRefresh()));
menu->addAction(tr("Export to csv..."), this, SLOT(exportModListCSV()));
}
@@ -5427,21 +5441,32 @@ void MainWindow::updateDownloadView()
void MainWindow::modDetailsUpdated(bool)
{
- if (--m_ModsToUpdate == 0) {
+ if (m_ModsToUpdate <= 0) {
statusBar()->hide();
- m_ModListSortProxy->setCategoryFilter(boost::assign::list_of(CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE));
- for (int i = 0; i < ui->categoriesList->topLevelItemCount(); ++i) {
- if (ui->categoriesList->topLevelItem(i)->data(0, Qt::UserRole) == CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE) {
- ui->categoriesList->setCurrentItem(ui->categoriesList->topLevelItem(i));
- break;
- }
- }
m_RefreshProgress->setVisible(false);
} else {
m_RefreshProgress->setValue(m_RefreshProgress->maximum() - m_ModsToUpdate);
}
}
+void MainWindow::modUpdateCheck()
+{
+ if (NexusInterface::instance(&m_PluginContainer)->getAccessManager()->validated()) {
+ m_ModsToUpdate += ModInfo::autoUpdateCheck(&m_PluginContainer, this);
+ m_RefreshProgress->setRange(0, m_ModsToUpdate);
+ } else {
+ QString apiKey;
+ if (m_OrganizerCore.settings().getNexusApiKey(apiKey)) {
+ m_OrganizerCore.doAfterLogin([this]() { this->checkModsForUpdates(); });
+ NexusInterface::instance(&m_PluginContainer)->getAccessManager()->apiCheck(apiKey);
+ } else { // otherwise there will be no endorsement info
+ MessageDialog::showMessage(tr("Not logged in, endorsement information will be wrong"),
+ this, true);
+ m_ModsToUpdate += ModInfo::autoUpdateCheck(&m_PluginContainer, this);
+ }
+ }
+}
+
void MainWindow::nxmUpdatesAvailable(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID)
{
QVariantMap resultInfo = resultData.toMap();
@@ -5458,12 +5483,7 @@ void MainWindow::nxmUpdatesAvailable(QString gameName, int modID, QVariant userD
}
}
std::vector<ModInfo::Ptr> modsList = ModInfo::getByModID(gameNameReal, modID);
- // Not clear to me what this is accomplishing?
- //if (sameNexus) {
- // std::vector<ModInfo::Ptr> mainInfo = ModInfo::getByModID(m_OrganizerCore.managedGame()->gameShortName(), modID);
- // info.reserve(info.size() + mainInfo.size());
- // info.insert(info.end(), mainInfo.begin(), mainInfo.end());
- //}
+
for (auto mod : modsList) {
QString installedFile = mod->getInstallationFile();
for (auto update : fileUpdates) {
@@ -5511,6 +5531,7 @@ void MainWindow::nxmUpdatesAvailable(QString gameName, int modID, QVariant userD
if (foundUpdate) {
// Just get the standard data updates for endorsements and descriptions
+ mod->setLastNexusUpdate(QDateTime::currentDateTimeUtc());
mod->updateNXMInfo();
} else {
// Scrape mod data here so we can use the mod version if no file update was located
@@ -5518,29 +5539,9 @@ void MainWindow::nxmUpdatesAvailable(QString gameName, int modID, QVariant userD
}
}
- // Old endorsement and mod info updater
- //for
- // if (updateData["old_file_id"].toInt() == mod->readMeta())
- // (*iter)->setNewestVersion(result["version"].toString());
- //(*iter)->setNexusDescription(result["description"].toString());
- //if (NexusInterface::instance(&m_PluginContainer)->getAccessManager()->validated() &&
- // result.contains("voted_by_user") &&
- // Settings::instance().endorsementIntegration()) {
- // // don't use endorsement info if we're not logged in or if the response doesn't contain it
- // (*iter)->setIsEndorsed(result["voted_by_user"].toBool());
- //}
-
- if (m_ModsToUpdate <= 0) {
+ if (--m_ModsToUpdate <= 0) {
statusBar()->hide();
- m_ModListSortProxy->setCategoryFilter(boost::assign::list_of(CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE));
- for (int i = 0; i < ui->categoriesList->topLevelItemCount(); ++i) {
- if (ui->categoriesList->topLevelItem(i)->data(0, Qt::UserRole) == CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE) {
- ui->categoriesList->setCurrentItem(ui->categoriesList->topLevelItem(i));
- break;
- }
- }
- }
- else {
+ } else {
m_RefreshProgress->setValue(m_RefreshProgress->maximum() - m_ModsToUpdate);
}
}
@@ -5570,9 +5571,8 @@ void MainWindow::nxmDescriptionAvailable(QString gameName, int modID, QVariant u
else
mod->setIsEndorsed(false);
}
+ mod->saveMeta();
}
- disconnect(sender(), SIGNAL(nxmDescriptionAvailable(QString, int, QVariant, QVariant, int)),
- this, SLOT(nxmDescriptionAvailable(QString, int, QVariant, QVariant, int)));
}
void MainWindow::nxmEndorsementToggled(QString, int, QVariant, QVariant resultData, int)
diff --git a/src/mainwindow.h b/src/mainwindow.h
index b3490d07..65ca99dd 100644
--- a/src/mainwindow.h
+++ b/src/mainwindow.h
@@ -348,6 +348,7 @@ private:
QTimer m_CheckBSATimer;
QTimer m_SaveMetaTimer;
QTimer m_UpdateProblemsTimer;
+ QTimer m_ModUpdateTimer;
QFuture<void> m_MetaSave;
@@ -508,6 +509,8 @@ private slots:
void modInstalled(const QString &modName);
+ void modUpdateCheck();
+
void nxmUpdatesAvailable(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID);
void nxmDescriptionAvailable(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID);
void nxmEndorsementToggled(QString, int, QVariant, QVariant resultData, int);
diff --git a/src/modinfo.cpp b/src/modinfo.cpp
index e7b7657b..5d5cb57e 100644
--- a/src/modinfo.cpp
+++ b/src/modinfo.cpp
@@ -295,20 +295,63 @@ int ModInfo::checkAllForUpdate(PluginContainer *pluginContainer, QObject *receiv
// NexusInterface::instance(pluginContainer)->requestUpdates(game->nexusModOrganizerID(), receiver, QVariant(), game->gameShortName(), QString());
//}
- std::multimap<QString, QSharedPointer<ModInfo>> organizedGames;
+ std::multimap<QString, int> organizedGames;
for (auto mod : s_Collection) {
if (mod->canBeUpdated()) {
- organizedGames.insert(std::pair<QString, QSharedPointer<ModInfo>>(mod->getGameName(), mod));
+ organizedGames.insert(std::make_pair<QString, int>(mod->getGameName(), mod->getNexusID()));
}
}
+ result = organizedGames.size();
+
for (auto game : organizedGames) {
- NexusInterface::instance(pluginContainer)->requestUpdates(game.second->getNexusID(), receiver, QVariant(), game.first, QString());
+ NexusInterface::instance(pluginContainer)->requestUpdates(game.second, receiver, QVariant(), game.first, QString());
+ }
+
+ return result;
+}
+
+
+int ModInfo::autoUpdateCheck(PluginContainer *pluginContainer, QObject *receiver)
+{
+ qInfo("Initializing periodic update check.");
+ int result = 0;
+
+ std::vector<QSharedPointer<ModInfo>> sortedMods;
+ std::multimap<QString, int> organizedGames;
+ for (auto mod : s_Collection) {
+ if (mod->canBeUpdated()) {
+ sortedMods.push_back(mod);
+ }
+ }
+
+ std::sort(sortedMods.begin(), sortedMods.end(), [](QSharedPointer<ModInfo> a, QSharedPointer<ModInfo> b) -> bool {
+ return a->getLastNexusUpdate() > b->getLastNexusUpdate();
+ });
+
+ if (sortedMods.size() > 10)
+ sortedMods.resize(10);
+
+ result = sortedMods.size();
+
+ if (sortedMods.size()) {
+ qInfo("Checking updates for %d mods...", sortedMods.size());
+
+ for (auto mod : sortedMods) {
+ organizedGames.insert(std::make_pair<QString, int>(mod->getGameName(), mod->getNexusID()));
+ }
+
+ for (auto game : organizedGames) {
+ NexusInterface::instance(pluginContainer)->requestUpdates(game.second, receiver, QVariant(), game.first, QString());
+ }
+ } else {
+ qInfo("No mods require updates at this time.");
}
return result;
}
+
void ModInfo::setVersion(const VersionInfo &version)
{
m_Version = version;
diff --git a/src/modinfo.h b/src/modinfo.h
index 6b7c714e..9c753752 100644
--- a/src/modinfo.h
+++ b/src/modinfo.h
@@ -1,710 +1,723 @@
-/*
-Copyright (C) 2012 Sebastian Herbord. All rights reserved.
-
-This file is part of Mod Organizer.
-
-Mod Organizer is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-Mod Organizer is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-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 MODINFO_H
-#define MODINFO_H
-
-#include "imodinterface.h"
-#include "versioninfo.h"
-
-class PluginContainer;
-
-class QDateTime;
-class QDir;
-#include <QMutex>
-#include <QSharedPointer>
-#include <QString>
-#include <QStringList>
-#include <QColor>
-
-#include <boost/function.hpp>
-
-#include <map>
-#include <set>
-#include <vector>
-
-namespace MOBase { class IPluginGame; }
-namespace MOShared { class DirectoryEntry; }
-
-/**
- * @brief Represents meta information about a single mod.
- *
- * Represents meta information about a single mod. The class interface is used
- * to manage the mod collection
- *
- **/
-class ModInfo : public QObject, public MOBase::IModInterface
-{
-
- Q_OBJECT
-
-public:
-
- typedef QSharedPointer<ModInfo> Ptr;
-
- static QString s_HiddenExt;
-
- enum EFlag {
- FLAG_INVALID,
- FLAG_BACKUP,
- FLAG_SEPARATOR,
- FLAG_OVERWRITE,
- FLAG_FOREIGN,
- FLAG_NOTENDORSED,
- FLAG_NOTES,
- FLAG_CONFLICT_OVERWRITE,
- FLAG_CONFLICT_OVERWRITTEN,
- FLAG_CONFLICT_MIXED,
- FLAG_CONFLICT_REDUNDANT,
- FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITE,
- FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITTEN,
- FLAG_ARCHIVE_CONFLICT_OVERWRITE,
- FLAG_ARCHIVE_CONFLICT_OVERWRITTEN,
- FLAG_ARCHIVE_CONFLICT_MIXED,
- FLAG_PLUGIN_SELECTED,
- FLAG_ALTERNATE_GAME
- };
-
- enum EContent {
- CONTENT_PLUGIN,
- CONTENT_TEXTURE,
- CONTENT_MESH,
- CONTENT_BSA,
- CONTENT_INTERFACE,
- CONTENT_SOUND,
- CONTENT_SCRIPT,
- CONTENT_SKSE,
- CONTENT_SKSEFILES,
- CONTENT_SKYPROC,
- CONTENT_MCM,
- CONTENT_INI,
- CONTENT_MODGROUP
- };
-
- static const int NUM_CONTENT_TYPES = CONTENT_MODGROUP + 1;
-
- enum EHighlight {
- HIGHLIGHT_NONE = 0,
- HIGHLIGHT_INVALID = 1,
- HIGHLIGHT_CENTER = 2,
- HIGHLIGHT_IMPORTANT = 4,
- HIGHLIGHT_PLUGIN = 8
- };
-
- enum EEndorsedState {
- ENDORSED_FALSE,
- ENDORSED_TRUE,
- ENDORSED_UNKNOWN,
- ENDORSED_NEVER
- };
-
- enum EModType {
- MOD_DEFAULT,
- MOD_DLC,
- MOD_CC
- };
-
-
-public:
-
- /**
- * @brief read the mod directory and Mod ModInfo objects for all subdirectories
- **/
- static void updateFromDisc(const QString &modDirectory,
- MOShared::DirectoryEntry **directoryStructure,
- PluginContainer *pluginContainer,
- bool displayForeign,
- MOBase::IPluginGame const *game);
-
- static void clear() { s_Collection.clear(); s_ModsByName.clear(); s_ModsByModID.clear(); }
-
- /**
- * @brief retrieve the number of mods
- *
- * @return number of mods
- **/
- static unsigned int getNumMods();
-
- /**
- * @brief retrieve a ModInfo object based on its index
- *
- * @param index the index to look up. the maximum is getNumMods() - 1
- * @return a reference counting pointer to the mod info.
- * @note since the pointer is reference counting, the pointer remains valid even if the collection is refreshed in a different thread
- **/
- static ModInfo::Ptr getByIndex(unsigned int index);
-
- /**
- * @brief retrieve a ModInfo object based on its nexus mod id
- *
- * @param modID the nexus mod id to look up
- * @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 std::vector<ModInfo::Ptr> getByModID(QString game, int modID);
-
- /**
- * @brief remove a mod by index
- *
- * this physically deletes the specified mod from the disc and updates the ModInfo collection
- * but not other structures that reference mods
- * @param index index of the mod to delete
- * @return true if removal was successful, fals otherwise
- **/
- static bool removeMod(unsigned int index);
-
- /**
- * @brief retrieve the mod index by the mod name
- *
- * @param name name of the mod to look up
- * @return the index of the mod. If the mod doesn't exist, UINT_MAX is returned
- **/
- static unsigned int getIndex(const QString &name);
-
- /**
- * @brief find the first mod that fulfills the filter function (after no particular order)
- * @param filter a function to filter by. should return true for a match
- * @return index of the matching mod or UINT_MAX if there wasn't a match
- */
- static unsigned int findMod(const boost::function<bool (ModInfo::Ptr)> &filter);
-
- /**
- * @brief check a bunch of mods for updates
- * @param modIDs list of mods (Nexus Mod IDs) to check for updates
- * @return
- */
- static void checkChunkForUpdate(PluginContainer *pluginContainer, const std::vector<int> &modIDs, QObject *receiver, QString gameName);
-
- /**
- * @brief query nexus information for every mod and update the "newest version" information
- **/
- static int checkAllForUpdate(PluginContainer *pluginContainer, QObject *receiver);
-
- /**
- * @brief create a new mod from the specified directory and add it to the collection
- * @param dir directory to create from
- * @return pointer to the info-structure of the newly created/added mod
- */
- static ModInfo::Ptr createFrom(PluginContainer *pluginContainer, const MOBase::IPluginGame *game, const QDir &dir, MOShared::DirectoryEntry **directoryStructure);
-
- /**
- * @brief create a new "foreign-managed" mod from a tuple of plugin and archives
- * @param espName name of the plugin
- * @param bsaNames names of archives
- * @return a new mod
- */
- static ModInfo::Ptr createFromPlugin(const QString &modName, const QString &espName, const QStringList &bsaNames, ModInfo::EModType modType, MOShared::DirectoryEntry **directoryStructure, PluginContainer *pluginContainer);
-
- /**
- * @brief retieve a name for one of the CONTENT_ enums
- * @param contentType the content value
- * @return a display string
- */
- static QString getContentTypeName(int contentType);
-
- virtual bool isRegular() const { return false; }
-
- virtual bool isEmpty() const { return false; }
-
- /**
- * @brief test if there is a newer version of the mod
- *
- * test if there is a newer version of the mod. This does NOT cause
- * information to be retrieved from the nexus, it will only test version information already
- * available locally. Use checkAllForUpdate() to update this version information
- *
- * @return true if there is a newer version
- **/
- virtual bool updateAvailable() const = 0;
-
- /**
- * @return true if the update currently available is ignored
- */
- virtual bool updateIgnored() const = 0;
-
- /**
- * @brief test if the "newest" version of the mod is older than the installed version
- *
- * test if there is a newer version of the mod. This does NOT cause
- * information to be retrieved from the nexus, it will only test version information already
- * available locally. Use checkAllForUpdate() to update this version information
- *
- * @return true if the newest version is older than the installed one
- **/
- virtual bool downgradeAvailable() const = 0;
-
- /**
- * @brief request an update of nexus description for this mod.
- *
- * This requests mod information from the nexus. This is an asynchronous request,
- * so there is no immediate effect of this call.
- * Right now, Mod Organizer interprets the "newest version" and "description" from the
- * response, though the description is only stored in memory
- *
- **/
- virtual bool updateNXMInfo() = 0;
-
- /**
- * @brief assign or unassign the specified category
- *
- * Every mod can have an arbitrary number of categories assigned to it
- *
- * @param categoryID id of the category to set
- * @param active determines wheter the category is assigned or unassigned
- * @note this function does not test whether categoryID actually identifies a valid category
- **/
- virtual void setCategory(int categoryID, bool active) = 0;
-
- /**
- * @brief changes the comments (manually set information displayed in the mod list) for this mod
- * @param comments new comments
- */
- virtual void setComments(const QString &comments) = 0;
-
- /**
- * @brief change the notes (manually set information) for this mod
- * @param notes new notes
- */
- virtual void setNotes(const QString &notes) = 0;
-
- /**
- * @brief set/change the source game of this mod
- *
- * @param gameName the source game shortName
- */
- virtual void setGameName(QString gameName) = 0;
-
- /**
- * @brief set/change the nexus mod id of this mod
- *
- * @param modID the nexus mod id
- **/
- virtual void setNexusID(int modID) = 0;
-
- /**
- * @brief set/change the version of this mod
- * @param version new version of the mod
- */
- virtual void setVersion(const MOBase::VersionInfo &version);
-
- /**
- * @brief Controls if mod should be highlighted based on plugin selection
- * @param isSelected whether or not the plugin has a selected mod
- **/
- virtual void setPluginSelected(const bool &isSelected);
-
- /**
- * @brief set the newest version of this mod on the nexus
- *
- * this can be used to overwrite the version of a mod without actually
- * updating the mod
- *
- * @param version the new version to use
- * @todo this function should be made obsolete. All queries for mod information should go through
- * this class so no public function for this change is required
- **/
- virtual void setNewestVersion(const MOBase::VersionInfo &version) = 0;
-
- /**
- * @brief sets the repository that was used to download the mod
- */
- virtual void setRepository(const QString &) {}
-
- /**
- * @brief changes/updates the nexus description text
- * @param description the current description text
- */
- virtual void setNexusDescription(const QString &description) = 0;
-
- /**
- * @brief sets the file this mod was installed from
- * @param fileName name of the file
- */
- virtual void setInstallationFile(const QString &fileName) = 0;
-
- /**
- * @brief sets the category id from a nexus category id. Conversion to MO id happens internally
- * @param categoryID the nexus category id
- * @note if a mapping is not possible, the category is set to the default value
- */
- virtual void addNexusCategory(int categoryID) = 0;
-
- virtual void addCategory(const QString &categoryName) override;
- virtual bool removeCategory(const QString &categoryName) override;
- virtual QStringList categories() override;
-
- /**
- * update the endorsement state for the mod. This only changes the
- * buffered state, it does not sync with Nexus
- * @param endorsed the new endorsement state
- */
- virtual void setIsEndorsed(bool endorsed) = 0;
-
- /**
- * set the mod to "i don't intend to endorse". The mod will not show as unendorsed but can still be endorsed
- */
- virtual void setNeverEndorse() = 0;
-
- /**
- * @brief delete the mod from the disc. This does not update the global ModInfo structure or indices
- * @return true if the mod was successfully removed
- **/
- virtual bool remove() = 0;
-
- /**
- * @brief endorse or un-endorse the mod. This will sync with nexus!
- * @param doEndorse if true, the mod is endorsed, if false, it's un-endorsed.
- * @note if doEndorse doesn't differ from the current value, nothing happens.
- */
- virtual void endorse(bool doEndorse) = 0;
-
- /**
- * @brief clear all caches held for this mod
- */
- virtual void clearCaches() {}
-
- /**
- * @brief getter for the mod name
- *
- * @return the mod name
- **/
- virtual QString name() const = 0;
-
- /**
- * @brief getter for an internal name. This is usually the same as the regular name, but with special mod types it might be
- * this is used to distinguish between mods that have the same visible name
- * @return internal mod name
- */
- virtual QString internalName() const { return name(); }
-
- /**
- * @brief getter for the mod path
- *
- * @return the (absolute) path to the mod
- **/
- virtual QString absolutePath() const = 0;
-
- /**
- * @brief getter for the installation file
- *
- * @return file used to install this mod from
- */
- virtual QString getInstallationFile() const = 0;
-
- /**
- * @return version object for machine based comparisons
- **/
- virtual MOBase::VersionInfo getVersion() const { return m_Version; }
-
- /**
- * @brief getter for the newest version number of this mod
- *
- * @return newest version of the mod
- **/
- virtual MOBase::VersionInfo getNewestVersion() const = 0;
-
- /**
- * @return the repository from which the file was downloaded. Only relevant regular mods
- */
- virtual QString repository() const { return ""; }
-
- /**
- * @brief ignore the newest version for updates
- */
- virtual void ignoreUpdate(bool ignore) = 0;
-
- /**
- * @brief getter for the nexus mod id
- *
- * @return the nexus mod id. may be 0 if the mod id isn't known or doesn't exist
- **/
- virtual int getNexusID() const = 0;
-
- /**
- * @brief getter for the source game repository
- *
- * @return the source game repository. should default to the active game.
- **/
- virtual QString getGameName() const = 0;
-
- /**
- * @return the fixed priority of mods of this type or INT_MIN if the priority of mods
- * needs to be user-modifiable. Can be < 0 to force a priority below user-modifable mods
- * or INT_MAX to force priority above all user-modifiables
- */
- virtual int getFixedPriority() const = 0;
-
- /**
- * @return true if the mod is always enabled
- */
- virtual bool alwaysEnabled() const { return false; }
-
- /**
- * @return true if the mod can be updated
- */
- virtual bool canBeUpdated() const { return false; }
-
- /**
- * @return true if the mod can be enabled/disabled
- */
- virtual bool canBeEnabled() const { return false; }
-
- /**
- * @return a list of flags for this mod
- */
- virtual std::vector<EFlag> getFlags() const = 0;
-
- /**
- * @return a list of content types contained in a mod
- */
- virtual std::vector<EContent> getContents() const { return std::vector<EContent>(); }
-
- /**
- * @brief test if the specified flag is set for this mod
- * @param flag the flag to test
- * @return true if the flag is set, false otherwise
- */
- bool hasFlag(EFlag flag) const;
-
- /**
- * @brief test if the mods contains the specified content
- * @param content the content to test
- * @return true if the content is there, false otherwise
- */
- bool hasContent(ModInfo::EContent content) const;
-
- /**
- * @return an indicator if and how this mod should be highlighted by the UI
- */
- virtual int getHighlight() const { return HIGHLIGHT_NONE; }
-
- /**
- * @return list of names of ini tweaks
- **/
- virtual std::vector<QString> getIniTweaks() const = 0;
-
- /**
- * @return a description about the mod, to be displayed in the ui
- */
- virtual QString getDescription() const = 0;
-
- /**
- * @return comments for this mod
- */
- virtual QString comments() const = 0;
-
- /**
- * @return notes for this mod
- */
- virtual QString notes() const = 0;
-
- /**
- * @return creation time of this mod
- */
- virtual QDateTime creationTime() const = 0;
-
- /**
- * @return nexus description of the mod (html)
- */
- virtual QString getNexusDescription() const = 0;
-
- /**
- * @return last time nexus was queried for infos on this mod
- */
- virtual QDateTime getLastNexusQuery() const = 0;
-
- /**
- * @return a list of files that, if they exist in the data directory are treated as files in THIS mod
- */
- virtual QStringList stealFiles() const { return QStringList(); }
-
- /**
- * @return a list of archives belonging to this mod (as absolute file paths)
- */
- virtual QStringList archives(bool checkOnDisk = false) = 0;
-
- /*
- *@return the color choosen by the user for the mod/separator
- */
- virtual QColor getColor() { return QColor(); }
-
- /*
- *@return true if the color has been set successfully.
- */
- virtual void setColor(QColor color) { }
-
- /**
- * @brief adds the information that a file has been installed into this mod
- * @param modId id of the mod installed
- * @param fileId id of the file installed
- */
- virtual void addInstalledFile(int modId, int fileId) = 0;
-
- /**
- * @brief test if the mod belongs to the specified category
- *
- * @param categoryID the category to test for.
- * @return true if the mod belongs to the specified category
- * @note this does not verify the id actually identifies a category
- **/
- bool categorySet(int categoryID) const;
-
- /**
- * @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 id of the primary category of this mod
- */
- int getPrimaryCategory() const { return m_PrimaryCategory; }
-
- /**
- * @brief sets the new primary category of the mod
- * @param categoryID the category to set
- */
- virtual void setPrimaryCategory(int categoryID) { m_PrimaryCategory = categoryID; }
-
- /**
- * @return true if this mod is considered "valid", that is: it contains data used by the game
- **/
- virtual bool isValid() const { return m_Valid; }
-
- /**
- * @return true if the file has been endorsed on nexus
- */
- virtual EEndorsedState endorsedState() const { return ENDORSED_NEVER; }
-
- /**
- * @brief updates the valid-flag for this mod
- */
- void testValid();
-
- /**
- * @brief updates the mod to flag it as converted in order to ignore the alternate game warning
- */
- virtual void markConverted(bool converted) {}
-
- /**
- * @brief updates the mod to flag it as valid in order to ignore the invalid game data flag
- */
- virtual void markValidated(bool validated) {}
-
- /**
- * @brief reads meta information from disk
- */
- virtual void readMeta() {}
-
- /**
- * @brief stores meta information back to disk
- */
- virtual void saveMeta() {}
-
- /**
- * @return retrieve list of mods (as mod index) that are overwritten by this one. Updates may be delayed
- */
- virtual std::set<unsigned int> getModOverwrite() { return std::set<unsigned int>(); }
-
- /**
- * @return list of mods (as mod index) that overwrite this one. Updates may be delayed
- */
- virtual std::set<unsigned int> getModOverwritten() { return std::set<unsigned int>(); }
-
- /**
- * @return retrieve list of mods (as mod index) with archives that are overwritten by this one. Updates may be delayed
- */
- virtual std::set<unsigned int> getModArchiveOverwrite() { return std::set<unsigned int>(); }
-
- /**
- * @return list of mods (as mod index) with archives that overwrite this one. Updates may be delayed
- */
- virtual std::set<unsigned int> getModArchiveOverwritten() { return std::set<unsigned int>(); }
-
- /**
- * @return retrieve list of mods (as mod index) with archives that are overwritten by thos mod's loose files. Updates may be delayed
- */
- virtual std::set<unsigned int> getModArchiveLooseOverwrite() { return std::set<unsigned int>(); }
-
- /**
- * @return list of mods (as mod index) with loose files that overwrite this one's archive files. Updates may be delayed
- */
- virtual std::set<unsigned int> getModArchiveLooseOverwritten() { return std::set<unsigned int>(); }
-
- /**
- * @brief update conflict information
- */
- virtual void doConflictCheck() const {}
-
- /**
- * @brief set the URL for a mod
- */
- virtual void setURL(QString const &) {}
-
- /**
- * @returns the URL for a mod
- */
- virtual QString getURL() const { return ""; }
-
-signals:
-
- /**
- * @brief emitted whenever the information of a mod changes
- *
- * @param success true if the mod details were updated successfully, false if not
- **/
- void modDetailsUpdated(bool success);
-
-protected:
-
- ModInfo(PluginContainer *pluginContainer);
-
- static void updateIndices();
- static bool ByName(const ModInfo::Ptr &LHS, const ModInfo::Ptr &RHS);
-
-private:
-
- static void createFromOverwrite(PluginContainer *pluginContainer);
-
-protected:
-
- static std::vector<ModInfo::Ptr> s_Collection;
- static std::map<QString, unsigned int> s_ModsByName;
-
- int m_PrimaryCategory;
- std::set<int> m_Categories;
-
- MOBase::VersionInfo m_Version;
-
- bool m_PluginSelected = false;
-
-private:
-
- static QMutex s_Mutex;
- static std::map<std::pair<QString, int>, std::vector<unsigned int> > s_ModsByModID;
- static int s_NextID;
-
- bool m_Valid;
-
-};
-
-
-#endif // MODINFO_H
+/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+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 MODINFO_H
+#define MODINFO_H
+
+#include "imodinterface.h"
+#include "versioninfo.h"
+
+class PluginContainer;
+
+class QDateTime;
+class QDir;
+#include <QMutex>
+#include <QSharedPointer>
+#include <QString>
+#include <QStringList>
+#include <QColor>
+
+#include <boost/function.hpp>
+
+#include <map>
+#include <set>
+#include <vector>
+
+namespace MOBase { class IPluginGame; }
+namespace MOShared { class DirectoryEntry; }
+
+/**
+ * @brief Represents meta information about a single mod.
+ *
+ * Represents meta information about a single mod. The class interface is used
+ * to manage the mod collection
+ *
+ **/
+class ModInfo : public QObject, public MOBase::IModInterface
+{
+
+ Q_OBJECT
+
+public:
+
+ typedef QSharedPointer<ModInfo> Ptr;
+
+ static QString s_HiddenExt;
+
+ enum EFlag {
+ FLAG_INVALID,
+ FLAG_BACKUP,
+ FLAG_SEPARATOR,
+ FLAG_OVERWRITE,
+ FLAG_FOREIGN,
+ FLAG_NOTENDORSED,
+ FLAG_NOTES,
+ FLAG_CONFLICT_OVERWRITE,
+ FLAG_CONFLICT_OVERWRITTEN,
+ FLAG_CONFLICT_MIXED,
+ FLAG_CONFLICT_REDUNDANT,
+ FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITE,
+ FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITTEN,
+ FLAG_ARCHIVE_CONFLICT_OVERWRITE,
+ FLAG_ARCHIVE_CONFLICT_OVERWRITTEN,
+ FLAG_ARCHIVE_CONFLICT_MIXED,
+ FLAG_PLUGIN_SELECTED,
+ FLAG_ALTERNATE_GAME
+ };
+
+ enum EContent {
+ CONTENT_PLUGIN,
+ CONTENT_TEXTURE,
+ CONTENT_MESH,
+ CONTENT_BSA,
+ CONTENT_INTERFACE,
+ CONTENT_SOUND,
+ CONTENT_SCRIPT,
+ CONTENT_SKSE,
+ CONTENT_SKSEFILES,
+ CONTENT_SKYPROC,
+ CONTENT_MCM,
+ CONTENT_INI,
+ CONTENT_MODGROUP
+ };
+
+ static const int NUM_CONTENT_TYPES = CONTENT_MODGROUP + 1;
+
+ enum EHighlight {
+ HIGHLIGHT_NONE = 0,
+ HIGHLIGHT_INVALID = 1,
+ HIGHLIGHT_CENTER = 2,
+ HIGHLIGHT_IMPORTANT = 4,
+ HIGHLIGHT_PLUGIN = 8
+ };
+
+ enum EEndorsedState {
+ ENDORSED_FALSE,
+ ENDORSED_TRUE,
+ ENDORSED_UNKNOWN,
+ ENDORSED_NEVER
+ };
+
+ enum EModType {
+ MOD_DEFAULT,
+ MOD_DLC,
+ MOD_CC
+ };
+
+
+public:
+
+ /**
+ * @brief read the mod directory and Mod ModInfo objects for all subdirectories
+ **/
+ static void updateFromDisc(const QString &modDirectory,
+ MOShared::DirectoryEntry **directoryStructure,
+ PluginContainer *pluginContainer,
+ bool displayForeign,
+ MOBase::IPluginGame const *game);
+
+ static void clear() { s_Collection.clear(); s_ModsByName.clear(); s_ModsByModID.clear(); }
+
+ /**
+ * @brief retrieve the number of mods
+ *
+ * @return number of mods
+ **/
+ static unsigned int getNumMods();
+
+ /**
+ * @brief retrieve a ModInfo object based on its index
+ *
+ * @param index the index to look up. the maximum is getNumMods() - 1
+ * @return a reference counting pointer to the mod info.
+ * @note since the pointer is reference counting, the pointer remains valid even if the collection is refreshed in a different thread
+ **/
+ static ModInfo::Ptr getByIndex(unsigned int index);
+
+ /**
+ * @brief retrieve a ModInfo object based on its nexus mod id
+ *
+ * @param modID the nexus mod id to look up
+ * @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 std::vector<ModInfo::Ptr> getByModID(QString game, int modID);
+
+ /**
+ * @brief remove a mod by index
+ *
+ * this physically deletes the specified mod from the disc and updates the ModInfo collection
+ * but not other structures that reference mods
+ * @param index index of the mod to delete
+ * @return true if removal was successful, fals otherwise
+ **/
+ static bool removeMod(unsigned int index);
+
+ /**
+ * @brief retrieve the mod index by the mod name
+ *
+ * @param name name of the mod to look up
+ * @return the index of the mod. If the mod doesn't exist, UINT_MAX is returned
+ **/
+ static unsigned int getIndex(const QString &name);
+
+ /**
+ * @brief find the first mod that fulfills the filter function (after no particular order)
+ * @param filter a function to filter by. should return true for a match
+ * @return index of the matching mod or UINT_MAX if there wasn't a match
+ */
+ static unsigned int findMod(const boost::function<bool (ModInfo::Ptr)> &filter);
+
+ /**
+ * @brief run a limited batch of mod update checks for "newest version" information
+ */
+ static int autoUpdateCheck(PluginContainer *pluginContainer, QObject *receiver);
+
+ /**
+ * @brief query nexus information for every mod and update the "newest version" information
+ **/
+ static int checkAllForUpdate(PluginContainer *pluginContainer, QObject *receiver);
+
+ /**
+ * @brief create a new mod from the specified directory and add it to the collection
+ * @param dir directory to create from
+ * @return pointer to the info-structure of the newly created/added mod
+ */
+ static ModInfo::Ptr createFrom(PluginContainer *pluginContainer, const MOBase::IPluginGame *game, const QDir &dir, MOShared::DirectoryEntry **directoryStructure);
+
+ /**
+ * @brief create a new "foreign-managed" mod from a tuple of plugin and archives
+ * @param espName name of the plugin
+ * @param bsaNames names of archives
+ * @return a new mod
+ */
+ static ModInfo::Ptr createFromPlugin(const QString &modName, const QString &espName, const QStringList &bsaNames, ModInfo::EModType modType, MOShared::DirectoryEntry **directoryStructure, PluginContainer *pluginContainer);
+
+ /**
+ * @brief retieve a name for one of the CONTENT_ enums
+ * @param contentType the content value
+ * @return a display string
+ */
+ static QString getContentTypeName(int contentType);
+
+ virtual bool isRegular() const { return false; }
+
+ virtual bool isEmpty() const { return false; }
+
+ /**
+ * @brief test if there is a newer version of the mod
+ *
+ * test if there is a newer version of the mod. This does NOT cause
+ * information to be retrieved from the nexus, it will only test version information already
+ * available locally. Use checkAllForUpdate() to update this version information
+ *
+ * @return true if there is a newer version
+ **/
+ virtual bool updateAvailable() const = 0;
+
+ /**
+ * @return true if the update currently available is ignored
+ */
+ virtual bool updateIgnored() const = 0;
+
+ /**
+ * @brief test if the "newest" version of the mod is older than the installed version
+ *
+ * test if there is a newer version of the mod. This does NOT cause
+ * information to be retrieved from the nexus, it will only test version information already
+ * available locally. Use checkAllForUpdate() to update this version information
+ *
+ * @return true if the newest version is older than the installed one
+ **/
+ virtual bool downgradeAvailable() const = 0;
+
+ /**
+ * @brief request an update of nexus description for this mod.
+ *
+ * This requests mod information from the nexus. This is an asynchronous request,
+ * so there is no immediate effect of this call.
+ * Right now, Mod Organizer interprets the "newest version" and "description" from the
+ * response, though the description is only stored in memory
+ *
+ **/
+ virtual bool updateNXMInfo() = 0;
+
+ /**
+ * @brief assign or unassign the specified category
+ *
+ * Every mod can have an arbitrary number of categories assigned to it
+ *
+ * @param categoryID id of the category to set
+ * @param active determines wheter the category is assigned or unassigned
+ * @note this function does not test whether categoryID actually identifies a valid category
+ **/
+ virtual void setCategory(int categoryID, bool active) = 0;
+
+ /**
+ * @brief changes the comments (manually set information displayed in the mod list) for this mod
+ * @param comments new comments
+ */
+ virtual void setComments(const QString &comments) = 0;
+
+ /**
+ * @brief change the notes (manually set information) for this mod
+ * @param notes new notes
+ */
+ virtual void setNotes(const QString &notes) = 0;
+
+ /**
+ * @brief set/change the source game of this mod
+ *
+ * @param gameName the source game shortName
+ */
+ virtual void setGameName(QString gameName) = 0;
+
+ /**
+ * @brief set/change the nexus mod id of this mod
+ *
+ * @param modID the nexus mod id
+ **/
+ virtual void setNexusID(int modID) = 0;
+
+ /**
+ * @brief set/change the version of this mod
+ * @param version new version of the mod
+ */
+ virtual void setVersion(const MOBase::VersionInfo &version);
+
+ /**
+ * @brief Controls if mod should be highlighted based on plugin selection
+ * @param isSelected whether or not the plugin has a selected mod
+ **/
+ virtual void setPluginSelected(const bool &isSelected);
+
+ /**
+ * @brief set the newest version of this mod on the nexus
+ *
+ * this can be used to overwrite the version of a mod without actually
+ * updating the mod
+ *
+ * @param version the new version to use
+ * @todo this function should be made obsolete. All queries for mod information should go through
+ * this class so no public function for this change is required
+ **/
+ virtual void setNewestVersion(const MOBase::VersionInfo &version) = 0;
+
+ /**
+ * @brief sets the repository that was used to download the mod
+ */
+ virtual void setRepository(const QString &) {}
+
+ /**
+ * @brief changes/updates the nexus description text
+ * @param description the current description text
+ */
+ virtual void setNexusDescription(const QString &description) = 0;
+
+ /**
+ * @brief sets the file this mod was installed from
+ * @param fileName name of the file
+ */
+ virtual void setInstallationFile(const QString &fileName) = 0;
+
+ /**
+ * @brief sets the category id from a nexus category id. Conversion to MO id happens internally
+ * @param categoryID the nexus category id
+ * @note if a mapping is not possible, the category is set to the default value
+ */
+ virtual void addNexusCategory(int categoryID) = 0;
+
+ virtual void addCategory(const QString &categoryName) override;
+ virtual bool removeCategory(const QString &categoryName) override;
+ virtual QStringList categories() override;
+
+ /**
+ * update the endorsement state for the mod. This only changes the
+ * buffered state, it does not sync with Nexus
+ * @param endorsed the new endorsement state
+ */
+ virtual void setIsEndorsed(bool endorsed) = 0;
+
+ /**
+ * set the mod to "i don't intend to endorse". The mod will not show as unendorsed but can still be endorsed
+ */
+ virtual void setNeverEndorse() = 0;
+
+ /**
+ * @brief delete the mod from the disc. This does not update the global ModInfo structure or indices
+ * @return true if the mod was successfully removed
+ **/
+ virtual bool remove() = 0;
+
+ /**
+ * @brief endorse or un-endorse the mod. This will sync with nexus!
+ * @param doEndorse if true, the mod is endorsed, if false, it's un-endorsed.
+ * @note if doEndorse doesn't differ from the current value, nothing happens.
+ */
+ virtual void endorse(bool doEndorse) = 0;
+
+ /**
+ * @brief clear all caches held for this mod
+ */
+ virtual void clearCaches() {}
+
+ /**
+ * @brief getter for the mod name
+ *
+ * @return the mod name
+ **/
+ virtual QString name() const = 0;
+
+ /**
+ * @brief getter for an internal name. This is usually the same as the regular name, but with special mod types it might be
+ * this is used to distinguish between mods that have the same visible name
+ * @return internal mod name
+ */
+ virtual QString internalName() const { return name(); }
+
+ /**
+ * @brief getter for the mod path
+ *
+ * @return the (absolute) path to the mod
+ **/
+ virtual QString absolutePath() const = 0;
+
+ /**
+ * @brief getter for the installation file
+ *
+ * @return file used to install this mod from
+ */
+ virtual QString getInstallationFile() const = 0;
+
+ /**
+ * @return version object for machine based comparisons
+ **/
+ virtual MOBase::VersionInfo getVersion() const { return m_Version; }
+
+ /**
+ * @brief getter for the newest version number of this mod
+ *
+ * @return newest version of the mod
+ **/
+ virtual MOBase::VersionInfo getNewestVersion() const = 0;
+
+ /**
+ * @return the repository from which the file was downloaded. Only relevant regular mods
+ */
+ virtual QString repository() const { return ""; }
+
+ /**
+ * @brief ignore the newest version for updates
+ */
+ virtual void ignoreUpdate(bool ignore) = 0;
+
+ /**
+ * @brief getter for the nexus mod id
+ *
+ * @return the nexus mod id. may be 0 if the mod id isn't known or doesn't exist
+ **/
+ virtual int getNexusID() const = 0;
+
+ /**
+ * @brief getter for the source game repository
+ *
+ * @return the source game repository. should default to the active game.
+ **/
+ virtual QString getGameName() const = 0;
+
+ /**
+ * @return the fixed priority of mods of this type or INT_MIN if the priority of mods
+ * needs to be user-modifiable. Can be < 0 to force a priority below user-modifable mods
+ * or INT_MAX to force priority above all user-modifiables
+ */
+ virtual int getFixedPriority() const = 0;
+
+ /**
+ * @return true if the mod is always enabled
+ */
+ virtual bool alwaysEnabled() const { return false; }
+
+ /**
+ * @return true if the mod can be updated
+ */
+ virtual bool canBeUpdated() const { return false; }
+
+ /**
+ * @return true if the mod can be enabled/disabled
+ */
+ virtual bool canBeEnabled() const { return false; }
+
+ /**
+ * @return a list of flags for this mod
+ */
+ virtual std::vector<EFlag> getFlags() const = 0;
+
+ /**
+ * @return a list of content types contained in a mod
+ */
+ virtual std::vector<EContent> getContents() const { return std::vector<EContent>(); }
+
+ /**
+ * @brief test if the specified flag is set for this mod
+ * @param flag the flag to test
+ * @return true if the flag is set, false otherwise
+ */
+ bool hasFlag(EFlag flag) const;
+
+ /**
+ * @brief test if the mods contains the specified content
+ * @param content the content to test
+ * @return true if the content is there, false otherwise
+ */
+ bool hasContent(ModInfo::EContent content) const;
+
+ /**
+ * @return an indicator if and how this mod should be highlighted by the UI
+ */
+ virtual int getHighlight() const { return HIGHLIGHT_NONE; }
+
+ /**
+ * @return list of names of ini tweaks
+ **/
+ virtual std::vector<QString> getIniTweaks() const = 0;
+
+ /**
+ * @return a description about the mod, to be displayed in the ui
+ */
+ virtual QString getDescription() const = 0;
+
+ /**
+ * @return comments for this mod
+ */
+ virtual QString comments() const = 0;
+
+ /**
+ * @return notes for this mod
+ */
+ virtual QString notes() const = 0;
+
+ /**
+ * @return creation time of this mod
+ */
+ virtual QDateTime creationTime() const = 0;
+
+ /**
+ * @return nexus description of the mod (html)
+ */
+ virtual QString getNexusDescription() const = 0;
+
+ /**
+ * @brief get the last time nexus was checked for file updates on this mod
+ */
+ virtual QDateTime getLastNexusUpdate() const = 0;
+
+ /**
+ * @brief set the last time nexus was checked for file updates on this mod
+ */
+ virtual void setLastNexusUpdate(QDateTime time) = 0;
+
+ /**
+ * @return last time nexus was queried for infos on this mod
+ */
+ virtual QDateTime getLastNexusQuery() const = 0;
+
+ /**
+ * @brief set the last time nexus was queried for info on this mod
+ */
+ virtual void setLastNexusQuery(QDateTime time) = 0;
+
+ /**
+ * @return a list of files that, if they exist in the data directory are treated as files in THIS mod
+ */
+ virtual QStringList stealFiles() const { return QStringList(); }
+
+ /**
+ * @return a list of archives belonging to this mod (as absolute file paths)
+ */
+ virtual QStringList archives(bool checkOnDisk = false) = 0;
+
+ /*
+ *@return the color choosen by the user for the mod/separator
+ */
+ virtual QColor getColor() { return QColor(); }
+
+ /*
+ *@return true if the color has been set successfully.
+ */
+ virtual void setColor(QColor color) { }
+
+ /**
+ * @brief adds the information that a file has been installed into this mod
+ * @param modId id of the mod installed
+ * @param fileId id of the file installed
+ */
+ virtual void addInstalledFile(int modId, int fileId) = 0;
+
+ /**
+ * @brief test if the mod belongs to the specified category
+ *
+ * @param categoryID the category to test for.
+ * @return true if the mod belongs to the specified category
+ * @note this does not verify the id actually identifies a category
+ **/
+ bool categorySet(int categoryID) const;
+
+ /**
+ * @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 id of the primary category of this mod
+ */
+ int getPrimaryCategory() const { return m_PrimaryCategory; }
+
+ /**
+ * @brief sets the new primary category of the mod
+ * @param categoryID the category to set
+ */
+ virtual void setPrimaryCategory(int categoryID) { m_PrimaryCategory = categoryID; }
+
+ /**
+ * @return true if this mod is considered "valid", that is: it contains data used by the game
+ **/
+ virtual bool isValid() const { return m_Valid; }
+
+ /**
+ * @return true if the file has been endorsed on nexus
+ */
+ virtual EEndorsedState endorsedState() const { return ENDORSED_NEVER; }
+
+ /**
+ * @brief updates the valid-flag for this mod
+ */
+ void testValid();
+
+ /**
+ * @brief updates the mod to flag it as converted in order to ignore the alternate game warning
+ */
+ virtual void markConverted(bool converted) {}
+
+ /**
+ * @brief updates the mod to flag it as valid in order to ignore the invalid game data flag
+ */
+ virtual void markValidated(bool validated) {}
+
+ /**
+ * @brief reads meta information from disk
+ */
+ virtual void readMeta() {}
+
+ /**
+ * @brief stores meta information back to disk
+ */
+ virtual void saveMeta() {}
+
+ /**
+ * @return retrieve list of mods (as mod index) that are overwritten by this one. Updates may be delayed
+ */
+ virtual std::set<unsigned int> getModOverwrite() { return std::set<unsigned int>(); }
+
+ /**
+ * @return list of mods (as mod index) that overwrite this one. Updates may be delayed
+ */
+ virtual std::set<unsigned int> getModOverwritten() { return std::set<unsigned int>(); }
+
+ /**
+ * @return retrieve list of mods (as mod index) with archives that are overwritten by this one. Updates may be delayed
+ */
+ virtual std::set<unsigned int> getModArchiveOverwrite() { return std::set<unsigned int>(); }
+
+ /**
+ * @return list of mods (as mod index) with archives that overwrite this one. Updates may be delayed
+ */
+ virtual std::set<unsigned int> getModArchiveOverwritten() { return std::set<unsigned int>(); }
+
+ /**
+ * @return retrieve list of mods (as mod index) with archives that are overwritten by thos mod's loose files. Updates may be delayed
+ */
+ virtual std::set<unsigned int> getModArchiveLooseOverwrite() { return std::set<unsigned int>(); }
+
+ /**
+ * @return list of mods (as mod index) with loose files that overwrite this one's archive files. Updates may be delayed
+ */
+ virtual std::set<unsigned int> getModArchiveLooseOverwritten() { return std::set<unsigned int>(); }
+
+ /**
+ * @brief update conflict information
+ */
+ virtual void doConflictCheck() const {}
+
+ /**
+ * @brief set the URL for a mod
+ */
+ virtual void setURL(QString const &) {}
+
+ /**
+ * @returns the URL for a mod
+ */
+ virtual QString getURL() const { return ""; }
+
+signals:
+
+ /**
+ * @brief emitted whenever the information of a mod changes
+ *
+ * @param success true if the mod details were updated successfully, false if not
+ **/
+ void modDetailsUpdated(bool success);
+
+protected:
+
+ ModInfo(PluginContainer *pluginContainer);
+
+ static void updateIndices();
+ static bool ByName(const ModInfo::Ptr &LHS, const ModInfo::Ptr &RHS);
+
+private:
+
+ static void createFromOverwrite(PluginContainer *pluginContainer);
+
+protected:
+
+ static std::vector<ModInfo::Ptr> s_Collection;
+ static std::map<QString, unsigned int> s_ModsByName;
+
+ int m_PrimaryCategory;
+ std::set<int> m_Categories;
+
+ MOBase::VersionInfo m_Version;
+
+ bool m_PluginSelected = false;
+
+private:
+
+ static QMutex s_Mutex;
+ static std::map<std::pair<QString, int>, std::vector<unsigned int> > s_ModsByModID;
+ static int s_NextID;
+
+ bool m_Valid;
+
+};
+
+
+#endif // MODINFO_H
diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp
index d086de08..db3e5de1 100644
--- a/src/modinfodialog.cpp
+++ b/src/modinfodialog.cpp
@@ -929,7 +929,7 @@ void ModInfoDialog::activateNexusTab()
visitNexusLabel->setToolTip(nexusLink);
if (m_ModInfo->getNexusDescription().isEmpty() ||
- QDateTime::currentDateTime() > m_ModInfo->getLastNexusQuery().addDays(1)) {
+ QDateTime::currentDateTimeUtc() > m_ModInfo->getLastNexusQuery().addDays(1)) {
refreshNexusData(modID);
} else {
this->modDetailsUpdated(true);
diff --git a/src/modinfoforeign.h b/src/modinfoforeign.h
index 20bfab2a..0702f268 100644
--- a/src/modinfoforeign.h
+++ b/src/modinfoforeign.h
@@ -47,7 +47,10 @@ public:
virtual std::vector<ModInfo::EFlag> getFlags() const;
virtual int getHighlight() const;
virtual QString getDescription() const;
+ virtual QDateTime getLastNexusUpdate() const { return QDateTime(); }
+ virtual void setLastNexusUpdate(QDateTime time) {}
virtual QDateTime getLastNexusQuery() const { return QDateTime(); }
+ virtual void setLastNexusQuery(QDateTime time) {}
virtual QString getNexusDescription() const { return QString(); }
virtual int getFixedPriority() const { return INT_MIN; }
virtual QStringList archives(bool checkOnDisk = false) { return m_Archives; }
diff --git a/src/modinfooverwrite.h b/src/modinfooverwrite.h
index b68fb15b..d2882301 100644
--- a/src/modinfooverwrite.h
+++ b/src/modinfooverwrite.h
@@ -50,7 +50,10 @@ public:
virtual std::vector<ModInfo::EFlag> getFlags() const;
virtual int getHighlight() const;
virtual QString getDescription() const;
+ virtual QDateTime getLastNexusUpdate() const { return QDateTime(); }
+ virtual void setLastNexusUpdate(QDateTime time) {}
virtual QDateTime getLastNexusQuery() const { return QDateTime(); }
+ virtual void setLastNexusQuery(QDateTime time) {}
virtual QString getNexusDescription() const { return QString(); }
virtual QStringList archives(bool checkOnDisk = false);
virtual void addInstalledFile(int, int) {}
diff --git a/src/modinforegular.cpp b/src/modinforegular.cpp
index 834c09ae..57c4baad 100644
--- a/src/modinforegular.cpp
+++ b/src/modinforegular.cpp
@@ -98,6 +98,7 @@ void ModInfoRegular::readMeta()
m_Validated = metaFile.value("validated", false).toBool();
m_URL = metaFile.value("url", "").toString();
m_LastNexusQuery = QDateTime::fromString(metaFile.value("lastNexusQuery", "").toString(), Qt::ISODate);
+ m_LastNexusUpdate = QDateTime::fromString(metaFile.value("lastNexusUpdate", "").toString(), Qt::ISODate);
m_Color = metaFile.value("color",QColor()).value<QColor>();
if (metaFile.contains("endorsed")) {
if (metaFile.value("endorsed").canConvert<int>()) {
@@ -161,6 +162,7 @@ void ModInfoRegular::saveMeta()
metaFile.setValue("nexusDescription", m_NexusDescription);
metaFile.setValue("url", m_URL);
metaFile.setValue("lastNexusQuery", m_LastNexusQuery.toString(Qt::ISODate));
+ metaFile.setValue("lastNexusUpdate", m_LastNexusUpdate.toString(Qt::ISODate));
metaFile.setValue("converted", m_Converted);
metaFile.setValue("validated", m_Validated);
metaFile.setValue("color", m_Color);
@@ -225,8 +227,8 @@ void ModInfoRegular::nxmDescriptionAvailable(QString, int, QVariant, QVariant re
else
setEndorsedState(ENDORSED_FALSE);
}
- m_LastNexusQuery = QDateTime::currentDateTime();
- //m_MetaInfoChanged = true;
+ m_LastNexusQuery = QDateTime::currentDateTimeUtc();
+ m_MetaInfoChanged = true;
saveMeta();
emit modDetailsUpdated(true);
}
@@ -263,10 +265,14 @@ void ModInfoRegular::nxmRequestFailed(QString, int, int, QVariant userData, QNet
bool ModInfoRegular::updateNXMInfo()
{
- if (m_NexusID > 0) {
+ QDateTime time = QDateTime::currentDateTimeUtc();
+ QDateTime target = m_LastNexusQuery.addSecs(3600);
+ if (m_NexusID > 0 && time >= target) {
m_NexusBridge.requestDescription(m_GameName, m_NexusID, QVariant());
return true;
}
+ QString warning("Please wait until %1 to request updated mod info from Nexus!");
+ qWarning() << warning.arg(target.toLocalTime().time().toString(Qt::DefaultLocaleShortDate));
return false;
}
@@ -483,6 +489,15 @@ void ModInfoRegular::ignoreUpdate(bool ignore)
m_MetaInfoChanged = true;
}
+bool ModInfoRegular::canBeUpdated() const
+{
+ QDateTime now = QDateTime::currentDateTimeUtc();
+ QDateTime target = m_LastNexusUpdate.addSecs(3600);
+ if (now >= target)
+ return m_NexusID > 0;
+ return false;
+}
+
std::vector<ModInfo::EFlag> ModInfoRegular::getFlags() const
{
@@ -628,11 +643,32 @@ ModInfoRegular::EEndorsedState ModInfoRegular::endorsedState() const
return m_EndorsedState;
}
+QDateTime ModInfoRegular::getLastNexusUpdate() const
+{
+ return m_LastNexusUpdate;
+}
+
+void ModInfoRegular::setLastNexusUpdate(QDateTime time)
+{
+ m_LastNexusUpdate = time;
+ m_MetaInfoChanged = true;
+ saveMeta();
+ emit modDetailsUpdated(true);
+}
+
QDateTime ModInfoRegular::getLastNexusQuery() const
{
return m_LastNexusQuery;
}
+void ModInfoRegular::setLastNexusQuery(QDateTime time)
+{
+ m_LastNexusQuery = time;
+ m_MetaInfoChanged = true;
+ saveMeta();
+ emit modDetailsUpdated(true);
+}
+
void ModInfoRegular::setURL(QString const &url)
{
m_URL = url;
diff --git a/src/modinforegular.h b/src/modinforegular.h
index fdb0e672..8e3952e8 100644
--- a/src/modinforegular.h
+++ b/src/modinforegular.h
@@ -256,7 +256,7 @@ public:
/**
* @return true if the mod can be updated
*/
- virtual bool canBeUpdated() const { return m_NexusID > 0; }
+ virtual bool canBeUpdated() const;
/**
* @return true if the mod can be enabled/disabled
@@ -316,10 +316,25 @@ public:
virtual EEndorsedState endorsedState() const;
/**
+ * @brief get the last time nexus was checked for file updates on this mod
+ */
+ virtual QDateTime getLastNexusUpdate() const;
+
+ /**
+ * @brief set the last time nexus was checked for file updates on this mod
+ */
+ virtual void setLastNexusUpdate(QDateTime time);
+
+ /**
* @return last time nexus was queried for infos on this mod
*/
virtual QDateTime getLastNexusQuery() const;
+ /**
+ * @brief set the last time nexus was queried for info on this mod
+ */
+ virtual void setLastNexusQuery(QDateTime time);
+
virtual QStringList archives(bool checkOnDisk = false);
virtual void setColor(QColor color);
@@ -376,6 +391,7 @@ private:
QDateTime m_CreationTime;
QDateTime m_LastNexusQuery;
+ QDateTime m_LastNexusUpdate;
QColor m_Color;
diff --git a/src/modinfoseparator.h b/src/modinfoseparator.h
index c5e0f0e5..1f3be861 100644
--- a/src/modinfoseparator.h
+++ b/src/modinfoseparator.h
@@ -43,8 +43,10 @@ public:
virtual QString getInstallationFile() const { return ""; }
virtual QString getURL() const { return ""; }
virtual QString repository() const { return ""; }
-
+ virtual QDateTime getLastNexusUpdate() const { return QDateTime(); }
+ virtual void setLastNexusUpdate(QDateTime time) {}
virtual QDateTime getLastNexusQuery() const { return QDateTime(); }
+ virtual void setLastNexusQuery(QDateTime time) {}
virtual QDateTime creationTime() const { return QDateTime(); }
virtual void getNexusFiles
diff --git a/src/modlist.cpp b/src/modlist.cpp
index daa6ce73..1ec5b556 100644
--- a/src/modlist.cpp
+++ b/src/modlist.cpp
@@ -1,1398 +1,1410 @@
-/*
-Copyright (C) 2012 Sebastian Herbord. All rights reserved.
-
-This file is part of Mod Organizer.
-
-Mod Organizer is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-Mod Organizer is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-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 "modlist.h"
-
-#include "messagedialog.h"
-#include "installationtester.h"
-#include "qtgroupingproxy.h"
-#include "viewmarkingscrollbar.h"
-#include "modlistsortproxy.h"
-#include "pluginlist.h"
-#include "settings.h"
-#include "modinforegular.h"
-#include <appconfig.h>
-#include <utility.h>
-#include <report.h>
-
-#include <QFileInfo>
-#include <QDir>
-#include <QDirIterator>
-#include <QMimeData>
-#include <QStandardItemModel>
-#include <QMessageBox>
-#include <QStringList>
-#include <QEvent>
-#include <QContextMenuEvent>
-#include <QMenu>
-#include <QCheckBox>
-#include <QWidgetAction>
-#include <QAbstractItemView>
-#include <QSortFilterProxyModel>
-#include <QApplication>
-#include <QFontDatabase>
-
-#include <sstream>
-#include <stdexcept>
-#include <algorithm>
-
-
-using namespace MOBase;
-
-
-ModList::ModList(PluginContainer *pluginContainer, QObject *parent)
- : QAbstractItemModel(parent)
- , m_Profile(nullptr)
- , m_NexusInterface(nullptr)
- , m_Modified(false)
- , m_FontMetrics(QFont())
- , m_DropOnItems(false)
- , m_PluginContainer(pluginContainer)
-{
- m_ContentIcons[ModInfo::CONTENT_PLUGIN] = std::make_tuple(":/MO/gui/content/plugin", tr("Game Plugins (ESP/ESM/ESL)"));
- m_ContentIcons[ModInfo::CONTENT_INTERFACE] = std::make_tuple(":/MO/gui/content/interface", tr("Interface"));
- m_ContentIcons[ModInfo::CONTENT_MESH] = std::make_tuple(":/MO/gui/content/mesh", tr("Meshes"));
- m_ContentIcons[ModInfo::CONTENT_BSA] = std::make_tuple(":/MO/gui/content/bsa", tr("Bethesda Archive"));
- m_ContentIcons[ModInfo::CONTENT_SCRIPT] = std::make_tuple(":/MO/gui/content/script", tr("Scripts (Papyrus)"));
- m_ContentIcons[ModInfo::CONTENT_SKSE] = std::make_tuple(":/MO/gui/content/skse", tr("Script Extender Plugin"));
- m_ContentIcons[ModInfo::CONTENT_SKYPROC] = std::make_tuple(":/MO/gui/content/skyproc", tr("SkyProc Patcher"));
- m_ContentIcons[ModInfo::CONTENT_SOUND] = std::make_tuple(":/MO/gui/content/sound", tr("Sound or Music"));
- m_ContentIcons[ModInfo::CONTENT_TEXTURE] = std::make_tuple(":/MO/gui/content/texture", tr("Textures"));
- m_ContentIcons[ModInfo::CONTENT_MCM] = std::make_tuple(":/MO/gui/content/menu", tr("MCM Configuration"));
- m_ContentIcons[ModInfo::CONTENT_INI] = std::make_tuple(":/MO/gui/content/inifile", tr("INI files"));
- m_ContentIcons[ModInfo::CONTENT_MODGROUP] = std::make_tuple(":/MO/gui/content/modgroup", tr("ModGroup files"));
-
- m_LastCheck.start();
-}
-
-ModList::~ModList()
-{
- m_ModStateChanged.disconnect_all_slots();
- m_ModMoved.disconnect_all_slots();
-}
-
-void ModList::setProfile(Profile *profile)
-{
- m_Profile = profile;
-}
-
-int ModList::rowCount(const QModelIndex &parent) const
-{
- if (!parent.isValid()) {
- return ModInfo::getNumMods();
- } else {
- return 0;
- }
-}
-
-bool ModList::hasChildren(const QModelIndex &parent) const
-{
- if (!parent.isValid()) {
- return ModInfo::getNumMods() > 0;
- } else {
- return false;
- }
-}
-
-
-int ModList::columnCount(const QModelIndex &) const
-{
- return COL_LASTCOLUMN + 1;
-}
-
-
-QVariant ModList::getOverwriteData(int column, int role) const
-{
- switch (role) {
- case Qt::DisplayRole: {
- if (column == 0) {
- return "Overwrite";
- } else {
- return QVariant();
- }
- } break;
- case Qt::CheckStateRole: {
- if (column == 0) {
- return Qt::Checked;
- } else {
- return QVariant();
- }
- } break;
- case Qt::TextAlignmentRole: return QVariant(Qt::AlignCenter | Qt::AlignVCenter);
- case Qt::UserRole: return -1;
- case Qt::ForegroundRole: return QBrush(Qt::red);
- case Qt::ToolTipRole: return tr("This entry contains files that have been created inside the virtual data tree (i.e. by the construction kit)");
- default: return QVariant();
- }
-}
-
-
-QString ModList::getFlagText(ModInfo::EFlag flag, ModInfo::Ptr modInfo) const
-{
- switch (flag) {
- case ModInfo::FLAG_BACKUP: return tr("Backup");
- case ModInfo::FLAG_SEPARATOR: return tr("Separator");
- case ModInfo::FLAG_INVALID: return tr("No valid game data");
- case ModInfo::FLAG_NOTENDORSED: return tr("Not endorsed yet");
- case ModInfo::FLAG_NOTES: {
- QStringList output;
- if (!modInfo->comments().isEmpty())
- output << QString("<i>%1</i>").arg(modInfo->comments());
- if (!modInfo->notes().isEmpty())
- output << QString("<i>%1</i>").arg(modInfo->notes());
- return output.join("");
- }
- case ModInfo::FLAG_CONFLICT_OVERWRITE: return tr("Overwrites loose files");
- case ModInfo::FLAG_CONFLICT_OVERWRITTEN: return tr("Overwritten loose files");
- case ModInfo::FLAG_CONFLICT_MIXED: return tr("Loose files Overwrites & Overwritten");
- case ModInfo::FLAG_CONFLICT_REDUNDANT: return tr("Redundant");
- case ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITE: return tr("Overwrites an archive with loose files");
- case ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITTEN: return tr("Archive is overwritten by loose files");
- case ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITE: return tr("Overwrites another archive file");
- case ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITTEN: return tr("Overwritten by another archive file");
- case ModInfo::FLAG_ARCHIVE_CONFLICT_MIXED: return tr("Archive files overwrites & overwritten");
- case ModInfo::FLAG_ALTERNATE_GAME: return tr("<br>This mod is for a different game, "
- "make sure it's compatible or it could cause crashes.");
- default: return "";
- }
-}
-
-
-QVariantList ModList::contentsToIcons(const std::vector<ModInfo::EContent> &contents) const
-{
- QVariantList result;
- std::set<ModInfo::EContent> contentsSet(contents.begin(), contents.end());
- for (auto iter = m_ContentIcons.begin(); iter != m_ContentIcons.end(); ++iter) {
- if (contentsSet.find(iter->first) != contentsSet.end()) {
- result.append(std::get<0>(iter->second));
- } else {
- result.append(QString());
- }
- }
- return result;
-}
-
-QString ModList::contentsToToolTip(const std::vector<ModInfo::EContent> &contents) const
-{
- QString result("<table cellspacing=7>");
-
- std::set<ModInfo::EContent> contentsSet(contents.begin(), contents.end());
- for (auto iter = m_ContentIcons.begin(); iter != m_ContentIcons.end(); ++iter) {
- if (contentsSet.find(iter->first) != contentsSet.end()) {
- result.append(QString("<tr><td><img src=\"%1\" width=32/></td>"
- "<td valign=\"middle\">%2</td></tr>")
- .arg(std::get<0>(iter->second)).arg(std::get<1>(iter->second)));
- }
- }
- result.append("</table>");
- return result;
-}
-
-
-QVariant ModList::data(const QModelIndex &modelIndex, int role) const
-{
- if (m_Profile == nullptr) return QVariant();
- if (!modelIndex.isValid()) return QVariant();
- unsigned int modIndex = modelIndex.row();
- int column = modelIndex.column();
-
- ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex);
- if ((role == Qt::DisplayRole) ||
- (role == Qt::EditRole)) {
- if ((column == COL_FLAGS)
- || (column == COL_CONTENT)) {
- return QVariant();
- } else if (column == COL_NAME) {
- auto flags = modInfo->getFlags();
- if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_SEPARATOR) != flags.end())
- {
- return modInfo->name().replace("_separator", "");
- }
- else
- return modInfo->name();
- } else if (column == COL_VERSION) {
- VersionInfo verInfo = modInfo->getVersion();
- QString version = verInfo.displayString();
- if (role != Qt::EditRole) {
- if (version.isEmpty() && modInfo->canBeUpdated()) {
- version = "?";
- }
- }
- return version;
- } else if (column == COL_PRIORITY) {
- int priority = modInfo->getFixedPriority();
- if (priority != INT_MIN) {
- return QVariant(); // hide priority for mods where it's fixed
- } else {
- return m_Profile->getModPriority(modIndex);
- }
- } else if (column == COL_MODID) {
- int modID = modInfo->getNexusID();
- if (modID >= 0) {
- return modID;
- }
- else {
- return QVariant();
- }
- } else if (column == COL_GAME) {
- if (m_PluginContainer != nullptr) {
- for (auto game : m_PluginContainer->plugins<IPluginGame>()) {
- if (game->gameShortName().compare(modInfo->getGameName(), Qt::CaseInsensitive) == 0)
- return game->gameName();
- }
- }
- return modInfo->getGameName();
- } else if (column == COL_CATEGORY) {
- if (modInfo->hasFlag(ModInfo::FLAG_FOREIGN)) {
- return tr("Non-MO");
- } else {
- int category = modInfo->getPrimaryCategory();
- if (category != -1) {
- CategoryFactory &categoryFactory = CategoryFactory::instance();
- if (categoryFactory.categoryExists(category)) {
- try {
- int categoryIdx = categoryFactory.getCategoryIndex(category);
- return categoryFactory.getCategoryName(categoryIdx);
- } catch (const std::exception &e) {
- qCritical("failed to retrieve category name: %s", e.what());
- return QString();
- }
- } else {
- qWarning("category %d doesn't exist (may have been removed)", category);
- modInfo->setCategory(category, false);
- return QString();
- }
- } else {
- return QVariant();
- }
- }
- } else if (column == COL_INSTALLTIME) {
- // display installation time for mods that can be updated
- if (modInfo->creationTime().isValid()) {
- return modInfo->creationTime();
- } else {
- return QVariant();
- }
- } else if (column == COL_NOTES) {
- return modInfo->comments();
- } else {
- return tr("invalid");
- }
- } else if ((role == Qt::CheckStateRole) && (column == 0)) {
- if (modInfo->canBeEnabled()) {
- return m_Profile->modEnabled(modIndex) ? Qt::Checked : Qt::Unchecked;
- } else {
- return QVariant();
- }
- } else if (role == Qt::TextAlignmentRole) {
- auto flags = modInfo->getFlags();
- if (column == COL_NAME) {
- if (modInfo->getHighlight() & ModInfo::HIGHLIGHT_CENTER) {
- return QVariant(Qt::AlignCenter | Qt::AlignVCenter);
- } else {
- return QVariant(Qt::AlignLeft | Qt::AlignVCenter);
- }
- } else if (column == COL_VERSION) {
- return QVariant(Qt::AlignRight | Qt::AlignVCenter);
- } else if (column == COL_NOTES) {
- return QVariant(Qt::AlignLeft | Qt::AlignVCenter);
- } else {
- return QVariant(Qt::AlignCenter | Qt::AlignVCenter);
- }
- } else if (role == Qt::UserRole) {
- if (column == COL_CATEGORY) {
- QVariantList categoryNames;
- std::set<int> categories = modInfo->getCategories();
- CategoryFactory &categoryFactory = CategoryFactory::instance();
- for (auto iter = categories.begin(); iter != categories.end(); ++iter) {
- categoryNames.append(categoryFactory.getCategoryName(categoryFactory.getCategoryIndex(*iter)));
- }
- if (categoryNames.count() != 0) {
- return categoryNames;
- } else {
- return QVariant();
- }
- } else if (column == COL_PRIORITY) {
- int priority = modInfo->getFixedPriority();
- if (priority != INT_MIN) {
- return priority;
- } else {
- return m_Profile->getModPriority(modIndex);
- }
- } else {
- return modInfo->getNexusID();
- }
- } else if (role == Qt::UserRole + 1) {
- return modIndex;
- } else if (role == Qt::UserRole + 2) {
- switch (column) {
- case COL_MODID: return QtGroupingProxy::AGGR_FIRST;
- case COL_VERSION: return QtGroupingProxy::AGGR_MAX;
- case COL_CATEGORY: return QtGroupingProxy::AGGR_FIRST;
- case COL_PRIORITY: return QtGroupingProxy::AGGR_MIN;
- default: return QtGroupingProxy::AGGR_NONE;
- }
- } else if (role == Qt::UserRole + 3) {
- return contentsToIcons(modInfo->getContents());
- } else if (role == Qt::UserRole + 4) {
- return modInfo->getGameName();
- } else if (role == Qt::FontRole) {
- QFont result;
- auto flags = modInfo->getFlags();
- if (column == COL_NAME) {
- if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_SEPARATOR) != flags.end())
- {
- //result.setCapitalization(QFont::AllUppercase);
- result.setItalic(true);
- //result.setUnderline(true);
- result.setBold(true);
- } else if (modInfo->getHighlight() & ModInfo::HIGHLIGHT_INVALID) {
- result.setItalic(true);
- }
- } else if ((column == COL_CATEGORY) && (modInfo->hasFlag(ModInfo::FLAG_FOREIGN))) {
- result.setItalic(true);
- } else if (column == COL_VERSION) {
- if (modInfo->updateAvailable() || modInfo->downgradeAvailable()) {
- result.setWeight(QFont::Bold);
- }
- }
- return result;
- } else if (role == Qt::DecorationRole) {
- if (column == COL_VERSION) {
- if (modInfo->updateAvailable()) {
- return QIcon(":/MO/gui/update_available");
- } else if (modInfo->downgradeAvailable()) {
- return QIcon(":/MO/gui/warning");
- } else if (modInfo->getVersion().scheme() == VersionInfo::SCHEME_DATE) {
- return QIcon(":/MO/gui/version_date");
- }
- }
- return QVariant();
- } else if (role == Qt::ForegroundRole) {
- if (modInfo->hasFlag(ModInfo::FLAG_SEPARATOR) && modInfo->getColor().isValid()) {
- return Settings::getIdealTextColor(modInfo->getColor());
- } else if (column == COL_NAME) {
- int highlight = modInfo->getHighlight();
- if (highlight & ModInfo::HIGHLIGHT_IMPORTANT)
- return QBrush(Qt::darkRed);
- else if (highlight & ModInfo::HIGHLIGHT_INVALID)
- return QBrush(Qt::darkGray);
- } else if (column == COL_VERSION) {
- if (!modInfo->getNewestVersion().isValid()) {
- return QVariant();
- } else if (modInfo->updateAvailable() || modInfo->downgradeAvailable()) {
- return QBrush(Qt::red);
- } else {
- return QBrush(Qt::darkGreen);
- }
- }
- return QVariant();
- } else if ((role == Qt::BackgroundRole)
- || (role == ViewMarkingScrollBar::DEFAULT_ROLE)) {
- bool overwrite = m_Overwrite.find(modIndex) != m_Overwrite.end();
- bool archiveOverwrite = m_ArchiveOverwrite.find(modIndex) != m_ArchiveOverwrite.end();
- bool archiveLooseOverwrite = m_ArchiveLooseOverwrite.find(modIndex) != m_ArchiveLooseOverwrite.end();
- bool overwritten = m_Overwritten.find(modIndex) != m_Overwritten.end();
- bool archiveOverwritten = m_ArchiveOverwritten.find(modIndex) != m_ArchiveOverwritten.end();
- bool archiveLooseOverwritten = m_ArchiveLooseOverwritten.find(modIndex) != m_ArchiveLooseOverwritten.end();
- if (modInfo->getHighlight() & ModInfo::HIGHLIGHT_PLUGIN) {
- return Settings::instance().modlistContainsPluginColor();
- } else if (overwritten || archiveLooseOverwritten) {
- return Settings::instance().modlistOverwritingLooseColor();
- } else if (overwrite || archiveLooseOverwrite) {
- return Settings::instance().modlistOverwrittenLooseColor();
- } else if (archiveOverwritten) {
- return Settings::instance().modlistOverwritingArchiveColor();
- } else if (archiveOverwrite) {
- return Settings::instance().modlistOverwrittenArchiveColor();
- } else if (modInfo->hasFlag(ModInfo::FLAG_SEPARATOR)
- && modInfo->getColor().isValid()
- && ((role != ViewMarkingScrollBar::DEFAULT_ROLE)
- || Settings::instance().colorSeparatorScrollbar())) {
- return modInfo->getColor();
- } else {
- return QVariant();
- }
- } else if (role == Qt::ToolTipRole) {
- if (column == COL_FLAGS) {
- QString result;
-
- for (ModInfo::EFlag flag : modInfo->getFlags()) {
- if (result.length() != 0) result += "<br>";
- result += getFlagText(flag, modInfo);
- }
-
- return result;
- } else if (column == COL_CONTENT) {
- return contentsToToolTip(modInfo->getContents());
- } else if (column == COL_NAME) {
- try {
- return modInfo->getDescription();
- } catch (const std::exception &e) {
- qCritical("invalid mod description: %s", e.what());
- return QString();
- }
- } else if (column == COL_VERSION) {
- QString text = tr("installed version: \"%1\", newest version: \"%2\"").arg(modInfo->getVersion().displayString(3)).arg(modInfo->getNewestVersion().displayString(3));
- if (modInfo->downgradeAvailable()) {
- text += "<br>" + tr("The newest version on Nexus seems to be older than the one you have installed. This could either mean the version you have has been withdrawn "
- "(i.e. due to a bug) or the author uses a non-standard versioning scheme and that newest version is actually newer. "
- "Either way you may want to \"upgrade\".");
- }
- return text;
- } else if (column == COL_CATEGORY) {
- const std::set<int> &categories = modInfo->getCategories();
- std::wostringstream categoryString;
- categoryString << ToWString(tr("Categories: <br>"));
- CategoryFactory &categoryFactory = CategoryFactory::instance();
- for (std::set<int>::const_iterator catIter = categories.begin();
- catIter != categories.end(); ++catIter) {
- if (catIter != categories.begin()) {
- categoryString << " , ";
- }
- try {
- categoryString << "<span style=\"white-space: nowrap;\"><i>" << ToWString(categoryFactory.getCategoryName(categoryFactory.getCategoryIndex(*catIter))) << "</font></span>";
- } catch (const std::exception &e) {
- qCritical("failed to generate tooltip: %s", e.what());
- return QString();
- }
- }
-
- return ToQString(categoryString.str());
- } else if (column == COL_NOTES) {
- return getFlagText(ModInfo::FLAG_NOTES, modInfo);
- } else {
- return QVariant();
- }
- } else {
- return QVariant();
- }
-}
-
-
-bool ModList::renameMod(int index, const QString &newName)
-{
- QString nameFixed = newName;
- if (!fixDirectoryName(nameFixed) || nameFixed.isEmpty()) {
- MessageDialog::showMessage(tr("Invalid name"), nullptr);
- return false;
- }
-
- if (ModList::allMods().contains(nameFixed, Qt::CaseInsensitive) && nameFixed.toLower()!=ModInfo::getByIndex(index)->name().toLower() ) {
- MessageDialog::showMessage(tr("Name is already in use by another mod"), nullptr);
- return false;
- }
-
- ModInfo::Ptr modInfo = ModInfo::getByIndex(index);
- QString oldName = modInfo->name();
- if (nameFixed != oldName) {
- // before we rename, ensure there is no scheduled asynchronous to rewrite
- m_Profile->cancelModlistWrite();
-
-
- if (modInfo->setName(nameFixed))
- // Notice there is a good chance that setName() updated the modinfo indexes
- // the modRenamed() call will refresh the indexes in the current profile
- // and update the modlists in all profiles
- emit modRenamed(oldName, nameFixed);
- }
-
- // invalidate the currently displayed state of this list
- notifyChange(-1);
- return true;
-}
-
-bool ModList::setData(const QModelIndex &index, const QVariant &value, int role)
-{
- if (m_Profile == nullptr) return false;
-
- if (static_cast<unsigned int>(index.row()) >= ModInfo::getNumMods()) {
- return false;
- }
-
- int modID = index.row();
-
- ModInfo::Ptr info = ModInfo::getByIndex(modID);
- IModList::ModStates oldState = state(modID);
-
- bool result = false;
-
- emit aboutToChangeData();
-
- if (role == Qt::CheckStateRole) {
- bool enabled = value.toInt() == Qt::Checked;
- if (m_Profile->modEnabled(modID) != enabled) {
- m_Profile->setModEnabled(modID, enabled);
- m_Modified = true;
- m_LastCheck.restart();
- emit modlistChanged(index, role);
- }
- result = true;
- emit dataChanged(index, index);
- } else if (role == Qt::EditRole) {
- switch (index.column()) {
- case COL_NAME: {
- auto flags = info->getFlags();
- if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_SEPARATOR) != flags.end())
- {
- result = renameMod(modID, value.toString() + "_separator");
- }
- else
- result = renameMod(modID, value.toString());
- } break;
- case COL_PRIORITY: {
- bool ok = false;
- int newPriority = value.toInt(&ok);
- if (ok && newPriority < 0) {
- newPriority = 0;
- }
- if (ok) {
- m_Profile->setModPriority(modID, newPriority);
-
- emit modorder_changed();
- result = true;
- } else {
- result = false;
- }
- } break;
- case COL_MODID: {
- bool ok = false;
- int newID = value.toInt(&ok);
- if (ok) {
- info->setNexusID(newID);
- emit modlistChanged(index, role);
- result = true;
- } else {
- result = false;
- }
- } break;
- case COL_VERSION: {
- VersionInfo::VersionScheme scheme = info->getVersion().scheme();
- VersionInfo version(value.toString(), scheme, true);
- if (version.isValid()) {
- info->setVersion(version);
- result = true;
- } else {
- result = false;
- }
- } break;
- case COL_NOTES: {
- info->setComments(value.toString());
- result = true;
- } break;
- default: {
- qWarning("edit on column \"%s\" not supported",
- getColumnName(index.column()).toUtf8().constData());
- result = false;
- } break;
- }
- if (result) {
- emit dataChanged(index, index);
- }
- }
-
- emit postDataChanged();
-
- IModList::ModStates newState = state(modID);
- if (oldState != newState) {
- try {
- m_ModStateChanged(info->name(), newState);
- } catch (const std::exception &e) {
- qCritical("failed to invoke state changed notification: %s", e.what());
- } catch (...) {
- qCritical("failed to invoke state changed notification: unknown exception");
- }
- }
-
- return result;
-}
-
-
-QVariant ModList::headerData(int section, Qt::Orientation orientation,
- int role) const
-{
- if (orientation == Qt::Horizontal) {
- if (role == Qt::DisplayRole) {
- return getColumnName(section);
- } else if (role == Qt::ToolTipRole) {
- return getColumnToolTip(section);
- } else if (role == Qt::TextAlignmentRole) {
- return QVariant(Qt::AlignCenter);
- } else if (role == Qt::SizeHintRole) {
- QSize temp = m_FontMetrics.size(Qt::TextSingleLine, getColumnName(section));
- temp.rwidth() += 20;
- temp.rheight() += 12;
- return temp;
- }
- }
- return QAbstractItemModel::headerData(section, orientation, role);
-}
-
-
-Qt::ItemFlags ModList::flags(const QModelIndex &modelIndex) const
-{
- Qt::ItemFlags result = QAbstractItemModel::flags(modelIndex);
- if (modelIndex.internalId() < 0) {
- return Qt::ItemIsEnabled;
- }
- if (modelIndex.isValid()) {
- ModInfo::Ptr modInfo = ModInfo::getByIndex(modelIndex.row());
- std::vector<ModInfo::EFlag> flags = modInfo->getFlags();
- if (modInfo->getFixedPriority() == INT_MIN) {
- result |= Qt::ItemIsDragEnabled;
- result |= Qt::ItemIsUserCheckable;
- if ((modelIndex.column() == COL_PRIORITY) ||
- (modelIndex.column() == COL_VERSION) ||
- (modelIndex.column() == COL_MODID)) {
- result |= Qt::ItemIsEditable;
- }
- if (((modelIndex.column() == COL_NAME) ||
- (modelIndex.column() == COL_NOTES))
- && (std::find(flags.begin(), flags.end(), ModInfo::FLAG_FOREIGN) == flags.end())) {
- result |= Qt::ItemIsEditable;
- }
- }
- if (m_DropOnItems
- && (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) == flags.end())) {
- result |= Qt::ItemIsDropEnabled;
- }
- } else {
- if (!m_DropOnItems) result |= Qt::ItemIsDropEnabled;
- }
- return result;
-}
-
-
-QStringList ModList::mimeTypes() const
-{
- QStringList result = QAbstractItemModel::mimeTypes();
- result.append("text/uri-list");
- return result;
-}
-
-QMimeData *ModList::mimeData(const QModelIndexList &indexes) const
-{
- QMimeData *result = QAbstractItemModel::mimeData(indexes);
- result->setData("text/plain", "mod");
- return result;
-}
-
-void ModList::changeModPriority(std::vector<int> sourceIndices, int newPriority)
-{
- if (m_Profile == nullptr) return;
-
- emit layoutAboutToBeChanged();
- Profile *profile = m_Profile;
-
- // sort the moving mods by ascending priorities
- std::sort(sourceIndices.begin(), sourceIndices.end(),
- [profile](const int &LHS, const int &RHS) {
- return profile->getModPriority(LHS) > profile->getModPriority(RHS);
- });
-
- // move mods that are decreasing in priority
- for (std::vector<int>::const_iterator iter = sourceIndices.begin();
- iter != sourceIndices.end(); ++iter) {
- int oldPriority = profile->getModPriority(*iter);
- if (oldPriority > newPriority) {
- profile->setModPriority(*iter, newPriority);
- m_ModMoved(ModInfo::getByIndex(*iter)->name(), oldPriority, newPriority);
- }
- }
-
- // sort the moving mods by descending priorities
- std::sort(sourceIndices.begin(), sourceIndices.end(),
- [profile](const int &LHS, const int &RHS) {
- return profile->getModPriority(LHS) < profile->getModPriority(RHS);
- });
-
- // if at least one mod is increasing in priority, the target index is
- // that of the row BELOW the dropped location, otherwise it's the one above
- for (std::vector<int>::const_iterator iter = sourceIndices.begin();
- iter != sourceIndices.end(); ++iter) {
- int oldPriority = profile->getModPriority(*iter);
- if (oldPriority < newPriority) {
- --newPriority;
- break;
- }
- }
-
- // move mods that are increasing in priority
- for (std::vector<int>::const_iterator iter = sourceIndices.begin();
- iter != sourceIndices.end(); ++iter) {
- int oldPriority = profile->getModPriority(*iter);
- if (oldPriority < newPriority) {
- profile->setModPriority(*iter, newPriority);
- m_ModMoved(ModInfo::getByIndex(*iter)->name(), oldPriority, newPriority);
- }
- }
-
- emit layoutChanged();
-
- emit modorder_changed();
-}
-
-
-void ModList::changeModPriority(int sourceIndex, int newPriority)
-{
- if (m_Profile == nullptr) return;
- emit layoutAboutToBeChanged();
-
- m_Profile->setModPriority(sourceIndex, newPriority);
-
- emit layoutChanged();
-
- emit modorder_changed();
-}
-
-void ModList::setOverwriteMarkers(const std::set<unsigned int> &overwrite, const std::set<unsigned int> &overwritten)
-{
- m_Overwrite = overwrite;
- m_Overwritten = overwritten;
- notifyChange(0, rowCount() - 1);
-}
-
-void ModList::setArchiveOverwriteMarkers(const std::set<unsigned int> &overwrite, const std::set<unsigned int> &overwritten)
-{
- m_ArchiveOverwrite = overwrite;
- m_ArchiveOverwritten = overwritten;
- notifyChange(0, rowCount() - 1);
-}
-
-void ModList::setArchiveLooseOverwriteMarkers(const std::set<unsigned int> &overwrite, const std::set<unsigned int> &overwritten)
-{
- m_ArchiveLooseOverwrite = overwrite;
- m_ArchiveLooseOverwritten = overwritten;
- notifyChange(0, rowCount() - 1);
-}
-
-void ModList::setPluginContainer(PluginContainer *pluginContianer)
-{
- m_PluginContainer = pluginContianer;
-}
-
-bool ModList::modInfoAboutToChange(ModInfo::Ptr info)
-{
- if (m_ChangeInfo.name.isEmpty()) {
- m_ChangeInfo.name = info->name();
- m_ChangeInfo.state = state(info->name());
- return true;
- } else {
- return false;
- }
-}
-
-void ModList::modInfoChanged(ModInfo::Ptr info)
-{
- if (info->name() == m_ChangeInfo.name) {
- IModList::ModStates newState = state(info->name());
- if (m_ChangeInfo.state != newState) {
- m_ModStateChanged(info->name(), newState);
- }
-
- int row = ModInfo::getIndex(info->name());
- info->testValid();
- emit aboutToChangeData();
- emit dataChanged(index(row, 0), index(row, columnCount()));
- emit postDataChanged();
- } else {
- qCritical("modInfoChanged not called after modInfoAboutToChange");
- }
- m_ChangeInfo.name = QString();
-}
-
-void ModList::disconnectSlots() {
- m_ModMoved.disconnect_all_slots();
- m_ModStateChanged.disconnect_all_slots();
-}
-
-int ModList::timeElapsedSinceLastChecked() const
-{
- return m_LastCheck.elapsed();
-}
-
-void ModList::highlightMods(const QItemSelectionModel *selection, const MOShared::DirectoryEntry &directoryEntry)
-{
- for (unsigned int i = 0; i < ModInfo::getNumMods(); ++i) {
- ModInfo::getByIndex(i)->setPluginSelected(false);
- }
- for (QModelIndex idx : selection->selectedRows(PluginList::COL_NAME)) {
- QString modName = idx.data().toString();
-
- const MOShared::FileEntry::Ptr fileEntry = directoryEntry.findFile(modName.toStdWString());
- if (fileEntry.get() != nullptr) {
- bool archive = false;
- std::vector<std::pair<int, std::pair<std::wstring, int>>> origins;
- {
- std::vector<std::pair<int, std::pair<std::wstring, int>>> alternatives = fileEntry->getAlternatives();
- origins.insert(origins.end(), std::pair<int, std::pair<std::wstring, int>>(fileEntry->getOrigin(archive), fileEntry->getArchive()));
- }
- for (auto originInfo : origins) {
- MOShared::FilesOrigin &origin = directoryEntry.getOriginByID(originInfo.first);
- for (unsigned int i = 0; i < ModInfo::getNumMods(); ++i) {
- if (ModInfo::getByIndex(i)->internalName() == QString::fromStdWString(origin.getName())) {
- ModInfo::getByIndex(i)->setPluginSelected(true);
- break;
- }
- }
- }
- }
- }
- notifyChange(0, rowCount() - 1);
-}
-
-IModList::ModStates ModList::state(unsigned int modIndex) const
-{
- IModList::ModStates result;
- if (modIndex != UINT_MAX) {
- result |= IModList::STATE_EXISTS;
- ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex);
- if (modInfo->isEmpty()) {
- result |= IModList::STATE_EMPTY;
- }
- if (modInfo->endorsedState() == ModInfo::ENDORSED_TRUE) {
- result |= IModList::STATE_ENDORSED;
- }
- if (modInfo->isValid()) {
- result |= IModList::STATE_VALID;
- }
- if (modInfo->isRegular()) {
- QSharedPointer<ModInfoRegular> modInfoRegular = modInfo.staticCast<ModInfoRegular>();
- if (modInfoRegular->isAlternate() && !modInfoRegular->isConverted())
- result |= IModList::STATE_ALTERNATE;
- if (!modInfo->isValid() && modInfoRegular->isValidated())
- result |= IModList::STATE_VALID;
- }
- if (modInfo->canBeEnabled()) {
- if (m_Profile->modEnabled(modIndex)) {
- result |= IModList::STATE_ACTIVE;
- }
- } else {
- result |= IModList::STATE_ESSENTIAL;
- }
- }
- return result;
-}
-
-QString ModList::displayName(const QString &internalName) const
-{
- unsigned int modIndex = ModInfo::getIndex(internalName);
- if (modIndex == UINT_MAX) {
- // might be better to throw an exception?
- return internalName;
- } else {
- return ModInfo::getByIndex(modIndex)->name();
- }
-}
-
-QStringList ModList::allMods() const
-{
- QStringList result;
- for (unsigned int i = 0; i < ModInfo::getNumMods(); ++i) {
- result.append(ModInfo::getByIndex(i)->internalName());
- }
- return result;
-}
-
-IModList::ModStates ModList::state(const QString &name) const
-{
- unsigned int modIndex = ModInfo::getIndex(name);
-
- return state(modIndex);
-}
-
-bool ModList::setActive(const QString &name, bool active)
-{
- unsigned int modIndex = ModInfo::getIndex(name);
- if (modIndex == UINT_MAX) {
- return false;
- } else {
- m_Profile->setModEnabled(modIndex, active);
-
- IModList::ModStates newState = state(modIndex);
- m_ModStateChanged(name, newState);
- return true;
- }
-}
-
-int ModList::priority(const QString &name) const
-{
- unsigned int modIndex = ModInfo::getIndex(name);
- if (modIndex == UINT_MAX) {
- return -1;
- } else {
- return m_Profile->getModPriority(modIndex);
- }
-}
-
-bool ModList::setPriority(const QString &name, int newPriority)
-{
- if ((newPriority < 0) || (newPriority >= static_cast<int>(m_Profile->numRegularMods()))) {
- return false;
- }
-
- unsigned int modIndex = ModInfo::getIndex(name);
- if (modIndex == UINT_MAX) {
- return false;
- } else {
- m_Profile->setModPriority(modIndex, newPriority);
- notifyChange(modIndex);
- return true;
- }
-}
-
-bool ModList::onModStateChanged(const std::function<void (const QString &, IModList::ModStates)> &func)
-{
- auto conn = m_ModStateChanged.connect(func);
- return conn.connected();
-}
-
-bool ModList::onModMoved(const std::function<void (const QString &, int, int)> &func)
-{
- auto conn = m_ModMoved.connect(func);
- return conn.connected();
-}
-
-bool ModList::dropURLs(const QMimeData *mimeData, int row, const QModelIndex &parent)
-{
- QStringList source;
- QStringList target;
-
- if (row == -1) {
- row = parent.row();
- }
- ModInfo::Ptr modInfo = ModInfo::getByIndex(row);
- QDir modDirectory(modInfo->absolutePath());
- QDir gameDirectory(Settings::instance().getOverwriteDirectory());
-
- unsigned int overwriteIndex = ModInfo::findMod([] (ModInfo::Ptr mod) -> bool {
- std::vector<ModInfo::EFlag> flags = mod->getFlags();
- return std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end(); });
-
- QString overwriteName = ModInfo::getByIndex(overwriteIndex)->name();
-
- for (const QUrl &url : mimeData->urls()) {
- //qDebug("URL drop requested: %s", qUtf8Printable(url.toLocalFile()));
- if (!url.isLocalFile()) {
- qDebug("URL drop ignored: Not a local file.");
- continue;
- }
- QString relativePath = gameDirectory.relativeFilePath(url.toLocalFile());
- if (relativePath.startsWith("..")) {
- qDebug("URL drop ignored: relative path starts with ..");
- continue;
- }
- source.append(url.toLocalFile());
- target.append(modDirectory.absoluteFilePath(relativePath));
- emit fileMoved(relativePath, overwriteName, modInfo->name());
- }
-
- if (source.count() != 0) {
- if (!shellMove(source, target)) {
- qDebug("Move failed %lu",::GetLastError());
- return false;
- }
- }
-
- if (!modInfo->isValid()) {
- modInfo->testValid();
- }
-
- return true;
-}
-
-bool ModList::dropMod(const QMimeData *mimeData, int row, const QModelIndex &parent)
-{
-
- try {
- QByteArray encoded = mimeData->data("application/x-qabstractitemmodeldatalist");
- QDataStream stream(&encoded, QIODevice::ReadOnly);
- std::vector<int> sourceRows;
-
- while (!stream.atEnd()) {
- int sourceRow, col;
- QMap<int, QVariant> roleDataMap;
- stream >> sourceRow >> col >> roleDataMap;
- if (col == 0) {
- sourceRows.push_back(sourceRow);
- }
- }
-
- if (row == -1) {
- row = parent.row();
- }
-
- if ((row < 0) || (static_cast<unsigned int>(row) >= ModInfo::getNumMods())) {
- return false;
- }
-
- int newPriority = 0;
- {
- if ((row < 0) || (row > static_cast<int>(m_Profile->numRegularMods()))) {
- newPriority = m_Profile->numRegularMods() + 1;
- } else {
- newPriority = m_Profile->getModPriority(row);
- }
- if (newPriority == -1) {
- newPriority = m_Profile->numRegularMods() + 1;
- }
- }
- changeModPriority(sourceRows, newPriority);
- } catch (const std::exception &e) {
- reportError(tr("drag&drop failed: %1").arg(e.what()));
- }
-
- return false;
-}
-
-
-bool ModList::dropMimeData(const QMimeData *mimeData, Qt::DropAction action, int row, int, const QModelIndex &parent)
-{
- if (action == Qt::IgnoreAction) {
- return true;
- }
-
- if (m_Profile == nullptr) return false;
-
- if (mimeData->hasUrls()) {
- return dropURLs(mimeData, row, parent);
- } else if (mimeData->hasText()) {
- return dropMod(mimeData, row, parent);
- } else {
- return false;
- }
-}
-
-void ModList::removeRowForce(int row, const QModelIndex &parent)
-{
- if (static_cast<unsigned int>(row) >= ModInfo::getNumMods()) {
- return;
- }
- if (m_Profile == nullptr) return;
-
- ModInfo::Ptr modInfo = ModInfo::getByIndex(row);
-
- bool wasEnabled = m_Profile->modEnabled(row);
-
- m_Profile->setModEnabled(row, false);
-
- m_Profile->cancelModlistWrite();
- beginRemoveRows(parent, row, row);
- ModInfo::removeMod(row);
- endRemoveRows();
- m_Profile->refreshModStatus(); // removes the mod from the status list
- m_Profile->writeModlist(); // this ensures the modified list gets written back before new mods can be installed
-
- if (wasEnabled) {
- emit removeOrigin(modInfo->name());
- }
-
- emit modUninstalled(modInfo->getInstallationFile());
-}
-
-bool ModList::removeRows(int row, int count, const QModelIndex &parent)
-{
- if (static_cast<unsigned int>(row) >= ModInfo::getNumMods()) {
- return false;
- }
- if (m_Profile == nullptr) {
- return false;
- }
-
- bool success = false;
-
- if (count == 1) {
- ModInfo::Ptr modInfo = ModInfo::getByIndex(row);
- std::vector<ModInfo::EFlag> flags = modInfo->getFlags();
- if ((std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end()) && (QDir(modInfo->absolutePath()).count() > 2)) {
- emit clearOverwrite();
- success = true;
- }
- }
-
- for (int i = 0; i < count; ++i) {
- ModInfo::Ptr modInfo = ModInfo::getByIndex(row + i);
- if (!modInfo->isRegular()) {
- continue;
- }
-
- success = true;
-
- QMessageBox confirmBox(QMessageBox::Question, tr("Confirm"),
- tr("Are you sure you want to remove \"%1\"?").arg(modInfo->name()),
- QMessageBox::Yes | QMessageBox::No);
-
- if (confirmBox.exec() == QMessageBox::Yes) {
- m_Profile->setModEnabled(row + i, false);
- removeRowForce(row + i, parent);
- }
- }
-
- return success;
-}
-
-
-void ModList::notifyChange(int rowStart, int rowEnd)
-{
- if (rowStart < 0) {
- m_Overwrite.clear();
- m_Overwritten.clear();
- m_ArchiveOverwrite.clear();
- m_ArchiveOverwritten.clear();
- m_ArchiveLooseOverwrite.clear();
- m_ArchiveLooseOverwritten.clear();
- beginResetModel();
- endResetModel();
- } else {
- if (rowEnd == -1) {
- rowEnd = rowStart;
- }
- emit dataChanged(this->index(rowStart, 0), this->index(rowEnd, this->columnCount() - 1));
- }
-}
-
-
-QModelIndex ModList::index(int row, int column, const QModelIndex&) const
-{
- if ((row < 0) || (row >= rowCount()) || (column < 0) || (column >= columnCount())) {
- return QModelIndex();
- }
- QModelIndex res = createIndex(row, column, row);
- return res;
-}
-
-
-QModelIndex ModList::parent(const QModelIndex&) const
-{
- return QModelIndex();
-}
-
-QMap<int, QVariant> ModList::itemData(const QModelIndex &index) const
-{
- QMap<int, QVariant> result = QAbstractItemModel::itemData(index);
- result[Qt::UserRole] = data(index, Qt::UserRole);
- return result;
-}
-
-
-void ModList::dropModeUpdate(bool dropOnItems)
-{
- if (m_DropOnItems != dropOnItems) {
- m_DropOnItems = dropOnItems;
- }
-}
-
-
-QString ModList::getColumnName(int column)
-{
- switch (column) {
- case COL_FLAGS: return tr("Flags");
- case COL_CONTENT: return tr("Content");
- case COL_NAME: return tr("Mod Name");
- case COL_VERSION: return tr("Version");
- case COL_PRIORITY: return tr("Priority");
- case COL_CATEGORY: return tr("Category");
- case COL_GAME: return tr("Source Game");
- case COL_MODID: return tr("Nexus ID");
- case COL_INSTALLTIME: return tr("Installation");
- case COL_NOTES: return tr("Notes");
- default: return tr("unknown");
- }
-}
-
-
-QString ModList::getColumnToolTip(int column)
-{
- switch (column) {
- case COL_NAME: return tr("Name of your mods");
- case COL_VERSION: return tr("Version of the mod (if available)");
- case COL_PRIORITY: return tr("Installation priority of your mod. The higher, the more \"important\" it is and thus "
- "overwrites files from mods with lower priority.");
- case COL_CATEGORY: return tr("Category of the mod.");
- case COL_GAME: return tr("The source game which was the origin of this mod.");
- case COL_MODID: return tr("Id of the mod as used on Nexus.");
- case COL_FLAGS: return tr("Emblemes to highlight things that might require attention.");
- case COL_CONTENT: return tr("Depicts the content of the mod:<br>"
- "<table cellspacing=7>"
- "<tr><td><img src=\":/MO/gui/content/plugin\" width=32/></td><td>Game plugins (esp/esm/esl)</td></tr>"
- "<tr><td><img src=\":/MO/gui/content/interface\" width=32/></td><td>Interface</td></tr>"
- "<tr><td><img src=\":/MO/gui/content/mesh\" width=32/></td><td>Meshes</td></tr>"
- "<tr><td><img src=\":/MO/gui/content/bsa\" width=32/></td><td>BSA</td></tr>"
- "<tr><td><img src=\":/MO/gui/content/texture\" width=32/></td><td>Textures</td></tr>"
- "<tr><td><img src=\":/MO/gui/content/sound\" width=32/></td><td>Sounds</td></tr>"
- "<tr><td><img src=\":/MO/gui/content/music\" width=32/></td><td>Music</td></tr>"
- "<tr><td><img src=\":/MO/gui/content/string\" width=32/></td><td>Strings</td></tr>"
- "<tr><td><img src=\":/MO/gui/content/script\" width=32/></td><td>Scripts (Papyrus)</td></tr>"
- "<tr><td><img src=\":/MO/gui/content/skse\" width=32/></td><td>Script Extender plugins</td></tr>"
- "<tr><td><img src=\":/MO/gui/content/skyproc\" width=32/></td><td>SkyProc Patcher</td></tr>"
- "<tr><td><img src=\":/MO/gui/content/menu\" width=32/></td><td>Mod Configuration Menu</td></tr>"
- "<tr><td><img src=\":/MO/gui/content/inifile\" width=32/></td><td>INI files</td></tr>"
- "<tr><td><img src=\":/MO/gui/content/modgroup\" width=32/></td><td>ModGroup files</td></tr>"
-
- "</table>");
- case COL_INSTALLTIME: return tr("Time this mod was installed");
- case COL_NOTES: return tr("User notes about the mod");
- default: return tr("unknown");
- }
-}
-
-
-bool ModList::moveSelection(QAbstractItemView *itemView, int direction)
-{
- QItemSelectionModel *selectionModel = itemView->selectionModel();
-
- const QAbstractProxyModel *proxyModel = qobject_cast<const QAbstractProxyModel*>(selectionModel->model());
- const QSortFilterProxyModel *filterModel = nullptr;
-
- while ((filterModel == nullptr) && (proxyModel != nullptr)) {
- filterModel = qobject_cast<const QSortFilterProxyModel*>(proxyModel);
- if (filterModel == nullptr) {
- proxyModel = qobject_cast<const QAbstractProxyModel*>(proxyModel->sourceModel());
- }
- }
- if (filterModel == nullptr) {
- return true;
- }
-
- int offset = -1;
- if (((direction < 0) && (filterModel->sortOrder() == Qt::DescendingOrder)) ||
- ((direction > 0) && (filterModel->sortOrder() == Qt::AscendingOrder))) {
- offset = 1;
- }
-
- QModelIndexList rows = selectionModel->selectedRows();
- if (direction > 0) {
- for (int i = 0; i < rows.size() / 2; ++i) {
- rows.swap(i, rows.size() - i - 1);
- }
- }
- for (QModelIndex idx : rows) {
- if (filterModel != nullptr) {
- idx = filterModel->mapToSource(idx);
- }
- int newPriority = m_Profile->getModPriority(idx.row()) + offset;
- if ((newPriority >= 0) && (newPriority < static_cast<int>(m_Profile->numRegularMods()))) {
- m_Profile->setModPriority(idx.row(), newPriority);
- notifyChange(idx.row());
- }
- }
- emit modorder_changed();
- return true;
-}
-
-bool ModList::deleteSelection(QAbstractItemView *itemView)
-{
- QItemSelectionModel *selectionModel = itemView->selectionModel();
-
- QModelIndexList rows = selectionModel->selectedRows();
- if (rows.count() > 1) {
- emit removeSelectedMods();
- } else if (rows.count() == 1) {
- removeRow(rows[0].data(Qt::UserRole + 1).toInt(), QModelIndex());
- }
- return true;
-}
-
-bool ModList::toggleSelection(QAbstractItemView *itemView)
-{
- emit aboutToChangeData();
-
- QItemSelectionModel *selectionModel = itemView->selectionModel();
-
- QList<unsigned int> modsToEnable;
- QList<unsigned int> modsToDisable;
- QModelIndexList dirtyMods;
- for (QModelIndex idx : selectionModel->selectedRows()) {
- int modId = idx.data(Qt::UserRole + 1).toInt();
- if (m_Profile->modEnabled(modId)) {
- modsToDisable.append(modId);
- dirtyMods.append(idx);
- } else {
- modsToEnable.append(modId);
- dirtyMods.append(idx);
- }
- }
-
- m_Profile->setModsEnabled(modsToEnable, modsToDisable);
-
- emit modlistChanged(dirtyMods, 0);
-
- m_Modified = true;
- m_LastCheck.restart();
-
- emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1));
-
- emit postDataChanged();
-
- return true;
-}
-
-bool ModList::eventFilter(QObject *obj, QEvent *event)
-{
- if (event->type() == QEvent::ContextMenu) {
- QContextMenuEvent *contextEvent = static_cast<QContextMenuEvent*>(event);
- QWidget *object = qobject_cast<QWidget*>(obj);
- if ((object != nullptr) && (contextEvent->reason() == QContextMenuEvent::Mouse)) {
- emit requestColumnSelect(object->mapToGlobal(contextEvent->pos()));
-
- return true;
- }
- } else if ((event->type() == QEvent::KeyPress) && (m_Profile != nullptr)) {
- QAbstractItemView *itemView = qobject_cast<QAbstractItemView*>(obj);
- QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
-
- if ((itemView != nullptr)
- && (keyEvent->modifiers() == Qt::ControlModifier)
- && ((keyEvent->key() == Qt::Key_Up) || (keyEvent->key() == Qt::Key_Down))) {
- return moveSelection(itemView, keyEvent->key() == Qt::Key_Up ? -1 : 1);
- } else if (keyEvent->key() == Qt::Key_Delete) {
- return deleteSelection(itemView);
- } else if (keyEvent->key() == Qt::Key_Space) {
- return toggleSelection(itemView);
- }
- return QAbstractItemModel::eventFilter(obj, event);
- }
- return QAbstractItemModel::eventFilter(obj, event);
-}
-
-//note: caller needs to make sure sort proxy is updated
-void ModList::enableSelected(const QItemSelectionModel *selectionModel)
-{
- if (selectionModel->hasSelection()) {
- QList<unsigned int> modsToEnable;
- for (auto row : selectionModel->selectedRows(COL_PRIORITY)) {
- int modID = m_Profile->modIndexByPriority(row.data().toInt());
- modsToEnable.append(modID);
- }
- m_Profile->setModsEnabled(modsToEnable, QList<unsigned int>());
- }
-}
-
-//note: caller needs to make sure sort proxy is updated
-void ModList::disableSelected(const QItemSelectionModel *selectionModel)
-{
- if (selectionModel->hasSelection()) {
- QList<unsigned int> modsToDisable;
- for (auto row : selectionModel->selectedRows(COL_PRIORITY)) {
- int modID = m_Profile->modIndexByPriority(row.data().toInt());
- modsToDisable.append(modID);
- }
- m_Profile->setModsEnabled(QList<unsigned int>(), modsToDisable);
- }
-}
+/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+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 "modlist.h"
+
+#include "messagedialog.h"
+#include "installationtester.h"
+#include "qtgroupingproxy.h"
+#include "viewmarkingscrollbar.h"
+#include "modlistsortproxy.h"
+#include "pluginlist.h"
+#include "settings.h"
+#include "modinforegular.h"
+#include <appconfig.h>
+#include <utility.h>
+#include <report.h>
+
+#include <QFileInfo>
+#include <QDir>
+#include <QDirIterator>
+#include <QMimeData>
+#include <QStandardItemModel>
+#include <QMessageBox>
+#include <QStringList>
+#include <QEvent>
+#include <QContextMenuEvent>
+#include <QMenu>
+#include <QCheckBox>
+#include <QWidgetAction>
+#include <QAbstractItemView>
+#include <QSortFilterProxyModel>
+#include <QApplication>
+#include <QFontDatabase>
+
+#include <sstream>
+#include <stdexcept>
+#include <algorithm>
+
+
+using namespace MOBase;
+
+
+ModList::ModList(PluginContainer *pluginContainer, QObject *parent)
+ : QAbstractItemModel(parent)
+ , m_Profile(nullptr)
+ , m_NexusInterface(nullptr)
+ , m_Modified(false)
+ , m_FontMetrics(QFont())
+ , m_DropOnItems(false)
+ , m_PluginContainer(pluginContainer)
+{
+ m_ContentIcons[ModInfo::CONTENT_PLUGIN] = std::make_tuple(":/MO/gui/content/plugin", tr("Game Plugins (ESP/ESM/ESL)"));
+ m_ContentIcons[ModInfo::CONTENT_INTERFACE] = std::make_tuple(":/MO/gui/content/interface", tr("Interface"));
+ m_ContentIcons[ModInfo::CONTENT_MESH] = std::make_tuple(":/MO/gui/content/mesh", tr("Meshes"));
+ m_ContentIcons[ModInfo::CONTENT_BSA] = std::make_tuple(":/MO/gui/content/bsa", tr("Bethesda Archive"));
+ m_ContentIcons[ModInfo::CONTENT_SCRIPT] = std::make_tuple(":/MO/gui/content/script", tr("Scripts (Papyrus)"));
+ m_ContentIcons[ModInfo::CONTENT_SKSE] = std::make_tuple(":/MO/gui/content/skse", tr("Script Extender Plugin"));
+ m_ContentIcons[ModInfo::CONTENT_SKYPROC] = std::make_tuple(":/MO/gui/content/skyproc", tr("SkyProc Patcher"));
+ m_ContentIcons[ModInfo::CONTENT_SOUND] = std::make_tuple(":/MO/gui/content/sound", tr("Sound or Music"));
+ m_ContentIcons[ModInfo::CONTENT_TEXTURE] = std::make_tuple(":/MO/gui/content/texture", tr("Textures"));
+ m_ContentIcons[ModInfo::CONTENT_MCM] = std::make_tuple(":/MO/gui/content/menu", tr("MCM Configuration"));
+ m_ContentIcons[ModInfo::CONTENT_INI] = std::make_tuple(":/MO/gui/content/inifile", tr("INI files"));
+ m_ContentIcons[ModInfo::CONTENT_MODGROUP] = std::make_tuple(":/MO/gui/content/modgroup", tr("ModGroup files"));
+
+ m_LastCheck.start();
+}
+
+ModList::~ModList()
+{
+ m_ModStateChanged.disconnect_all_slots();
+ m_ModMoved.disconnect_all_slots();
+}
+
+void ModList::setProfile(Profile *profile)
+{
+ m_Profile = profile;
+}
+
+int ModList::rowCount(const QModelIndex &parent) const
+{
+ if (!parent.isValid()) {
+ return ModInfo::getNumMods();
+ } else {
+ return 0;
+ }
+}
+
+bool ModList::hasChildren(const QModelIndex &parent) const
+{
+ if (!parent.isValid()) {
+ return ModInfo::getNumMods() > 0;
+ } else {
+ return false;
+ }
+}
+
+
+int ModList::columnCount(const QModelIndex &) const
+{
+ return COL_LASTCOLUMN + 1;
+}
+
+
+QVariant ModList::getOverwriteData(int column, int role) const
+{
+ switch (role) {
+ case Qt::DisplayRole: {
+ if (column == 0) {
+ return "Overwrite";
+ } else {
+ return QVariant();
+ }
+ } break;
+ case Qt::CheckStateRole: {
+ if (column == 0) {
+ return Qt::Checked;
+ } else {
+ return QVariant();
+ }
+ } break;
+ case Qt::TextAlignmentRole: return QVariant(Qt::AlignCenter | Qt::AlignVCenter);
+ case Qt::UserRole: return -1;
+ case Qt::ForegroundRole: return QBrush(Qt::red);
+ case Qt::ToolTipRole: return tr("This entry contains files that have been created inside the virtual data tree (i.e. by the construction kit)");
+ default: return QVariant();
+ }
+}
+
+
+QString ModList::getFlagText(ModInfo::EFlag flag, ModInfo::Ptr modInfo) const
+{
+ switch (flag) {
+ case ModInfo::FLAG_BACKUP: return tr("Backup");
+ case ModInfo::FLAG_SEPARATOR: return tr("Separator");
+ case ModInfo::FLAG_INVALID: return tr("No valid game data");
+ case ModInfo::FLAG_NOTENDORSED: return tr("Not endorsed yet");
+ case ModInfo::FLAG_NOTES: {
+ QStringList output;
+ if (!modInfo->comments().isEmpty())
+ output << QString("<i>%1</i>").arg(modInfo->comments());
+ if (!modInfo->notes().isEmpty())
+ output << QString("<i>%1</i>").arg(modInfo->notes());
+ return output.join("");
+ }
+ case ModInfo::FLAG_CONFLICT_OVERWRITE: return tr("Overwrites loose files");
+ case ModInfo::FLAG_CONFLICT_OVERWRITTEN: return tr("Overwritten loose files");
+ case ModInfo::FLAG_CONFLICT_MIXED: return tr("Loose files Overwrites & Overwritten");
+ case ModInfo::FLAG_CONFLICT_REDUNDANT: return tr("Redundant");
+ case ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITE: return tr("Overwrites an archive with loose files");
+ case ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITTEN: return tr("Archive is overwritten by loose files");
+ case ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITE: return tr("Overwrites another archive file");
+ case ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITTEN: return tr("Overwritten by another archive file");
+ case ModInfo::FLAG_ARCHIVE_CONFLICT_MIXED: return tr("Archive files overwrites & overwritten");
+ case ModInfo::FLAG_ALTERNATE_GAME: return tr("<br>This mod is for a different game, "
+ "make sure it's compatible or it could cause crashes.");
+ default: return "";
+ }
+}
+
+
+QVariantList ModList::contentsToIcons(const std::vector<ModInfo::EContent> &contents) const
+{
+ QVariantList result;
+ std::set<ModInfo::EContent> contentsSet(contents.begin(), contents.end());
+ for (auto iter = m_ContentIcons.begin(); iter != m_ContentIcons.end(); ++iter) {
+ if (contentsSet.find(iter->first) != contentsSet.end()) {
+ result.append(std::get<0>(iter->second));
+ } else {
+ result.append(QString());
+ }
+ }
+ return result;
+}
+
+QString ModList::contentsToToolTip(const std::vector<ModInfo::EContent> &contents) const
+{
+ QString result("<table cellspacing=7>");
+
+ std::set<ModInfo::EContent> contentsSet(contents.begin(), contents.end());
+ for (auto iter = m_ContentIcons.begin(); iter != m_ContentIcons.end(); ++iter) {
+ if (contentsSet.find(iter->first) != contentsSet.end()) {
+ result.append(QString("<tr><td><img src=\"%1\" width=32/></td>"
+ "<td valign=\"middle\">%2</td></tr>")
+ .arg(std::get<0>(iter->second)).arg(std::get<1>(iter->second)));
+ }
+ }
+ result.append("</table>");
+ return result;
+}
+
+
+QVariant ModList::data(const QModelIndex &modelIndex, int role) const
+{
+ if (m_Profile == nullptr) return QVariant();
+ if (!modelIndex.isValid()) return QVariant();
+ unsigned int modIndex = modelIndex.row();
+ int column = modelIndex.column();
+
+ ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex);
+ if ((role == Qt::DisplayRole) ||
+ (role == Qt::EditRole)) {
+ if ((column == COL_FLAGS)
+ || (column == COL_CONTENT)) {
+ return QVariant();
+ } else if (column == COL_NAME) {
+ auto flags = modInfo->getFlags();
+ if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_SEPARATOR) != flags.end())
+ {
+ return modInfo->name().replace("_separator", "");
+ }
+ else
+ return modInfo->name();
+ } else if (column == COL_VERSION) {
+ VersionInfo verInfo = modInfo->getVersion();
+ QString version = verInfo.displayString();
+ if (role != Qt::EditRole) {
+ if (version.isEmpty() && modInfo->canBeUpdated()) {
+ version = "?";
+ }
+ }
+ return version;
+ } else if (column == COL_PRIORITY) {
+ int priority = modInfo->getFixedPriority();
+ if (priority != INT_MIN) {
+ return QVariant(); // hide priority for mods where it's fixed
+ } else {
+ return m_Profile->getModPriority(modIndex);
+ }
+ } else if (column == COL_MODID) {
+ int modID = modInfo->getNexusID();
+ if (modID >= 0) {
+ return modID;
+ }
+ else {
+ return QVariant();
+ }
+ } else if (column == COL_GAME) {
+ if (m_PluginContainer != nullptr) {
+ for (auto game : m_PluginContainer->plugins<IPluginGame>()) {
+ if (game->gameShortName().compare(modInfo->getGameName(), Qt::CaseInsensitive) == 0)
+ return game->gameName();
+ }
+ }
+ return modInfo->getGameName();
+ } else if (column == COL_CATEGORY) {
+ if (modInfo->hasFlag(ModInfo::FLAG_FOREIGN)) {
+ return tr("Non-MO");
+ } else {
+ int category = modInfo->getPrimaryCategory();
+ if (category != -1) {
+ CategoryFactory &categoryFactory = CategoryFactory::instance();
+ if (categoryFactory.categoryExists(category)) {
+ try {
+ int categoryIdx = categoryFactory.getCategoryIndex(category);
+ return categoryFactory.getCategoryName(categoryIdx);
+ } catch (const std::exception &e) {
+ qCritical("failed to retrieve category name: %s", e.what());
+ return QString();
+ }
+ } else {
+ qWarning("category %d doesn't exist (may have been removed)", category);
+ modInfo->setCategory(category, false);
+ return QString();
+ }
+ } else {
+ return QVariant();
+ }
+ }
+ } else if (column == COL_INSTALLTIME) {
+ // display installation time for mods that can be updated
+ if (modInfo->creationTime().isValid()) {
+ return modInfo->creationTime();
+ } else {
+ return QVariant();
+ }
+ } else if (column == COL_NOTES) {
+ return modInfo->comments();
+ } else {
+ return tr("invalid");
+ }
+ } else if ((role == Qt::CheckStateRole) && (column == 0)) {
+ if (modInfo->canBeEnabled()) {
+ return m_Profile->modEnabled(modIndex) ? Qt::Checked : Qt::Unchecked;
+ } else {
+ return QVariant();
+ }
+ } else if (role == Qt::TextAlignmentRole) {
+ auto flags = modInfo->getFlags();
+ if (column == COL_NAME) {
+ if (modInfo->getHighlight() & ModInfo::HIGHLIGHT_CENTER) {
+ return QVariant(Qt::AlignCenter | Qt::AlignVCenter);
+ } else {
+ return QVariant(Qt::AlignLeft | Qt::AlignVCenter);
+ }
+ } else if (column == COL_VERSION) {
+ return QVariant(Qt::AlignRight | Qt::AlignVCenter);
+ } else if (column == COL_NOTES) {
+ return QVariant(Qt::AlignLeft | Qt::AlignVCenter);
+ } else {
+ return QVariant(Qt::AlignCenter | Qt::AlignVCenter);
+ }
+ } else if (role == Qt::UserRole) {
+ if (column == COL_CATEGORY) {
+ QVariantList categoryNames;
+ std::set<int> categories = modInfo->getCategories();
+ CategoryFactory &categoryFactory = CategoryFactory::instance();
+ for (auto iter = categories.begin(); iter != categories.end(); ++iter) {
+ categoryNames.append(categoryFactory.getCategoryName(categoryFactory.getCategoryIndex(*iter)));
+ }
+ if (categoryNames.count() != 0) {
+ return categoryNames;
+ } else {
+ return QVariant();
+ }
+ } else if (column == COL_PRIORITY) {
+ int priority = modInfo->getFixedPriority();
+ if (priority != INT_MIN) {
+ return priority;
+ } else {
+ return m_Profile->getModPriority(modIndex);
+ }
+ } else {
+ return modInfo->getNexusID();
+ }
+ } else if (role == Qt::UserRole + 1) {
+ return modIndex;
+ } else if (role == Qt::UserRole + 2) {
+ switch (column) {
+ case COL_MODID: return QtGroupingProxy::AGGR_FIRST;
+ case COL_VERSION: return QtGroupingProxy::AGGR_MAX;
+ case COL_CATEGORY: return QtGroupingProxy::AGGR_FIRST;
+ case COL_PRIORITY: return QtGroupingProxy::AGGR_MIN;
+ default: return QtGroupingProxy::AGGR_NONE;
+ }
+ } else if (role == Qt::UserRole + 3) {
+ return contentsToIcons(modInfo->getContents());
+ } else if (role == Qt::UserRole + 4) {
+ return modInfo->getGameName();
+ } else if (role == Qt::FontRole) {
+ QFont result;
+ auto flags = modInfo->getFlags();
+ if (column == COL_NAME) {
+ if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_SEPARATOR) != flags.end())
+ {
+ //result.setCapitalization(QFont::AllUppercase);
+ result.setItalic(true);
+ //result.setUnderline(true);
+ result.setBold(true);
+ } else if (modInfo->getHighlight() & ModInfo::HIGHLIGHT_INVALID) {
+ result.setItalic(true);
+ }
+ } else if ((column == COL_CATEGORY) && (modInfo->hasFlag(ModInfo::FLAG_FOREIGN))) {
+ result.setItalic(true);
+ } else if (column == COL_VERSION) {
+ if (modInfo->updateAvailable() || modInfo->downgradeAvailable()) {
+ result.setWeight(QFont::Bold);
+ }
+ if (modInfo->canBeUpdated()) {
+ result.setItalic(true);
+ }
+ }
+ return result;
+ } else if (role == Qt::DecorationRole) {
+ if (column == COL_VERSION) {
+ if (modInfo->updateAvailable()) {
+ return QIcon(":/MO/gui/update_available");
+ } else if (modInfo->downgradeAvailable()) {
+ return QIcon(":/MO/gui/warning");
+ } else if (modInfo->getVersion().scheme() == VersionInfo::SCHEME_DATE) {
+ return QIcon(":/MO/gui/version_date");
+ }
+ }
+ return QVariant();
+ } else if (role == Qt::ForegroundRole) {
+ if (modInfo->hasFlag(ModInfo::FLAG_SEPARATOR) && modInfo->getColor().isValid()) {
+ return Settings::getIdealTextColor(modInfo->getColor());
+ } else if (column == COL_NAME) {
+ int highlight = modInfo->getHighlight();
+ if (highlight & ModInfo::HIGHLIGHT_IMPORTANT)
+ return QBrush(Qt::darkRed);
+ else if (highlight & ModInfo::HIGHLIGHT_INVALID)
+ return QBrush(Qt::darkGray);
+ } else if (column == COL_VERSION) {
+ if (!modInfo->getNewestVersion().isValid()) {
+ return QVariant();
+ } else if (modInfo->updateAvailable() || modInfo->downgradeAvailable()) {
+ return QBrush(Qt::red);
+ } else {
+ return QBrush(Qt::darkGreen);
+ }
+ }
+ return QVariant();
+ } else if ((role == Qt::BackgroundRole)
+ || (role == ViewMarkingScrollBar::DEFAULT_ROLE)) {
+ bool overwrite = m_Overwrite.find(modIndex) != m_Overwrite.end();
+ bool archiveOverwrite = m_ArchiveOverwrite.find(modIndex) != m_ArchiveOverwrite.end();
+ bool archiveLooseOverwrite = m_ArchiveLooseOverwrite.find(modIndex) != m_ArchiveLooseOverwrite.end();
+ bool overwritten = m_Overwritten.find(modIndex) != m_Overwritten.end();
+ bool archiveOverwritten = m_ArchiveOverwritten.find(modIndex) != m_ArchiveOverwritten.end();
+ bool archiveLooseOverwritten = m_ArchiveLooseOverwritten.find(modIndex) != m_ArchiveLooseOverwritten.end();
+ if (modInfo->getHighlight() & ModInfo::HIGHLIGHT_PLUGIN) {
+ return Settings::instance().modlistContainsPluginColor();
+ } else if (overwritten || archiveLooseOverwritten) {
+ return Settings::instance().modlistOverwritingLooseColor();
+ } else if (overwrite || archiveLooseOverwrite) {
+ return Settings::instance().modlistOverwrittenLooseColor();
+ } else if (archiveOverwritten) {
+ return Settings::instance().modlistOverwritingArchiveColor();
+ } else if (archiveOverwrite) {
+ return Settings::instance().modlistOverwrittenArchiveColor();
+ } else if (modInfo->hasFlag(ModInfo::FLAG_SEPARATOR)
+ && modInfo->getColor().isValid()
+ && ((role != ViewMarkingScrollBar::DEFAULT_ROLE)
+ || Settings::instance().colorSeparatorScrollbar())) {
+ return modInfo->getColor();
+ } else {
+ return QVariant();
+ }
+ } else if (role == Qt::ToolTipRole) {
+ if (column == COL_FLAGS) {
+ QString result;
+
+ for (ModInfo::EFlag flag : modInfo->getFlags()) {
+ if (result.length() != 0) result += "<br>";
+ result += getFlagText(flag, modInfo);
+ }
+
+ return result;
+ } else if (column == COL_CONTENT) {
+ return contentsToToolTip(modInfo->getContents());
+ } else if (column == COL_NAME) {
+ try {
+ return modInfo->getDescription();
+ } catch (const std::exception &e) {
+ qCritical("invalid mod description: %s", e.what());
+ return QString();
+ }
+ } else if (column == COL_VERSION) {
+ QString text = tr("installed version: \"%1\", newest version: \"%2\"").arg(modInfo->getVersion().displayString(3)).arg(modInfo->getNewestVersion().displayString(3));
+ if (modInfo->downgradeAvailable()) {
+ text += "<br>" + tr("The newest version on Nexus seems to be older than the one you have installed. This could either mean the version you have has been withdrawn "
+ "(i.e. due to a bug) or the author uses a non-standard versioning scheme and that newest version is actually newer. "
+ "Either way you may want to \"upgrade\".");
+ }
+ if (modInfo->getNexusID() > 0) {
+ if (!modInfo->canBeUpdated()) {
+ text += "<br>" + tr("This mod was last checked on %1. It will be available to check after %2.")
+ .arg(modInfo->getLastNexusUpdate().toLocalTime().toString(Qt::DefaultLocaleShortDate))
+ .arg(modInfo->getLastNexusUpdate().toLocalTime().addSecs(3600).time().toString(Qt::DefaultLocaleShortDate));
+ } else {
+ text += "<br>" + tr("This mod is eligible for an update check.");
+ }
+ }
+ return text;
+ } else if (column == COL_CATEGORY) {
+ const std::set<int> &categories = modInfo->getCategories();
+ std::wostringstream categoryString;
+ categoryString << ToWString(tr("Categories: <br>"));
+ CategoryFactory &categoryFactory = CategoryFactory::instance();
+ for (std::set<int>::const_iterator catIter = categories.begin();
+ catIter != categories.end(); ++catIter) {
+ if (catIter != categories.begin()) {
+ categoryString << " , ";
+ }
+ try {
+ categoryString << "<span style=\"white-space: nowrap;\"><i>" << ToWString(categoryFactory.getCategoryName(categoryFactory.getCategoryIndex(*catIter))) << "</font></span>";
+ } catch (const std::exception &e) {
+ qCritical("failed to generate tooltip: %s", e.what());
+ return QString();
+ }
+ }
+
+ return ToQString(categoryString.str());
+ } else if (column == COL_NOTES) {
+ return getFlagText(ModInfo::FLAG_NOTES, modInfo);
+ } else {
+ return QVariant();
+ }
+ } else {
+ return QVariant();
+ }
+}
+
+
+bool ModList::renameMod(int index, const QString &newName)
+{
+ QString nameFixed = newName;
+ if (!fixDirectoryName(nameFixed) || nameFixed.isEmpty()) {
+ MessageDialog::showMessage(tr("Invalid name"), nullptr);
+ return false;
+ }
+
+ if (ModList::allMods().contains(nameFixed, Qt::CaseInsensitive) && nameFixed.toLower()!=ModInfo::getByIndex(index)->name().toLower() ) {
+ MessageDialog::showMessage(tr("Name is already in use by another mod"), nullptr);
+ return false;
+ }
+
+ ModInfo::Ptr modInfo = ModInfo::getByIndex(index);
+ QString oldName = modInfo->name();
+ if (nameFixed != oldName) {
+ // before we rename, ensure there is no scheduled asynchronous to rewrite
+ m_Profile->cancelModlistWrite();
+
+
+ if (modInfo->setName(nameFixed))
+ // Notice there is a good chance that setName() updated the modinfo indexes
+ // the modRenamed() call will refresh the indexes in the current profile
+ // and update the modlists in all profiles
+ emit modRenamed(oldName, nameFixed);
+ }
+
+ // invalidate the currently displayed state of this list
+ notifyChange(-1);
+ return true;
+}
+
+bool ModList::setData(const QModelIndex &index, const QVariant &value, int role)
+{
+ if (m_Profile == nullptr) return false;
+
+ if (static_cast<unsigned int>(index.row()) >= ModInfo::getNumMods()) {
+ return false;
+ }
+
+ int modID = index.row();
+
+ ModInfo::Ptr info = ModInfo::getByIndex(modID);
+ IModList::ModStates oldState = state(modID);
+
+ bool result = false;
+
+ emit aboutToChangeData();
+
+ if (role == Qt::CheckStateRole) {
+ bool enabled = value.toInt() == Qt::Checked;
+ if (m_Profile->modEnabled(modID) != enabled) {
+ m_Profile->setModEnabled(modID, enabled);
+ m_Modified = true;
+ m_LastCheck.restart();
+ emit modlistChanged(index, role);
+ }
+ result = true;
+ emit dataChanged(index, index);
+ } else if (role == Qt::EditRole) {
+ switch (index.column()) {
+ case COL_NAME: {
+ auto flags = info->getFlags();
+ if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_SEPARATOR) != flags.end())
+ {
+ result = renameMod(modID, value.toString() + "_separator");
+ }
+ else
+ result = renameMod(modID, value.toString());
+ } break;
+ case COL_PRIORITY: {
+ bool ok = false;
+ int newPriority = value.toInt(&ok);
+ if (ok && newPriority < 0) {
+ newPriority = 0;
+ }
+ if (ok) {
+ m_Profile->setModPriority(modID, newPriority);
+
+ emit modorder_changed();
+ result = true;
+ } else {
+ result = false;
+ }
+ } break;
+ case COL_MODID: {
+ bool ok = false;
+ int newID = value.toInt(&ok);
+ if (ok) {
+ info->setNexusID(newID);
+ emit modlistChanged(index, role);
+ result = true;
+ } else {
+ result = false;
+ }
+ } break;
+ case COL_VERSION: {
+ VersionInfo::VersionScheme scheme = info->getVersion().scheme();
+ VersionInfo version(value.toString(), scheme, true);
+ if (version.isValid()) {
+ info->setVersion(version);
+ result = true;
+ } else {
+ result = false;
+ }
+ } break;
+ case COL_NOTES: {
+ info->setComments(value.toString());
+ result = true;
+ } break;
+ default: {
+ qWarning("edit on column \"%s\" not supported",
+ getColumnName(index.column()).toUtf8().constData());
+ result = false;
+ } break;
+ }
+ if (result) {
+ emit dataChanged(index, index);
+ }
+ }
+
+ emit postDataChanged();
+
+ IModList::ModStates newState = state(modID);
+ if (oldState != newState) {
+ try {
+ m_ModStateChanged(info->name(), newState);
+ } catch (const std::exception &e) {
+ qCritical("failed to invoke state changed notification: %s", e.what());
+ } catch (...) {
+ qCritical("failed to invoke state changed notification: unknown exception");
+ }
+ }
+
+ return result;
+}
+
+
+QVariant ModList::headerData(int section, Qt::Orientation orientation,
+ int role) const
+{
+ if (orientation == Qt::Horizontal) {
+ if (role == Qt::DisplayRole) {
+ return getColumnName(section);
+ } else if (role == Qt::ToolTipRole) {
+ return getColumnToolTip(section);
+ } else if (role == Qt::TextAlignmentRole) {
+ return QVariant(Qt::AlignCenter);
+ } else if (role == Qt::SizeHintRole) {
+ QSize temp = m_FontMetrics.size(Qt::TextSingleLine, getColumnName(section));
+ temp.rwidth() += 20;
+ temp.rheight() += 12;
+ return temp;
+ }
+ }
+ return QAbstractItemModel::headerData(section, orientation, role);
+}
+
+
+Qt::ItemFlags ModList::flags(const QModelIndex &modelIndex) const
+{
+ Qt::ItemFlags result = QAbstractItemModel::flags(modelIndex);
+ if (modelIndex.internalId() < 0) {
+ return Qt::ItemIsEnabled;
+ }
+ if (modelIndex.isValid()) {
+ ModInfo::Ptr modInfo = ModInfo::getByIndex(modelIndex.row());
+ std::vector<ModInfo::EFlag> flags = modInfo->getFlags();
+ if (modInfo->getFixedPriority() == INT_MIN) {
+ result |= Qt::ItemIsDragEnabled;
+ result |= Qt::ItemIsUserCheckable;
+ if ((modelIndex.column() == COL_PRIORITY) ||
+ (modelIndex.column() == COL_VERSION) ||
+ (modelIndex.column() == COL_MODID)) {
+ result |= Qt::ItemIsEditable;
+ }
+ if (((modelIndex.column() == COL_NAME) ||
+ (modelIndex.column() == COL_NOTES))
+ && (std::find(flags.begin(), flags.end(), ModInfo::FLAG_FOREIGN) == flags.end())) {
+ result |= Qt::ItemIsEditable;
+ }
+ }
+ if (m_DropOnItems
+ && (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) == flags.end())) {
+ result |= Qt::ItemIsDropEnabled;
+ }
+ } else {
+ if (!m_DropOnItems) result |= Qt::ItemIsDropEnabled;
+ }
+ return result;
+}
+
+
+QStringList ModList::mimeTypes() const
+{
+ QStringList result = QAbstractItemModel::mimeTypes();
+ result.append("text/uri-list");
+ return result;
+}
+
+QMimeData *ModList::mimeData(const QModelIndexList &indexes) const
+{
+ QMimeData *result = QAbstractItemModel::mimeData(indexes);
+ result->setData("text/plain", "mod");
+ return result;
+}
+
+void ModList::changeModPriority(std::vector<int> sourceIndices, int newPriority)
+{
+ if (m_Profile == nullptr) return;
+
+ emit layoutAboutToBeChanged();
+ Profile *profile = m_Profile;
+
+ // sort the moving mods by ascending priorities
+ std::sort(sourceIndices.begin(), sourceIndices.end(),
+ [profile](const int &LHS, const int &RHS) {
+ return profile->getModPriority(LHS) > profile->getModPriority(RHS);
+ });
+
+ // move mods that are decreasing in priority
+ for (std::vector<int>::const_iterator iter = sourceIndices.begin();
+ iter != sourceIndices.end(); ++iter) {
+ int oldPriority = profile->getModPriority(*iter);
+ if (oldPriority > newPriority) {
+ profile->setModPriority(*iter, newPriority);
+ m_ModMoved(ModInfo::getByIndex(*iter)->name(), oldPriority, newPriority);
+ }
+ }
+
+ // sort the moving mods by descending priorities
+ std::sort(sourceIndices.begin(), sourceIndices.end(),
+ [profile](const int &LHS, const int &RHS) {
+ return profile->getModPriority(LHS) < profile->getModPriority(RHS);
+ });
+
+ // if at least one mod is increasing in priority, the target index is
+ // that of the row BELOW the dropped location, otherwise it's the one above
+ for (std::vector<int>::const_iterator iter = sourceIndices.begin();
+ iter != sourceIndices.end(); ++iter) {
+ int oldPriority = profile->getModPriority(*iter);
+ if (oldPriority < newPriority) {
+ --newPriority;
+ break;
+ }
+ }
+
+ // move mods that are increasing in priority
+ for (std::vector<int>::const_iterator iter = sourceIndices.begin();
+ iter != sourceIndices.end(); ++iter) {
+ int oldPriority = profile->getModPriority(*iter);
+ if (oldPriority < newPriority) {
+ profile->setModPriority(*iter, newPriority);
+ m_ModMoved(ModInfo::getByIndex(*iter)->name(), oldPriority, newPriority);
+ }
+ }
+
+ emit layoutChanged();
+
+ emit modorder_changed();
+}
+
+
+void ModList::changeModPriority(int sourceIndex, int newPriority)
+{
+ if (m_Profile == nullptr) return;
+ emit layoutAboutToBeChanged();
+
+ m_Profile->setModPriority(sourceIndex, newPriority);
+
+ emit layoutChanged();
+
+ emit modorder_changed();
+}
+
+void ModList::setOverwriteMarkers(const std::set<unsigned int> &overwrite, const std::set<unsigned int> &overwritten)
+{
+ m_Overwrite = overwrite;
+ m_Overwritten = overwritten;
+ notifyChange(0, rowCount() - 1);
+}
+
+void ModList::setArchiveOverwriteMarkers(const std::set<unsigned int> &overwrite, const std::set<unsigned int> &overwritten)
+{
+ m_ArchiveOverwrite = overwrite;
+ m_ArchiveOverwritten = overwritten;
+ notifyChange(0, rowCount() - 1);
+}
+
+void ModList::setArchiveLooseOverwriteMarkers(const std::set<unsigned int> &overwrite, const std::set<unsigned int> &overwritten)
+{
+ m_ArchiveLooseOverwrite = overwrite;
+ m_ArchiveLooseOverwritten = overwritten;
+ notifyChange(0, rowCount() - 1);
+}
+
+void ModList::setPluginContainer(PluginContainer *pluginContianer)
+{
+ m_PluginContainer = pluginContianer;
+}
+
+bool ModList::modInfoAboutToChange(ModInfo::Ptr info)
+{
+ if (m_ChangeInfo.name.isEmpty()) {
+ m_ChangeInfo.name = info->name();
+ m_ChangeInfo.state = state(info->name());
+ return true;
+ } else {
+ return false;
+ }
+}
+
+void ModList::modInfoChanged(ModInfo::Ptr info)
+{
+ if (info->name() == m_ChangeInfo.name) {
+ IModList::ModStates newState = state(info->name());
+ if (m_ChangeInfo.state != newState) {
+ m_ModStateChanged(info->name(), newState);
+ }
+
+ int row = ModInfo::getIndex(info->name());
+ info->testValid();
+ emit aboutToChangeData();
+ emit dataChanged(index(row, 0), index(row, columnCount()));
+ emit postDataChanged();
+ } else {
+ qCritical("modInfoChanged not called after modInfoAboutToChange");
+ }
+ m_ChangeInfo.name = QString();
+}
+
+void ModList::disconnectSlots() {
+ m_ModMoved.disconnect_all_slots();
+ m_ModStateChanged.disconnect_all_slots();
+}
+
+int ModList::timeElapsedSinceLastChecked() const
+{
+ return m_LastCheck.elapsed();
+}
+
+void ModList::highlightMods(const QItemSelectionModel *selection, const MOShared::DirectoryEntry &directoryEntry)
+{
+ for (unsigned int i = 0; i < ModInfo::getNumMods(); ++i) {
+ ModInfo::getByIndex(i)->setPluginSelected(false);
+ }
+ for (QModelIndex idx : selection->selectedRows(PluginList::COL_NAME)) {
+ QString modName = idx.data().toString();
+
+ const MOShared::FileEntry::Ptr fileEntry = directoryEntry.findFile(modName.toStdWString());
+ if (fileEntry.get() != nullptr) {
+ bool archive = false;
+ std::vector<std::pair<int, std::pair<std::wstring, int>>> origins;
+ {
+ std::vector<std::pair<int, std::pair<std::wstring, int>>> alternatives = fileEntry->getAlternatives();
+ origins.insert(origins.end(), std::pair<int, std::pair<std::wstring, int>>(fileEntry->getOrigin(archive), fileEntry->getArchive()));
+ }
+ for (auto originInfo : origins) {
+ MOShared::FilesOrigin &origin = directoryEntry.getOriginByID(originInfo.first);
+ for (unsigned int i = 0; i < ModInfo::getNumMods(); ++i) {
+ if (ModInfo::getByIndex(i)->internalName() == QString::fromStdWString(origin.getName())) {
+ ModInfo::getByIndex(i)->setPluginSelected(true);
+ break;
+ }
+ }
+ }
+ }
+ }
+ notifyChange(0, rowCount() - 1);
+}
+
+IModList::ModStates ModList::state(unsigned int modIndex) const
+{
+ IModList::ModStates result;
+ if (modIndex != UINT_MAX) {
+ result |= IModList::STATE_EXISTS;
+ ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex);
+ if (modInfo->isEmpty()) {
+ result |= IModList::STATE_EMPTY;
+ }
+ if (modInfo->endorsedState() == ModInfo::ENDORSED_TRUE) {
+ result |= IModList::STATE_ENDORSED;
+ }
+ if (modInfo->isValid()) {
+ result |= IModList::STATE_VALID;
+ }
+ if (modInfo->isRegular()) {
+ QSharedPointer<ModInfoRegular> modInfoRegular = modInfo.staticCast<ModInfoRegular>();
+ if (modInfoRegular->isAlternate() && !modInfoRegular->isConverted())
+ result |= IModList::STATE_ALTERNATE;
+ if (!modInfo->isValid() && modInfoRegular->isValidated())
+ result |= IModList::STATE_VALID;
+ }
+ if (modInfo->canBeEnabled()) {
+ if (m_Profile->modEnabled(modIndex)) {
+ result |= IModList::STATE_ACTIVE;
+ }
+ } else {
+ result |= IModList::STATE_ESSENTIAL;
+ }
+ }
+ return result;
+}
+
+QString ModList::displayName(const QString &internalName) const
+{
+ unsigned int modIndex = ModInfo::getIndex(internalName);
+ if (modIndex == UINT_MAX) {
+ // might be better to throw an exception?
+ return internalName;
+ } else {
+ return ModInfo::getByIndex(modIndex)->name();
+ }
+}
+
+QStringList ModList::allMods() const
+{
+ QStringList result;
+ for (unsigned int i = 0; i < ModInfo::getNumMods(); ++i) {
+ result.append(ModInfo::getByIndex(i)->internalName());
+ }
+ return result;
+}
+
+IModList::ModStates ModList::state(const QString &name) const
+{
+ unsigned int modIndex = ModInfo::getIndex(name);
+
+ return state(modIndex);
+}
+
+bool ModList::setActive(const QString &name, bool active)
+{
+ unsigned int modIndex = ModInfo::getIndex(name);
+ if (modIndex == UINT_MAX) {
+ return false;
+ } else {
+ m_Profile->setModEnabled(modIndex, active);
+
+ IModList::ModStates newState = state(modIndex);
+ m_ModStateChanged(name, newState);
+ return true;
+ }
+}
+
+int ModList::priority(const QString &name) const
+{
+ unsigned int modIndex = ModInfo::getIndex(name);
+ if (modIndex == UINT_MAX) {
+ return -1;
+ } else {
+ return m_Profile->getModPriority(modIndex);
+ }
+}
+
+bool ModList::setPriority(const QString &name, int newPriority)
+{
+ if ((newPriority < 0) || (newPriority >= static_cast<int>(m_Profile->numRegularMods()))) {
+ return false;
+ }
+
+ unsigned int modIndex = ModInfo::getIndex(name);
+ if (modIndex == UINT_MAX) {
+ return false;
+ } else {
+ m_Profile->setModPriority(modIndex, newPriority);
+ notifyChange(modIndex);
+ return true;
+ }
+}
+
+bool ModList::onModStateChanged(const std::function<void (const QString &, IModList::ModStates)> &func)
+{
+ auto conn = m_ModStateChanged.connect(func);
+ return conn.connected();
+}
+
+bool ModList::onModMoved(const std::function<void (const QString &, int, int)> &func)
+{
+ auto conn = m_ModMoved.connect(func);
+ return conn.connected();
+}
+
+bool ModList::dropURLs(const QMimeData *mimeData, int row, const QModelIndex &parent)
+{
+ QStringList source;
+ QStringList target;
+
+ if (row == -1) {
+ row = parent.row();
+ }
+ ModInfo::Ptr modInfo = ModInfo::getByIndex(row);
+ QDir modDirectory(modInfo->absolutePath());
+ QDir gameDirectory(Settings::instance().getOverwriteDirectory());
+
+ unsigned int overwriteIndex = ModInfo::findMod([] (ModInfo::Ptr mod) -> bool {
+ std::vector<ModInfo::EFlag> flags = mod->getFlags();
+ return std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end(); });
+
+ QString overwriteName = ModInfo::getByIndex(overwriteIndex)->name();
+
+ for (const QUrl &url : mimeData->urls()) {
+ //qDebug("URL drop requested: %s", qUtf8Printable(url.toLocalFile()));
+ if (!url.isLocalFile()) {
+ qDebug("URL drop ignored: Not a local file.");
+ continue;
+ }
+ QString relativePath = gameDirectory.relativeFilePath(url.toLocalFile());
+ if (relativePath.startsWith("..")) {
+ qDebug("URL drop ignored: relative path starts with ..");
+ continue;
+ }
+ source.append(url.toLocalFile());
+ target.append(modDirectory.absoluteFilePath(relativePath));
+ emit fileMoved(relativePath, overwriteName, modInfo->name());
+ }
+
+ if (source.count() != 0) {
+ if (!shellMove(source, target)) {
+ qDebug("Move failed %lu",::GetLastError());
+ return false;
+ }
+ }
+
+ if (!modInfo->isValid()) {
+ modInfo->testValid();
+ }
+
+ return true;
+}
+
+bool ModList::dropMod(const QMimeData *mimeData, int row, const QModelIndex &parent)
+{
+
+ try {
+ QByteArray encoded = mimeData->data("application/x-qabstractitemmodeldatalist");
+ QDataStream stream(&encoded, QIODevice::ReadOnly);
+ std::vector<int> sourceRows;
+
+ while (!stream.atEnd()) {
+ int sourceRow, col;
+ QMap<int, QVariant> roleDataMap;
+ stream >> sourceRow >> col >> roleDataMap;
+ if (col == 0) {
+ sourceRows.push_back(sourceRow);
+ }
+ }
+
+ if (row == -1) {
+ row = parent.row();
+ }
+
+ if ((row < 0) || (static_cast<unsigned int>(row) >= ModInfo::getNumMods())) {
+ return false;
+ }
+
+ int newPriority = 0;
+ {
+ if ((row < 0) || (row > static_cast<int>(m_Profile->numRegularMods()))) {
+ newPriority = m_Profile->numRegularMods() + 1;
+ } else {
+ newPriority = m_Profile->getModPriority(row);
+ }
+ if (newPriority == -1) {
+ newPriority = m_Profile->numRegularMods() + 1;
+ }
+ }
+ changeModPriority(sourceRows, newPriority);
+ } catch (const std::exception &e) {
+ reportError(tr("drag&drop failed: %1").arg(e.what()));
+ }
+
+ return false;
+}
+
+
+bool ModList::dropMimeData(const QMimeData *mimeData, Qt::DropAction action, int row, int, const QModelIndex &parent)
+{
+ if (action == Qt::IgnoreAction) {
+ return true;
+ }
+
+ if (m_Profile == nullptr) return false;
+
+ if (mimeData->hasUrls()) {
+ return dropURLs(mimeData, row, parent);
+ } else if (mimeData->hasText()) {
+ return dropMod(mimeData, row, parent);
+ } else {
+ return false;
+ }
+}
+
+void ModList::removeRowForce(int row, const QModelIndex &parent)
+{
+ if (static_cast<unsigned int>(row) >= ModInfo::getNumMods()) {
+ return;
+ }
+ if (m_Profile == nullptr) return;
+
+ ModInfo::Ptr modInfo = ModInfo::getByIndex(row);
+
+ bool wasEnabled = m_Profile->modEnabled(row);
+
+ m_Profile->setModEnabled(row, false);
+
+ m_Profile->cancelModlistWrite();
+ beginRemoveRows(parent, row, row);
+ ModInfo::removeMod(row);
+ endRemoveRows();
+ m_Profile->refreshModStatus(); // removes the mod from the status list
+ m_Profile->writeModlist(); // this ensures the modified list gets written back before new mods can be installed
+
+ if (wasEnabled) {
+ emit removeOrigin(modInfo->name());
+ }
+
+ emit modUninstalled(modInfo->getInstallationFile());
+}
+
+bool ModList::removeRows(int row, int count, const QModelIndex &parent)
+{
+ if (static_cast<unsigned int>(row) >= ModInfo::getNumMods()) {
+ return false;
+ }
+ if (m_Profile == nullptr) {
+ return false;
+ }
+
+ bool success = false;
+
+ if (count == 1) {
+ ModInfo::Ptr modInfo = ModInfo::getByIndex(row);
+ std::vector<ModInfo::EFlag> flags = modInfo->getFlags();
+ if ((std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end()) && (QDir(modInfo->absolutePath()).count() > 2)) {
+ emit clearOverwrite();
+ success = true;
+ }
+ }
+
+ for (int i = 0; i < count; ++i) {
+ ModInfo::Ptr modInfo = ModInfo::getByIndex(row + i);
+ if (!modInfo->isRegular()) {
+ continue;
+ }
+
+ success = true;
+
+ QMessageBox confirmBox(QMessageBox::Question, tr("Confirm"),
+ tr("Are you sure you want to remove \"%1\"?").arg(modInfo->name()),
+ QMessageBox::Yes | QMessageBox::No);
+
+ if (confirmBox.exec() == QMessageBox::Yes) {
+ m_Profile->setModEnabled(row + i, false);
+ removeRowForce(row + i, parent);
+ }
+ }
+
+ return success;
+}
+
+
+void ModList::notifyChange(int rowStart, int rowEnd)
+{
+ if (rowStart < 0) {
+ m_Overwrite.clear();
+ m_Overwritten.clear();
+ m_ArchiveOverwrite.clear();
+ m_ArchiveOverwritten.clear();
+ m_ArchiveLooseOverwrite.clear();
+ m_ArchiveLooseOverwritten.clear();
+ beginResetModel();
+ endResetModel();
+ } else {
+ if (rowEnd == -1) {
+ rowEnd = rowStart;
+ }
+ emit dataChanged(this->index(rowStart, 0), this->index(rowEnd, this->columnCount() - 1));
+ }
+}
+
+
+QModelIndex ModList::index(int row, int column, const QModelIndex&) const
+{
+ if ((row < 0) || (row >= rowCount()) || (column < 0) || (column >= columnCount())) {
+ return QModelIndex();
+ }
+ QModelIndex res = createIndex(row, column, row);
+ return res;
+}
+
+
+QModelIndex ModList::parent(const QModelIndex&) const
+{
+ return QModelIndex();
+}
+
+QMap<int, QVariant> ModList::itemData(const QModelIndex &index) const
+{
+ QMap<int, QVariant> result = QAbstractItemModel::itemData(index);
+ result[Qt::UserRole] = data(index, Qt::UserRole);
+ return result;
+}
+
+
+void ModList::dropModeUpdate(bool dropOnItems)
+{
+ if (m_DropOnItems != dropOnItems) {
+ m_DropOnItems = dropOnItems;
+ }
+}
+
+
+QString ModList::getColumnName(int column)
+{
+ switch (column) {
+ case COL_FLAGS: return tr("Flags");
+ case COL_CONTENT: return tr("Content");
+ case COL_NAME: return tr("Mod Name");
+ case COL_VERSION: return tr("Version");
+ case COL_PRIORITY: return tr("Priority");
+ case COL_CATEGORY: return tr("Category");
+ case COL_GAME: return tr("Source Game");
+ case COL_MODID: return tr("Nexus ID");
+ case COL_INSTALLTIME: return tr("Installation");
+ case COL_NOTES: return tr("Notes");
+ default: return tr("unknown");
+ }
+}
+
+
+QString ModList::getColumnToolTip(int column)
+{
+ switch (column) {
+ case COL_NAME: return tr("Name of your mods");
+ case COL_VERSION: return tr("Version of the mod (if available)");
+ case COL_PRIORITY: return tr("Installation priority of your mod. The higher, the more \"important\" it is and thus "
+ "overwrites files from mods with lower priority.");
+ case COL_CATEGORY: return tr("Category of the mod.");
+ case COL_GAME: return tr("The source game which was the origin of this mod.");
+ case COL_MODID: return tr("Id of the mod as used on Nexus.");
+ case COL_FLAGS: return tr("Emblemes to highlight things that might require attention.");
+ case COL_CONTENT: return tr("Depicts the content of the mod:<br>"
+ "<table cellspacing=7>"
+ "<tr><td><img src=\":/MO/gui/content/plugin\" width=32/></td><td>Game plugins (esp/esm/esl)</td></tr>"
+ "<tr><td><img src=\":/MO/gui/content/interface\" width=32/></td><td>Interface</td></tr>"
+ "<tr><td><img src=\":/MO/gui/content/mesh\" width=32/></td><td>Meshes</td></tr>"
+ "<tr><td><img src=\":/MO/gui/content/bsa\" width=32/></td><td>BSA</td></tr>"
+ "<tr><td><img src=\":/MO/gui/content/texture\" width=32/></td><td>Textures</td></tr>"
+ "<tr><td><img src=\":/MO/gui/content/sound\" width=32/></td><td>Sounds</td></tr>"
+ "<tr><td><img src=\":/MO/gui/content/music\" width=32/></td><td>Music</td></tr>"
+ "<tr><td><img src=\":/MO/gui/content/string\" width=32/></td><td>Strings</td></tr>"
+ "<tr><td><img src=\":/MO/gui/content/script\" width=32/></td><td>Scripts (Papyrus)</td></tr>"
+ "<tr><td><img src=\":/MO/gui/content/skse\" width=32/></td><td>Script Extender plugins</td></tr>"
+ "<tr><td><img src=\":/MO/gui/content/skyproc\" width=32/></td><td>SkyProc Patcher</td></tr>"
+ "<tr><td><img src=\":/MO/gui/content/menu\" width=32/></td><td>Mod Configuration Menu</td></tr>"
+ "<tr><td><img src=\":/MO/gui/content/inifile\" width=32/></td><td>INI files</td></tr>"
+ "<tr><td><img src=\":/MO/gui/content/modgroup\" width=32/></td><td>ModGroup files</td></tr>"
+
+ "</table>");
+ case COL_INSTALLTIME: return tr("Time this mod was installed");
+ case COL_NOTES: return tr("User notes about the mod");
+ default: return tr("unknown");
+ }
+}
+
+
+bool ModList::moveSelection(QAbstractItemView *itemView, int direction)
+{
+ QItemSelectionModel *selectionModel = itemView->selectionModel();
+
+ const QAbstractProxyModel *proxyModel = qobject_cast<const QAbstractProxyModel*>(selectionModel->model());
+ const QSortFilterProxyModel *filterModel = nullptr;
+
+ while ((filterModel == nullptr) && (proxyModel != nullptr)) {
+ filterModel = qobject_cast<const QSortFilterProxyModel*>(proxyModel);
+ if (filterModel == nullptr) {
+ proxyModel = qobject_cast<const QAbstractProxyModel*>(proxyModel->sourceModel());
+ }
+ }
+ if (filterModel == nullptr) {
+ return true;
+ }
+
+ int offset = -1;
+ if (((direction < 0) && (filterModel->sortOrder() == Qt::DescendingOrder)) ||
+ ((direction > 0) && (filterModel->sortOrder() == Qt::AscendingOrder))) {
+ offset = 1;
+ }
+
+ QModelIndexList rows = selectionModel->selectedRows();
+ if (direction > 0) {
+ for (int i = 0; i < rows.size() / 2; ++i) {
+ rows.swap(i, rows.size() - i - 1);
+ }
+ }
+ for (QModelIndex idx : rows) {
+ if (filterModel != nullptr) {
+ idx = filterModel->mapToSource(idx);
+ }
+ int newPriority = m_Profile->getModPriority(idx.row()) + offset;
+ if ((newPriority >= 0) && (newPriority < static_cast<int>(m_Profile->numRegularMods()))) {
+ m_Profile->setModPriority(idx.row(), newPriority);
+ notifyChange(idx.row());
+ }
+ }
+ emit modorder_changed();
+ return true;
+}
+
+bool ModList::deleteSelection(QAbstractItemView *itemView)
+{
+ QItemSelectionModel *selectionModel = itemView->selectionModel();
+
+ QModelIndexList rows = selectionModel->selectedRows();
+ if (rows.count() > 1) {
+ emit removeSelectedMods();
+ } else if (rows.count() == 1) {
+ removeRow(rows[0].data(Qt::UserRole + 1).toInt(), QModelIndex());
+ }
+ return true;
+}
+
+bool ModList::toggleSelection(QAbstractItemView *itemView)
+{
+ emit aboutToChangeData();
+
+ QItemSelectionModel *selectionModel = itemView->selectionModel();
+
+ QList<unsigned int> modsToEnable;
+ QList<unsigned int> modsToDisable;
+ QModelIndexList dirtyMods;
+ for (QModelIndex idx : selectionModel->selectedRows()) {
+ int modId = idx.data(Qt::UserRole + 1).toInt();
+ if (m_Profile->modEnabled(modId)) {
+ modsToDisable.append(modId);
+ dirtyMods.append(idx);
+ } else {
+ modsToEnable.append(modId);
+ dirtyMods.append(idx);
+ }
+ }
+
+ m_Profile->setModsEnabled(modsToEnable, modsToDisable);
+
+ emit modlistChanged(dirtyMods, 0);
+
+ m_Modified = true;
+ m_LastCheck.restart();
+
+ emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1));
+
+ emit postDataChanged();
+
+ return true;
+}
+
+bool ModList::eventFilter(QObject *obj, QEvent *event)
+{
+ if (event->type() == QEvent::ContextMenu) {
+ QContextMenuEvent *contextEvent = static_cast<QContextMenuEvent*>(event);
+ QWidget *object = qobject_cast<QWidget*>(obj);
+ if ((object != nullptr) && (contextEvent->reason() == QContextMenuEvent::Mouse)) {
+ emit requestColumnSelect(object->mapToGlobal(contextEvent->pos()));
+
+ return true;
+ }
+ } else if ((event->type() == QEvent::KeyPress) && (m_Profile != nullptr)) {
+ QAbstractItemView *itemView = qobject_cast<QAbstractItemView*>(obj);
+ QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
+
+ if ((itemView != nullptr)
+ && (keyEvent->modifiers() == Qt::ControlModifier)
+ && ((keyEvent->key() == Qt::Key_Up) || (keyEvent->key() == Qt::Key_Down))) {
+ return moveSelection(itemView, keyEvent->key() == Qt::Key_Up ? -1 : 1);
+ } else if (keyEvent->key() == Qt::Key_Delete) {
+ return deleteSelection(itemView);
+ } else if (keyEvent->key() == Qt::Key_Space) {
+ return toggleSelection(itemView);
+ }
+ return QAbstractItemModel::eventFilter(obj, event);
+ }
+ return QAbstractItemModel::eventFilter(obj, event);
+}
+
+//note: caller needs to make sure sort proxy is updated
+void ModList::enableSelected(const QItemSelectionModel *selectionModel)
+{
+ if (selectionModel->hasSelection()) {
+ QList<unsigned int> modsToEnable;
+ for (auto row : selectionModel->selectedRows(COL_PRIORITY)) {
+ int modID = m_Profile->modIndexByPriority(row.data().toInt());
+ modsToEnable.append(modID);
+ }
+ m_Profile->setModsEnabled(modsToEnable, QList<unsigned int>());
+ }
+}
+
+//note: caller needs to make sure sort proxy is updated
+void ModList::disableSelected(const QItemSelectionModel *selectionModel)
+{
+ if (selectionModel->hasSelection()) {
+ QList<unsigned int> modsToDisable;
+ for (auto row : selectionModel->selectedRows(COL_PRIORITY)) {
+ int modID = m_Profile->modIndexByPriority(row.data().toInt());
+ modsToDisable.append(modID);
+ }
+ m_Profile->setModsEnabled(QList<unsigned int>(), modsToDisable);
+ }
+}