From d5332c5080ea5e1d0812cc900bb5ed6e20ff8f0e Mon Sep 17 00:00:00 2001 From: LostDragonist Date: Wed, 6 Mar 2019 20:12:09 -0600 Subject: Add support for displaying tracked mods and setting tracked status --- src/mainwindow.cpp | 112 ++++ src/mainwindow.h | 4 + src/modflagicondelegate.cpp | 33 +- src/modinfo.h | 28 +- src/modinfoforeign.h | 2 + src/modinfooverwrite.h | 2 + src/modinforegular.cpp | 51 +- src/modinforegular.h | 22 + src/modlist.cpp | 1 + src/nexusinterface.cpp | 100 +++- src/nexusinterface.h | 46 +- src/organizer_en.ts | 1251 +++++++++++++++++++++---------------------- src/resources.qrc | 1 + src/resources/tracked.png | Bin 0 -> 1969 bytes 14 files changed, 1002 insertions(+), 651 deletions(-) create mode 100644 src/resources/tracked.png diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 58c80b87..3f6da2a6 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2826,6 +2826,73 @@ void MainWindow::unendorse_clicked() } } + +void MainWindow::trackMod(ModInfo::Ptr mod, bool doTrack) +{ + if (NexusInterface::instance(&m_PluginContainer)->getAccessManager()->validated()) { + ModInfo::getByIndex(m_ContextRow)->track(doTrack); + } else { + QString apiKey; + if (m_OrganizerCore.settings().getNexusApiKey(apiKey)) { + m_OrganizerCore.doAfterLogin([&]() { this->trackMod(mod, doTrack); }); + NexusInterface::instance(&m_PluginContainer)->getAccessManager()->apiCheck(apiKey); + } else { + MessageDialog::showMessage(tr("You need to be logged in with Nexus to track"), this); + } + } +} + + +void MainWindow::track_clicked() +{ + QItemSelectionModel *selection = ui->modList->selectionModel(); + if (selection->hasSelection() && selection->selectedRows().count() > 1) { + if (NexusInterface::instance(&m_PluginContainer)->getAccessManager()->validated()) { + for (auto idx : selection->selectedRows()) { + ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt())->track(true); + } + } else { + QString apiKey; + if (m_OrganizerCore.settings().getNexusApiKey(apiKey)) { + for (auto idx : selection->selectedRows()) { + auto modInfo = ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt()); + m_OrganizerCore.doAfterLogin([&]() { this->trackMod(modInfo, true); }); + } + NexusInterface::instance(&m_PluginContainer)->getAccessManager()->apiCheck(apiKey); + } else { + MessageDialog::showMessage(tr("You need to be logged in with Nexus to track"), this); + } + } + } else { + trackMod(ModInfo::getByIndex(m_ContextRow), true); + } +} + +void MainWindow::untrack_clicked() +{ + QItemSelectionModel *selection = ui->modList->selectionModel(); + if (selection->hasSelection() && selection->selectedRows().count() > 1) { + if (NexusInterface::instance(&m_PluginContainer)->getAccessManager()->validated()) { + for (auto idx : selection->selectedRows()) { + ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt())->track(false); + } + } else { + QString apiKey; + if (m_OrganizerCore.settings().getNexusApiKey(apiKey)) { + for (auto idx : selection->selectedRows()) { + auto modInfo = ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt()); + m_OrganizerCore.doAfterLogin([&]() { this->trackMod(modInfo, false); }); + } + NexusInterface::instance(&m_PluginContainer)->getAccessManager()->apiCheck(apiKey); + } else { + MessageDialog::showMessage(tr("You need to be logged in with Nexus to track"), this); + } + } + } else { + trackMod(ModInfo::getByIndex(m_ContextRow), false); + } +} + void MainWindow::validationFailed(const QString &error) { qDebug("Nexus API validation failed: %s", qUtf8Printable(error)); @@ -4009,6 +4076,7 @@ void MainWindow::checkModsForUpdates() if (NexusInterface::instance(&m_PluginContainer)->getAccessManager()->validated()) { ModInfo::checkAllForUpdate(&m_PluginContainer, this); NexusInterface::instance(&m_PluginContainer)->requestEndorsementInfo(this, QVariant(), QString()); + NexusInterface::instance(&m_PluginContainer)->requestTrackingInfo(this, QVariant(), QString()); } else { QString apiKey; if (m_OrganizerCore.settings().getNexusApiKey(apiKey)) { @@ -4595,6 +4663,22 @@ void MainWindow::on_modList_customContextMenuRequested(const QPoint &pos) } } + if (info->getNexusID() > 0) { + switch (info->trackedState()) { + case ModInfo::TRACKED_FALSE: { + menu.addAction(tr("Start tracking"), this, SLOT(track_clicked())); + } break; + case ModInfo::TRACKED_TRUE: { + menu.addAction(tr("Stop tracking"), this, SLOT(untrack_clicked())); + } break; + default: { + QAction *action = new QAction(tr("Tracked state unknown"), &menu); + action->setEnabled(false); + menu.addAction(action); + } break; + } + } + menu.addSeparator(); std::vector flags = info->getFlags(); @@ -5741,6 +5825,34 @@ void MainWindow::nxmEndorsementToggled(QString, int, QVariant, QVariant resultDa } } +void MainWindow::nxmTrackedModsAvailable(QVariant userData, QVariant resultData, int) +{ + QMap gameNames; + for (auto game : m_PluginContainer.plugins()) { + gameNames[game->gameNexusName()] = game->gameShortName(); + } + + for (unsigned int i = 0; i < ModInfo::getNumMods(); i++) { + auto modInfo = ModInfo::getByIndex(i); + if (modInfo->getNexusID() <= 0) + continue; + + bool found = false; + auto resultsList = resultData.toList(); + for (auto item : resultsList) { + auto results = item.toMap(); + if ((gameNames[results["domain_name"].toString()].compare(modInfo->getGameName(), Qt::CaseInsensitive) == 0) && + (results["mod_id"].toInt() == modInfo->getNexusID())) { + found = true; + break; + } + } + + modInfo->setIsTracked(found); + modInfo->saveMeta(); + } +} + void MainWindow::nxmDownloadURLs(QString, int, int, QVariant, QVariant resultData, int) { QVariantList serverList = resultData.toList(); diff --git a/src/mainwindow.h b/src/mainwindow.h index ff23b8fb..152591e8 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -428,6 +428,8 @@ private slots: void endorse_clicked(); void dontendorse_clicked(); void unendorse_clicked(); + void track_clicked(); + void untrack_clicked(); void ignoreMissingData_clicked(); void markConverted_clicked(); void visitOnNexus_clicked(); @@ -517,6 +519,7 @@ private slots: void nxmUpdatesAvailable(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID); void nxmModInfoAvailable(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID); void nxmEndorsementToggled(QString, int, QVariant, QVariant resultData, int); + void nxmTrackedModsAvailable(QVariant userData, QVariant resultData, int); void nxmDownloadURLs(QString, int modID, int fileID, QVariant userData, QVariant resultData, int requestID); void nxmRequestFailed(QString gameName, int modID, int fileID, QVariant userData, int requestID, QNetworkReply::NetworkError error, const QString &errorString); @@ -539,6 +542,7 @@ private slots: void resumeDownload(int downloadIndex); void endorseMod(ModInfo::Ptr mod); void unendorseMod(ModInfo::Ptr mod); + void trackMod(ModInfo::Ptr mod, bool doTrack); void cancelModListEditor(); void lockESPIndex(); diff --git a/src/modflagicondelegate.cpp b/src/modflagicondelegate.cpp index 343c6ee3..fbd951eb 100644 --- a/src/modflagicondelegate.cpp +++ b/src/modflagicondelegate.cpp @@ -93,26 +93,27 @@ QList ModFlagIconDelegate::getIcons(const QModelIndex &index) const { QString ModFlagIconDelegate::getFlagIcon(ModInfo::EFlag flag) const { switch (flag) { - case ModInfo::FLAG_BACKUP: return ":/MO/gui/emblem_backup"; - case ModInfo::FLAG_INVALID: return ":/MO/gui/problem"; - case ModInfo::FLAG_NOTENDORSED: return ":/MO/gui/emblem_notendorsed"; - case ModInfo::FLAG_NOTES: return ":/MO/gui/emblem_notes"; - case ModInfo::FLAG_CONFLICT_MIXED: return ":/MO/gui/emblem_conflict_mixed"; - case ModInfo::FLAG_CONFLICT_OVERWRITE: return ":/MO/gui/emblem_conflict_overwrite"; - case ModInfo::FLAG_CONFLICT_OVERWRITTEN: return ":/MO/gui/emblem_conflict_overwritten"; - case ModInfo::FLAG_CONFLICT_REDUNDANT: return ":MO/gui/emblem_conflict_redundant"; - case ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITE: return ":/MO/gui/archive_loose_conflict_overwrite"; - case ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITTEN: return ":/MO/gui/archive_loose_conflict_overwritten"; - case ModInfo::FLAG_ARCHIVE_CONFLICT_MIXED: return ":/MO/gui/archive_conflict_mixed"; - case ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITE: return ":/MO/gui/archive_conflict_winner"; - case ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITTEN: return ":/MO/gui/archive_conflict_loser"; - case ModInfo::FLAG_ALTERNATE_GAME: return ":MO/gui/alternate_game"; + case ModInfo::FLAG_BACKUP: return QStringLiteral(":/MO/gui/emblem_backup"); + case ModInfo::FLAG_INVALID: return QStringLiteral(":/MO/gui/problem"); + case ModInfo::FLAG_NOTENDORSED: return QStringLiteral(":/MO/gui/emblem_notendorsed"); + case ModInfo::FLAG_NOTES: return QStringLiteral(":/MO/gui/emblem_notes"); + case ModInfo::FLAG_CONFLICT_MIXED: return QStringLiteral(":/MO/gui/emblem_conflict_mixed"); + case ModInfo::FLAG_CONFLICT_OVERWRITE: return QStringLiteral(":/MO/gui/emblem_conflict_overwrite"); + case ModInfo::FLAG_CONFLICT_OVERWRITTEN: return QStringLiteral(":/MO/gui/emblem_conflict_overwritten"); + case ModInfo::FLAG_CONFLICT_REDUNDANT: return QStringLiteral(":/MO/gui/emblem_conflict_redundant"); + case ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITE: return QStringLiteral(":/MO/gui/archive_loose_conflict_overwrite"); + case ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITTEN: return QStringLiteral(":/MO/gui/archive_loose_conflict_overwritten"); + case ModInfo::FLAG_ARCHIVE_CONFLICT_MIXED: return QStringLiteral(":/MO/gui/archive_conflict_mixed"); + case ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITE: return QStringLiteral(":/MO/gui/archive_conflict_winner"); + case ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITTEN: return QStringLiteral(":/MO/gui/archive_conflict_loser"); + case ModInfo::FLAG_ALTERNATE_GAME: return QStringLiteral(":/MO/gui/alternate_game"); case ModInfo::FLAG_FOREIGN: return QString(); case ModInfo::FLAG_SEPARATOR: return QString(); case ModInfo::FLAG_OVERWRITE: return QString(); case ModInfo::FLAG_PLUGIN_SELECTED: return QString(); - default: - qWarning("ModInfo flag %d has no defined icon", flag); + case ModInfo::FLAG_TRACKED: return QStringLiteral(":/MO/gui/tracked"); + default: + qWarning("ModInfo flag %d has no defined icon", flag); return QString(); } } diff --git a/src/modinfo.h b/src/modinfo.h index 52c29154..365dfe50 100644 --- a/src/modinfo.h +++ b/src/modinfo.h @@ -78,7 +78,8 @@ public: FLAG_ARCHIVE_CONFLICT_OVERWRITTEN, FLAG_ARCHIVE_CONFLICT_MIXED, FLAG_PLUGIN_SELECTED, - FLAG_ALTERNATE_GAME + FLAG_ALTERNATE_GAME, + FLAG_TRACKED, }; enum EContent { @@ -114,6 +115,12 @@ public: ENDORSED_NEVER }; + enum ETrackedState { + TRACKED_FALSE, + TRACKED_TRUE, + TRACKED_UNKNOWN, + }; + enum EModType { MOD_DEFAULT, MOD_DLC, @@ -362,6 +369,13 @@ public: */ virtual void setNeverEndorse() = 0; + /** + * update the tracked state for the mod. This only changes the + * buffered state, it does not sync with Nexus + * @param tracked the new tracked state + */ + virtual void setIsTracked(bool tracked) = 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 @@ -375,6 +389,13 @@ public: */ virtual void endorse(bool doEndorse) = 0; + /** + * @brief track or untrack the mod. This will sync with nexus! + * @param doTrack if true, the mod is tracked, if false, it's untracked. + * @note if doTrack doesn't differ from the current value, nothing happens. + */ + virtual void track(bool doTrack) = 0; + /** * @brief clear all caches held for this mod */ @@ -636,6 +657,11 @@ public: */ virtual EEndorsedState endorsedState() const { return ENDORSED_NEVER; } + /** + * @return true if the file is being tracked on nexus + */ + virtual ETrackedState trackedState() const { return TRACKED_FALSE; } + /** * @brief updates the valid-flag for this mod */ diff --git a/src/modinfoforeign.h b/src/modinfoforeign.h index 1fdf5409..72fbb04f 100644 --- a/src/modinfoforeign.h +++ b/src/modinfoforeign.h @@ -29,8 +29,10 @@ public: virtual void addNexusCategory(int) {} virtual void setIsEndorsed(bool) {} virtual void setNeverEndorse() {} + virtual void setIsTracked(bool) {} virtual bool remove() { return false; } virtual void endorse(bool) {} + virtual void track(bool) {} virtual void parseNexusInfo() {} virtual bool isEmpty() const { return false; } virtual QString name() const; diff --git a/src/modinfooverwrite.h b/src/modinfooverwrite.h index e32653d5..ff0d8cd4 100644 --- a/src/modinfooverwrite.h +++ b/src/modinfooverwrite.h @@ -31,8 +31,10 @@ public: virtual void addNexusCategory(int) {} virtual void setIsEndorsed(bool) {} virtual void setNeverEndorse() {} + virtual void setIsTracked(bool) {} virtual bool remove() { return false; } virtual void endorse(bool) {} + virtual void track(bool) {} virtual void parseNexusInfo() {} virtual bool alwaysEnabled() const { return true; } virtual bool isEmpty() const; diff --git a/src/modinforegular.cpp b/src/modinforegular.cpp index 2545b3f8..a271c4e8 100644 --- a/src/modinforegular.cpp +++ b/src/modinforegular.cpp @@ -35,6 +35,7 @@ ModInfoRegular::ModInfoRegular(PluginContainer *pluginContainer, const IPluginGa , m_Validated(false) , m_MetaInfoChanged(false) , m_EndorsedState(ENDORSED_UNKNOWN) + , m_TrackedState(TRACKED_UNKNOWN) , m_NexusBridge(pluginContainer) { testValid(); @@ -55,6 +56,8 @@ ModInfoRegular::ModInfoRegular(PluginContainer *pluginContainer, const IPluginGa , this, SLOT(nxmDescriptionAvailable(QString,int,QVariant,QVariant))); connect(&m_NexusBridge, SIGNAL(endorsementToggled(QString,int,QVariant,QVariant)) , this, SLOT(nxmEndorsementToggled(QString,int,QVariant,QVariant))); + connect(&m_NexusBridge, SIGNAL(trackingToggled(QString,int,QVariant,bool)) + , this, SLOT(nxmTrackingToggled(QString,int,QVariant,bool))); connect(&m_NexusBridge, SIGNAL(requestFailed(QString,int,int,QVariant,QNetworkReply::NetworkError,QString)) , this, SLOT(nxmRequestFailed(QString,int,int,QVariant,QNetworkReply::NetworkError,QString))); } @@ -102,6 +105,7 @@ void ModInfoRegular::readMeta() m_LastNexusUpdate = QDateTime::fromString(metaFile.value("lastNexusUpdate", "").toString(), Qt::ISODate); m_NexusLastModified = QDateTime::fromString(metaFile.value("nexusLastModified", QDateTime::currentDateTimeUtc()).toString(), Qt::ISODate); m_Color = metaFile.value("color",QColor()).value(); + m_TrackedState = metaFile.value("tracked", false).toBool() ? TRACKED_TRUE : TRACKED_FALSE; if (metaFile.contains("endorsed")) { if (metaFile.value("endorsed").canConvert()) { switch (metaFile.value("endorsed").toInt()) { @@ -114,7 +118,7 @@ void ModInfoRegular::readMeta() m_EndorsedState = metaFile.value("endorsed", false).toBool() ? ENDORSED_TRUE : ENDORSED_FALSE; } } - + QString categoriesString = metaFile.value("category", "").toString(); QStringList categories = categoriesString.split(',', QString::SkipEmptyParts); @@ -173,6 +177,7 @@ void ModInfoRegular::saveMeta() if (m_EndorsedState != ENDORSED_UNKNOWN) { metaFile.setValue("endorsed", m_EndorsedState); } + metaFile.setValue("tracked", m_TrackedState); metaFile.remove("installedFiles"); metaFile.beginWriteArray("installedFiles"); @@ -259,6 +264,18 @@ void ModInfoRegular::nxmEndorsementToggled(QString, int, QVariant, QVariant resu } +void ModInfoRegular::nxmTrackingToggled(QString, int, QVariant, bool tracked) +{ + if (tracked) + m_TrackedState = TRACKED_TRUE; + else + m_TrackedState = TRACKED_FALSE; + m_MetaInfoChanged = true; + saveMeta(); + emit modDetailsUpdated(true); +} + + void ModInfoRegular::nxmRequestFailed(QString, int, int, QVariant userData, QNetworkReply::NetworkError error, const QString &errorMessage) { QString fullMessage = errorMessage; @@ -414,6 +431,14 @@ void ModInfoRegular::setEndorsedState(EEndorsedState endorsedState) } } +void ModInfoRegular::setTrackedState(ETrackedState trackedState) +{ + if (trackedState != m_TrackedState) { + m_TrackedState = trackedState; + m_MetaInfoChanged = true; + } +} + void ModInfoRegular::setInstallationFile(const QString &fileName) { m_InstallationFile = fileName; @@ -433,13 +458,19 @@ void ModInfoRegular::setIsEndorsed(bool endorsed) } } - void ModInfoRegular::setNeverEndorse() { m_EndorsedState = ENDORSED_NEVER; m_MetaInfoChanged = true; } +void ModInfoRegular::setIsTracked(bool tracked) +{ + if (tracked != (m_TrackedState == TRACKED_TRUE)) { + m_TrackedState = tracked ? TRACKED_TRUE : TRACKED_FALSE; + m_MetaInfoChanged = true; + } +} void ModInfoRegular::setColor(QColor color) { @@ -465,6 +496,13 @@ void ModInfoRegular::endorse(bool doEndorse) } } +void ModInfoRegular::track(bool doTrack) +{ + if (doTrack != (m_TrackedState == TRACKED_TRUE)) { + m_NexusBridge.requestToggleTracking(m_GameName, getNexusID(), doTrack, QVariant(1)); + } +} + void ModInfoRegular::markConverted(bool converted) { m_Converted = converted; @@ -518,6 +556,10 @@ std::vector ModInfoRegular::getFlags() const Settings::instance().endorsementIntegration()) { result.push_back(ModInfo::FLAG_NOTENDORSED); } + if ((m_NexusID > 0) && + (trackedState() == TRACKED_TRUE)) { + result.push_back(ModInfo::FLAG_TRACKED); + } if (!isValid() && !m_Validated) { result.push_back(ModInfo::FLAG_INVALID); } @@ -667,6 +709,11 @@ ModInfoRegular::EEndorsedState ModInfoRegular::endorsedState() const return m_EndorsedState; } +ModInfoRegular::ETrackedState ModInfoRegular::trackedState() const +{ + return m_TrackedState; +} + QDateTime ModInfoRegular::getLastNexusUpdate() const { return m_LastNexusUpdate; diff --git a/src/modinforegular.h b/src/modinforegular.h index aaab778a..f70487a2 100644 --- a/src/modinforegular.h +++ b/src/modinforegular.h @@ -177,6 +177,13 @@ public: */ virtual void setNeverEndorse(); + /** + * update the tracked state for the mod. This only changes the + * buffered state. It does not sync with Nexus + * @param tracked the new tracked state + */ + virtual void setIsTracked(bool tracked); + /** * @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 @@ -190,6 +197,13 @@ public: */ virtual void endorse(bool doEndorse); + /** + * @brief track or untrack the mod. This will sync with nexus! + * @param doTrack if true, the mod is tracked, if false, it's untracked. + * @note if doTrack doesn't differ from the current value, nothing happens. + */ + virtual void track(bool doTrack); + /** * @brief updates the mod to flag it as converted in order to ignore the alternate game warning */ @@ -333,6 +347,11 @@ public: */ virtual EEndorsedState endorsedState() const; + /** + * @return true if the file is being tracked on nexus + */ + virtual ETrackedState trackedState() const; + /** * @brief get the last time nexus was checked for file updates on this mod */ @@ -391,11 +410,13 @@ public: private: void setEndorsedState(EEndorsedState endorsedState); + void setTrackedState(ETrackedState trackedState); private slots: void nxmDescriptionAvailable(QString, int modID, QVariant userData, QVariant resultData); void nxmEndorsementToggled(QString, int, QVariant userData, QVariant resultData); + void nxmTrackingToggled(QString, int, QVariant userData, bool tracked); void nxmRequestFailed(QString, int modID, int fileID, QVariant userData, QNetworkReply::NetworkError error, const QString &errorMessage); protected: @@ -436,6 +457,7 @@ private: MOBase::VersionInfo m_IgnoredVersion; EEndorsedState m_EndorsedState; + ETrackedState m_TrackedState; NexusBridge m_NexusBridge; diff --git a/src/modlist.cpp b/src/modlist.cpp index 8e8ab2d2..3ca53949 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -169,6 +169,7 @@ QString ModList::getFlagText(ModInfo::EFlag flag, ModInfo::Ptr modInfo) const case ModInfo::FLAG_ARCHIVE_CONFLICT_MIXED: return tr("Archive files overwrites & overwritten"); case ModInfo::FLAG_ALTERNATE_GAME: return tr("
This mod is for a different game, " "make sure it's compatible or it could cause crashes."); + case ModInfo::FLAG_TRACKED: return tr("Mod is being tracked on the website"); default: return ""; } } diff --git a/src/nexusinterface.cpp b/src/nexusinterface.cpp index 23dd7dbe..b1f50fd8 100644 --- a/src/nexusinterface.cpp +++ b/src/nexusinterface.cpp @@ -29,6 +29,7 @@ along with Mod Organizer. If not, see . #include #include #include +#include #include @@ -68,6 +69,11 @@ void NexusBridge::requestToggleEndorsement(QString gameName, int modID, QString m_RequestIDs.insert(m_Interface->requestToggleEndorsement(gameName, modID, modVersion, endorse, this, userData, m_SubModule)); } +void NexusBridge::requestToggleTracking(QString gameName, int modID, bool track, QVariant userData) +{ + m_RequestIDs.insert(m_Interface->requestToggleTracking(gameName, modID, track, this, userData, m_SubModule)); +} + void NexusBridge::nxmDescriptionAvailable(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID) { std::set::iterator iter = m_RequestIDs.find(requestID); @@ -142,6 +148,24 @@ void NexusBridge::nxmEndorsementToggled(QString gameName, int modID, QVariant us } } +void NexusBridge::nxmTrackedModsAvailable(QVariant userData, QVariant resultData, int requestID) +{ + std::set::iterator iter = m_RequestIDs.find(requestID); + if (iter != m_RequestIDs.end()) { + m_RequestIDs.erase(iter); + emit trackedModsAvailable(userData, resultData); + } +} + +void NexusBridge::nxmTrackingToggled(QString gameName, int modID, QVariant userData, bool tracked, int requestID) +{ + std::set::iterator iter = m_RequestIDs.find(requestID); + if (iter != m_RequestIDs.end()) { + m_RequestIDs.erase(iter); + emit trackingToggled(gameName, modID, userData, tracked); + } +} + void NexusBridge::nxmRequestFailed(QString gameName, int modID, int fileID, QVariant userData, int requestID, QNetworkReply::NetworkError error, const QString &errorMessage) { std::set::iterator iter = m_RequestIDs.find(requestID); @@ -472,7 +496,6 @@ int NexusInterface::requestEndorsementInfo(QObject *receiver, QVariant userData, return requestInfo.m_ID; } - int NexusInterface::requestToggleEndorsement(QString gameName, int modID, QString modVersion, bool endorse, QObject *receiver, QVariant userData, const QString &subModule, MOBase::IPluginGame const *game) { @@ -495,6 +518,43 @@ int NexusInterface::requestToggleEndorsement(QString gameName, int modID, QStrin return -1; } +int NexusInterface::requestTrackingInfo(QObject *receiver, QVariant userData, const QString &subModule) +{ + NXMRequestInfo requestInfo(NXMRequestInfo::TYPE_TRACKEDMODS, userData, subModule); + m_RequestQueue.enqueue(requestInfo); + + connect(this, SIGNAL(nxmTrackedModsAvailable(QVariant, QVariant, int)), + receiver, SLOT(nxmTrackedModsAvailable(QVariant, QVariant, int)), Qt::UniqueConnection); + + connect(this, SIGNAL(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)), + receiver, SLOT(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)), Qt::UniqueConnection); + + nextRequest(); + return requestInfo.m_ID; +} + +int NexusInterface::requestToggleTracking(QString gameName, int modID, bool track, QObject *receiver, QVariant userData, + const QString &subModule, MOBase::IPluginGame const *game) +{ + if (std::max(m_RemainingDailyRequests, m_RemainingHourlyRequests) >= 200) { + NXMRequestInfo requestInfo(modID, NXMRequestInfo::TYPE_TOGGLETRACKING, userData, subModule, game); + requestInfo.m_Track = track; + m_RequestQueue.enqueue(requestInfo); + + connect(this, SIGNAL(nxmTrackingToggled(QString, int, QVariant, bool, int)), + receiver, SLOT(nxmTrackingToggled(QString, int, QVariant, bool, int)), Qt::UniqueConnection); + + connect(this, SIGNAL(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)), + receiver, SLOT(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)), Qt::UniqueConnection); + + nextRequest(); + return requestInfo.m_ID; + } + qCritical() << QString("You have fewer than 200 requests remaining (%1). Only downloads and login validation are being allowed.") + .arg(std::max(m_RemainingDailyRequests, m_RemainingHourlyRequests)); + return -1; +} + IPluginGame* NexusInterface::getGame(QString gameName) const { auto gamePlugins = m_PluginContainer->plugins(); @@ -555,6 +615,7 @@ void NexusInterface::nextRequest() QJsonObject postObject; QJsonDocument postData(postObject); + bool requestIsDelete = false; QString url; if (!info.m_Reroute) { @@ -603,6 +664,15 @@ void NexusInterface::nextRequest() postObject.insert("Version", info.m_ModVersion); postData.setObject(postObject); } break; + case NXMRequestInfo::TYPE_TOGGLETRACKING: { + url = QStringLiteral("%1/user/tracked_mods?domain_name=%2").arg(info.m_URL).arg(info.m_GameName); + postObject.insert("mod_id", info.m_ModID); + postData.setObject(postObject); + requestIsDelete = !info.m_Track; + } break; + case NXMRequestInfo::TYPE_TRACKEDMODS: { + url = QStringLiteral("%1/user/tracked_mods").arg(info.m_URL); + } break; } } else { url = info.m_URL; @@ -617,10 +687,18 @@ void NexusInterface::nextRequest() request.setRawHeader("Application-Name", "MO2"); request.setRawHeader("Application-Version", QApplication::applicationVersion().toUtf8()); - if (postData.object().isEmpty()) - info.m_Reply = m_AccessManager->get(request); - else + if (postData.object().isEmpty()) { + if (!requestIsDelete) { + info.m_Reply = m_AccessManager->get(request); + } else { + info.m_Reply = m_AccessManager->deleteResource(request); + } + } else if (!requestIsDelete) { info.m_Reply = m_AccessManager->post(request, postData.toJson()); + } else { + // Qt doesn't support DELETE with a payload as that's technically against the HTTP standard... + info.m_Reply = m_AccessManager->sendCustomRequest(request, "DELETE", postData.toJson()); + } connect(info.m_Reply, SIGNAL(finished()), this, SLOT(requestFinished())); connect(info.m_Reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(requestError(QNetworkReply::NetworkError))); @@ -701,6 +779,20 @@ void NexusInterface::requestFinished(std::list::iterator iter) case NXMRequestInfo::TYPE_TOGGLEENDORSEMENT: { emit nxmEndorsementToggled(iter->m_GameName, iter->m_ModID, iter->m_UserData, result, iter->m_ID); } break; + case NXMRequestInfo::TYPE_TOGGLETRACKING: { + auto results = result.toMap(); + auto message = results["message"].toString(); + if (message.contains(QRegularExpression("User [0-9]+ is already Tracking Mod: [0-9]+")) || + message.contains(QRegularExpression("User [0-9]+ is now Tracking Mod: [0-9]+"))) { + emit nxmTrackingToggled(iter->m_GameName, iter->m_ModID, iter->m_UserData, true, iter->m_ID); + } else if (message.contains(QRegularExpression("User [0-9]+ is no longer tracking [0-9]+")) || + message.contains(QRegularExpression("Users is not tracking mod. Unable to untrack."))) { + emit nxmTrackingToggled(iter->m_GameName, iter->m_ModID, iter->m_UserData, false, iter->m_ID); + } + } break; + case NXMRequestInfo::TYPE_TRACKEDMODS: { + emit nxmTrackedModsAvailable(iter->m_UserData, result, iter->m_ID); + } break; } m_RemainingDailyRequests = reply->rawHeader("x-rl-daily-remaining").toInt(); diff --git a/src/nexusinterface.h b/src/nexusinterface.h index c1f9da41..a0f94562 100644 --- a/src/nexusinterface.h +++ b/src/nexusinterface.h @@ -98,6 +98,13 @@ public: */ virtual void requestToggleEndorsement(QString gameName, int modID, QString modVersion, bool endorse, QVariant userData); + /** + * @brief requestToggleTracking + * @param modID id of the mod caller is interested in + * @param userData user data to be returned with the result + */ + virtual void requestToggleTracking(QString gameName, int modID, bool track, QVariant userData); + public slots: void nxmDescriptionAvailable(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID); @@ -106,6 +113,8 @@ public slots: void nxmDownloadURLsAvailable(QString gameName, int modID, int fileID, QVariant userData, QVariant resultData, int requestID); void nxmEndorsementsAvailable(QVariant userData, QVariant resultData, int requestID); void nxmEndorsementToggled(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID); + void nxmTrackedModsAvailable(QVariant userData, QVariant resultData, int requestID); + void nxmTrackingToggled(QString gameName, int modID, QVariant userData, bool tracked, int requestID); void nxmRequestFailed(QString gameName, int modID, int fileID, QVariant userData, int requestID, QNetworkReply::NetworkError error, const QString &errorMessage); private: @@ -330,6 +339,36 @@ public: int requestToggleEndorsement(QString gameName, int modID, QString modVersion, bool endorse, QObject *receiver, QVariant userData, const QString &subModule, MOBase::IPluginGame const *game); + int requestTrackingInfo(QObject *receiver, QVariant userData, const QString &subModule); + + /** + * @param gameName the game short name to support multiple game sources + * @brief toggle tracking state of the mod + * @param modID id of the mod + * @param track true if the mod should be tracked, false for not tracked + * @param receiver the object to receive the result asynchronously via a signal (nxmFilesAvailable) + * @param userData user data to be returned with the result + * @param game the game with which the mods are associated + * @return int an id to identify the request + */ + int requestToggleTracking(QString gameName, int modID, bool track, QObject *receiver, QVariant userData, const QString &subModule) + { + return requestToggleTracking(gameName, modID, track, receiver, userData, subModule, getGame(gameName)); + } + + /** + * @param gameName the game short name to support multiple game sources + * @brief toggle tracking state of the mod + * @param modID id of the mod + * @param track true if the mod should be tracked, false for not tracked + * @param receiver the object to receive the result asynchronously via a signal (nxmFilesAvailable) + * @param userData user data to be returned with the result + * @param game the game with which the mods are associated + * @return int an id to identify the request + */ + int requestToggleTracking(QString gameName, int modID, bool track, QObject *receiver, QVariant userData, const QString &subModule, + MOBase::IPluginGame const *game); + /** * @param directory the directory to store cache files **/ @@ -402,6 +441,8 @@ signals: void nxmDownloadURLsAvailable(QString gameName, int modID, int fileID, QVariant userData, QVariant resultData, int requestID); void nxmEndorsementsAvailable(QVariant userData, QVariant resultData, int requestID); void nxmEndorsementToggled(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID); + void nxmTrackedModsAvailable(QVariant userData, QVariant resultData, int requestID); + void nxmTrackingToggled(QString gameName, int modID, QVariant userData, bool tracked, int requestID); void nxmRequestFailed(QString gameName, int modID, int fileID, QVariant userData, int requestID, QNetworkReply::NetworkError error, const QString &errorString); void requestsChanged(int queueCount, std::tuple requestsRemaining); @@ -436,7 +477,9 @@ private: TYPE_ENDORSEMENTS, TYPE_TOGGLEENDORSEMENT, TYPE_GETUPDATES, - TYPE_CHECKUPDATES + TYPE_CHECKUPDATES, + TYPE_TOGGLETRACKING, + TYPE_TRACKEDMODS, } m_Type; UpdatePeriod m_UpdatePeriod; QVariant m_UserData; @@ -448,6 +491,7 @@ private: bool m_Reroute; int m_ID; int m_Endorse; + int m_Track; NXMRequestInfo(int modID, Type type, QVariant userData, const QString &subModule, MOBase::IPluginGame const *game); NXMRequestInfo(int modID, QString modVersion, Type type, QVariant userData, const QString &subModule, MOBase::IPluginGame const *game); diff --git a/src/organizer_en.ts b/src/organizer_en.ts index 7f8d3a9b..c8e7c286 100644 --- a/src/organizer_en.ts +++ b/src/organizer_en.ts @@ -829,58 +829,58 @@ File %3: %4 - + Information updated - - + + No matching file found on Nexus! Maybe this file is no longer available or it was renamed? - + No file on Nexus matches the selected file by name. Please manually choose the correct one. - + No download server available. Please try again later. - + Failed to request file info from nexus: %1 - + Warning: Content type is: %1 - + Download header content length: %1 downloaded file size: %2 - + Download failed: %1 (%2) - + We were unable to download the file due to errors after four retries. There may be an issue with the Nexus servers. - + failed to re-open %1 - + Unable to write download to drive (return %1). Check the drive's available storage. @@ -1497,7 +1497,7 @@ This is likely due to a corrupted or incompatible download or unrecognized archi MainWindow - + Categories @@ -1563,20 +1563,20 @@ p, li { white-space: pre-wrap; } - + Restore Backup... - - + + Create Backup - + Active: @@ -1586,60 +1586,60 @@ p, li { white-space: pre-wrap; } - + List of available mods. - + This is a list of installed mods. Use the checkboxes to activate/deactivate mods and drag & drop mods to change their "installation" orders. - - - - + + + + Filter - + Nexus API Queued and Remaining Requests - + <html><head/><body><p>This tracks the number of queued Nexus API requests on the left (<span style=" font-weight:600;">Q</span>) and the remaining daily (<span style=" font-weight:600;">D</span>) and hourly (<span style=" font-weight:600;">H</span>) requests on the right. The Nexus API limits you to a pool of requests per day and requests per hour. It is dynamically updated every time a request is completed. If you run out of requests, you will be unable to queue downloads, check updates, parse mod info, or even log in. Both pools must be consumed before this happens.</p></body></html> - + API: Q: 0 | D: 0 | H: 0 - + Clear all Filters - + No groups - + Nexus IDs - + Pick a program to run. - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -1649,12 +1649,12 @@ p, li { white-space: pre-wrap; } - + Run program - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -1663,17 +1663,17 @@ p, li { white-space: pre-wrap; } - + Run - + Create a shortcut in your start menu or on the desktop to the specified program - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -1682,32 +1682,32 @@ p, li { white-space: pre-wrap; } - + Shortcut - + Plugins - + Sort - + This provides statistics about the plugin list. The total number of active plugins is normally displayed. Other statistics may be accessed with the tooltip of this counter. - + List of available esp/esm files - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -1716,27 +1716,27 @@ p, li { white-space: pre-wrap; } - + Archives - + <html><head/><body><p>BSAs / BA2s are bundles of game assets (textures, scripts, etc.). By default, the engine loads these bundles in a separate step from loose files. <p>Their load order is specified by the priority of the corresponding plugin (right pane, plugins tab).</p><p>If there is a matching plugin, the game will load them no matter what.</p></body></html> - + <html><head/><body><p>Currently detected archives. (<a href="#"><span style=" text-decoration: underline; color:#0000ff;">What is an archive?</span></a>)</p></body></html> - + List of available BS Archives. Archives not checked here are not managed by MO and ignore installation order. - + BSA files are archives (comparable to .zip files) that contain data assets (meshes, textures, ...) to be used by the game. As such they "compete" with loose files in your data directory over which is loaded. By default, BSAs that share their base name with an enabled ESP (i.e. plugin.esp and plugin.bsa) are automatically loaded and will have precedence over all loose files, the installation order you set up to the left is then ignored! @@ -1744,72 +1744,72 @@ p, li { white-space: pre-wrap; } - + Data - + refresh data-directory overview - + Refresh the overview. This may take a moment. - - - - + + + + Refresh - + This is an overview of your data directory as visible to the game (and tools). - + File - + Mod - - + + Filters the above list so that only conflicts are displayed. - + Show only conflicts - - + + Filters the above list so that files from archives are not shown - + Show files from Archives - + Saves - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -1820,197 +1820,197 @@ p, li { white-space: pre-wrap; } - + Downloads - + Refresh downloads view - + This is a list of mods you downloaded from Nexus. Double click one to install it. You can also drag an archive into here. - + Show Hidden - + Tool Bar - + Install Mod - + Install &Mod - + Install a new mod from an archive - + Ctrl+M - + Profiles - + &Profiles - + Configure Profiles - + Ctrl+P - + Executables - + &Executables - + Configure the executables that can be started through Mod Organizer - + Ctrl+E - - + + Tools - + &Tools - + Ctrl+I - + Settings - + &Settings - + Configure settings and workarounds - + Ctrl+S - + Nexus - + Search nexus network for more mods - + Ctrl+N - - + + Update - + Mod Organizer is up-to-date - - + + No Notifications - + This button will be highlighted if MO discovered potential problems in your setup and provide tips on how to fix them. - - + + Help - + Ctrl+H - + Endorse MO - - + + Endorse Mod Organizer - + Copy Log to Clipboard - + Change Game - + Open the Instance selection dialog to manage a different Game @@ -2035,860 +2035,860 @@ p, li { white-space: pre-wrap; } - + Crash on exit - + MO crashed while exiting. Some settings may not be saved. Error: %1 - + Notifications - + There are notifications to read - + There are no notifications - - - + + + Endorse - + Won't Endorse - + Help on UI - + Documentation - + Chat on Discord - + Report Issue - + Tutorials - + About - + About Qt - + Name - + Please enter a name for the new profile - + failed to create profile: %1 - + Show tutorial? - + You are starting Mod Organizer for the first time. Do you want to show a tutorial of its basic features? If you choose no you can always start the tutorial from the "Help"-menu. - + Downloads in progress - + There are still downloads in progress, do you really want to quit? - + Plugin "%1" failed: %2 - + Plugin "%1" failed - + Browse Mod Page - + Also in: <br> - + No conflict - + <Edit...> - + This bsa is enabled in the ini file so it may be required! - + Activating Network Proxy - + Notice: Your current MO version (%1) is lower than the previously used one (%2). The GUI may not downgrade gracefully, so you may experience oddities. However, there should be no serious issues. - + Choose Mod - + Mod Archive - + Start Tutorial? - + You're about to start a tutorial. For technical reasons it's not possible to end the tutorial early. Continue? - + failed to spawn notepad.exe: %1 - + failed to change origin name: %1 - + failed to move "%1" from mod "%2" to "%3": %4 - + <Contains %1> - + <Checked> - + <Unchecked> - + <Update> - + <Mod Backup> - + <Managed by MO> - + <Managed outside MO> - + <No category> - + <Conflicted> - + <Not Endorsed> - + failed to rename mod: %1 - + Overwrite? - + This will replace the existing mod "%1". Continue? - + failed to remove mod "%1" - - - + + + failed to rename "%1" to "%2" - - - - + + + + Confirm - + Remove the following mods?<br><ul>%1</ul> - + failed to remove mod: %1 - - - + + + Failed - + Installation file no longer exists - + Mods installed with old versions of MO can't be reinstalled in this way. - + Failed to create backup. - + You need to be logged in with Nexus to resume a download - - - - + + + + You need to be logged in with Nexus to endorse - - + + Endorsing multiple mods will take a while. Please wait... - - + + Unendorsing multiple mods will take a while. Please wait... - + Failed to display overwrite dialog: %1 - + Opening Nexus Links - + You are trying to open %1 links to Nexus Mods. Are you sure you want to do this? - + Nexus ID for this Mod is unknown - + Opening Web Pages - + You are trying to open %1 Web Pages. Are you sure you want to do this? - + Web page for this mod is unknown - + <table cellspacing="5"><tr><th>Type</th><th>All</th><th>Visible</th><tr><td>Enabled mods:&emsp;</td><td align=right>%1 / %2</td><td align=right>%3 / %4</td></tr><tr><td>Unmanaged/DLCs:&emsp;</td><td align=right>%5</td><td align=right>%6</td></tr><tr><td>Mod backups:&emsp;</td><td align=right>%7</td><td align=right>%8</td></tr><tr><td>Separators:&emsp;</td><td align=right>%9</td><td align=right>%10</td></tr></table> - + <table cellspacing="6"><tr><th>Type</th><th>Active </th><th>Total</th></tr><tr><td>All plugins:</td><td align=right>%1 </td><td align=right>%2</td></tr><tr><td>ESMs:</td><td align=right>%3 </td><td align=right>%4</td></tr><tr><td>ESPs:</td><td align=right>%7 </td><td align=right>%8</td></tr><tr><td>ESMs+ESPs:</td><td align=right>%9 </td><td align=right>%10</td></tr><tr><td>ESLs:</td><td align=right>%5 </td><td align=right>%6</td></tr></table> - - - + + + Create Mod... - + This will create an empty mod. Please enter a name: - - + + A mod with this name already exists - + Create Separator... - + This will create a new separator. Please enter a name: - + A separator with this name already exists - + This will move all files from overwrite into a new, regular mod. Please enter a name: - + Move successful. - - + + Are you sure? - + About to recursively delete: - + Continue? - + The versioning scheme decides which version is considered newer than another. This function will guess the versioning scheme under the assumption that the installed version is outdated. - - + + Sorry - + I don't know a versioning scheme where %1 is newer than %2. - + Really enable all visible mods? - + Really disable all visible mods? - + Export to csv - + CSV (Comma Separated Values) is a format that can be imported in programs like Excel to create a spreadsheet. You can also use online editors and converters instead. - + Select what mods you want export: - + All installed mods - + Only active (checked) mods from your current profile - + All currently visible mods in the mod list - + Choose what Columns to export: - + Mod_Priority - + Mod_Name - + Notes_column - + Mod_Status - + Primary_Category - + Nexus_ID - + Mod_Nexus_URL - + Mod_Version - + Install_Date - + Download_File_Name - + export failed: %1 - + Open Game folder - + Open MyGames folder - + Open INIs folder - + Open Instance folder - + Open Mods folder - + Open Profile folder - + Open Downloads folder - + Open MO2 Install folder - + Open MO2 Plugins folder - + Open MO2 Logs folder - + Install Mod... - + Create empty mod - + Create Separator - + Enable all visible - + Disable all visible - + Check for updates - + Export to csv... - - + + Send to - - + + Top - - + + Bottom - - + + Priority... - + Separator... - + All Mods - + Sync to Mods... - + Move content to Mod... - + Clear Overwrite... - - + + Open in Explorer - + Restore Backup - + Remove Backup... - - + + Change Categories - - + + Primary Category - + Rename Separator... - + Remove Separator... - + Select Color... - + Reset Color - + Change versioning scheme - + Force-check updates - + Un-ignore update - + Ignore update - - + + Enable selected - - + + Disable selected - + Rename Mod... - + Reinstall Mod - + Remove Mod... - + Un-Endorse - + Won't endorse - + Endorsement state unknown - + Ignore missing data - + Mark as converted/working - + Visit on Nexus - + Visit web page - + Information... - - + + Exception: - - + + Unknown exception - + <All> - + <Multiple> - + %1 more - + Are you sure you want to remove the following %n save(s)?<br><ul>%1</ul><br>Removed saves will be sent to the Recycle Bin. @@ -2896,12 +2896,12 @@ You can also use online editors and converters instead. - + Enable Mods... - + Delete %n save(s) @@ -2909,22 +2909,22 @@ You can also use online editors and converters instead. - + failed to remove %1 - + failed to create %1 - + Restarting MO - + Changing the managed game directory requires restarting MO. Any pending downloads will be paused. @@ -2932,373 +2932,373 @@ Click OK to restart MO now. - + Can't change download directory while downloads are in progress! - + failed to write to file %1 - + %1 written - + Select binary - + Binary - + Enter Name - + Please enter a name for the executable - + Not an executable - + This is not a recognized executable. - - + + Replace file? - + There already is a hidden version of this file. Replace it? - - + + File operation failed - - + + Failed to remove "%1". Maybe you lack the required file permissions? - + There already is a visible version of this file. Replace it? - - + + Set Priority - + Set the priority of the selected plugins - + file not found: %1 - + failed to generate preview for %1 - + Sorry, can't preview anything. This function currently does not support extracting from bsas. - + Update available - + Open/Execute - + Add as Executable - + Preview - + Un-Hide - + Hide - + Write To File... - + Do you want to endorse Mod Organizer on %1 now? - + Abstain from Endorsing Mod Organizer - + Are you sure you want to abstain from endorsing Mod Organizer 2? You will have to visit the mod page on the %1 Nexus site to change your mind. - - + + Thank you for endorsing MO2! :) - - + + Please reconsider endorsing MO2 on Nexus! - + Thank you! - + Thank you for your endorsement! - + Okay. - + This mod will not be endorsed and will no longer ask you to endorse. - + Mod ID %1 no longer seems to be available on Nexus. - + Request to Nexus failed: %1 - - + + failed to read %1: %2 - - + + Error - + failed to extract %1 (errorcode %2) - + Extract BSA - + This archive contains invalid hashes. Some files may be broken. - + Extract... - + This will restart MO, continue? - + Edit Categories... - + Deselect filter - + Remove - + Enable all - + Disable all - + Unlock load order - + Lock load order - + Open Origin in Explorer - + Open Origin Info... - + depends on missing "%1" - + incompatible with "%1" - + Please wait while LOOT is running - + loot failed. Exit code was: %1 - + failed to start loot - + failed to run loot: %1 - + Errors occured - + Backup of load order created - + Choose backup to restore - + No Backups - + There are no backups to restore - - + + Restore failed - - + + Failed to restore the backup. Errorcode: %1 - + Backup of modlist created - + A file with the same name has already been downloaded. What would you like to do? - + Overwrite - + Rename new file - + Ignore file - + Set the priority of the selected mods @@ -4043,18 +4043,18 @@ p, li { white-space: pre-wrap; } ModInfoRegular - + failed to write %1/meta.ini: error %2 - + %1 contains no esp/esm/esl and no asset (textures, meshes, interface, ...) directory - + Categories: <br> @@ -4205,178 +4205,183 @@ p, li { white-space: pre-wrap; } - + + Mod is being tracked on the website + + + + Non-MO - + invalid - + installed version: "%1", newest version: "%2" - + 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". - + This file has been marked as "Old". There is most likely an updated version of this file available. - + This file has been marked as "Deleted"! You may want to check for an update or remove the nexus ID from this mod! - + %1 minute(s) and %2 second(s) - + This mod will be available to check in %2. - + Categories: <br> - + Invalid name - + Name is already in use by another mod - + drag&drop failed: %1 - + Confirm - + Are you sure you want to remove "%1"? - + Flags - + Content - + Mod Name - + Version - + Priority - + Category - + Source Game - + Nexus ID - + Installation - + Notes - - + + unknown - + Name of your mods - + Version of the mod (if available) - + Installation priority of your mod. The higher, the more "important" it is and thus overwrites files from mods with lower priority. - + Category of the mod. - + The source game which was the origin of this mod. - + Id of the mod as used on Nexus. - + Emblemes to highlight things that might require attention. - + 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> - + Time this mod was installed - + User notes about the mod @@ -4418,37 +4423,37 @@ p, li { white-space: pre-wrap; } NXMAccessManager - + Validating Nexus Connection - + Verifying Nexus login - + There was a timeout during the request - + Unknown error - + Validation failed, please reauthenticate in the Settings -> Nexus tab: %1 - + Could not parse response. Invalid JSON. - + Unknown error. @@ -4464,17 +4469,17 @@ p, li { white-space: pre-wrap; } NexusInterface - + Failed to guess mod id for "%1", please pick the correct one - + empty response - + invalid response @@ -4482,234 +4487,234 @@ p, li { white-space: pre-wrap; } OrganizerCore - - + + Failed to write settings - + An error occured trying to update MO settings to %1: %2 - + File is write protected - + Invalid file format (probably a bug) - + Unknown error %1 - + An error occured trying to write back MO settings to %1: %2 - - + + Download started - + Download failed - - - - + + + + Installation cancelled - - + + Another installation is currently in progress. - - + + Installation successful - - + + Configure Mod - - + + This mod contains ini tweaks. Do you want to configure them now? - - + + mod not found: %1 - - + + The mod was not installed completely. - + Executable not found: %1 - + Start Steam? - + Steam is required to be running already to correctly start the game. Should MO try to start steam now? - + Steam: Access Denied - + MO was denied access to the Steam process. This normally indicates that Steam is being run as administrator while MO is not. This can cause issues launching the game. It is recommended to not run Steam as administrator unless absolutely neccessary. Restart MO as administrator? - + Error - + Windows Event Log Error - + The Windows Event Log service is disabled and/or not running. This prevents USVFS from running properly. Your mods may not be working in the executable that you are launching. Note that you may have to restart MO and/or your PC after the service is fixed. Continue launching %1? - + Blacklisted Executable - + The executable you are attempted to launch is blacklisted in the virtual file system. This will likely prevent the executable, and any executables that are launched by this one, from seeing any mods. This could extend to INI files, save games and any other virtualized files. Continue launching %1? - + No profile set - + Failed to refresh list of esps: %1 - + Multiple esps/esls activated, please check that they don't conflict. - + Download? - + A download has been started but no installed page plugin recognizes it. If you download anyway no information (i.e. version) will be associated with the download. Continue? - - + + failed to update mod list: %1 - - + + login successful - + Login failed - + Login failed, try again? - + login failed: %1. Download will not be associated with an account - + login failed: %1 - + login failed: %1. You need to log-in with Nexus to update MO. - + MO1 "Script Extender" load mechanism has left hook.dll in your game folder - - + + Description missing - + <a href="%1">hook.dll</a> has been found in your game folder (right click to copy the full path). This is most likely a leftover of setting the ModOrganizer 1 load mechanism to "Script Extender", in which case you must remove this file either by changing the load mechanism in ModOrganizer 1 or manually removing the file, otherwise the game is likely to crash and burn. - + failed to save load order: %1 - + The designated write target "%1" is not enabled. @@ -5367,7 +5372,7 @@ p, li { white-space: pre-wrap; } - + Error @@ -5689,7 +5694,7 @@ If the folder was still in use, restart MO and try again. - + Failed to create "%1". Your user account probably lacks permission. @@ -5711,59 +5716,59 @@ If the folder was still in use, restart MO and try again. - + Canceled finding game in "%1". - + No game identified in "%1". The directory is required to contain the game binary. - + Please select the game edition you have (MO can't start the game correctly if this is set incorrectly!) - + failed to start shortcut: %1 - + failed to start application: %1 - - + + Mod Organizer - + An instance of Mod Organizer is already running - + Failed to set up instance - + Please use "Help" from the toolbar to get usage instructions to all elements - - + + <Manage...> - + failed to parse profile %1: %2 @@ -5814,12 +5819,12 @@ If the folder was still in use, restart MO and try again. - + Script Extender - + Proxy DLL @@ -5966,58 +5971,58 @@ You will be asked if you want to allow helper.exe to make changes to the system. - + New update available (%1) - + Do you want to install update? All your mods and setup will be left untouched. Select Show Details option to see the full change-log. - + Install - + Download failed - + Failed to find correct download, please try again later. - + Update - + Download in progress - + Download failed: %1 - + Failed to install update: %1 - + Failed to start %1: %2 - + Error @@ -6035,35 +6040,35 @@ Select Show Details option to see the full change-log. - - + + attempt to store setting for unknown plugin "%1" - - + + Error - + Failed to retrieve a Nexus API key! Please try again. A browser window should open asking you to authorize. Failed to retrieve a Nexus API key! Please try again.A browser window should open asking you to authorize. - + Failed to create "%1", you may not have the necessary permission. path remains unchanged. - + Restart Mod Organizer? - + In order to reset the window geometries, MO must be restarted. Restart it now? @@ -6391,92 +6396,103 @@ p, li { white-space: pre-wrap; } - + + + <html><head/><body><p>By default, a counter is displayed under the mod list. This informs the user of their remaining API requests. The Nexus API becomes unusable once these API requests run out. Checking this option will hide that counter.</p></body></html> + + + + + Hide API Request Counter + + + + Associate with "Download with manager" links - + Known Servers (updated on download) - + Preferred Servers (Drag & Drop) - + Steam - + Username - + Password - + If you save your steam user ID and password here, they will be used when logging into steam. Note, however, your password will be stored unencrypted, so make sure your computer is secure. - + Plugins - + Author: - + Version: - + Description: - + Key - + Value - + Blacklisted Plugins (use <del> to remove): - + Workarounds - + Steam App ID - + The Steam AppID for your game - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -6492,17 +6508,17 @@ p, li { white-space: pre-wrap; } - + Load Mechanism - + Select loading mechanism. See help for details. - + Mod Organizer needs a dll to be injected into the game so all mods are visible to it. There are several means to do this: *Mod Organizer* (default) In this mode the Mod Organizer itself injects the dll. The disadvantage is that you always have to start the game through MO or a link created by it. @@ -6513,47 +6529,28 @@ If you use the Steam version of Oblivion the default will NOT work. In this case - - NMM Version - - - - - The Version of Nexus Mod Manager to impersonate. - - - - - Mod Organizer uses an API provided by the Nexus to provide features like checking for updates and downloading files. Unfortunately this API has not been made available officially to third party tools like MO so we have to impersonate the Nexus Mod Manager to be allowed in. -On top of this Nexus has used the client identification to lock out outdated versions of NMM to force users to update. This means that MO also needs to impersonate the new version of NMM even if MO doesn't need an update. Therefore you can configure the version to identify as here. -Please note that MO does identify itself as MO to the webserver, it's not lying about what it is. It is merely adding a "compatible" NMM version to the user agent. - -tl;dr-version: If Nexus-features don't work, insert the current version number of NMM here and try again. - - - - + Enforces that inactive ESPs and ESMs are never loaded. - + It seems that the Games occasionally load ESP or ESM files even if they haven't been activated as plugins. I don't yet know what the circumstances are, but user reports imply it is in some cases unwanted. If this is checked, ESPs and ESMs not checked in the List are invisible to the game and can not be loaded. - + Hide inactive ESPs/ESMs - + Disable this to no longer display mods installed outside MO in the mod list (left pane). Assets from those mods will then be treated as having lowest mod priority together with the original game content. - + By default Mod Organizer will display esp+bsa bundles installed with foreign tools as mods (left pane). This allows you to control their priority in relation to other mods. This is particularly useful if you also use Steam Workshop to install mods. However, if you installed loose file mods outside MO which conflict with BSAs also installed outside MO those conflicts can't be resolved correctly. @@ -6561,66 +6558,66 @@ If you disable this feature, MO will only display official DLCs this way. Please - + Display mods installed outside MO - + If checked, files (i.e. esps, esms and bsas) belonging to the core game can not be disabled in the UI. (default: on) - + If checked, files (i.e. esps, esms and bsas) belonging to the core game can not be disabled in the UI. (default: on) Uncheck this if you want to use Mod Organizer with total conversions (like Nehrim) but be aware that the game will crash if required files are not enabled. - + Force-enable game files - - + + Disable this to prevent the GUI from being locked when running an executable. This may result in abnormal behavior. - + Lock GUI when running executable - + Enable parsing of Archives. This is an Experimental Feature. Has negative effects on performance and known incorrectness. - + <html><head/><body><p>By default, MO will parse archive files (BSA, BA2) to calculate conflicts between the contents of the archive files and other loose files. This process has a noticeable cost in performance.</p><p>This feature should not be confused with the archive management feature offered by MO1. MO2 will only show conflicts with archives and will NOT load them into the game or program.</p><p>If you disable this feature, MO will only display conflicts between loose files.</p></body></html> - + Enable parsing of Archives (Experimental Feature) - - + + For Skyrim, this can be used instead of Archive Invalidation. It should make AI redundant for all Profiles. For the other games this is not a sufficient replacement for AI! - + Back-date BSAs - + Add executables to the blacklist to prevent them from accessing the virtual file system. This is useful to prevent unintended programs from being hooked. Hooking unintended @@ -6629,48 +6626,48 @@ programs you are intentionally running. - + Add executables to the blacklist to prevent them from accessing the virtual file system. This is useful to prevent unintended programs from being hooked. Hooking unintended programs may affect the execution of these programs or the programs you are intentionally running. - + Configure Executables Blacklist - - + + Resets the window geometries for all windows. This can be useful if a window becomes too small or too large, if a column becomes too thin or too wide, and in similar situations. - + Reset Window Geometries - + These are workarounds for problems with Mod Organizer. Please make sure you read the help text before changing anything here. - + Diagnostics - + Max Dumps To Keep - + Maximum number of crash dumps to keep on disk. Use 0 for unlimited. - + Maximum number of crash dumps to keep on disk. Use 0 for unlimited. Set "Crash Dumps" above to None to disable crash dump collection. @@ -6678,12 +6675,12 @@ programs you are intentionally running. - + Hint: right click link and copy link location - + Logs and crash dumps are stored under your current instance in the <a href="LOGS_FULL_PATH">LOGS_DIR</a> and <a href="DUMPS_FULL_PATH">DUMPS_DIR</a> folders. @@ -6693,17 +6690,17 @@ programs you are intentionally running. - + Crash Dumps - + Decides which type of crash dumps are collected when injected processes crash. - + Decides which type of crash dumps are collected when injected processes crash. "None" Disables the generation of crash dumps by MO. @@ -6714,37 +6711,37 @@ programs you are intentionally running. - + None - + Mini (recommended) - + Data - + Full - + Log Level - + Decides the amount of data printed to "ModOrganizer.log" - + Decides the amount of data printed to "ModOrganizer.log". "Debug" produces very useful information for finding problems. There is usually no noteworthy performance impact but the file may become rather large. If this is a problem you may prefer the "Info" level for regluar use. On the "Error" level the log file usually remains empty. @@ -6752,22 +6749,22 @@ programs you are intentionally running. - + Debug - + Info (recommended) - + Warning - + Error @@ -6782,12 +6779,12 @@ programs you are intentionally running. - + Executables Blacklist - + Enter one executable per line to be blacklisted from the virtual file system. Mods and other virtualized files will not be visible to these executables and any executables launched by them. @@ -6798,47 +6795,47 @@ Example: - + Select base directory - + Select download directory - + Select mod directory - + Select cache directory - + Select profiles directory - + Select overwrite directory - + Select game executable - + Confirm? - + This will make all dialogs show up again where you checked the "Remember selection"-box. Continue? diff --git a/src/resources.qrc b/src/resources.qrc index 5ed1780e..8645b27e 100644 --- a/src/resources.qrc +++ b/src/resources.qrc @@ -82,6 +82,7 @@ resources/archive-conflict-winner.png resources/game-warning.png resources/game-warning-16.png + resources/tracked.png resources/contents/jigsaw-piece.png diff --git a/src/resources/tracked.png b/src/resources/tracked.png new file mode 100644 index 00000000..11b1e8c8 Binary files /dev/null and b/src/resources/tracked.png differ -- cgit v1.3.1