From 38391b10e968d22f0e5c731642407412178f29b5 Mon Sep 17 00:00:00 2001 From: Silarn Date: Thu, 10 May 2018 09:21:02 -0500 Subject: [PATCH 01/67] Fix plugin lock slot typo --- src/mainwindow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 2830c776a..5b6525bdc 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -4637,7 +4637,7 @@ void MainWindow::on_espList_customContextMenuRequested(const QPoint &pos) menu.addAction(tr("Unlock load order"), this, SLOT(unlockESPIndex())); } if (hasUnlocked) { - menu.addAction(tr("Lock load order"), this, SLOT(f())); + menu.addAction(tr("Lock load order"), this, SLOT(lockESPIndex())); } try { From aeb0b42cde85c8075918d564e8ca9e5652b2c39d Mon Sep 17 00:00:00 2001 From: Silarn Date: Thu, 10 May 2018 11:21:27 -0500 Subject: [PATCH 02/67] Refresh the sort proxy when the data is changed --- src/modlist.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/modlist.cpp b/src/modlist.cpp index 2d58081df..bf57ef6eb 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -699,7 +699,9 @@ void ModList::modInfoChanged(ModInfo::Ptr info) 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"); } From decbbb611edf002bc1458a651c331d1832ed2a6a Mon Sep 17 00:00:00 2001 From: Silarn Date: Sun, 13 May 2018 18:32:59 -0500 Subject: [PATCH 03/67] Download resume fixes for restarting MO, hung downloads --- src/downloadmanager.cpp | 19 ++++++++++++++++++- src/downloadmanager.h | 4 ++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index bfc4de3ea..55801e812 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -70,6 +70,7 @@ DownloadManager::DownloadInfo *DownloadManager::DownloadInfo::createNew(const Mo info->m_Tries = AUTOMATIC_RETRIES; info->m_State = STATE_STARTED; info->m_TaskProgressId = TaskProgressManager::instance().getId(); + info->m_Reply = nullptr; return info; } @@ -136,6 +137,7 @@ DownloadManager::DownloadInfo *DownloadManager::DownloadInfo::createFromMeta(con info->m_FileInfo->fileCategory = metaFile.value("fileCategory", 0).toInt(); info->m_FileInfo->repository = metaFile.value("repository", "Nexus").toString(); info->m_FileInfo->userData = metaFile.value("userData").toMap(); + info->m_Reply = nullptr; return info; } @@ -182,6 +184,9 @@ DownloadManager::DownloadManager(NexusInterface *nexusInterface, QObject *parent m_DateExpression("/Date\\((\\d+)\\)/") { connect(&m_DirWatcher, SIGNAL(directoryChanged(QString)), this, SLOT(directoryChanged(QString))); + m_TimeoutTimer.setSingleShot(false); + connect(&m_TimeoutTimer, SIGNAL(timeout()), this, SLOT(checkDownloadTimeout())); + m_TimeoutTimer.start(5 * 1000); } @@ -695,7 +700,7 @@ void DownloadManager::resumeDownloadInt(int index) DownloadInfo *info = m_ActiveDownloads[index]; // Check for finished download; - if (info->m_TotalSize <= info->m_Output.size()) { + if (info->m_TotalSize <= info->m_Output.size() && info->m_Reply != nullptr) { setState(info, STATE_DOWNLOADING); downloadFinished(index); return; @@ -1632,3 +1637,15 @@ void DownloadManager::managedGameChanged(MOBase::IPluginGame const *managedGame) { m_ManagedGame = managedGame; } + +void DownloadManager::checkDownloadTimeout() +{ + for (int i = 0; i < m_ActiveDownloads.size(); ++i) { + if (m_ActiveDownloads[i]->m_StartTime.elapsed() - std::get<3>(m_ActiveDownloads[i]->m_SpeedDiff) > 5 * 1000 && + m_ActiveDownloads[i]->m_State == STATE_DOWNLOADING) { + pauseDownload(i); + downloadFinished(i); + resumeDownload(i); + } + } +} \ No newline at end of file diff --git a/src/downloadmanager.h b/src/downloadmanager.h index 98f5e4687..17ae8a359 100644 --- a/src/downloadmanager.h +++ b/src/downloadmanager.h @@ -29,6 +29,7 @@ along with Mod Organizer. If not, see . #include #include #include +#include #include #include #include @@ -443,6 +444,7 @@ private slots: void downloadError(QNetworkReply::NetworkError error); void metaDataChanged(); void directoryChanged(const QString &dirctory); + void checkDownloadTimeout(); private: @@ -524,6 +526,8 @@ private slots: QRegExp m_DateExpression; MOBase::IPluginGame const *m_ManagedGame; + + QTimer m_TimeoutTimer; }; From 0d220b6b41afdc06d2f7c2c6dce99453323ee4f4 Mon Sep 17 00:00:00 2001 From: Silarn Date: Tue, 15 May 2018 11:13:16 -0500 Subject: [PATCH 04/67] Fixes for download errors and retrying with alternate mirrors --- src/downloadmanager.cpp | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index 55801e812..31671529a 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -700,7 +700,8 @@ void DownloadManager::resumeDownloadInt(int index) DownloadInfo *info = m_ActiveDownloads[index]; // Check for finished download; - if (info->m_TotalSize <= info->m_Output.size() && info->m_Reply != nullptr) { + if (info->m_TotalSize <= info->m_Output.size() && info->m_Reply != nullptr + && info->m_Reply->isOpen() && info->m_Reply->isFinished() && info->m_State != STATE_ERROR) { setState(info, STATE_DOWNLOADING); downloadFinished(index); return; @@ -717,15 +718,17 @@ void DownloadManager::resumeDownloadInt(int index) } qDebug("request resume from url %s", qPrintable(info->currentURL())); QNetworkRequest request(QUrl::fromEncoded(info->currentURL().toLocal8Bit())); - info->m_ResumePos = info->m_Output.size(); + if (info->m_State != STATE_ERROR) { + info->m_ResumePos = info->m_Output.size(); + QByteArray rangeHeader = "bytes=" + QByteArray::number(info->m_ResumePos) + "-"; + request.setRawHeader("Range", rangeHeader); + } std::get<0>(info->m_SpeedDiff) = 0; std::get<1>(info->m_SpeedDiff) = 0; std::get<2>(info->m_SpeedDiff) = 0; std::get<3>(info->m_SpeedDiff) = 0; std::get<4>(info->m_SpeedDiff) = 0; qDebug("resume at %lld bytes", info->m_ResumePos); - QByteArray rangeHeader = "bytes=" + QByteArray::number(info->m_ResumePos) + "-"; - request.setRawHeader("Range", rangeHeader); startDownload(m_NexusInterface->getAccessManager()->get(request), info, true); } emit update(index); @@ -1511,15 +1514,14 @@ void DownloadManager::downloadFinished(int index) emit showMessage(tr("Warning: Content type is: %1").arg(reply->header(QNetworkRequest::ContentTypeHeader).toString())); if ((info->m_Output.size() == 0) || ((reply->error() != QNetworkReply::NoError) - && (reply->error() != QNetworkReply::OperationCanceledError) - && (reply->error() == QNetworkReply::UnknownContentError && (info->m_Output.size() != reply->header(QNetworkRequest::ContentLengthHeader).toLongLong())))) { + && (reply->error() != QNetworkReply::OperationCanceledError))) { if (reply->error() == QNetworkReply::UnknownContentError) emit showMessage(tr("Download header content length: %1 downloaded file size: %2").arg(reply->header(QNetworkRequest::ContentLengthHeader).toLongLong()).arg(info->m_Output.size())); if (info->m_Tries == 0) { emit showMessage(tr("Download failed: %1 (%2)").arg(reply->errorString()).arg(reply->error())); } error = true; - setState(info, STATE_PAUSING); + setState(info, STATE_ERROR); } } @@ -1529,19 +1531,15 @@ void DownloadManager::downloadFinished(int index) if (info->m_Output.isOpen()) { info->m_Output.write(info->m_Reply->readAll()); } - - if (error) { - setState(info, STATE_ERROR); - } else { - setState(info, STATE_PAUSED); - } } - if (info->m_State == STATE_CANCELED) { + if (info->m_State == STATE_CANCELED || (info->m_Tries == 0 && error)) { emit aboutToUpdate(); info->m_Output.remove(); delete info; m_ActiveDownloads.erase(m_ActiveDownloads.begin() + index); + if (error) + emit showMessage(tr("We were unable to download the file due to errors after four retries. There may be an issue with the Nexus servers.")); emit update(-1); } else if (info->isPausedState()) { info->m_Output.close(); From 2ee5f0e96dd6928c271e1b37cd1f77ddc456ef9c Mon Sep 17 00:00:00 2001 From: Silarn Date: Tue, 15 May 2018 12:46:49 -0500 Subject: [PATCH 05/67] Push version update for 2.1.4 dev --- src/version.rc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/version.rc b/src/version.rc index 083cbfb01..700e24888 100644 --- a/src/version.rc +++ b/src/version.rc @@ -1,13 +1,13 @@ #include "Winver.h" -#define VER_FILEVERSION 2,1,3 -#define VER_FILEVERSION_STR "2.1.3\0" +#define VER_FILEVERSION 2,1,4 +#define VER_FILEVERSION_STR "2.1.4-dev\0" VS_VERSION_INFO VERSIONINFO FILEVERSION VER_FILEVERSION PRODUCTVERSION VER_FILEVERSION FILEFLAGSMASK VS_FFI_FILEFLAGSMASK -FILEFLAGS (0) +FILEFLAGS VS_FF_PRERELEASE FILEOS VOS__WINDOWS32 FILETYPE VFT_APP FILESUBTYPE (0) From 8f905e1d498f0c264529737f4b8f7a47c0de59ee Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Tue, 15 May 2018 23:43:35 +0100 Subject: [PATCH 06/67] Change how the VersionInfo object is created from version.rc such that non-release builds are possible and get labelled as such --- src/selfupdater.cpp | 44 ++++++++++++++++++++++++++++++++++++++++---- src/shared/util.cpp | 28 ++++++++++++++++++++++++++++ src/shared/util.h | 1 + src/version.rc | 5 ++++- 4 files changed, 73 insertions(+), 5 deletions(-) diff --git a/src/selfupdater.cpp b/src/selfupdater.cpp index d671bafcb..8027e804f 100644 --- a/src/selfupdater.cpp +++ b/src/selfupdater.cpp @@ -104,10 +104,46 @@ SelfUpdater::SelfUpdater(NexusInterface *nexusInterface) VS_FIXEDFILEINFO version = GetFileVersion(ToWString(QApplication::applicationFilePath())); - m_MOVersion = VersionInfo(version.dwFileVersionMS >> 16, - version.dwFileVersionMS & 0xFFFF, - version.dwFileVersionLS >> 16, - version.dwFileVersionLS & 0xFFFF); + if (version.dwFileFlags | VS_FF_PRERELEASE) + { + // Pre-release builds need annotating + QString versionString = QString::fromStdWString(GetFileVersionString(ToWString(QApplication::applicationFilePath()))); + + // The pre-release flag can be set without the string specifying what type of pre-release + bool noLetters = true; + for (QChar character : versionString) + { + if (character.isLetter()) + { + noLetters = false; + break; + } + } + + if (noLetters) + { + // Default to pre-alpha when release type is unspecified + m_MOVersion = VersionInfo(version.dwFileVersionMS >> 16, + version.dwFileVersionMS & 0xFFFF, + version.dwFileVersionLS >> 16, + version.dwFileVersionLS & 0xFFFF, + VersionInfo::RELEASE_PREALPHA); + } + else + { + // Trust the string to make sense + m_MOVersion = VersionInfo(versionString); + qDebug() << "<: " << (m_MOVersion < VersionInfo(2, 1, 4)) << ", >: " << (m_MOVersion > VersionInfo(2, 1, 4)); + } + } + else + { + // Non-pre-release builds just need their version numbers reading + m_MOVersion = VersionInfo(version.dwFileVersionMS >> 16, + version.dwFileVersionMS & 0xFFFF, + version.dwFileVersionLS >> 16, + version.dwFileVersionLS & 0xFFFF); + } } diff --git a/src/shared/util.cpp b/src/shared/util.cpp index 5491a9e6c..008c5050c 100644 --- a/src/shared/util.cpp +++ b/src/shared/util.cpp @@ -174,5 +174,33 @@ VS_FIXEDFILEINFO GetFileVersion(const std::wstring &fileName) } } +std::wstring GetFileVersionString(const std::wstring &fileName) +{ + DWORD handle = 0UL; + DWORD size = ::GetFileVersionInfoSizeW(fileName.c_str(), &handle); + if (size == 0) { + throw windows_error("failed to determine file version info size"); + } + + boost::scoped_array buffer(new char[size]); + try { + handle = 0UL; + if (!::GetFileVersionInfoW(fileName.c_str(), handle, size, buffer.get())) { + throw windows_error("failed to determine file version info"); + } + + LPVOID strBuffer = nullptr; + UINT strLength = 0; + if (!::VerQueryValue(buffer.get(), L"\\StringFileInfo\\040904B0\\ProductVersion", &strBuffer, &strLength)) { + throw windows_error("failed to determine file version"); + } + + return std::wstring((LPCTSTR)strBuffer); + } + catch (...) { + throw; + } +} + } // namespace MOShared diff --git a/src/shared/util.h b/src/shared/util.h index 1e4980597..75c382a70 100644 --- a/src/shared/util.h +++ b/src/shared/util.h @@ -45,6 +45,7 @@ std::wstring ToLower(const std::wstring &text); bool CaseInsensitiveEqual(const std::wstring &lhs, const std::wstring &rhs); VS_FIXEDFILEINFO GetFileVersion(const std::wstring &fileName); +std::wstring GetFileVersionString(const std::wstring &fileName); } // namespace MOShared diff --git a/src/version.rc b/src/version.rc index 700e24888..39565d70c 100644 --- a/src/version.rc +++ b/src/version.rc @@ -1,7 +1,10 @@ #include "Winver.h" +// If VS_FF_PRERELEASE is not set, MO labels the build as a release and uses VER_FILEVERSION to determine version number. +// Otherwise, if letters are used in VER_FILEVERSION_STR, uses the full MOBase::VersionInfo parser +// Otherwise, uses the numbers from VER_FILEVERSION and sets the release type as pre-alpha #define VER_FILEVERSION 2,1,4 -#define VER_FILEVERSION_STR "2.1.4-dev\0" +#define VER_FILEVERSION_STR "2.1.4\0" VS_VERSION_INFO VERSIONINFO FILEVERSION VER_FILEVERSION From 9c1806cc74a1600f1c28610532d9a06434adf30d Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Tue, 15 May 2018 23:45:15 +0100 Subject: [PATCH 07/67] Remove sneaky test qDebug instance. --- src/selfupdater.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/selfupdater.cpp b/src/selfupdater.cpp index 8027e804f..a57b79620 100644 --- a/src/selfupdater.cpp +++ b/src/selfupdater.cpp @@ -133,7 +133,6 @@ SelfUpdater::SelfUpdater(NexusInterface *nexusInterface) { // Trust the string to make sense m_MOVersion = VersionInfo(versionString); - qDebug() << "<: " << (m_MOVersion < VersionInfo(2, 1, 4)) << ", >: " << (m_MOVersion > VersionInfo(2, 1, 4)); } } else From 15db1b76f72a7dd88dbb84df42951c57dfe9917e Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 16 May 2018 00:42:31 +0100 Subject: [PATCH 08/67] Move improved version parsing to util.h and use it everywhere. --- src/main.cpp | 6 +----- src/nexusinterface.cpp | 5 +---- src/selfupdater.cpp | 42 +------------------------------------- src/shared/util.cpp | 46 ++++++++++++++++++++++++++++++++++++++++++ src/shared/util.h | 3 +++ 5 files changed, 52 insertions(+), 50 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 9c30a1c61..bfc3b926c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -446,11 +446,7 @@ static void preloadSsl() static QString getVersionDisplayString() { - VS_FIXEDFILEINFO version = GetFileVersion(ToWString(QApplication::applicationFilePath())); - return VersionInfo(version.dwFileVersionMS >> 16, - version.dwFileVersionMS & 0xFFFF, - version.dwFileVersionLS >> 16, - version.dwFileVersionLS & 0xFFFF).displayString(); + return createVersionInfo().displayString(); } int runApplication(MOApplication &application, SingleInstance &instance, diff --git a/src/nexusinterface.cpp b/src/nexusinterface.cpp index d2a52c7bf..e97e98006 100644 --- a/src/nexusinterface.cpp +++ b/src/nexusinterface.cpp @@ -147,10 +147,7 @@ QAtomicInt NexusInterface::NXMRequestInfo::s_NextID(0); NexusInterface::NexusInterface(PluginContainer *pluginContainer) : m_NMMVersion(), m_PluginContainer(pluginContainer) { - VS_FIXEDFILEINFO version = GetFileVersion(ToWString(QApplication::applicationFilePath())); - m_MOVersion = VersionInfo(version.dwFileVersionMS >> 16, - version.dwFileVersionMS & 0xFFFF, - version.dwFileVersionLS >> 16); + m_MOVersion = createVersionInfo(); m_AccessManager = new NXMAccessManager(this, m_MOVersion.displayString()); m_DiskCache = new QNetworkDiskCache(this); diff --git a/src/selfupdater.cpp b/src/selfupdater.cpp index a57b79620..2b051b09a 100644 --- a/src/selfupdater.cpp +++ b/src/selfupdater.cpp @@ -102,47 +102,7 @@ SelfUpdater::SelfUpdater(NexusInterface *nexusInterface) throw MyException(InstallationManager::getErrorString(m_ArchiveHandler->getLastError())); } - VS_FIXEDFILEINFO version = GetFileVersion(ToWString(QApplication::applicationFilePath())); - - if (version.dwFileFlags | VS_FF_PRERELEASE) - { - // Pre-release builds need annotating - QString versionString = QString::fromStdWString(GetFileVersionString(ToWString(QApplication::applicationFilePath()))); - - // The pre-release flag can be set without the string specifying what type of pre-release - bool noLetters = true; - for (QChar character : versionString) - { - if (character.isLetter()) - { - noLetters = false; - break; - } - } - - if (noLetters) - { - // Default to pre-alpha when release type is unspecified - m_MOVersion = VersionInfo(version.dwFileVersionMS >> 16, - version.dwFileVersionMS & 0xFFFF, - version.dwFileVersionLS >> 16, - version.dwFileVersionLS & 0xFFFF, - VersionInfo::RELEASE_PREALPHA); - } - else - { - // Trust the string to make sense - m_MOVersion = VersionInfo(versionString); - } - } - else - { - // Non-pre-release builds just need their version numbers reading - m_MOVersion = VersionInfo(version.dwFileVersionMS >> 16, - version.dwFileVersionMS & 0xFFFF, - version.dwFileVersionLS >> 16, - version.dwFileVersionLS & 0xFFFF); - } + m_MOVersion = createVersionInfo(); } diff --git a/src/shared/util.cpp b/src/shared/util.cpp index 008c5050c..102565f50 100644 --- a/src/shared/util.cpp +++ b/src/shared/util.cpp @@ -27,6 +27,7 @@ along with Mod Organizer. If not, see . #include #include #include +#include namespace MOShared { @@ -202,5 +203,50 @@ std::wstring GetFileVersionString(const std::wstring &fileName) } } +MOBase::VersionInfo createVersionInfo() +{ + VS_FIXEDFILEINFO version = GetFileVersion(QApplication::applicationFilePath().toStdWString()); + + if (version.dwFileFlags | VS_FF_PRERELEASE) + { + // Pre-release builds need annotating + QString versionString = QString::fromStdWString(GetFileVersionString(QApplication::applicationFilePath().toStdWString())); + + // The pre-release flag can be set without the string specifying what type of pre-release + bool noLetters = true; + for (QChar character : versionString) + { + if (character.isLetter()) + { + noLetters = false; + break; + } + } + + if (noLetters) + { + // Default to pre-alpha when release type is unspecified + return MOBase::VersionInfo(version.dwFileVersionMS >> 16, + version.dwFileVersionMS & 0xFFFF, + version.dwFileVersionLS >> 16, + version.dwFileVersionLS & 0xFFFF, + MOBase::VersionInfo::RELEASE_PREALPHA); + } + else + { + // Trust the string to make sense + return MOBase::VersionInfo(versionString); + } + } + else + { + // Non-pre-release builds just need their version numbers reading + return MOBase::VersionInfo(version.dwFileVersionMS >> 16, + version.dwFileVersionMS & 0xFFFF, + version.dwFileVersionLS >> 16, + version.dwFileVersionLS & 0xFFFF); + } +} + } // namespace MOShared diff --git a/src/shared/util.h b/src/shared/util.h index 75c382a70..1fdfb0891 100644 --- a/src/shared/util.h +++ b/src/shared/util.h @@ -25,6 +25,8 @@ along with Mod Organizer. If not, see . #define WIN32_LEAN_AND_MEAN #include +#include + namespace MOShared { /// Test if a file (or directory) by the specified name exists @@ -46,6 +48,7 @@ bool CaseInsensitiveEqual(const std::wstring &lhs, const std::wstring &rhs); VS_FIXEDFILEINFO GetFileVersion(const std::wstring &fileName); std::wstring GetFileVersionString(const std::wstring &fileName); +MOBase::VersionInfo createVersionInfo(); } // namespace MOShared From 1e6e055f398cda4ba417015fa85d6eb3eb8345fe Mon Sep 17 00:00:00 2001 From: Silarn Date: Wed, 16 May 2018 11:12:15 -0500 Subject: [PATCH 09/67] Add the MO user agent string to the download requests (fixes EU#1) --- src/downloadmanager.cpp | 1 + src/nxmaccessmanager.cpp | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index 31671529a..1ffd29f00 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -356,6 +356,7 @@ bool DownloadManager::addDownload(const QStringList &URLs, QString gameName, QUrl preferredUrl = QUrl::fromEncoded(URLs.first().toLocal8Bit()); qDebug("selected download url: %s", qPrintable(preferredUrl.toString())); QNetworkRequest request(preferredUrl); + request.setHeader(QNetworkRequest::UserAgentHeader, m_NexusInterface->getAccessManager()->userAgent()); return addDownload(m_NexusInterface->getAccessManager()->get(request), URLs, fileName, gameName, modID, fileID, fileInfo); } diff --git a/src/nxmaccessmanager.cpp b/src/nxmaccessmanager.cpp index 05016e8f9..426c8b9cc 100644 --- a/src/nxmaccessmanager.cpp +++ b/src/nxmaccessmanager.cpp @@ -224,12 +224,12 @@ void NXMAccessManager::login(const QString &username, const QString &password) QString NXMAccessManager::userAgent(const QString &subModule) const { QStringList comments; - comments << "compatible to Nexus Client v" + m_NMMVersion; + comments << "Nexus Client v" + m_NMMVersion; if (!subModule.isEmpty()) { comments << "module: " + subModule; } - return QString("Mod Organizer v%1 (%2)").arg(m_MOVersion, comments.join("; ")); + return QString("Mod Organizer/%1 (%2)").arg(m_MOVersion, comments.join("; ")); } From 941c50bff903bdd241764240167d21013d9e8f5c Mon Sep 17 00:00:00 2001 From: Silarn Date: Wed, 16 May 2018 14:58:43 -0500 Subject: [PATCH 10/67] Add UA to resume and fix edge case for hung download check --- src/downloadmanager.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index 1ffd29f00..8aba42aac 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -719,6 +719,7 @@ void DownloadManager::resumeDownloadInt(int index) } qDebug("request resume from url %s", qPrintable(info->currentURL())); QNetworkRequest request(QUrl::fromEncoded(info->currentURL().toLocal8Bit())); + request.setHeader(QNetworkRequest::UserAgentHeader, m_NexusInterface->getAccessManager()->userAgent()); if (info->m_State != STATE_ERROR) { info->m_ResumePos = info->m_Output.size(); QByteArray rangeHeader = "bytes=" + QByteArray::number(info->m_ResumePos) + "-"; @@ -1641,7 +1642,8 @@ void DownloadManager::checkDownloadTimeout() { for (int i = 0; i < m_ActiveDownloads.size(); ++i) { if (m_ActiveDownloads[i]->m_StartTime.elapsed() - std::get<3>(m_ActiveDownloads[i]->m_SpeedDiff) > 5 * 1000 && - m_ActiveDownloads[i]->m_State == STATE_DOWNLOADING) { + m_ActiveDownloads[i]->m_State == STATE_DOWNLOADING && m_ActiveDownloads[i]->m_Reply != nullptr && + m_ActiveDownloads[i]->m_Reply->isOpen()) { pauseDownload(i); downloadFinished(i); resumeDownload(i); From 1c6543454ef4d3b84e2ce1f6928b6c99005eee29 Mon Sep 17 00:00:00 2001 From: Al12rs Date: Fri, 11 May 2018 20:16:48 +0200 Subject: [PATCH 11/67] Initial work on avoiding the spamming of the Downlods tab refresh caused by the fileSystemWatcher picking up changes made by MO itself and triggering multiple refreshes --- src/downloadmanager.cpp | 85 ++++++++++++++++++++++++++++++++++++++++- src/downloadmanager.h | 16 ++++++++ 2 files changed, 99 insertions(+), 2 deletions(-) diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index 8aba42aac..ea8253bec 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -180,7 +180,7 @@ QString DownloadManager::DownloadInfo::currentURL() DownloadManager::DownloadManager(NexusInterface *nexusInterface, QObject *parent) - : IDownloadManager(parent), m_NexusInterface(nexusInterface), m_DirWatcher(), m_ShowHidden(false), + : IDownloadManager(parent), m_NexusInterface(nexusInterface), m_DirWatcher(), m_DirWatcherDisabler(0), m_ShowHidden(false), m_DateExpression("/Date\\((\\d+)\\)/") { connect(&m_DirWatcher, SIGNAL(directoryChanged(QString)), this, SLOT(directoryChanged(QString))); @@ -209,8 +209,10 @@ bool DownloadManager::downloadsInProgress() return false; } + void DownloadManager::pauseAll() { + // first loop: pause all downloads for (int i = 0; i < m_ActiveDownloads.count(); ++i) { if (m_ActiveDownloads[i]->m_State < STATE_READY) { @@ -238,6 +240,7 @@ void DownloadManager::pauseAll() ::Sleep(100); } } + } @@ -276,9 +279,30 @@ void DownloadManager::setPluginContainer(PluginContainer *pluginContainer) m_NexusInterface->setPluginContainer(pluginContainer); } + +void DownloadManager::startDisableDirWatcher() +{ + m_DirWatcherDisabler++; +} + + +void DownloadManager::endDisableDirWatcher() +{ + QCoreApplication::processEvents(); + if (m_DirWatcherDisabler > 0) + m_DirWatcherDisabler--; + else + m_DirWatcherDisabler = 0; +} + + + void DownloadManager::refreshList() { try { + //avoid triggering other refreshes + startDisableDirWatcher(); + int downloadsBefore = m_ActiveDownloads.size(); // remove finished downloads @@ -339,6 +363,10 @@ void DownloadManager::refreshList() qDebug("downloads after refresh: %d", m_ActiveDownloads.size()); } emit update(-1); + + //let watcher trigger refreshes again + endDisableDirWatcher(); + } catch (const std::bad_alloc&) { reportError(tr("Memory allocation error (in refreshing directory).")); } @@ -388,7 +416,10 @@ bool DownloadManager::addDownload(QNetworkReply *reply, const QStringList &URLs, baseName = dispoName; } } + + startDisableDirWatcher(); newDownload->setName(getDownloadFileName(baseName), false); + endDisableDirWatcher(); startDownload(reply, newDownload, false); // emit update(-1); @@ -458,7 +489,9 @@ void DownloadManager::startDownload(QNetworkReply *reply, DownloadInfo *newDownl else setState(newDownload, STATE_CANCELING); } else { + startDisableDirWatcher(); newDownload->setName(getDownloadFileName(newDownload->m_FileName, true), true); + endDisableDirWatcher(); if (newDownload->m_State == STATE_PAUSED) resumeDownload(indexByName(newDownload->m_FileName)); else @@ -530,6 +563,9 @@ void DownloadManager::addNXMDownload(const QString &url) void DownloadManager::removeFile(int index, bool deleteFile) { + //Avoid triggering refreshes from DirWatcher + startDisableDirWatcher(); + if (index >= m_ActiveDownloads.size()) { throw MyException(tr("remove: invalid download index %1").arg(index)); } @@ -540,6 +576,7 @@ void DownloadManager::removeFile(int index, bool deleteFile) (download->m_State == STATE_DOWNLOADING)) { // shouldn't have been possible qCritical("tried to remove active download"); + endDisableDirWatcher(); return; } @@ -550,6 +587,7 @@ void DownloadManager::removeFile(int index, bool deleteFile) if (deleteFile) { if (!shellDelete(QStringList(filePath), true)) { reportError(tr("failed to delete %1").arg(filePath)); + endDisableDirWatcher(); return; } @@ -561,6 +599,8 @@ void DownloadManager::removeFile(int index, bool deleteFile) QSettings metaSettings(filePath.append(".meta"), QSettings::IniFormat); metaSettings.setValue("removed", true); } + + endDisableDirWatcher(); } class LessThanWrapper @@ -606,14 +646,22 @@ void DownloadManager::restoreDownload(int index) QString filePath = m_OutputDirectory + "/" + download->m_FileName; + //avoid dirWatcher triggering refreshes + startDisableDirWatcher(); + QSettings metaSettings(filePath.append(".meta"), QSettings::IniFormat); metaSettings.setValue("removed", false); + + endDisableDirWatcher(); } void DownloadManager::removeDownload(int index, bool deleteFile) { try { + //avoid dirWatcher triggering refreshes + startDisableDirWatcher(); + emit aboutToUpdate(); if (index < 0) { @@ -634,6 +682,7 @@ void DownloadManager::removeDownload(int index, bool deleteFile) } else { if (index >= m_ActiveDownloads.size()) { reportError(tr("remove: invalid download index %1").arg(index)); + endDisableDirWatcher(); return; } @@ -641,6 +690,7 @@ void DownloadManager::removeDownload(int index, bool deleteFile) delete m_ActiveDownloads.at(index); m_ActiveDownloads.erase(m_ActiveDownloads.begin() + index); } + endDisableDirWatcher(); emit update(-1); } catch (const std::exception &e) { qCritical("failed to remove download: %s", e.what()); @@ -970,11 +1020,16 @@ void DownloadManager::markInstalled(int index) throw MyException(tr("mark installed: invalid download index %1").arg(index)); } + //Avoid triggering refreshes from DirWatcher + startDisableDirWatcher(); + DownloadInfo *info = m_ActiveDownloads.at(index); QSettings metaFile(info->m_Output.fileName() + ".meta", QSettings::IniFormat); metaFile.setValue("installed", true); metaFile.setValue("uninstalled", false); + endDisableDirWatcher(); + setState(m_ActiveDownloads.at(index), STATE_INSTALLED); } @@ -986,10 +1041,15 @@ void DownloadManager::markInstalled(QString fileName) } else { DownloadInfo *info = getDownloadInfo(fileName); if (info != nullptr) { + //Avoid triggering refreshes from DirWatcher + startDisableDirWatcher(); + QSettings metaFile(info->m_Output.fileName() + ".meta", QSettings::IniFormat); metaFile.setValue("installed", true); metaFile.setValue("uninstalled", false); delete info; + + endDisableDirWatcher(); } } } @@ -1005,10 +1065,15 @@ void DownloadManager::markUninstalled(int index) throw MyException(tr("mark uninstalled: invalid download index %1").arg(index)); } + //Avoid triggering refreshes from DirWatcher + startDisableDirWatcher(); + DownloadInfo *info = m_ActiveDownloads.at(index); QSettings metaFile(info->m_Output.fileName() + ".meta", QSettings::IniFormat); metaFile.setValue("uninstalled", true); + endDisableDirWatcher(); + setState(m_ActiveDownloads.at(index), STATE_UNINSTALLED); } @@ -1022,9 +1087,15 @@ void DownloadManager::markUninstalled(QString fileName) QString filePath = QDir::fromNativeSeparators(m_OutputDirectory) + "/" + fileName; DownloadInfo *info = getDownloadInfo(filePath); if (info != nullptr) { + + //Avoid triggering refreshes from DirWatcher + startDisableDirWatcher(); + QSettings metaFile(info->m_Output.fileName() + ".meta", QSettings::IniFormat); metaFile.setValue("uninstalled", true); delete info; + + endDisableDirWatcher(); } } } @@ -1183,6 +1254,9 @@ void DownloadManager::downloadReadyRead() void DownloadManager::createMetaFile(DownloadInfo *info) { + //Avoid triggering refreshes from DirWatcher + startDisableDirWatcher(); + QSettings metaFile(QString("%1.meta").arg(info->m_Output.fileName()), QSettings::IniFormat); metaFile.setValue("gameName", info->m_FileInfo->gameName); metaFile.setValue("modID", info->m_FileInfo->modID); @@ -1204,6 +1278,7 @@ void DownloadManager::createMetaFile(DownloadInfo *info) (info->m_State == DownloadManager::STATE_ERROR)); metaFile.setValue("removed", info->m_Hidden); + endDisableDirWatcher(); // slightly hackish... for (int i = 0; i < m_ActiveDownloads.size(); ++i) { if (m_ActiveDownloads[i] == info) { @@ -1572,11 +1647,14 @@ void DownloadManager::downloadFinished(int index) QString newName = getFileNameFromNetworkReply(reply); QString oldName = QFileInfo(info->m_Output).fileName(); + + startDisableDirWatcher(); if (!newName.isEmpty() && (oldName.isEmpty())) { info->setName(getDownloadFileName(newName), true); } else { info->setName(m_OutputDirectory + "/" + info->m_FileName, true); // don't rename but remove the ".unfinished" extension } + endDisableDirWatcher(); if (!isNexus) { setState(info, STATE_READY); @@ -1616,7 +1694,9 @@ void DownloadManager::metaDataChanged() if (info != nullptr) { QString newName = getFileNameFromNetworkReply(info->m_Reply); if (!newName.isEmpty() && (info->m_FileName.isEmpty())) { + startDisableDirWatcher(); info->setName(getDownloadFileName(newName), true); + endDisableDirWatcher(); refreshAlphabeticalTranslation(); if (!info->m_Output.isOpen() && !info->m_Output.open(QIODevice::WriteOnly | QIODevice::Append)) { reportError(tr("failed to re-open %1").arg(info->m_FileName)); @@ -1630,7 +1710,8 @@ void DownloadManager::metaDataChanged() void DownloadManager::directoryChanged(const QString&) { - refreshList(); + if(m_DirWatcherDisabler==0) + refreshList(); } void DownloadManager::managedGameChanged(MOBase::IPluginGame const *managedGame) diff --git a/src/downloadmanager.h b/src/downloadmanager.h index 17ae8a359..74627be57 100644 --- a/src/downloadmanager.h +++ b/src/downloadmanager.h @@ -144,6 +144,17 @@ class DownloadManager : public MOBase::IDownloadManager **/ void setOutputDirectory(const QString &outputDirectory); + /** + * @brief disables feedback from the downlods fileSystemWhatcher untill disableDownloadsWatcherEnd() is called + * + **/ + void startDisableDirWatcher(); + + /** + * @brief re-enables feedback from the downlods fileSystemWhatcher after disableDownloadsWatcherStart() was called + **/ + void endDisableDirWatcher(); + /** * @return current download directory **/ @@ -519,6 +530,11 @@ private slots: QFileSystemWatcher m_DirWatcher; + //The dirWatcher is actually triggering off normal Mo operations such as deleting downloads or editing .meta files + //so it needs to be disabled during operations that are known to cause the creation or deletion of files in the Downloads folder. + //Notably using QSettings to edit a file creates a temporarily .lock file that causes the Watcher to trigger multiple listRefreshes freezing the ui. + int m_DirWatcherDisabler; + std::map m_DownloadFails; bool m_ShowHidden; From 8731159bfbaa34c431343b4e415590ae079a24bb Mon Sep 17 00:00:00 2001 From: Al12rs Date: Thu, 17 May 2018 00:01:54 +0200 Subject: [PATCH 12/67] Huge performace improvements for downloads tab by disabling the dirWatcher triggered refreshes. Avoid app freezes caused by stacked refresh calls. Added chack to see if a download is alerady hidden before trying to hide it. Made sure to refresh Download tab where it was othwerwise relying in the dirwatcher to refresh. --- src/downloadmanager.cpp | 50 ++++++++++++++++++++++++----------------- src/downloadmanager.h | 7 +++--- 2 files changed, 34 insertions(+), 23 deletions(-) diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index ea8253bec..6cd86ece3 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -54,6 +54,7 @@ using namespace MOBase; static const char UNFINISHED[] = ".unfinished"; unsigned int DownloadManager::DownloadInfo::s_NextDownloadID = 1U; +int DownloadManager::m_DirWatcherDisabler = 0; DownloadManager::DownloadInfo *DownloadManager::DownloadInfo::createNew(const ModRepositoryFileInfo *fileInfo, const QStringList &URLs) @@ -142,6 +143,25 @@ DownloadManager::DownloadInfo *DownloadManager::DownloadInfo::createFromMeta(con return info; } +void DownloadManager::startDisableDirWatcher() +{ + DownloadManager::m_DirWatcherDisabler++; +} + + +void DownloadManager::endDisableDirWatcher() +{ + if (DownloadManager::m_DirWatcherDisabler > 0) + { + if (DownloadManager::m_DirWatcherDisabler == 1) + QCoreApplication::processEvents(); + DownloadManager::m_DirWatcherDisabler--; + } + else { + DownloadManager::m_DirWatcherDisabler = 0; + } +} + void DownloadManager::DownloadInfo::setName(QString newName, bool renameFile) { QString oldMetaFileName = QString("%1.meta").arg(m_FileName); @@ -180,7 +200,7 @@ QString DownloadManager::DownloadInfo::currentURL() DownloadManager::DownloadManager(NexusInterface *nexusInterface, QObject *parent) - : IDownloadManager(parent), m_NexusInterface(nexusInterface), m_DirWatcher(), m_DirWatcherDisabler(0), m_ShowHidden(false), + : IDownloadManager(parent), m_NexusInterface(nexusInterface), m_DirWatcher(), m_ShowHidden(false), m_DateExpression("/Date\\((\\d+)\\)/") { connect(&m_DirWatcher, SIGNAL(directoryChanged(QString)), this, SLOT(directoryChanged(QString))); @@ -280,20 +300,7 @@ void DownloadManager::setPluginContainer(PluginContainer *pluginContainer) } -void DownloadManager::startDisableDirWatcher() -{ - m_DirWatcherDisabler++; -} - -void DownloadManager::endDisableDirWatcher() -{ - QCoreApplication::processEvents(); - if (m_DirWatcherDisabler > 0) - m_DirWatcherDisabler--; - else - m_DirWatcherDisabler = 0; -} @@ -359,9 +366,9 @@ void DownloadManager::refreshList() } } - if (m_ActiveDownloads.size() != downloadsBefore) { + //if (m_ActiveDownloads.size() != downloadsBefore) { qDebug("downloads after refresh: %d", m_ActiveDownloads.size()); - } + //} emit update(-1); //let watcher trigger refreshes again @@ -597,9 +604,10 @@ void DownloadManager::removeFile(int index, bool deleteFile) } } else { QSettings metaSettings(filePath.append(".meta"), QSettings::IniFormat); - metaSettings.setValue("removed", true); + if(!download->m_Hidden) + metaSettings.setValue("removed", true); } - + endDisableDirWatcher(); } @@ -682,6 +690,7 @@ void DownloadManager::removeDownload(int index, bool deleteFile) } else { if (index >= m_ActiveDownloads.size()) { reportError(tr("remove: invalid download index %1").arg(index)); + //emit update(-1); endDisableDirWatcher(); return; } @@ -690,11 +699,12 @@ void DownloadManager::removeDownload(int index, bool deleteFile) delete m_ActiveDownloads.at(index); m_ActiveDownloads.erase(m_ActiveDownloads.begin() + index); } - endDisableDirWatcher(); emit update(-1); + endDisableDirWatcher(); } catch (const std::exception &e) { qCritical("failed to remove download: %s", e.what()); } + refreshList(); } @@ -1710,7 +1720,7 @@ void DownloadManager::metaDataChanged() void DownloadManager::directoryChanged(const QString&) { - if(m_DirWatcherDisabler==0) + if(DownloadManager::m_DirWatcherDisabler==0) refreshList(); } diff --git a/src/downloadmanager.h b/src/downloadmanager.h index 74627be57..aa859b13f 100644 --- a/src/downloadmanager.h +++ b/src/downloadmanager.h @@ -148,12 +148,12 @@ class DownloadManager : public MOBase::IDownloadManager * @brief disables feedback from the downlods fileSystemWhatcher untill disableDownloadsWatcherEnd() is called * **/ - void startDisableDirWatcher(); + static void startDisableDirWatcher(); /** * @brief re-enables feedback from the downlods fileSystemWhatcher after disableDownloadsWatcherStart() was called **/ - void endDisableDirWatcher(); + static void endDisableDirWatcher(); /** * @return current download directory @@ -533,7 +533,8 @@ private slots: //The dirWatcher is actually triggering off normal Mo operations such as deleting downloads or editing .meta files //so it needs to be disabled during operations that are known to cause the creation or deletion of files in the Downloads folder. //Notably using QSettings to edit a file creates a temporarily .lock file that causes the Watcher to trigger multiple listRefreshes freezing the ui. - int m_DirWatcherDisabler; + static int m_DirWatcherDisabler; + std::map m_DownloadFails; From 2fc55ccc869c22aaea2524e05b0f0221399fb44d Mon Sep 17 00:00:00 2001 From: Silarn Date: Wed, 16 May 2018 23:54:50 -0500 Subject: [PATCH 13/67] Updated strings for downloads --- src/organizer_en.ts | 208 ++++++++++++++++++++++---------------------- 1 file changed, 106 insertions(+), 102 deletions(-) diff --git a/src/organizer_en.ts b/src/organizer_en.ts index 0a604b277..1b9c2eeb4 100644 --- a/src/organizer_en.ts +++ b/src/organizer_en.ts @@ -620,276 +620,281 @@ p, li { white-space: pre-wrap; } DownloadManager - + failed to rename "%1" to "%2" - + Memory allocation error (in refreshing directory). - + failed to download %1: could not open output file: %2 - + Download again? - + A file with the same name has already been downloaded. Do you want to download it again? The new file will receive a different name. - + Wrong Game - + The download link is for a mod for "%1" but this instance of MO has been set up for "%2". - - + + Already Started - + A download for this mod file has already been queued. - + There is already a download started for this file (mod: %1, file: %2). - - + + remove: invalid download index %1 - + failed to delete %1 - + failed to delete meta file for %1 - + restore: invalid download index: %1 - + cancel: invalid download index %1 - + pause: invalid download index %1 - + resume: invalid download index %1 - + resume (int): invalid download index %1 - + No known download urls. Sorry, this download can't be resumed. - + query: invalid download index %1 - + Please enter the nexus mod id - + Mod ID: - + Please select the source game code for %1 - + get pending: invalid download index %1 - + get path: invalid download index %1 - + Main - + Update - + Optional - + Old - + Misc - + Unknown - + display name: invalid download index %1 - + file name: invalid download index %1 - + file time: invalid download index %1 - + file size: invalid download index %1 - + progress: invalid download index %1 - + state: invalid download index %1 - + infocomplete: invalid download index %1 - - + + mod id: invalid download index %1 - + ishidden: invalid download index %1 - + file info: invalid download index %1 - + mark installed: invalid download index %1 - + mark uninstalled: invalid download index %1 - + Memory allocation error (in processing progress event). - + Memory allocation error (in processing downloaded data). - + 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 @@ -1256,7 +1261,6 @@ p, li { white-space: pre-wrap; } None of the available installer plugins were able to handle that archive. This is likely due to a corrupted or incompatible download or unrecognized archive format. - None of the available installer plugins were able to handle that archive @@ -3749,113 +3753,113 @@ p, li { white-space: pre-wrap; } - + 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 - - + + 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></table> - + Time this mod was installed @@ -3933,17 +3937,17 @@ p, li { white-space: pre-wrap; } NexusInterface - + Failed to guess mod id for "%1", please pick the correct one - + empty response - + invalid response @@ -5038,33 +5042,33 @@ If the folder was still in use, restart MO and try again. - + 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 @@ -5281,58 +5285,58 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe - + 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 From 6617be8ef9a015dd4109729155fc9a772997b5ed Mon Sep 17 00:00:00 2001 From: Diana Date: Fri, 18 May 2018 07:45:13 -0400 Subject: [PATCH 14/67] Fix a memory leak in updateToolBar updateToolBar was creating new QActions and QWidgets every call, without cleaning them up. The spacer, help widgets, and toolbuttons only need to be created once. Those have been moved out into the MainWindow constructor updateToolBar is called in various places, importantly when adding/removing executable shortcuts. Also adds a deleteLater() call to clean up executable shortcut actions. --- src/mainwindow.cpp | 66 ++++++++++++++++++++++++---------------------- src/mainwindow.h | 2 ++ 2 files changed, 36 insertions(+), 32 deletions(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 5b6525bdc..4d1a3b035 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -242,7 +242,28 @@ MainWindow::MainWindow(QSettings &initSettings updateProblemsButton(); - updateToolBar(); + // Setup toolbar + QWidget *spacer = new QWidget(ui->toolBar); + spacer->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); + QWidget *widget = ui->toolBar->widgetForAction(ui->actionTool); + QToolButton *toolBtn = qobject_cast(widget); + + if (toolBtn->menu() == nullptr) { + actionToToolButton(ui->actionTool); + } + + actionToToolButton(ui->actionHelp); + createHelpWidget(); + + for (QAction *action : ui->toolBar->actions()) { + if (action->isSeparator()) { + // insert spacers + ui->toolBar->insertWidget(action, spacer); + m_Sep = action; + // m_Sep would only use the last seperator anyway, and we only have the one anyway? + break; + } + } TaskProgressManager::instance().tryCreateTaskbar(); @@ -560,41 +581,22 @@ void MainWindow::updateToolBar() for (QAction *action : ui->toolBar->actions()) { if (action->objectName().startsWith("custom__")) { ui->toolBar->removeAction(action); + action->deleteLater(); } } - QWidget *spacer = new QWidget(ui->toolBar); - spacer->setObjectName("custom__spacer"); - spacer->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); - QWidget *widget = ui->toolBar->widgetForAction(ui->actionTool); - QToolButton *toolBtn = qobject_cast(widget); - - if (toolBtn->menu() == nullptr) { - actionToToolButton(ui->actionTool); - } - - actionToToolButton(ui->actionHelp); - createHelpWidget(); - - for (QAction *action : ui->toolBar->actions()) { - if (action->isSeparator()) { - // insert spacers - ui->toolBar->insertWidget(action, spacer); - - std::vector::iterator begin, end; - m_OrganizerCore.executablesList()->getExecutables(begin, end); - for (auto iter = begin; iter != end; ++iter) { - if (iter->isShownOnToolbar()) { - QAction *exeAction = new QAction(iconForExecutable(iter->m_BinaryInfo.filePath()), - iter->m_Title, - ui->toolBar); - exeAction->setObjectName(QString("custom__") + iter->m_Title); - if (!connect(exeAction, SIGNAL(triggered()), this, SLOT(startExeAction()))) { - qDebug("failed to connect trigger?"); - } - ui->toolBar->insertAction(action, exeAction); - } + std::vector::iterator begin, end; + m_OrganizerCore.executablesList()->getExecutables(begin, end); + for (auto iter = begin; iter != end; ++iter) { + if (iter->isShownOnToolbar()) { + QAction *exeAction = new QAction(iconForExecutable(iter->m_BinaryInfo.filePath()), + iter->m_Title, + ui->toolBar); + exeAction->setObjectName(QString("custom__") + iter->m_Title); + if (!connect(exeAction, SIGNAL(triggered()), this, SLOT(startExeAction()))) { + qDebug("failed to connect trigger?"); } + ui->toolBar->insertAction(m_Sep, exeAction); } } } diff --git a/src/mainwindow.h b/src/mainwindow.h index bbff0d938..02094344c 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -304,6 +304,8 @@ private slots: Ui::MainWindow *ui; + QAction *m_Sep; // Executable Shortcuts are added after this. Non owning. + bool m_WasVisible; MOBase::TutorialControl m_Tutorial; From 4b66c6c1ef52a68cbca752be69fc57b47a25104c Mon Sep 17 00:00:00 2001 From: Al12rs Date: Sun, 20 May 2018 23:02:50 +0200 Subject: [PATCH 15/67] Added "Open Plugins folder" option to Open folder menu. --- src/mainwindow.cpp | 17 +++++++++-------- src/mainwindow.h | 1 + 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 4d1a3b035..a428d5b31 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -3155,6 +3155,13 @@ void MainWindow::openInstallFolder() ::ShellExecuteW(nullptr, L"explore", ToWString(qApp->applicationDirPath()).c_str(), nullptr, nullptr, SW_SHOWNORMAL); } +void MainWindow::openPluginsFolder() +{ + QString pluginsPath = QCoreApplication::applicationDirPath() + "/" + ToQString(AppConfig::pluginPath()); + ::ShellExecuteW(nullptr, L"explore", ToWString(pluginsPath).c_str(), nullptr, nullptr, SW_SHOWNORMAL); +} + + void MainWindow::openProfileFolder() { ::ShellExecuteW(nullptr, L"explore", ToWString(m_OrganizerCore.currentProfile()->absolutePath()).c_str(), nullptr, nullptr, SW_SHOWNORMAL); @@ -3366,15 +3373,9 @@ QMenu *MainWindow::openFolderMenu() FolderMenu->addAction(tr("Open MO2 Install folder"), this, SLOT(openInstallFolder())); - FolderMenu->addAction(tr("Open MO2 Logs folder"), this, SLOT(openLogsFolder())); - - - - - - - + FolderMenu->addAction(tr("Open MO2 Plugins folder"), this, SLOT(openPluginsFolder())); + FolderMenu->addAction(tr("Open MO2 Logs folder"), this, SLOT(openLogsFolder())); return FolderMenu; diff --git a/src/mainwindow.h b/src/mainwindow.h index 02094344c..737a15340 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -500,6 +500,7 @@ private slots: void openInstanceFolder(); void openLogsFolder(); void openInstallFolder(); + void openPluginsFolder(); void openDownloadsFolder(); void openProfileFolder(); void openGameFolder(); From 6d6444c126c59c4307873755b08fc5773b3d3955 Mon Sep 17 00:00:00 2001 From: Al12rs Date: Sun, 20 May 2018 23:53:57 +0200 Subject: [PATCH 16/67] Made downlods file sizes human readable instead of just being KB --- src/downloadlistwidget.cpp | 21 ++++++++++++++++++++- src/downloadlistwidget.h | 2 ++ src/downloadlistwidget.ui | 3 +++ 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/downloadlistwidget.cpp b/src/downloadlistwidget.cpp index 2af74cc26..943947789 100644 --- a/src/downloadlistwidget.cpp +++ b/src/downloadlistwidget.cpp @@ -87,6 +87,25 @@ void DownloadListWidgetDelegate::drawCache(QPainter *painter, const QStyleOption } +QString DownloadListWidgetDelegate::sizeFormat(quint64 size) const +{ + qreal calc = size; + QStringList list; + list << "KB" << "MB" << "GB" << "TB"; + + QStringListIterator i(list); + QString unit("byte(s)"); + + while (calc >= 1024.0 && i.hasNext()) + { + unit = i.next(); + calc /= 1024.0; + } + + return QString().setNum(calc, 'f', 2) + " " + unit; +} + + void DownloadListWidgetDelegate::paintPendingDownload(int downloadIndex) const { std::tuple nexusids = m_Manager->getPendingDownload(downloadIndex); @@ -106,7 +125,7 @@ void DownloadListWidgetDelegate::paintRegularDownload(int downloadIndex) const name.append("..."); } m_NameLabel->setText(name); - m_SizeLabel->setText(QString::number(m_Manager->getFileSize(downloadIndex) / 1024)); + m_SizeLabel->setText(sizeFormat(m_Manager->getFileSize(downloadIndex) )); DownloadManager::DownloadState state = m_Manager->getState(downloadIndex); if ((state == DownloadManager::STATE_PAUSED) || (state == DownloadManager::STATE_ERROR)) { QPalette labelPalette; diff --git a/src/downloadlistwidget.h b/src/downloadlistwidget.h index c1dfe4cd5..62f218379 100644 --- a/src/downloadlistwidget.h +++ b/src/downloadlistwidget.h @@ -74,11 +74,13 @@ class DownloadListWidgetDelegate : public QItemDelegate protected: + QString sizeFormat(quint64 size) const; bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index); private: + void drawCache(QPainter *painter, const QStyleOptionViewItem &option, const QPixmap &cache) const; private slots: diff --git a/src/downloadlistwidget.ui b/src/downloadlistwidget.ui index 7a6ce8ba2..9e2385092 100644 --- a/src/downloadlistwidget.ui +++ b/src/downloadlistwidget.ui @@ -86,6 +86,9 @@ KB + + + false From 186f26b71e4597e9999a3d946b0d9497255fc986 Mon Sep 17 00:00:00 2001 From: Al12rs Date: Mon, 21 May 2018 01:27:13 +0200 Subject: [PATCH 17/67] Added un-hide all option to downlods tab. Improved performace when hiding/unhiding. There is still room for improvement for hiding but it requires refactoring the function --- src/downloadlistwidget.cpp | 24 ++++++++++++++----- src/downloadlistwidget.h | 1 + src/downloadlistwidgetcompact.cpp | 22 ++++++++++++----- src/downloadlistwidgetcompact.h | 1 + src/downloadmanager.cpp | 40 +++++++++++++++++++++---------- 5 files changed, 64 insertions(+), 24 deletions(-) diff --git a/src/downloadlistwidget.cpp b/src/downloadlistwidget.cpp index 943947789..d7bd01bb7 100644 --- a/src/downloadlistwidget.cpp +++ b/src/downloadlistwidget.cpp @@ -255,9 +255,15 @@ void DownloadListWidgetDelegate::issueRemoveFromView() void DownloadListWidgetDelegate::issueRestoreToView() { - emit restoreDownload(m_ContextRow); + emit restoreDownload(m_ContextRow); } +void DownloadListWidgetDelegate::issueRestoreToViewAll() +{ + emit restoreDownload(-1); +} + + void DownloadListWidgetDelegate::issueCancel() { emit cancelDownload(m_ContextRow); @@ -353,11 +359,17 @@ bool DownloadListWidgetDelegate::editorEvent(QEvent *event, QAbstractItemModel * } menu.addAction(tr("Delete Installed..."), this, SLOT(issueDeleteCompleted())); menu.addAction(tr("Delete All..."), this, SLOT(issueDeleteAll())); - if (!hidden) { - menu.addSeparator(); - menu.addAction(tr("Hide Installed..."), this, SLOT(issueRemoveFromViewCompleted())); - menu.addAction(tr("Hide All..."), this, SLOT(issueRemoveFromViewAll())); - } + + if (!hidden) { + menu.addSeparator(); + menu.addAction(tr("Hide Installed..."), this, SLOT(issueRemoveFromViewCompleted())); + menu.addAction(tr("Hide All..."), this, SLOT(issueRemoveFromViewAll())); + } + if (hidden) { + menu.addSeparator(); + menu.addAction(tr("Un-Hide All..."), this, SLOT(issueRestoreToViewAll())); + } + menu.exec(mouseEvent->globalPos()); event->accept(); diff --git a/src/downloadlistwidget.h b/src/downloadlistwidget.h index 62f218379..6e628cc71 100644 --- a/src/downloadlistwidget.h +++ b/src/downloadlistwidget.h @@ -89,6 +89,7 @@ private slots: void issueDelete(); void issueRemoveFromView(); void issueRestoreToView(); + void issueRestoreToViewAll(); void issueCancel(); void issuePause(); void issueResume(); diff --git a/src/downloadlistwidgetcompact.cpp b/src/downloadlistwidgetcompact.cpp index 898d400ae..97e3655b7 100644 --- a/src/downloadlistwidgetcompact.cpp +++ b/src/downloadlistwidgetcompact.cpp @@ -219,9 +219,15 @@ void DownloadListWidgetCompactDelegate::issueRemoveFromView() void DownloadListWidgetCompactDelegate::issueRestoreToView() { - emit restoreDownload(m_ContextIndex.row()); + emit restoreDownload(m_ContextIndex.row()); } +void DownloadListWidgetCompactDelegate::issueRestoreToViewAll() +{ + emit restoreDownload(-1); +} + + void DownloadListWidgetCompactDelegate::issueCancel() { emit cancelDownload(m_ContextIndex.row()); @@ -318,11 +324,15 @@ bool DownloadListWidgetCompactDelegate::editorEvent(QEvent *event, QAbstractItem } menu.addAction(tr("Delete Installed..."), this, SLOT(issueDeleteCompleted())); menu.addAction(tr("Delete All..."), this, SLOT(issueDeleteAll())); - if (!hidden) { - menu.addSeparator(); - menu.addAction(tr("Hide Installed..."), this, SLOT(issueRemoveFromViewCompleted())); - menu.addAction(tr("Hide All..."), this, SLOT(issueRemoveFromViewAll())); - } + if (!hidden) { + menu.addSeparator(); + menu.addAction(tr("Hide Installed..."), this, SLOT(issueRemoveFromViewCompleted())); + menu.addAction(tr("Hide All..."), this, SLOT(issueRemoveFromViewAll())); + } + if (hidden) { + menu.addSeparator(); + menu.addAction(tr("Un-Hide All..."), this, SLOT(issueRestoreToViewAll())); + } menu.exec(mouseEvent->globalPos()); event->accept(); diff --git a/src/downloadlistwidgetcompact.h b/src/downloadlistwidgetcompact.h index bf855d5f7..df1a5f586 100644 --- a/src/downloadlistwidgetcompact.h +++ b/src/downloadlistwidgetcompact.h @@ -87,6 +87,7 @@ private slots: void issueDelete(); void issueRemoveFromView(); void issueRestoreToView(); + void issueRestoreToViewAll(); void issueCancel(); void issuePause(); void issueResume(); diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index 6cd86ece3..93a46d787 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -645,22 +645,38 @@ void DownloadManager::refreshAlphabeticalTranslation() void DownloadManager::restoreDownload(int index) { - if ((index < 0) || (index >= m_ActiveDownloads.size())) { - throw MyException(tr("restore: invalid download index: %1").arg(index)); - } - DownloadInfo *download = m_ActiveDownloads.at(index); - download->m_Hidden = false; + if (index < 0) { + DownloadState minState = STATE_READY ; + index = 0; - QString filePath = m_OutputDirectory + "/" + download->m_FileName; + for (QVector::const_iterator iter = m_ActiveDownloads.begin(); iter != m_ActiveDownloads.end(); ++iter ) { + + if ((*iter)->m_State >= minState) { + restoreDownload(index); + } + index++; + } + } + else { + if (index >= m_ActiveDownloads.size()) { + throw MyException(tr("restore: invalid download index: %1").arg(index)); + } - //avoid dirWatcher triggering refreshes - startDisableDirWatcher(); + DownloadInfo *download = m_ActiveDownloads.at(index); + if (download->m_Hidden) { + download->m_Hidden = false; - QSettings metaSettings(filePath.append(".meta"), QSettings::IniFormat); - metaSettings.setValue("removed", false); + QString filePath = m_OutputDirectory + "/" + download->m_FileName; - endDisableDirWatcher(); + //avoid dirWatcher triggering refreshes + startDisableDirWatcher(); + QSettings metaSettings(filePath.append(".meta"), QSettings::IniFormat); + metaSettings.setValue("removed", false); + + endDisableDirWatcher(); + } + } } @@ -680,7 +696,7 @@ void DownloadManager::removeDownload(int index, bool deleteFile) removeFile(index, deleteFile); delete *iter; iter = m_ActiveDownloads.erase(iter); - QCoreApplication::processEvents(); + //QCoreApplication::processEvents(); } else { ++iter; From ddb40b712edf0676f6083a0ecd5a67509e60e6fe Mon Sep 17 00:00:00 2001 From: Al12rs Date: Fri, 25 May 2018 15:33:53 +0200 Subject: [PATCH 18/67] *Avoided some unnecessary refreshes while removing mods. *Added "Visit on Nexus" menu entry in the downloads tab. *Added confirmation message when deleting a single download. *Changed text of messages to more easily distinguish deleting from hiding. --- src/downloadlistwidget.cpp | 20 +++++++++++++++--- src/downloadlistwidget.h | 6 ++++-- src/downloadlistwidgetcompact.cpp | 33 +++++++++++++++++++---------- src/downloadlistwidgetcompact.h | 7 ++++--- src/downloadmanager.cpp | 35 ++++++++++++++++++++++++++++--- src/downloadmanager.h | 2 ++ src/mainwindow.cpp | 11 ++++++---- 7 files changed, 88 insertions(+), 26 deletions(-) diff --git a/src/downloadlistwidget.cpp b/src/downloadlistwidget.cpp index d7bd01bb7..754700567 100644 --- a/src/downloadlistwidget.cpp +++ b/src/downloadlistwidget.cpp @@ -245,7 +245,11 @@ void DownloadListWidgetDelegate::issueQueryInfo() void DownloadListWidgetDelegate::issueDelete() { - emit removeDownload(m_ContextRow, true); + if (QMessageBox::question(nullptr, tr("Delete Files?"), + tr("This will permanently delete the selected download."), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + emit removeDownload(m_ContextRow, true); + } } void DownloadListWidgetDelegate::issueRemoveFromView() @@ -263,6 +267,11 @@ void DownloadListWidgetDelegate::issueRestoreToViewAll() emit restoreDownload(-1); } +void DownloadListWidgetDelegate::issueVisitOnNexus() +{ + emit visitOnNexus(m_ContextRow); +} + void DownloadListWidgetDelegate::issueCancel() { @@ -281,7 +290,7 @@ void DownloadListWidgetDelegate::issueResume() void DownloadListWidgetDelegate::issueDeleteAll() { - if (QMessageBox::question(nullptr, tr("Are you sure?"), + if (QMessageBox::question(nullptr, tr("Delete Files?"), tr("This will remove all finished downloads from this list and from disk."), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { emit removeDownload(-1, true); @@ -290,7 +299,7 @@ void DownloadListWidgetDelegate::issueDeleteAll() void DownloadListWidgetDelegate::issueDeleteCompleted() { - if (QMessageBox::question(nullptr, tr("Are you sure?"), + if (QMessageBox::question(nullptr, tr("Delete Files?"), tr("This will remove all installed downloads from this list and from disk."), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { emit removeDownload(-2, true); @@ -340,7 +349,12 @@ bool DownloadListWidgetDelegate::editorEvent(QEvent *event, QAbstractItemModel * menu.addAction(tr("Install"), this, SLOT(issueInstall())); if (m_Manager->isInfoIncomplete(m_ContextRow)) { menu.addAction(tr("Query Info"), this, SLOT(issueQueryInfo())); + }else { + menu.addAction(tr("Visit on Nexus"), this,SLOT(issueVisitOnNexus())); } + + menu.addSeparator(); + menu.addAction(tr("Delete"), this, SLOT(issueDelete())); if (hidden) { menu.addAction(tr("Un-Hide"), this, SLOT(issueRestoreToView())); diff --git a/src/downloadlistwidget.h b/src/downloadlistwidget.h index 6e628cc71..e2746f3ad 100644 --- a/src/downloadlistwidget.h +++ b/src/downloadlistwidget.h @@ -71,6 +71,7 @@ class DownloadListWidgetDelegate : public QItemDelegate void cancelDownload(int index); void pauseDownload(int index); void resumeDownload(int index); + void visitOnNexus(int index); protected: @@ -80,7 +81,7 @@ class DownloadListWidgetDelegate : public QItemDelegate private: - + void drawCache(QPainter *painter, const QStyleOptionViewItem &option, const QPixmap &cache) const; private slots: @@ -89,7 +90,8 @@ private slots: void issueDelete(); void issueRemoveFromView(); void issueRestoreToView(); - void issueRestoreToViewAll(); + void issueRestoreToViewAll(); + void issueVisitOnNexus(); void issueCancel(); void issuePause(); void issueResume(); diff --git a/src/downloadlistwidgetcompact.cpp b/src/downloadlistwidgetcompact.cpp index 97e3655b7..cdce8d7f5 100644 --- a/src/downloadlistwidgetcompact.cpp +++ b/src/downloadlistwidgetcompact.cpp @@ -209,7 +209,11 @@ void DownloadListWidgetCompactDelegate::issueQueryInfo() void DownloadListWidgetCompactDelegate::issueDelete() { - emit removeDownload(m_ContextIndex.row(), true); + if (QMessageBox::question(nullptr, tr("Are you sure?"), + tr("This will permanently delete the selected download."), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + emit removeDownload(m_ContextIndex.row(), true); + } } void DownloadListWidgetCompactDelegate::issueRemoveFromView() @@ -217,6 +221,11 @@ void DownloadListWidgetCompactDelegate::issueRemoveFromView() emit removeDownload(m_ContextIndex.row(), false); } +void DownloadListWidgetCompactDelegate::issueVisitOnNexus() +{ + emit visitOnNexus(m_ContextIndex.row()); +} + void DownloadListWidgetCompactDelegate::issueRestoreToView() { emit restoreDownload(m_ContextIndex.row()); @@ -305,7 +314,10 @@ bool DownloadListWidgetCompactDelegate::editorEvent(QEvent *event, QAbstractItem menu.addAction(tr("Install"), this, SLOT(issueInstall())); if (m_Manager->isInfoIncomplete(m_ContextIndex.row())) { menu.addAction(tr("Query Info"), this, SLOT(issueQueryInfo())); + }else { + menu.addAction(tr("Visit on Nexus"), this, SLOT(issueVisitOnNexus())); } + menu.addSeparator(); menu.addAction(tr("Delete"), this, SLOT(issueDelete())); if (hidden) { menu.addAction(tr("Un-Hide"), this, SLOT(issueRestoreToView())); @@ -324,15 +336,15 @@ bool DownloadListWidgetCompactDelegate::editorEvent(QEvent *event, QAbstractItem } menu.addAction(tr("Delete Installed..."), this, SLOT(issueDeleteCompleted())); menu.addAction(tr("Delete All..."), this, SLOT(issueDeleteAll())); - if (!hidden) { - menu.addSeparator(); - menu.addAction(tr("Hide Installed..."), this, SLOT(issueRemoveFromViewCompleted())); - menu.addAction(tr("Hide All..."), this, SLOT(issueRemoveFromViewAll())); - } - if (hidden) { - menu.addSeparator(); - menu.addAction(tr("Un-Hide All..."), this, SLOT(issueRestoreToViewAll())); - } + if (!hidden) { + menu.addSeparator(); + menu.addAction(tr("Hide Installed..."), this, SLOT(issueRemoveFromViewCompleted())); + menu.addAction(tr("Hide All..."), this, SLOT(issueRemoveFromViewAll())); + } + if (hidden) { + menu.addSeparator(); + menu.addAction(tr("Un-Hide All..."), this, SLOT(issueRestoreToViewAll())); + } menu.exec(mouseEvent->globalPos()); event->accept(); @@ -345,4 +357,3 @@ bool DownloadListWidgetCompactDelegate::editorEvent(QEvent *event, QAbstractItem return QItemDelegate::editorEvent(event, model, option, index); } - diff --git a/src/downloadlistwidgetcompact.h b/src/downloadlistwidgetcompact.h index df1a5f586..28a376ebf 100644 --- a/src/downloadlistwidgetcompact.h +++ b/src/downloadlistwidgetcompact.h @@ -35,7 +35,7 @@ class DownloadListWidgetCompact; class DownloadListWidgetCompact : public QWidget { Q_OBJECT - + public: explicit DownloadListWidgetCompact(QWidget *parent = 0); ~DownloadListWidgetCompact(); @@ -69,6 +69,7 @@ class DownloadListWidgetCompactDelegate : public QItemDelegate void cancelDownload(int index); void pauseDownload(int index); void resumeDownload(int index); + void visitOnNexus(int index); protected: @@ -87,7 +88,8 @@ private slots: void issueDelete(); void issueRemoveFromView(); void issueRestoreToView(); - void issueRestoreToViewAll(); + void issueRestoreToViewAll(); + void issueVisitOnNexus(); void issueCancel(); void issuePause(); void issueResume(); @@ -120,4 +122,3 @@ private slots: }; #endif // DOWNLOADLISTWIDGETCOMPACT_H - diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index 93a46d787..0341152a3 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -36,6 +36,7 @@ along with Mod Organizer. If not, see . #include #include #include +#include #include #include #include @@ -607,7 +608,7 @@ void DownloadManager::removeFile(int index, bool deleteFile) if(!download->m_Hidden) metaSettings.setValue("removed", true); } - + endDisableDirWatcher(); } @@ -651,7 +652,7 @@ void DownloadManager::restoreDownload(int index) index = 0; for (QVector::const_iterator iter = m_ActiveDownloads.begin(); iter != m_ActiveDownloads.end(); ++iter ) { - + if ((*iter)->m_State >= minState) { restoreDownload(index); } @@ -876,6 +877,34 @@ void DownloadManager::queryInfo(int index) setState(info, STATE_FETCHINGMODINFO); } +void DownloadManager::visitOnNexus(int index) +{ + if ((index < 0) || (index >= m_ActiveDownloads.size())) { + reportError(tr("VisitNexus: invalid download index %1").arg(index)); + return; + } + DownloadInfo *info = m_ActiveDownloads[index]; + + if (info->m_FileInfo->repository != "Nexus") { + qWarning("Visiting mod page is currently only possible with Nexus"); + return; + } + + if (info->m_State < DownloadManager::STATE_READY) { + // UI shouldn't allow this + return; + } + int modID = info->m_FileInfo->modID; + + QString gameName = info->m_FileInfo->gameName; + if (modID > 0) { + QDesktopServices::openUrl(QUrl(m_NexusInterface->getModURL(modID, gameName))); + } + else { + emit showMessage(tr("Nexus ID for this Mod is unknown")); + } +} + int DownloadManager::numTotalDownloads() const { @@ -1756,4 +1785,4 @@ void DownloadManager::checkDownloadTimeout() resumeDownload(i); } } -} \ No newline at end of file +} diff --git a/src/downloadmanager.h b/src/downloadmanager.h index aa859b13f..24bc6d7f2 100644 --- a/src/downloadmanager.h +++ b/src/downloadmanager.h @@ -435,6 +435,8 @@ public slots: void queryInfo(int index); + void visitOnNexus(int index); + void nxmDescriptionAvailable(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID); void nxmFilesAvailable(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index a428d5b31..7ddd35c57 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -254,7 +254,7 @@ MainWindow::MainWindow(QSettings &initSettings actionToToolButton(ui->actionHelp); createHelpWidget(); - + for (QAction *action : ui->toolBar->actions()) { if (action->isSeparator()) { // insert spacers @@ -400,7 +400,7 @@ MainWindow::MainWindow(QSettings &initSettings new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_Enter), this, SLOT(openExplorer_activated())); new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_Return), this, SLOT(openExplorer_activated())); - + new QShortcut(QKeySequence::Refresh, this, SLOT(refreshProfile_activated())); @@ -2336,9 +2336,11 @@ void MainWindow::removeMod_clicked() tr("Remove the following mods?
    %1
").arg(mods), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { // use mod names instead of indexes because those become invalid during the removal + DownloadManager::startDisableDirWatcher(); for (QString name : modNames) { m_OrganizerCore.modList()->removeRowForce(ModInfo::getIndex(name), QModelIndex()); } + DownloadManager::endDisableDirWatcher(); } } else { m_OrganizerCore.modList()->removeRow(m_ContextRow, QModelIndex()); @@ -2500,7 +2502,7 @@ void MainWindow::displayModInformation(ModInfo::Ptr modInfo, unsigned int index, connect(&dialog, SIGNAL(modOpenPrev(int)), this, SLOT(modOpenPrev(int)), Qt::QueuedConnection); connect(&dialog, SIGNAL(originModified(int)), this, SLOT(originModified(int))); connect(&dialog, SIGNAL(endorseMod(ModInfo::Ptr)), this, SLOT(endorseMod(ModInfo::Ptr))); - + //Open the tab first if we want to use the standard indexes of the tabs. if (tab != -1) { dialog.openTab(tab); @@ -2687,7 +2689,7 @@ void MainWindow::openExplorer_activated() QModelIndex idx = selection->currentIndex(); QString fileName = idx.data().toString(); - + ModInfo::Ptr modInfo = ModInfo::getByIndex(ModInfo::getIndex(m_OrganizerCore.pluginList()->origin(fileName))); std::vector flags = modInfo->getFlags(); @@ -4239,6 +4241,7 @@ void MainWindow::updateDownloadListDelegate() connect(ui->downloadView->itemDelegate(), SIGNAL(installDownload(int)), &m_OrganizerCore, SLOT(installDownload(int))); connect(ui->downloadView->itemDelegate(), SIGNAL(queryInfo(int)), m_OrganizerCore.downloadManager(), SLOT(queryInfo(int))); + connect(ui->downloadView->itemDelegate(), SIGNAL(visitOnNexus(int)), m_OrganizerCore.downloadManager(), SLOT(visitOnNexus(int))); connect(ui->downloadView->itemDelegate(), SIGNAL(removeDownload(int, bool)), m_OrganizerCore.downloadManager(), SLOT(removeDownload(int, bool))); connect(ui->downloadView->itemDelegate(), SIGNAL(restoreDownload(int)), m_OrganizerCore.downloadManager(), SLOT(restoreDownload(int))); connect(ui->downloadView->itemDelegate(), SIGNAL(cancelDownload(int)), m_OrganizerCore.downloadManager(), SLOT(cancelDownload(int))); From f09ec53fcfc83f2647bf4d04cfa280f172ae0ed7 Mon Sep 17 00:00:00 2001 From: Silarn Date: Fri, 8 Jun 2018 18:05:01 -0500 Subject: [PATCH 19/67] Updated strings --- src/organizer_en.ts | 737 +++++++++++++++++++++++--------------------- 1 file changed, 394 insertions(+), 343 deletions(-) diff --git a/src/organizer_en.ts b/src/organizer_en.ts index 1b9c2eeb4..60fd66e73 100644 --- a/src/organizer_en.ts +++ b/src/organizer_en.ts @@ -326,27 +326,27 @@ p, li { white-space: pre-wrap; } - - - + + + Done - Double Click to install - - + + Paused - Double Click to resume - - + + Installed - Double Click to re-install - - + + Uninstalled - Double Click to re-install @@ -408,493 +408,539 @@ p, li { white-space: pre-wrap; } - - - - + + + + + Are you sure? - + + This will permanently delete the selected download. + + + + This will remove all finished downloads from this list and from disk. - + This will remove all installed downloads from this list and from disk. - + This will permanently remove all finished downloads from this list (but NOT from disk). - + This will permanently remove all installed downloads from this list (but NOT from disk). - + Install - + Query Info - + + Visit on Nexus + + + + Delete - + Un-Hide - + Hide - + Cancel - + Pause - + Remove - + Resume - + Delete Installed... - + Delete All... - + Hide Installed... - + Hide All... + + + Un-Hide All... + +
DownloadListWidgetDelegate - + < game %1 mod %2 file %3 > - + Pending - + Fetching Info 1 - + Fetching Info 2 - - - - + + Are you sure? - + This will remove all finished downloads from this list and from disk. - + + + + Delete Files? + + + + + This will permanently delete the selected download. + + + + This will remove all installed downloads from this list and from disk. - + This will remove all finished downloads from this list (but NOT from disk). - + This will remove all installed downloads from this list (but NOT from disk). - + Install - + Query Info - + + Visit on Nexus + + + + Delete - + Un-Hide - + Hide - + Cancel - + Pause - + Remove - + Resume - + Delete Installed... - + Delete All... - + Hide Installed... - + Hide All... + + + Un-Hide All... + + DownloadManager - + failed to rename "%1" to "%2" - + Memory allocation error (in refreshing directory). - + failed to download %1: could not open output file: %2 - + Download again? - + A file with the same name has already been downloaded. Do you want to download it again? The new file will receive a different name. - + Wrong Game - + The download link is for a mod for "%1" but this instance of MO has been set up for "%2". - - + + Already Started - + A download for this mod file has already been queued. - + There is already a download started for this file (mod: %1, file: %2). - - + + remove: invalid download index %1 - + failed to delete %1 - + failed to delete meta file for %1 - + restore: invalid download index: %1 - + cancel: invalid download index %1 - + pause: invalid download index %1 - + resume: invalid download index %1 - + resume (int): invalid download index %1 - + No known download urls. Sorry, this download can't be resumed. - + query: invalid download index %1 - + Please enter the nexus mod id - + Mod ID: - + Please select the source game code for %1 - + + VisitNexus: invalid download index %1 + + + + + Nexus ID for this Mod is unknown + + + + get pending: invalid download index %1 - + get path: invalid download index %1 - + Main - + Update - + Optional - + Old - + Misc - + Unknown - + display name: invalid download index %1 - + file name: invalid download index %1 - + file time: invalid download index %1 - + file size: invalid download index %1 - + progress: invalid download index %1 - + state: invalid download index %1 - + infocomplete: invalid download index %1 - - + + mod id: invalid download index %1 - + ishidden: invalid download index %1 - + file info: invalid download index %1 - + mark installed: invalid download index %1 - + mark uninstalled: invalid download index %1 - + Memory allocation error (in processing progress event). - + Memory allocation error (in processing downloaded data). - + 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 @@ -1642,8 +1688,8 @@ p, li { white-space: pre-wrap; } - - + + Refresh @@ -1817,7 +1863,7 @@ p, li { white-space: pre-wrap; } - + Update @@ -1828,7 +1874,7 @@ p, li { white-space: pre-wrap; } - + No Problems @@ -1858,7 +1904,7 @@ Right now this has very limited functionality - + Endorse Mod Organizer @@ -1883,688 +1929,693 @@ Right now this has very limited functionality - + Toolbar - + Desktop - + Start Menu - + There is no supported sort mechanism for this game. You will probably have to use a third-party tool. - + Problems - + There are potential problems with your setup - + Everything seems to be in order - + Help on UI - + Documentation Wiki - + 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 previous version (%2).<br>The GUI may not downgrade gracefully, so you may experience oddities.<br>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> - + <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. - + You need to be logged in with Nexus to resume a download - - + + You need to be logged in with Nexus to endorse - + Failed to display overwrite dialog: %1 - + Nexus ID for this Mod is unknown - + Web page for this mod is unknown - - - + + + Create Mod... - + This will create an empty mod. Please enter a name: - - + + A mod with this name already exists - + This will move all files from overwrite into a new, regular mod. Please enter a name: - - + + Are you sure? - + About to recursively delete: - + Not logged in, endorsement information will be wrong - + 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 - + 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 Instance folder - + Open Profile folder - + Open Downloads folder - + Open MO2 Install folder - + + Open MO2 Plugins folder + + + + Open MO2 Logs folder - + Install Mod... - + Create empty mod - + Enable all visible - + Disable all visible - + Check all for update - + Export to csv... - + All Mods - + Sync to Mods... - + Clear Overwrite... - - + + Open in explorer - + Restore Backup - + Remove Backup... - + Change Categories - + Primary Category - + Change versioning scheme - + Un-ignore update - + Ignore update - + Rename Mod... - + Reinstall Mod - + Remove Mod... - + Un-Endorse - - + + 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. @@ -2572,12 +2623,12 @@ You can also use online editors and converters instead. - + Enable Mods... - + Delete %n save(s) @@ -2585,319 +2636,319 @@ You can also use online editors and converters instead. - + failed to remove %1 - + failed to create %1 - + 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? - + 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? - + Thank you! - + Thank you for your endorsement! - + 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 - + 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 @@ -5073,18 +5124,18 @@ If the folder was still in use, restart MO and try again. - + Please use "Help" from the toolbar to get usage instructions to all elements - - + + <Manage...> - + failed to parse profile %1: %2 From cacf7e05428fa7ff8d3c6d1d5413460f21395d1c Mon Sep 17 00:00:00 2001 From: Lost Dragonist Date: Fri, 8 Jun 2018 22:38:43 -0500 Subject: [PATCH 20/67] Allow sorting by "source game" column --- src/modlistsortproxy.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/modlistsortproxy.cpp b/src/modlistsortproxy.cpp index 435967c9b..88084ae19 100644 --- a/src/modlistsortproxy.cpp +++ b/src/modlistsortproxy.cpp @@ -208,6 +208,16 @@ bool ModListSortProxy::lessThan(const QModelIndex &left, if (leftTime != rightTime) return leftTime < rightTime; } break; + case ModList::COL_GAME: { + if (leftMod->getGameName() != rightMod->getGameName()) { + lt = leftMod->getGameName() < rightMod->getGameName(); + } + else { + int comp = QString::compare(leftMod->name(), rightMod->name(), Qt::CaseInsensitive); + if (comp != 0) + lt = comp < 0; + } + } break; case ModList::COL_PRIORITY: { // nop, already compared by priority } break; From 0c4b1e9a928236542703a50cbe2c4b2d216feb0e Mon Sep 17 00:00:00 2001 From: LostDragonist Date: Sat, 9 Jun 2018 10:44:23 -0700 Subject: [PATCH 21/67] Prevent instances from ending in a space Windows directories are not allowed to end in a space. Allowing this creates a broken directory that is difficult for the user to remove. --- src/instancemanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/instancemanager.cpp b/src/instancemanager.cpp index 43ae152b3..2b718cb2f 100644 --- a/src/instancemanager.cpp +++ b/src/instancemanager.cpp @@ -146,7 +146,7 @@ QString InstanceManager::queryInstanceName(const QStringList &instanceList) cons if (dialog.exec() == QDialog::Rejected) { throw MOBase::MyException(QObject::tr("Canceled")); } - instanceId = dialog.textValue().replace(QRegExp("[^0-9a-zA-Z ]"), ""); + instanceId = dialog.textValue().replace(QRegExp("[^0-9a-zA-Z ]"), "").remove(QRegExp("( )*$")); bool alreadyExists=false; for (const QString &instance : instanceList) { From b6aa12b323a81f4ac8e1bb76c2fea780a1698d97 Mon Sep 17 00:00:00 2001 From: Al12rs Date: Tue, 19 Jun 2018 23:13:06 +0200 Subject: [PATCH 22/67] Fix for downloads getting insta-completed if there isn't enough bandwidth for them to procede. Now they will instead remain in the download state and continue once the bandwidth is available. There is much room for improvement here but for now I guess this will avoid MO simply stopping the download. --- src/downloadmanager.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index 0341152a3..6ed93ec15 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -785,7 +785,13 @@ void DownloadManager::resumeDownloadInt(int index) return; } - if (info->isPausedState()) { + if (info->isPausedState() || info->m_State == STATE_PAUSING) { + if (info->m_State == STATE_PAUSING) { + if (info->m_Output.isOpen()) { + info->m_Output.write(info->m_Reply->readAll()); + setState(info, STATE_PAUSED); + } + } if ((info->m_Urls.size() == 0) || ((info->m_Urls.size() == 1) && (info->m_Urls[0].size() == 0))) { emit showMessage(tr("No known download urls. Sorry, this download can't be resumed.")); @@ -1662,6 +1668,7 @@ void DownloadManager::downloadFinished(int index) } else if (info->m_State == STATE_PAUSING) { if (info->m_Output.isOpen()) { info->m_Output.write(info->m_Reply->readAll()); + setState(info, STATE_PAUSED); } } @@ -1673,7 +1680,7 @@ void DownloadManager::downloadFinished(int index) if (error) emit showMessage(tr("We were unable to download the file due to errors after four retries. There may be an issue with the Nexus servers.")); emit update(-1); - } else if (info->isPausedState()) { + } else if (info->isPausedState() || info->m_State == STATE_PAUSING) { info->m_Output.close(); createMetaFile(info); emit update(index); From 30f170c10d7838a855524c5466eb9d7ea70d34db Mon Sep 17 00:00:00 2001 From: LostDragonist Date: Sat, 16 Jun 2018 23:18:25 -0500 Subject: [PATCH 23/67] Extend mod backups and load order backups to a maximum of 10 --- src/mainwindow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 7ddd35c57..fc666a49e 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -4971,7 +4971,7 @@ bool MainWindow::createBackup(const QString &filePath, const QDateTime &time) QString outPath = filePath + "." + time.toString(PATTERN_BACKUP_DATE); if (shellCopy(QStringList(filePath), QStringList(outPath), this)) { QFileInfo fileInfo(filePath); - removeOldFiles(fileInfo.absolutePath(), fileInfo.fileName() + PATTERN_BACKUP_GLOB, 3, QDir::Name); + removeOldFiles(fileInfo.absolutePath(), fileInfo.fileName() + PATTERN_BACKUP_GLOB, 10, QDir::Name); return true; } else { return false; From b7d6bbb4513028340a2720070c7a7a968ded1681 Mon Sep 17 00:00:00 2001 From: Al12rs Date: Wed, 20 Jun 2018 19:33:56 +0200 Subject: [PATCH 24/67] Fix for downloads getting stuck after pausing them. Disabled timer check as it appeared to terminate some downloads early. --- src/downloadlistwidget.cpp | 6 +++--- src/downloadlistwidgetcompact.cpp | 6 +++--- src/downloadmanager.cpp | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/downloadlistwidget.cpp b/src/downloadlistwidget.cpp index 754700567..5ab5d3976 100644 --- a/src/downloadlistwidget.cpp +++ b/src/downloadlistwidget.cpp @@ -127,7 +127,7 @@ void DownloadListWidgetDelegate::paintRegularDownload(int downloadIndex) const m_NameLabel->setText(name); m_SizeLabel->setText(sizeFormat(m_Manager->getFileSize(downloadIndex) )); DownloadManager::DownloadState state = m_Manager->getState(downloadIndex); - if ((state == DownloadManager::STATE_PAUSED) || (state == DownloadManager::STATE_ERROR)) { + if ((state == DownloadManager::STATE_PAUSED) || (state == DownloadManager::STATE_ERROR) || (state == DownloadManager::STATE_PAUSING)) { QPalette labelPalette; m_InstallLabel->setVisible(true); m_Progress->setVisible(false); @@ -332,7 +332,7 @@ bool DownloadListWidgetDelegate::editorEvent(QEvent *event, QAbstractItemModel * QModelIndex sourceIndex = qobject_cast(model)->mapToSource(index); if (m_Manager->getState(sourceIndex.row()) >= DownloadManager::STATE_READY) { emit installDownload(sourceIndex.row()); - } else if (m_Manager->getState(sourceIndex.row()) >= DownloadManager::STATE_PAUSED) { + } else if ((m_Manager->getState(sourceIndex.row()) >= DownloadManager::STATE_PAUSED) || (m_Manager->getState(sourceIndex.row()) == DownloadManager::STATE_PAUSING)) { emit resumeDownload(sourceIndex.row()); } return true; @@ -364,7 +364,7 @@ bool DownloadListWidgetDelegate::editorEvent(QEvent *event, QAbstractItemModel * } else if (state == DownloadManager::STATE_DOWNLOADING){ menu.addAction(tr("Cancel"), this, SLOT(issueCancel())); menu.addAction(tr("Pause"), this, SLOT(issuePause())); - } else if ((state == DownloadManager::STATE_PAUSED) || (state == DownloadManager::STATE_ERROR)) { + } else if ((state == DownloadManager::STATE_PAUSED) || (state == DownloadManager::STATE_ERROR) || (state == DownloadManager::STATE_PAUSING)) { menu.addAction(tr("Remove"), this, SLOT(issueDelete())); menu.addAction(tr("Resume"), this, SLOT(issueResume())); } diff --git a/src/downloadlistwidgetcompact.cpp b/src/downloadlistwidgetcompact.cpp index cdce8d7f5..99cbd7cb9 100644 --- a/src/downloadlistwidgetcompact.cpp +++ b/src/downloadlistwidgetcompact.cpp @@ -114,7 +114,7 @@ void DownloadListWidgetCompactDelegate::paintRegularDownload(int downloadIndex) m_SizeLabel->setText(QString::number(m_Manager->getFileSize(downloadIndex) / 1048576)); } - if ((state == DownloadManager::STATE_PAUSED) || (state == DownloadManager::STATE_ERROR)) { + if ((state == DownloadManager::STATE_PAUSED) || (state == DownloadManager::STATE_ERROR) || (state == DownloadManager::STATE_PAUSING)) { m_DoneLabel->setVisible(true); m_Progress->setVisible(false); m_DoneLabel->setText(QString("%1").arg(tr("Paused"))); @@ -297,7 +297,7 @@ bool DownloadListWidgetCompactDelegate::editorEvent(QEvent *event, QAbstractItem QModelIndex sourceIndex = qobject_cast(model)->mapToSource(index); if (m_Manager->getState(sourceIndex.row()) >= DownloadManager::STATE_READY) { emit installDownload(sourceIndex.row()); - } else if (m_Manager->getState(sourceIndex.row()) >= DownloadManager::STATE_PAUSED) { + } else if ((m_Manager->getState(sourceIndex.row()) >= DownloadManager::STATE_PAUSED) || (m_Manager->getState(sourceIndex.row()) == DownloadManager::STATE_PAUSING)) { emit resumeDownload(sourceIndex.row()); } return true; @@ -327,7 +327,7 @@ bool DownloadListWidgetCompactDelegate::editorEvent(QEvent *event, QAbstractItem } else if (state == DownloadManager::STATE_DOWNLOADING){ menu.addAction(tr("Cancel"), this, SLOT(issueCancel())); menu.addAction(tr("Pause"), this, SLOT(issuePause())); - } else if ((state == DownloadManager::STATE_PAUSED) || (state == DownloadManager::STATE_ERROR)) { + } else if ((state == DownloadManager::STATE_PAUSED) || (state == DownloadManager::STATE_ERROR) || (state == DownloadManager::STATE_PAUSING)) { menu.addAction(tr("Remove"), this, SLOT(issueDelete())); menu.addAction(tr("Resume"), this, SLOT(issueResume())); } diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index 6ed93ec15..ecbcc03bc 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -206,7 +206,7 @@ DownloadManager::DownloadManager(NexusInterface *nexusInterface, QObject *parent { connect(&m_DirWatcher, SIGNAL(directoryChanged(QString)), this, SLOT(directoryChanged(QString))); m_TimeoutTimer.setSingleShot(false); - connect(&m_TimeoutTimer, SIGNAL(timeout()), this, SLOT(checkDownloadTimeout())); + //connect(&m_TimeoutTimer, SIGNAL(timeout()), this, SLOT(checkDownloadTimeout())); m_TimeoutTimer.start(5 * 1000); } @@ -1668,8 +1668,8 @@ void DownloadManager::downloadFinished(int index) } else if (info->m_State == STATE_PAUSING) { if (info->m_Output.isOpen()) { info->m_Output.write(info->m_Reply->readAll()); - setState(info, STATE_PAUSED); } + setState(info, STATE_PAUSED); } if (info->m_State == STATE_CANCELED || (info->m_Tries == 0 && error)) { From 2d49ba2bef029d753c1afb923be887c7276d94f3 Mon Sep 17 00:00:00 2001 From: Al12rs Date: Thu, 21 Jun 2018 18:54:20 +0200 Subject: [PATCH 25/67] Add open in explorer button to Overwrite dialog. --- src/overwriteinfodialog.cpp | 4 ++++ src/overwriteinfodialog.h | 1 + src/overwriteinfodialog.ui | 7 +++++++ 3 files changed, 12 insertions(+) diff --git a/src/overwriteinfodialog.cpp b/src/overwriteinfodialog.cpp index 3bb67354c..1fe3f6453 100644 --- a/src/overwriteinfodialog.cpp +++ b/src/overwriteinfodialog.cpp @@ -261,6 +261,10 @@ void OverwriteInfoDialog::createDirectoryTriggered() ui->filesView->edit(newIndex); } +void OverwriteInfoDialog::on_explorerButton_clicked() +{ + ::ShellExecuteW(nullptr, L"explore", ToWString(m_ModInfo->absolutePath()).c_str(), nullptr, nullptr, SW_SHOWNORMAL); +} void OverwriteInfoDialog::on_filesView_customContextMenuRequested(const QPoint &pos) { diff --git a/src/overwriteinfodialog.h b/src/overwriteinfodialog.h index 7d44c9e80..4b731736d 100644 --- a/src/overwriteinfodialog.h +++ b/src/overwriteinfodialog.h @@ -56,6 +56,7 @@ private slots: void openTriggered(); void createDirectoryTriggered(); + void on_explorerButton_clicked(); void on_filesView_customContextMenuRequested(const QPoint &pos); private: diff --git a/src/overwriteinfodialog.ui b/src/overwriteinfodialog.ui index 5e5986ca7..8a09ce590 100644 --- a/src/overwriteinfodialog.ui +++ b/src/overwriteinfodialog.ui @@ -14,6 +14,13 @@ Overwrite + + + + Open in Explorer + + + From 1f9103c24e1a9bc929507bd67814df3973c9e1db Mon Sep 17 00:00:00 2001 From: Al12rs Date: Fri, 22 Jun 2018 02:47:58 +0200 Subject: [PATCH 26/67] Added the path of the managed game in the settings menu under the path tab, it's read only for now as allowing the user to change it would probably require a lot more work. --- src/settings.cpp | 3 +- src/settings.h | 1 + src/settingsdialog.ui | 478 ++++++++++++++++++++++-------------------- 3 files changed, 257 insertions(+), 225 deletions(-) diff --git a/src/settings.cpp b/src/settings.cpp index 2bcf2d02f..db4ea5651 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -712,9 +712,10 @@ Settings::PathsTab::PathsTab(Settings *parent, SettingsDialog &dialog) , m_cacheDirEdit(m_dialog.findChild("cacheDirEdit")) , m_profilesDirEdit(m_dialog.findChild("profilesDirEdit")) , m_overwriteDirEdit(m_dialog.findChild("overwriteDirEdit")) + , m_managedGameDirEdit(m_dialog.findChild("managedGameDirEdit")) { m_baseDirEdit->setText(m_parent->getBaseDirectory()); - + m_managedGameDirEdit->setText(m_parent->m_GamePlugin->gameDirectory().absoluteFilePath(m_parent->m_GamePlugin->binaryName())); QString basePath = parent->getBaseDirectory(); QDir baseDir(basePath); for (const auto &dir : { diff --git a/src/settings.h b/src/settings.h index 76ab55daa..c49edfcbe 100644 --- a/src/settings.h +++ b/src/settings.h @@ -403,6 +403,7 @@ public slots: QLineEdit *m_cacheDirEdit; QLineEdit *m_profilesDirEdit; QLineEdit *m_overwriteDirEdit; + QLineEdit *m_managedGameDirEdit; }; class DiagnosticsTab : public SettingsTab diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui index 85688cb8f..374e1b84e 100644 --- a/src/settingsdialog.ui +++ b/src/settingsdialog.ui @@ -17,7 +17,7 @@ - 0 + 1 @@ -170,26 +170,23 @@ If you use pre-releases, never contact me directly by e-mail or via private mess - - + + - Base Directory + ... - - + + - ... + ... - - - @@ -197,12 +194,8 @@ If you use pre-releases, never contact me directly by e-mail or via private mess - - - - Overwrite - - + + @@ -211,37 +204,57 @@ If you use pre-releases, never contact me directly by e-mail or via private mess - - + + - ... + Overwrite + + + + + + + Directory where downloads are stored. + + + Directory where downloads are stored. - - + + - Downloads + ... - - + + - ... + Downloads - - - - Directory where mods are stored. + + + + Qt::Vertical - - Directory where mods are stored. Please note that changing this will break all associations of profiles with mods that don't exist in the new location (with the same name). + + + 20 + 40 + + + + + + + + Profiles @@ -258,20 +271,16 @@ If you use pre-releases, never contact me directly by e-mail or via private mess - - + + + + + - Directory where downloads are stored. + Directory where mods are stored. - Directory where downloads are stored. - - - - - - - Mods + Directory where mods are stored. Please note that changing this will break all associations of profiles with mods that don't exist in the new location (with the same name). @@ -282,18 +291,25 @@ If you use pre-releases, never contact me directly by e-mail or via private mess - - + + + + false + + + true + + - - + + - Profiles + Mods - - + + Qt::Vertical @@ -305,15 +321,29 @@ If you use pre-releases, never contact me directly by e-mail or via private mess + + + + Managed Game + + + + + + + Base Directory + + + + + + + Use %BASE_DIR% to refer to the Base Directory. + + + - - - - Use %BASE_DIR% to refer to the Base Directory. - - - @@ -336,7 +366,7 @@ If you use pre-releases, never contact me directly by e-mail or via private mess - + Nexus @@ -890,8 +920,8 @@ tl;dr-version: If Nexus-features don't work, insert the current version number o - - false + + false Enforces that inactive ESPs and ESMs are never loaded. @@ -986,187 +1016,187 @@ For the other games this is not a sufficient replacement for AI! - - Diagnostics - - - - - - - - Log Level - - - - - - - Decides the amount of data printed to "ModOrganizer.log" - - - + + Diagnostics + + + + + + + + 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. - - - - Debug - - - - - Info (recommended) - - - - - Warning - - - - - Error - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - Crash Dumps - - - - - - - Decides which type of crash dumps are collected when injected processes crash. - - - + + + + Debug + + + + + Info (recommended) + + + + + Warning + + + + + Error + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + 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. "Mini" Default level which generates small dumps (only stack traces). "Data" Much larger dumps with additional information which may be need (also data segments). "Full" Even larger dumps with a full memory dump of the process. - - - - None - - - - - Mini (recommended) - - - - - Data - - - - - Full - - - - - - - - - - - - Max Dumps To Keep - - - - - - - Qt::Horizontal - - - - 60 - 20 - - - - - - - - Maximum number of crash dumps to keep on disk. Use 0 for unlimited. - - - + + + + None + + + + + Mini (recommended) + + + + + Data + + + + + Full + + + + + + + + + + + + Max Dumps To Keep + + + + + + + Qt::Horizontal + + + + 60 + 20 + + + + + + + + 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. + Set "Crash Dumps" above to None to disable crash dump collection. - - - - - - - - - + + + + + + + + + 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. Sending logs and/or crash dumps to the developers can help investigate issues. It is recommended to compress large log and dmp files before sending. - - - true - - - Hint: right click link and copy link location - - - - - - - Qt::Vertical - - - QSizePolicy::Expanding - - - - 20 - 232 - - - - - - + + + true + + + + + + + Qt::Vertical + + + QSizePolicy::Expanding + + + + 20 + 232 + + + + + + From 181de232f43941b5ffefbccca78a6076e1e854a9 Mon Sep 17 00:00:00 2001 From: Silarn Date: Mon, 25 Jun 2018 20:50:49 -0500 Subject: [PATCH 27/67] Potential fix for corrupted downloads --- src/downloadmanager.cpp | 5 +++-- src/downloadmanager.h | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index ecbcc03bc..6d8658c99 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -1251,6 +1251,7 @@ void DownloadManager::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) try { DownloadInfo *info = findDownload(this->sender(), &index); if (info != nullptr) { + info->m_HasData = true; if (info->m_State == STATE_CANCELING) { setState(info, STATE_CANCELED); } else if (info->m_State == STATE_PAUSING) { @@ -1637,7 +1638,7 @@ void DownloadManager::downloadFinished(int index) if (info != nullptr) { QNetworkReply *reply = info->m_Reply; QByteArray data; - if (reply->isOpen()) { + if (reply->isOpen() && info->m_HasData) { data = reply->readAll(); info->m_Output.write(data); } @@ -1666,7 +1667,7 @@ void DownloadManager::downloadFinished(int index) if (info->m_State == STATE_CANCELING) { setState(info, STATE_CANCELED); } else if (info->m_State == STATE_PAUSING) { - if (info->m_Output.isOpen()) { + if (info->m_Output.isOpen() && info->m_HasData) { info->m_Output.write(info->m_Reply->readAll()); } setState(info, STATE_PAUSED); diff --git a/src/downloadmanager.h b/src/downloadmanager.h index 24bc6d7f2..fa3764b74 100644 --- a/src/downloadmanager.h +++ b/src/downloadmanager.h @@ -78,6 +78,7 @@ class DownloadManager : public MOBase::IDownloadManager qint64 m_PreResumeSize; std::pair m_Progress; std::tuple m_SpeedDiff; + bool m_HasData; DownloadState m_State; int m_CurrentUrl; QStringList m_Urls; @@ -115,7 +116,7 @@ class DownloadManager : public MOBase::IDownloadManager private: static unsigned int s_NextDownloadID; private: - DownloadInfo() : m_TotalSize(0), m_ReQueried(false), m_Hidden(false), m_SpeedDiff(std::tuple(0,0,0,0,0)) {} + DownloadInfo() : m_TotalSize(0), m_ReQueried(false), m_Hidden(false), m_SpeedDiff(std::tuple(0,0,0,0,0)), m_HasData(false) {} }; public: From 5efddbd5156f914de9406775f06111b899060177 Mon Sep 17 00:00:00 2001 From: Al12rs Date: Mon, 16 Jul 2018 14:18:25 +0200 Subject: [PATCH 28/67] Chnged download speed to be a little more consistant in display, to avoid the progrss bar jumping around all the time. Chnaged the kb->MB jumps to happen at 1000 instead of 1024 to avoid going in the 4 digits, but still divide by 1024 to keep units acuurate. --- src/downloadmanager.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index 6d8658c99..9761fee4f 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -64,7 +64,7 @@ DownloadManager::DownloadInfo *DownloadManager::DownloadInfo::createNew(const Mo info->m_DownloadID = s_NextDownloadID++; info->m_StartTime.start(); info->m_PreResumeSize = 0LL; - info->m_Progress = std::make_pair(0, "0 bytes/sec"); + info->m_Progress = std::make_pair(0, " 0.0 Bytes/s "); info->m_ResumePos = 0; info->m_FileInfo = new ModRepositoryFileInfo(*fileInfo); info->m_Urls = URLs; @@ -1277,19 +1277,19 @@ void DownloadManager::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) double speed = (std::get<4>(info->m_SpeedDiff) * 1000.0) / (5 * 1000); QString unit; - if (speed < 1024) { - unit = "bytes/sec"; + if (speed < 1000) { + unit = "Bytes/s"; } - else if (speed < 1024 * 1024) { + else if (speed/1024 < 1000) { speed /= 1024; - unit = "kB/s"; + unit = "KB/s"; } else { speed /= 1024 * 1024; unit = "MB/s"; } - info->m_Progress.second = QString::fromLatin1("%1% - %2 %3").arg(info->m_Progress.first).arg(speed, 3, 'f', 1).arg(unit); + info->m_Progress.second = QString::fromLatin1("%1% - %2 %3").arg(info->m_Progress.first).arg(speed, 8, 'f', 1,' ').arg(unit, -8, ' '); TaskProgressManager::instance().updateProgress(info->m_TaskProgressId, bytesReceived, bytesTotal); emit update(index); From 2524d65441e99cbb14037144b8af60e1d25e1179 Mon Sep 17 00:00:00 2001 From: Al12rs Date: Mon, 16 Jul 2018 20:56:47 +0200 Subject: [PATCH 29/67] Added "Open in Folder" option to downloads tab. --- src/downloadlistwidget.cpp | 10 +++++++++- src/downloadlistwidget.h | 2 ++ src/downloadlistwidgetcompact.cpp | 9 ++++++++- src/downloadlistwidgetcompact.h | 2 ++ src/downloadmanager.cpp | 25 +++++++++++++++++++++++++ src/downloadmanager.h | 2 ++ src/mainwindow.cpp | 1 + 7 files changed, 49 insertions(+), 2 deletions(-) diff --git a/src/downloadlistwidget.cpp b/src/downloadlistwidget.cpp index 5ab5d3976..0ef408fb7 100644 --- a/src/downloadlistwidget.cpp +++ b/src/downloadlistwidget.cpp @@ -272,6 +272,10 @@ void DownloadListWidgetDelegate::issueVisitOnNexus() emit visitOnNexus(m_ContextRow); } +void DownloadListWidgetDelegate::issueOpenInDownloadsFolder() +{ + emit openInDownloadsFolder(m_ContextRow); +} void DownloadListWidgetDelegate::issueCancel() { @@ -352,6 +356,8 @@ bool DownloadListWidgetDelegate::editorEvent(QEvent *event, QAbstractItemModel * }else { menu.addAction(tr("Visit on Nexus"), this,SLOT(issueVisitOnNexus())); } + + menu.addAction(tr("Show in Folder"), this, SLOT(issueOpenInDownloadsFolder())); menu.addSeparator(); @@ -364,9 +370,11 @@ bool DownloadListWidgetDelegate::editorEvent(QEvent *event, QAbstractItemModel * } else if (state == DownloadManager::STATE_DOWNLOADING){ menu.addAction(tr("Cancel"), this, SLOT(issueCancel())); menu.addAction(tr("Pause"), this, SLOT(issuePause())); + menu.addAction(tr("Show in Folder"), this, SLOT(issueOpenInDownloadsFolder())); } else if ((state == DownloadManager::STATE_PAUSED) || (state == DownloadManager::STATE_ERROR) || (state == DownloadManager::STATE_PAUSING)) { - menu.addAction(tr("Remove"), this, SLOT(issueDelete())); + menu.addAction(tr("Delete"), this, SLOT(issueDelete())); menu.addAction(tr("Resume"), this, SLOT(issueResume())); + menu.addAction(tr("Show in Folder"), this, SLOT(issueOpenInDownloadsFolder())); } menu.addSeparator(); diff --git a/src/downloadlistwidget.h b/src/downloadlistwidget.h index e2746f3ad..4dcbea992 100644 --- a/src/downloadlistwidget.h +++ b/src/downloadlistwidget.h @@ -72,6 +72,7 @@ class DownloadListWidgetDelegate : public QItemDelegate void pauseDownload(int index); void resumeDownload(int index); void visitOnNexus(int index); + void openInDownloadsFolder(int index); protected: @@ -92,6 +93,7 @@ private slots: void issueRestoreToView(); void issueRestoreToViewAll(); void issueVisitOnNexus(); + void issueOpenInDownloadsFolder(); void issueCancel(); void issuePause(); void issueResume(); diff --git a/src/downloadlistwidgetcompact.cpp b/src/downloadlistwidgetcompact.cpp index 99cbd7cb9..1d1805efe 100644 --- a/src/downloadlistwidgetcompact.cpp +++ b/src/downloadlistwidgetcompact.cpp @@ -226,6 +226,11 @@ void DownloadListWidgetCompactDelegate::issueVisitOnNexus() emit visitOnNexus(m_ContextIndex.row()); } +void DownloadListWidgetCompactDelegate::issueOpenInDownloadsFolder() +{ + emit openInDownloadsFolder(m_ContextIndex.row()); +} + void DownloadListWidgetCompactDelegate::issueRestoreToView() { emit restoreDownload(m_ContextIndex.row()); @@ -317,6 +322,7 @@ bool DownloadListWidgetCompactDelegate::editorEvent(QEvent *event, QAbstractItem }else { menu.addAction(tr("Visit on Nexus"), this, SLOT(issueVisitOnNexus())); } + menu.addAction(tr("Show in Folder"), this, SLOT(issueOpenInDownloadsFolder())); menu.addSeparator(); menu.addAction(tr("Delete"), this, SLOT(issueDelete())); if (hidden) { @@ -327,11 +333,12 @@ bool DownloadListWidgetCompactDelegate::editorEvent(QEvent *event, QAbstractItem } else if (state == DownloadManager::STATE_DOWNLOADING){ menu.addAction(tr("Cancel"), this, SLOT(issueCancel())); menu.addAction(tr("Pause"), this, SLOT(issuePause())); + menu.addAction(tr("Show in Folder"), this, SLOT(issueOpenInDownloadsFolder())); } else if ((state == DownloadManager::STATE_PAUSED) || (state == DownloadManager::STATE_ERROR) || (state == DownloadManager::STATE_PAUSING)) { menu.addAction(tr("Remove"), this, SLOT(issueDelete())); menu.addAction(tr("Resume"), this, SLOT(issueResume())); + menu.addAction(tr("Show in Folder"), this, SLOT(issueOpenInDownloadsFolder())); } - menu.addSeparator(); } menu.addAction(tr("Delete Installed..."), this, SLOT(issueDeleteCompleted())); diff --git a/src/downloadlistwidgetcompact.h b/src/downloadlistwidgetcompact.h index 28a376ebf..c25b7e997 100644 --- a/src/downloadlistwidgetcompact.h +++ b/src/downloadlistwidgetcompact.h @@ -70,6 +70,7 @@ class DownloadListWidgetCompactDelegate : public QItemDelegate void pauseDownload(int index); void resumeDownload(int index); void visitOnNexus(int index); + void openInDownloadsFolder(int index); protected: @@ -90,6 +91,7 @@ private slots: void issueRestoreToView(); void issueRestoreToViewAll(); void issueVisitOnNexus(); + void issueOpenInDownloadsFolder(); void issueCancel(); void issuePause(); void issueResume(); diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index 9761fee4f..1a2934a5e 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -911,6 +911,31 @@ void DownloadManager::visitOnNexus(int index) } } +void DownloadManager::openInDownloadsFolder(int index) +{ + if ((index < 0) || (index >= m_ActiveDownloads.size())) { + reportError(tr("VisitNexus: invalid download index %1").arg(index)); + return; + } + QString params = "/select,\""; + QDir path = QDir(m_OutputDirectory); + if (path.exists(getFileName(index))) { + params = params + QDir::toNativeSeparators(getFilePath(index)) + "\""; + + ::ShellExecuteW(nullptr, nullptr, L"explorer", ToWString(params).c_str(), nullptr, SW_SHOWNORMAL); + return; + } + else if (path.exists(getFileName(index) + ".unfinished")) { + params = params + QDir::toNativeSeparators(getFilePath(index)+".unfinished") + "\""; + + ::ShellExecuteW(nullptr, nullptr, L"explorer", ToWString(params).c_str(), nullptr, SW_SHOWNORMAL); + return; + } + + ::ShellExecuteW(nullptr, L"explore", ToWString(QDir::toNativeSeparators(m_OutputDirectory)).c_str(), nullptr, nullptr, SW_SHOWNORMAL); + return; +} + int DownloadManager::numTotalDownloads() const { diff --git a/src/downloadmanager.h b/src/downloadmanager.h index fa3764b74..514402ee4 100644 --- a/src/downloadmanager.h +++ b/src/downloadmanager.h @@ -438,6 +438,8 @@ public slots: void visitOnNexus(int index); + void openInDownloadsFolder(int index); + void nxmDescriptionAvailable(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID); void nxmFilesAvailable(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index fc666a49e..436d4d26f 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -4242,6 +4242,7 @@ void MainWindow::updateDownloadListDelegate() connect(ui->downloadView->itemDelegate(), SIGNAL(installDownload(int)), &m_OrganizerCore, SLOT(installDownload(int))); connect(ui->downloadView->itemDelegate(), SIGNAL(queryInfo(int)), m_OrganizerCore.downloadManager(), SLOT(queryInfo(int))); connect(ui->downloadView->itemDelegate(), SIGNAL(visitOnNexus(int)), m_OrganizerCore.downloadManager(), SLOT(visitOnNexus(int))); + connect(ui->downloadView->itemDelegate(), SIGNAL(openInDownloadsFolder(int)), m_OrganizerCore.downloadManager(), SLOT(openInDownloadsFolder(int))); connect(ui->downloadView->itemDelegate(), SIGNAL(removeDownload(int, bool)), m_OrganizerCore.downloadManager(), SLOT(removeDownload(int, bool))); connect(ui->downloadView->itemDelegate(), SIGNAL(restoreDownload(int)), m_OrganizerCore.downloadManager(), SLOT(restoreDownload(int))); connect(ui->downloadView->itemDelegate(), SIGNAL(cancelDownload(int)), m_OrganizerCore.downloadManager(), SLOT(cancelDownload(int))); From 7f3dc586b0422a31d34d00fcf23b2f2922418650 Mon Sep 17 00:00:00 2001 From: LostDragonist Date: Wed, 11 Jul 2018 05:45:58 -0500 Subject: [PATCH 30/67] Redirect endorsements of MO2 to the Skyrim SE nexus --- src/mainwindow.cpp | 17 ++++++++++++----- src/modinfo.cpp | 6 +++--- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 436d4d26f..01f37d755 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -4204,12 +4204,16 @@ void MainWindow::on_actionUpdate_triggered() void MainWindow::on_actionEndorseMO_triggered() { + // Normally this would be the managed game but MO2 is only uploaded to the Skyrim SE site right now + IPluginGame * game = m_OrganizerCore.getGame("skyrimse"); + if (!game) return; + if (QMessageBox::question(this, tr("Endorse Mod Organizer"), tr("Do you want to endorse Mod Organizer on %1 now?").arg( - NexusInterface::instance(&m_PluginContainer)->getGameURL(m_OrganizerCore.managedGame()->gameShortName())), + NexusInterface::instance(&m_PluginContainer)->getGameURL(game->gameShortName())), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { NexusInterface::instance(&m_PluginContainer)->requestToggleEndorsement( - m_OrganizerCore.managedGame()->gameShortName(), m_OrganizerCore.managedGame()->nexusModOrganizerID(), true, this, QVariant(), QString()); + game->gameShortName(), game->nexusModOrganizerID(), true, this, QVariant(), QString()); } } @@ -4274,8 +4278,11 @@ void MainWindow::nxmUpdatesAvailable(const std::vector &modIDs, QVariant us QVariantList resultList = resultData.toList(); for (auto iter = resultList.begin(); iter != resultList.end(); ++iter) { QVariantMap result = iter->toMap(); - if (result["id"].toInt() == m_OrganizerCore.managedGame()->nexusModOrganizerID() - && result["game_id"].toInt() == m_OrganizerCore.managedGame()->nexusGameID()) { + // Normally this would be the managed game but MO2 is only uploaded to the Skyrim SE site right now + IPluginGame * game = m_OrganizerCore.getGame("skyrimse"); + if (game + && result["id"].toInt() == game->nexusModOrganizerID() + && result["game_id"].toInt() == game->nexusGameID()) { if (!result["voted_by_user"].toBool()) { ui->actionEndorseMO->setVisible(true); } @@ -4326,7 +4333,7 @@ void MainWindow::nxmEndorsementToggled(QString, int, QVariant, QVariant resultDa { if (resultData.toBool()) { ui->actionEndorseMO->setVisible(false); - QMessageBox::question(this, tr("Thank you!"), tr("Thank you for your endorsement!")); + QMessageBox::information(this, tr("Thank you!"), tr("Thank you for your endorsement!")); } if (!disconnect(sender(), SIGNAL(nxmEndorsementToggled(QString, int, QVariant, QVariant, int)), diff --git a/src/modinfo.cpp b/src/modinfo.cpp index bd4c12543..1f2520c97 100644 --- a/src/modinfo.cpp +++ b/src/modinfo.cpp @@ -286,9 +286,9 @@ int ModInfo::checkAllForUpdate(PluginContainer *pluginContainer, QObject *receiv int result = 0; std::vector modIDs; - //I ought to store this, it's used elsewhere - IPluginGame const *game = qApp->property("managed_game").value(); - if (game->nexusModOrganizerID()) { + // Normally this would be the managed game but MO2 is only uploaded to the Skyrim SE site right now + IPluginGame const *game = pluginContainer->managedGame("Skyrim Special Edition"); + if (game && game->nexusModOrganizerID()) { modIDs.push_back(game->nexusModOrganizerID()); checkChunkForUpdate(pluginContainer, modIDs, receiver, game->gameShortName()); modIDs.clear(); From 942e656a71f452cd3cdbca2a4264db3cc2014ae9 Mon Sep 17 00:00:00 2001 From: LostDragonist Date: Mon, 16 Jul 2018 15:00:35 -0500 Subject: [PATCH 31/67] Fix download index in downloadFinished An index of 0 is valid as this is a bog standard array. This resolves an instance of the warning "no download index 0". --- src/downloadmanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index 1a2934a5e..9e523bf98 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -1655,7 +1655,7 @@ void DownloadManager::nxmRequestFailed(QString gameName, int modID, int fileID, void DownloadManager::downloadFinished(int index) { DownloadInfo *info; - if (index) + if (index >= 0) info = m_ActiveDownloads[index]; else info = findDownload(this->sender(), &index); From f20a1f6a849510438ca5c9abe8fdac1dada263d2 Mon Sep 17 00:00:00 2001 From: LostDragonist Date: Mon, 16 Jul 2018 15:22:51 -0500 Subject: [PATCH 32/67] Allow "ignore missing data" to work with multiple mods --- src/mainwindow.cpp | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 01f37d755..1f5cef2f5 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2619,12 +2619,25 @@ void MainWindow::displayModInformation(int row, int tab) void MainWindow::ignoreMissingData_clicked() { - ModInfo::Ptr info = ModInfo::getByIndex(m_ContextRow); - QDir(info->absolutePath()).mkdir("textures"); - info->testValid(); - connect(this, SIGNAL(modListDataChanged(QModelIndex,QModelIndex)), m_OrganizerCore.modList(), SIGNAL(dataChanged(QModelIndex,QModelIndex))); + QItemSelectionModel *selection = ui->modList->selectionModel(); + if (selection->hasSelection() && selection->selectedRows().count() > 1) { + for (QModelIndex idx : selection->selectedRows()) { + int row_idx = idx.data(Qt::UserRole + 1).toInt(); + ModInfo::Ptr info = ModInfo::getByIndex(row_idx); + QDir(info->absolutePath()).mkdir("textures"); + info->testValid(); + connect(this, SIGNAL(modListDataChanged(QModelIndex, QModelIndex)), m_OrganizerCore.modList(), SIGNAL(dataChanged(QModelIndex, QModelIndex))); - emit modListDataChanged(m_OrganizerCore.modList()->index(m_ContextRow, 0), m_OrganizerCore.modList()->index(m_ContextRow, m_OrganizerCore.modList()->columnCount() - 1)); + emit modListDataChanged(m_OrganizerCore.modList()->index(row_idx, 0), m_OrganizerCore.modList()->index(row_idx, m_OrganizerCore.modList()->columnCount() - 1)); + } + } else { + ModInfo::Ptr info = ModInfo::getByIndex(m_ContextRow); + QDir(info->absolutePath()).mkdir("textures"); + info->testValid(); + connect(this, SIGNAL(modListDataChanged(QModelIndex, QModelIndex)), m_OrganizerCore.modList(), SIGNAL(dataChanged(QModelIndex, QModelIndex))); + + emit modListDataChanged(m_OrganizerCore.modList()->index(m_ContextRow, 0), m_OrganizerCore.modList()->index(m_ContextRow, m_OrganizerCore.modList()->columnCount() - 1)); + } } void MainWindow::markConverted_clicked() From b4530eb84321f684e0ad764ed3cdafbbd9e4315c Mon Sep 17 00:00:00 2001 From: LostDragonist Date: Mon, 16 Jul 2018 15:31:08 -0500 Subject: [PATCH 33/67] Allow "mark as converted" to work with multiple mods --- src/mainwindow.cpp | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 1f5cef2f5..5041f6619 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2642,10 +2642,21 @@ void MainWindow::ignoreMissingData_clicked() void MainWindow::markConverted_clicked() { - ModInfo::Ptr info = ModInfo::getByIndex(m_ContextRow); - info->markConverted(true); - connect(this, SIGNAL(modListDataChanged(QModelIndex, QModelIndex)), m_OrganizerCore.modList(), SIGNAL(dataChanged(QModelIndex, QModelIndex))); - emit modListDataChanged(m_OrganizerCore.modList()->index(m_ContextRow, 0), m_OrganizerCore.modList()->index(m_ContextRow, m_OrganizerCore.modList()->columnCount() - 1)); + QItemSelectionModel *selection = ui->modList->selectionModel(); + if (selection->hasSelection() && selection->selectedRows().count() > 1) { + for (QModelIndex idx : selection->selectedRows()) { + int row_idx = idx.data(Qt::UserRole + 1).toInt(); + ModInfo::Ptr info = ModInfo::getByIndex(row_idx); + info->markConverted(true); + connect(this, SIGNAL(modListDataChanged(QModelIndex, QModelIndex)), m_OrganizerCore.modList(), SIGNAL(dataChanged(QModelIndex, QModelIndex))); + emit modListDataChanged(m_OrganizerCore.modList()->index(row_idx, 0), m_OrganizerCore.modList()->index(row_idx, m_OrganizerCore.modList()->columnCount() - 1)); + } + } else { + ModInfo::Ptr info = ModInfo::getByIndex(m_ContextRow); + info->markConverted(true); + connect(this, SIGNAL(modListDataChanged(QModelIndex, QModelIndex)), m_OrganizerCore.modList(), SIGNAL(dataChanged(QModelIndex, QModelIndex))); + emit modListDataChanged(m_OrganizerCore.modList()->index(m_ContextRow, 0), m_OrganizerCore.modList()->index(m_ContextRow, m_OrganizerCore.modList()->columnCount() - 1)); + } } From 952f1fe8cd6dda5f97fd29fce9e7e8372093c3f4 Mon Sep 17 00:00:00 2001 From: LostDragonist Date: Mon, 16 Jul 2018 15:35:11 -0500 Subject: [PATCH 34/67] Allow "open in explorer" to work with multiple mods --- src/mainwindow.cpp | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 5041f6619..dc4ede28a 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2683,9 +2683,17 @@ void MainWindow::visitWebPage_clicked() void MainWindow::openExplorer_clicked() { - ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow); - - ::ShellExecuteW(nullptr, L"explore", ToWString(modInfo->absolutePath()).c_str(), nullptr, nullptr, SW_SHOWNORMAL); + QItemSelectionModel *selection = ui->modList->selectionModel(); + if (selection->hasSelection() && selection->selectedRows().count() > 1) { + for (QModelIndex idx : selection->selectedRows()) { + ModInfo::Ptr info = ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt()); + ::ShellExecuteW(nullptr, L"explore", ToWString(info->absolutePath()).c_str(), nullptr, nullptr, SW_SHOWNORMAL); + } + } + else { + ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow); + ::ShellExecuteW(nullptr, L"explore", ToWString(modInfo->absolutePath()).c_str(), nullptr, nullptr, SW_SHOWNORMAL); + } } void MainWindow::openExplorer_activated() From eaf3655492e34a5b613a3f75576c872f8f932d17 Mon Sep 17 00:00:00 2001 From: LostDragonist Date: Mon, 16 Jul 2018 15:35:41 -0500 Subject: [PATCH 35/67] Allow "ignore update" to work with multiple mods --- src/mainwindow.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index dc4ede28a..19f61ae86 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -3108,8 +3108,17 @@ void MainWindow::changeVersioningScheme() { } void MainWindow::ignoreUpdate() { - ModInfo::Ptr info = ModInfo::getByIndex(m_ContextRow); - info->ignoreUpdate(true); + QItemSelectionModel *selection = ui->modList->selectionModel(); + if (selection->hasSelection() && selection->selectedRows().count() > 1) { + for (QModelIndex idx : selection->selectedRows()) { + ModInfo::Ptr info = ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt()); + info->ignoreUpdate(true); + } + } + else { + ModInfo::Ptr info = ModInfo::getByIndex(m_ContextRow); + info->ignoreUpdate(true); + } } void MainWindow::unignoreUpdate() From a830f3e09efe60d10dc7d97fa645a164f54994a0 Mon Sep 17 00:00:00 2001 From: LostDragonist Date: Mon, 16 Jul 2018 15:36:06 -0500 Subject: [PATCH 36/67] Allow "unignore update" to work with multiple mods --- src/mainwindow.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 19f61ae86..f70e66a6a 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -3123,8 +3123,17 @@ void MainWindow::ignoreUpdate() { void MainWindow::unignoreUpdate() { - ModInfo::Ptr info = ModInfo::getByIndex(m_ContextRow); - info->ignoreUpdate(false); + QItemSelectionModel *selection = ui->modList->selectionModel(); + if (selection->hasSelection() && selection->selectedRows().count() > 1) { + for (QModelIndex idx : selection->selectedRows()) { + ModInfo::Ptr info = ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt()); + info->ignoreUpdate(false); + } + } + else { + ModInfo::Ptr info = ModInfo::getByIndex(m_ContextRow); + info->ignoreUpdate(false); + } } void MainWindow::addPrimaryCategoryCandidates(QMenu *primaryCategoryMenu, From 6552054e58d8c108c59b38335870529da78a27f1 Mon Sep 17 00:00:00 2001 From: LostDragonist Date: Mon, 9 Jul 2018 23:59:35 -0500 Subject: [PATCH 37/67] Detect if the Windows Event Log service is not running --- src/organizercore.cpp | 109 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 102 insertions(+), 7 deletions(-) diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 35486f984..142faabed 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -186,6 +186,86 @@ QStringList toStringList(InputIterator current, InputIterator end) return result; } +bool checkService() +{ + SC_HANDLE serviceManagerHandle = NULL; + SC_HANDLE serviceHandle = NULL; + LPSERVICE_STATUS_PROCESS serviceStatus = NULL; + LPQUERY_SERVICE_CONFIG serviceConfig = NULL; + bool serviceRunning = true; + + DWORD bytesNeeded; + + try { + serviceManagerHandle = OpenSCManager(NULL, NULL, SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG); + if (!serviceManagerHandle) { + qWarning("failed to open service manager (query status) (error %d)", GetLastError()); + throw 1; + } + + serviceHandle = OpenService(serviceManagerHandle, L"EventLog", SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG); + if (!serviceHandle) { + qWarning("failed to open EventLog service (query status) (error %d)", GetLastError()); + throw 2; + } + + if (QueryServiceConfig(serviceHandle, NULL, 0, &bytesNeeded) + || (GetLastError() != ERROR_INSUFFICIENT_BUFFER)) { + qWarning("failed to get size of service config (error %d)", GetLastError()); + throw 3; + } + + DWORD serviceConfigSize = bytesNeeded; + serviceConfig = (LPQUERY_SERVICE_CONFIG)LocalAlloc(LMEM_FIXED, serviceConfigSize); + if (!QueryServiceConfig(serviceHandle, serviceConfig, serviceConfigSize, &bytesNeeded)) { + qWarning("failed to query service config (error %d)", GetLastError()); + throw 4; + } + + if (serviceConfig->dwStartType == SERVICE_DISABLED) { + qCritical("Windows Event Log service is disabled!"); + serviceRunning = false; + } + + if (QueryServiceStatusEx(serviceHandle, SC_STATUS_PROCESS_INFO, NULL, 0, &bytesNeeded) + || (GetLastError() != ERROR_INSUFFICIENT_BUFFER)) { + qWarning("failed to get size of service status (error %d)", GetLastError()); + throw 5; + } + + DWORD serviceStatusSize = bytesNeeded; + serviceStatus = (LPSERVICE_STATUS_PROCESS)LocalAlloc(LMEM_FIXED, serviceStatusSize); + if (!QueryServiceStatusEx(serviceHandle, SC_STATUS_PROCESS_INFO, (LPBYTE)serviceStatus, serviceStatusSize, &bytesNeeded)) { + qWarning("failed to query service status (error %d)", GetLastError()); + throw 6; + } + + if (serviceStatus->dwCurrentState != SERVICE_RUNNING) { + qCritical("Windows Event Log service is not running"); + serviceRunning = false; + } + } + catch (int e) { + UNUSED_VAR(e); + serviceRunning = false; + } + + if (serviceStatus) { + LocalFree(serviceStatus); + } + if (serviceConfig) { + LocalFree(serviceConfig); + } + if (serviceHandle) { + CloseServiceHandle(serviceHandle); + } + if (serviceManagerHandle) { + CloseServiceHandle(serviceManagerHandle); + } + + return serviceRunning; +} + OrganizerCore::OrganizerCore(const QSettings &initSettings) : m_UserInterface(nullptr) , m_PluginContainer(nullptr) @@ -1190,6 +1270,11 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary, ToWString(m_Settings.getSteamAppID()).c_str()); } + QWidget *window = qApp->activeWindow(); + if ((window != nullptr) && (!window->isVisible())) { + window = nullptr; + } + // This could possibly be extracted somewhere else but it's probably for when // we have more than one provider of game registration. if ((QFileInfo( @@ -1200,16 +1285,12 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary, .exists()) && (m_Settings.getLoadMechanism() == LoadMechanism::LOAD_MODORGANIZER)) { if (!testForSteam()) { - QWidget *window = qApp->activeWindow(); - if ((window != nullptr) && (!window->isVisible())) { - window = nullptr; - } if (QuestionBoxMemory::query(window, "steamQuery", binary.fileName(), tr("Start Steam?"), tr("Steam is required to be running already to correctly start the game. " "Should MO try to start steam now?"), QDialogButtonBox::Yes | QDialogButtonBox::No) == QDialogButtonBox::Yes) { - startSteam(qApp->activeWindow()); + startSteam(window); } } } @@ -1229,10 +1310,24 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary, try { m_USVFS.updateMapping(fileMapping(profileName, customOverwrite)); } catch (const std::exception &e) { - QMessageBox::warning(qApp->activeWindow(), tr("Error"), e.what()); + QMessageBox::warning(window, tr("Error"), e.what()); return INVALID_HANDLE_VALUE; } + // Check if the Windows Event Logging service is running. For some reason, this seems to be + // critical to the successful running of usvfs. + if (!checkService()) { + if (QuestionBoxMemory::query(window, QString("eventLogService"), binary.fileName(), + tr("Windows Event Log Error"), + tr("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.\n\nContinue launching %1?").arg(binary.fileName()), + QDialogButtonBox::Yes | QDialogButtonBox::No) == QDialogButtonBox::No) { + return INVALID_HANDLE_VALUE; + } + } + QString modsPath = settings().getModDirectory(); // Check if this a request with either an executable or a working directory under our mods folder @@ -2204,4 +2299,4 @@ std::vector OrganizerCore::fileMapping( result.insert(result.end(), subRes.begin(), subRes.end()); } return result; -} +} \ No newline at end of file From 1867c20f02d44f98154955a89686fe9edf09d7f5 Mon Sep 17 00:00:00 2001 From: Al12rs Date: Tue, 17 Jul 2018 00:10:03 +0200 Subject: [PATCH 38/67] Added sortable "Size" column to the downlaods tab. --- src/downloadlist.cpp | 3 ++- src/downloadlist.h | 3 ++- src/downloadlistsortproxy.cpp | 2 ++ src/downloadlistwidget.cpp | 4 ++-- src/downloadlistwidgetcompact.cpp | 25 +++++++++++++++++++++---- 5 files changed, 29 insertions(+), 8 deletions(-) diff --git a/src/downloadlist.cpp b/src/downloadlist.cpp index f13cdef14..1e31973d6 100644 --- a/src/downloadlist.cpp +++ b/src/downloadlist.cpp @@ -40,7 +40,7 @@ int DownloadList::rowCount(const QModelIndex&) const int DownloadList::columnCount(const QModelIndex&) const { - return 3; + return 4; } @@ -63,6 +63,7 @@ QVariant DownloadList::headerData(int section, Qt::Orientation orientation, int switch (section) { case COL_NAME: return tr("Name"); case COL_FILETIME: return tr("Filetime"); + case COL_SIZE: return tr("Size"); default: return tr("Done"); } } else { diff --git a/src/downloadlist.h b/src/downloadlist.h index 2316dddce..e8833f0f6 100644 --- a/src/downloadlist.h +++ b/src/downloadlist.h @@ -39,7 +39,8 @@ class DownloadList : public QAbstractTableModel enum EColumn { COL_NAME = 0, COL_FILETIME, - COL_STATUS + COL_STATUS, + COL_SIZE }; public: diff --git a/src/downloadlistsortproxy.cpp b/src/downloadlistsortproxy.cpp index 2780f9739..f791617ae 100644 --- a/src/downloadlistsortproxy.cpp +++ b/src/downloadlistsortproxy.cpp @@ -47,6 +47,8 @@ bool DownloadListSortProxy::lessThan(const QModelIndex &left, return m_Manager->getFileTime(leftIndex) < m_Manager->getFileTime(rightIndex); } else if (left.column() == DownloadList::COL_STATUS) { return m_Manager->getState(leftIndex) < m_Manager->getState(rightIndex); + } else if(left.column() == DownloadList::COL_SIZE){ + return m_Manager->getFileSize(leftIndex) < m_Manager->getFileSize(rightIndex); } else { return leftIndex < rightIndex; } diff --git a/src/downloadlistwidget.cpp b/src/downloadlistwidget.cpp index 0ef408fb7..1a401d105 100644 --- a/src/downloadlistwidget.cpp +++ b/src/downloadlistwidget.cpp @@ -82,7 +82,7 @@ void DownloadListWidgetDelegate::drawCache(QPainter *painter, const QStyleOption { QRect rect = option.rect; rect.setLeft(0); - rect.setWidth(m_View->columnWidth(0) + m_View->columnWidth(1) + m_View->columnWidth(2)); + rect.setWidth(m_View->columnWidth(0) + m_View->columnWidth(1) + m_View->columnWidth(2) + m_View->columnWidth(3)); painter->drawPixmap(rect, cache); } @@ -193,7 +193,7 @@ void DownloadListWidgetDelegate::paint(QPainter *painter, const QStyleOptionView return; } - m_ItemWidget->resize(QSize(m_View->columnWidth(0) + m_View->columnWidth(1) + m_View->columnWidth(2), option.rect.height())); + m_ItemWidget->resize(QSize(m_View->columnWidth(0) + m_View->columnWidth(1) + m_View->columnWidth(2) + m_View->columnWidth(3), option.rect.height())); int downloadIndex = index.data().toInt(); diff --git a/src/downloadlistwidgetcompact.cpp b/src/downloadlistwidgetcompact.cpp index 1d1805efe..ad9e0994c 100644 --- a/src/downloadlistwidgetcompact.cpp +++ b/src/downloadlistwidgetcompact.cpp @@ -81,10 +81,27 @@ void DownloadListWidgetCompactDelegate::drawCache(QPainter *painter, const QStyl { QRect rect = option.rect; rect.setLeft(0); - rect.setWidth(m_View->columnWidth(0) + m_View->columnWidth(1) + m_View->columnWidth(2)); + rect.setWidth(m_View->columnWidth(0) + m_View->columnWidth(1) + m_View->columnWidth(2) + m_View->columnWidth(3)); painter->drawPixmap(rect, cache); } +QString DownloadListWidgetCompactDelegate::sizeFormat(quint64 size) const +{ + qreal calc = size; + QStringList list; + list << "KB" << "MB" << "GB" << "TB"; + + QStringListIterator i(list); + QString unit("byte(s)"); + + while (calc >= 1024.0 && i.hasNext()) + { + unit = i.next(); + calc /= 1024.0; + } + + return QString().setNum(calc, 'f', 2) + " " + unit; +} void DownloadListWidgetCompactDelegate::paintPendingDownload(int downloadIndex) const { @@ -110,8 +127,8 @@ void DownloadListWidgetCompactDelegate::paintRegularDownload(int downloadIndex) DownloadManager::DownloadState state = m_Manager->getState(downloadIndex); - if ((m_SizeLabel != nullptr) && (state >= DownloadManager::STATE_READY)) { - m_SizeLabel->setText(QString::number(m_Manager->getFileSize(downloadIndex) / 1048576)); + if ((m_SizeLabel != nullptr)) { + m_SizeLabel->setText(sizeFormat(m_Manager->getFileSize(downloadIndex))); } if ((state == DownloadManager::STATE_PAUSED) || (state == DownloadManager::STATE_ERROR) || (state == DownloadManager::STATE_PAUSING)) { @@ -153,7 +170,7 @@ void DownloadListWidgetCompactDelegate::paint(QPainter *painter, const QStyleOpt return; } - m_ItemWidget->resize(QSize(m_View->columnWidth(0) + m_View->columnWidth(1) + m_View->columnWidth(2), option.rect.height())); + m_ItemWidget->resize(QSize(m_View->columnWidth(0) + m_View->columnWidth(1) + m_View->columnWidth(2) + m_View->columnWidth(3), option.rect.height())); if (index.row() % 2 == 1) { m_ItemWidget->setBackgroundRole(QPalette::AlternateBase); } else { From 9ddb26ea5100581c832920015f79de8d7366463f Mon Sep 17 00:00:00 2001 From: Al12rs Date: Tue, 17 Jul 2018 00:10:30 +0200 Subject: [PATCH 39/67] Added size label to compact download view. --- src/downloadlistwidgetcompact.h | 1 + src/downloadlistwidgetcompact.ui | 22 +++++++++++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/downloadlistwidgetcompact.h b/src/downloadlistwidgetcompact.h index c25b7e997..1fcab3e69 100644 --- a/src/downloadlistwidgetcompact.h +++ b/src/downloadlistwidgetcompact.h @@ -74,6 +74,7 @@ class DownloadListWidgetCompactDelegate : public QItemDelegate protected: + QString sizeFormat(quint64 size) const; bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index); diff --git a/src/downloadlistwidgetcompact.ui b/src/downloadlistwidgetcompact.ui index b43f1fb35..a3cf3f029 100644 --- a/src/downloadlistwidgetcompact.ui +++ b/src/downloadlistwidgetcompact.ui @@ -19,7 +19,7 @@ true - + 2 @@ -57,6 +57,26 @@ + + + + Qt::Horizontal + + + + 10 + 20 + + + + + + + + + + + From 872c33fe5592eb84a2f6c01a2e47b72602b6139e Mon Sep 17 00:00:00 2001 From: Al12rs Date: Tue, 17 Jul 2018 00:49:41 +0200 Subject: [PATCH 40/67] Added new "Hide Uninstalled" and "Delete Uninstalled" options to downloads tab. --- src/downloadlistwidget.cpp | 20 ++++++++++++++++++++ src/downloadlistwidget.h | 2 ++ src/downloadlistwidgetcompact.cpp | 20 ++++++++++++++++++++ src/downloadlistwidgetcompact.h | 2 ++ src/downloadmanager.cpp | 8 +++++++- 5 files changed, 51 insertions(+), 1 deletion(-) diff --git a/src/downloadlistwidget.cpp b/src/downloadlistwidget.cpp index 1a401d105..ad694107a 100644 --- a/src/downloadlistwidget.cpp +++ b/src/downloadlistwidget.cpp @@ -310,6 +310,15 @@ void DownloadListWidgetDelegate::issueDeleteCompleted() } } +void DownloadListWidgetDelegate::issueDeleteUninstalled() +{ + if (QMessageBox::question(nullptr, tr("Delete Files?"), + tr("This will remove all uninstalled downloads from this list and from disk."), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + emit removeDownload(-3, true); + } +} + void DownloadListWidgetDelegate::issueRemoveFromViewAll() { if (QMessageBox::question(nullptr, tr("Are you sure?"), @@ -328,6 +337,15 @@ void DownloadListWidgetDelegate::issueRemoveFromViewCompleted() } } +void DownloadListWidgetDelegate::issueRemoveFromViewUninstalled() +{ + if (QMessageBox::question(nullptr, tr("Are you sure?"), + tr("This will remove all uninstalled downloads from this list (but NOT from disk)."), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + emit removeDownload(-3, false); + } +} + bool DownloadListWidgetDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index) { @@ -380,11 +398,13 @@ bool DownloadListWidgetDelegate::editorEvent(QEvent *event, QAbstractItemModel * menu.addSeparator(); } menu.addAction(tr("Delete Installed..."), this, SLOT(issueDeleteCompleted())); + menu.addAction(tr("Delete Uninstalled..."), this, SLOT(issueDeleteUninstalled())); menu.addAction(tr("Delete All..."), this, SLOT(issueDeleteAll())); if (!hidden) { menu.addSeparator(); menu.addAction(tr("Hide Installed..."), this, SLOT(issueRemoveFromViewCompleted())); + menu.addAction(tr("Hide Uninstalled..."), this, SLOT(issueRemoveFromViewUninstalled())); menu.addAction(tr("Hide All..."), this, SLOT(issueRemoveFromViewAll())); } if (hidden) { diff --git a/src/downloadlistwidget.h b/src/downloadlistwidget.h index 4dcbea992..2dd73e731 100644 --- a/src/downloadlistwidget.h +++ b/src/downloadlistwidget.h @@ -99,8 +99,10 @@ private slots: void issueResume(); void issueDeleteAll(); void issueDeleteCompleted(); + void issueDeleteUninstalled(); void issueRemoveFromViewAll(); void issueRemoveFromViewCompleted(); + void issueRemoveFromViewUninstalled(); void issueQueryInfo(); void stateChanged(int row, DownloadManager::DownloadState); diff --git a/src/downloadlistwidgetcompact.cpp b/src/downloadlistwidgetcompact.cpp index ad9e0994c..34e31006a 100644 --- a/src/downloadlistwidgetcompact.cpp +++ b/src/downloadlistwidgetcompact.cpp @@ -292,6 +292,15 @@ void DownloadListWidgetCompactDelegate::issueDeleteCompleted() } } +void DownloadListWidgetCompactDelegate::issueDeleteUninstalled() +{ + if (QMessageBox::question(nullptr, tr("Are you sure?"), + tr("This will remove all uninstalled downloads from this list and from disk."), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + emit removeDownload(-3, true); + } +} + void DownloadListWidgetCompactDelegate::issueRemoveFromViewAll() { if (QMessageBox::question(nullptr, tr("Are you sure?"), @@ -310,6 +319,15 @@ void DownloadListWidgetCompactDelegate::issueRemoveFromViewCompleted() } } +void DownloadListWidgetCompactDelegate::issueRemoveFromViewUninstalled() +{ + if (QMessageBox::question(nullptr, tr("Are you sure?"), + tr("This will permanently remove all uninstalled downloads from this list (but NOT from disk)."), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + emit removeDownload(-3, false); + } +} + bool DownloadListWidgetCompactDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index) @@ -359,10 +377,12 @@ bool DownloadListWidgetCompactDelegate::editorEvent(QEvent *event, QAbstractItem menu.addSeparator(); } menu.addAction(tr("Delete Installed..."), this, SLOT(issueDeleteCompleted())); + menu.addAction(tr("Delete Uninstalled..."), this, SLOT(issueDeleteUninstalled())); menu.addAction(tr("Delete All..."), this, SLOT(issueDeleteAll())); if (!hidden) { menu.addSeparator(); menu.addAction(tr("Hide Installed..."), this, SLOT(issueRemoveFromViewCompleted())); + menu.addAction(tr("Hide Uninstalled..."), this, SLOT(issueRemoveFromViewUninstalled())); menu.addAction(tr("Hide All..."), this, SLOT(issueRemoveFromViewAll())); } if (hidden) { diff --git a/src/downloadlistwidgetcompact.h b/src/downloadlistwidgetcompact.h index 1fcab3e69..b1b3c6176 100644 --- a/src/downloadlistwidgetcompact.h +++ b/src/downloadlistwidgetcompact.h @@ -98,8 +98,10 @@ private slots: void issueResume(); void issueDeleteAll(); void issueDeleteCompleted(); + void issueDeleteUninstalled(); void issueRemoveFromViewAll(); void issueRemoveFromViewCompleted(); + void issueRemoveFromViewUninstalled(); void issueQueryInfo(); void stateChanged(int row, DownloadManager::DownloadState); diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index 1a2934a5e..0c83bf923 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -690,7 +690,13 @@ void DownloadManager::removeDownload(int index, bool deleteFile) emit aboutToUpdate(); if (index < 0) { - DownloadState minState = index == -1 ? STATE_READY : STATE_INSTALLED; + DownloadState minState; + if (index == -3) { + minState = STATE_UNINSTALLED; + } + else + minState = index == -1 ? STATE_READY : STATE_INSTALLED; + index = 0; for (QVector::iterator iter = m_ActiveDownloads.begin(); iter != m_ActiveDownloads.end();) { if ((*iter)->m_State >= minState) { From 61b33a83519d1473b6b7a62d3d571c5097572204 Mon Sep 17 00:00:00 2001 From: Al12rs Date: Tue, 17 Jul 2018 12:38:17 +0200 Subject: [PATCH 41/67] Added feather and scroll content icon to rappresent ini files. Icon curtesy of WolfHeartAurora. --- src/resources.qrc | 1 + src/resources/contents/feather-and-scroll.png | Bin 0 -> 616 bytes 2 files changed, 1 insertion(+) create mode 100644 src/resources/contents/feather-and-scroll.png diff --git a/src/resources.qrc b/src/resources.qrc index a18baf45d..f3459ea7d 100644 --- a/src/resources.qrc +++ b/src/resources.qrc @@ -93,6 +93,7 @@ resources/contents/conversation.png resources/contents/locked-chest.png resources/contents/config.png + resources/contents/feather-and-scroll.png qt.conf diff --git a/src/resources/contents/feather-and-scroll.png b/src/resources/contents/feather-and-scroll.png new file mode 100644 index 0000000000000000000000000000000000000000..f82694ca484f07d4d598b8b8219a9bce7170e9b7 GIT binary patch literal 616 zcmV-u0+;=XP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGf5dZ)S5dnW>Uy%R+02y>eSaefwW^{L9 za%BK;VQFr3E^cLXAT%y8E;2FkAZe8V00G!ZL_t(YOWl^)iYhS_MfJfMM8$ao|Nrj< zXRThHeGFwKR=NusE_XeIGwf`uPs-S}|Al(JZqeV7WtrA$uy-^XIj4ZxY-Xw~n@lDa z{T{Q6Li_oAO1ECG6?pFV`{VII;32);ZtD5~%r32y*IoXU6mMYGvdJ%UMM$VD2RL0-?pbvm71uSd*CoLLO1KjGi$f>^PN zP&OrPh_c)56iB9VF+jh|8PvMnu7Wt|*lEc-`~6-ntofm<>w^T^6SCcI6`;}af*yrH zFM=~j;WkFl2ZVz8d~Q+Y58xeFJ?v^|9%qokwC|{1=oZ|;8Kn3mRUL>|s}++`-J@0- zxfGzzRWY4T^LKlQ91@r|N3IVm4kcvK=>M;&u3OR9*R{H{}VQ@gZD|58~0000 Date: Tue, 17 Jul 2018 13:06:38 +0200 Subject: [PATCH 42/67] Added Contains INI files content filter and checking. --- src/modinfo.cpp | 1 + src/modinfo.h | 5 +++-- src/modinforegular.cpp | 4 ++++ src/modlist.cpp | 2 ++ 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/modinfo.cpp b/src/modinfo.cpp index 1f2520c97..91f3c1a17 100644 --- a/src/modinfo.cpp +++ b/src/modinfo.cpp @@ -102,6 +102,7 @@ QString ModInfo::getContentTypeName(int contentType) case CONTENT_SKSE: return tr("Script Extender"); case CONTENT_SKYPROC: return tr("SkyProc Tools"); case CONTENT_MCM: return tr("MCM Data"); + case CONTENT_INI: return tr("INI files"); default: throw MyException(tr("invalid content type %1").arg(contentType)); } } diff --git a/src/modinfo.h b/src/modinfo.h index 001a78dc4..4cdd7bcf8 100644 --- a/src/modinfo.h +++ b/src/modinfo.h @@ -84,10 +84,11 @@ class ModInfo : public QObject, public MOBase::IModInterface CONTENT_SCRIPT, CONTENT_SKSE, CONTENT_SKYPROC, - CONTENT_MCM + CONTENT_MCM, + CONTENT_INI }; - static const int NUM_CONTENT_TYPES = CONTENT_MCM + 1; + static const int NUM_CONTENT_TYPES = CONTENT_INI + 1; enum EHighlight { HIGHLIGHT_NONE = 0, diff --git a/src/modinforegular.cpp b/src/modinforegular.cpp index c19294f11..5b6ddcda6 100644 --- a/src/modinforegular.cpp +++ b/src/modinforegular.cpp @@ -466,6 +466,10 @@ std::vector ModInfoRegular::getContents() const if (dir.entryList(QStringList() << "*.bsa" << "*.ba2").size() > 0) { m_CachedContent.push_back(CONTENT_BSA); } + //use >1 for ini files since there is meta.ini in all mods already. + if (dir.entryList(QStringList() << "*.ini").size() > 1) { + m_CachedContent.push_back(CONTENT_INI); + } ScriptExtender *extender = qApp->property("managed_game") .value() diff --git a/src/modlist.cpp b/src/modlist.cpp index bf57ef6eb..aaa4ba797 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -73,6 +73,7 @@ ModList::ModList(PluginContainer *pluginContainer, QObject *parent) 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_LastCheck.start(); } @@ -1109,6 +1110,7 @@ QString ModList::getColumnToolTip(int column) "Script Extender plugins" "SkyProc Patcher" "Mod Configuration Menu" + "INI files" ""); case COL_INSTALLTIME: return tr("Time this mod was installed"); default: return tr("unknown"); From 75478f68a12c89af790ba2aae127d4834c6d44a5 Mon Sep 17 00:00:00 2001 From: Al12rs Date: Tue, 17 Jul 2018 13:32:53 +0200 Subject: [PATCH 43/67] Avoid asking users if they are sure they want to close of all downlaods are paused. --- src/downloadmanager.cpp | 10 ++++++++++ src/downloadmanager.h | 7 +++++++ src/mainwindow.cpp | 2 +- 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index 385d22465..beb498c18 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -230,6 +230,16 @@ bool DownloadManager::downloadsInProgress() return false; } +bool DownloadManager::downloadsInProgressNoPause() +{ + for (QVector::iterator iter = m_ActiveDownloads.begin(); iter != m_ActiveDownloads.end(); ++iter) { + if ((*iter)->m_State < STATE_READY && (*iter)->m_State != STATE_PAUSED) { + return true; + } + } + return false; +} + void DownloadManager::pauseAll() { diff --git a/src/downloadmanager.h b/src/downloadmanager.h index 514402ee4..136ecf2aa 100644 --- a/src/downloadmanager.h +++ b/src/downloadmanager.h @@ -138,6 +138,13 @@ class DownloadManager : public MOBase::IDownloadManager **/ bool downloadsInProgress(); + /** + * @brief determine if a download is currently in progress, does not count paused ones. + * + * @return true if there is currently a download in progress (that is not paused already). + **/ + bool downloadsInProgressNoPause(); + /** * @brief set the output directory to write to * diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index f70e66a6a..5999608cd 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -883,7 +883,7 @@ void MainWindow::closeEvent(QCloseEvent* event) { m_closing = true; - if (m_OrganizerCore.downloadManager()->downloadsInProgress()) { + if (m_OrganizerCore.downloadManager()->downloadsInProgressNoPause()) { if (QMessageBox::question(this, tr("Downloads in progress"), tr("There are still downloads in progress, do you really want to quit?"), QMessageBox::Yes | QMessageBox::Cancel) == QMessageBox::Cancel) { From 35bbe0001efa9aac48c56503643b98ec03c080e7 Mon Sep 17 00:00:00 2001 From: Al12rs Date: Tue, 17 Jul 2018 14:37:50 +0200 Subject: [PATCH 44/67] Added a flag to the plugin list indicating if a certain plugin has one or more archives attached. Changed the tooltip to list the loaded archives. Fixed tool tip to have titles for subsections. --- src/pluginlist.cpp | 26 ++++++++++++++++++++++---- src/pluginlist.h | 3 ++- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp index a6ae8fa76..9153df720 100644 --- a/src/pluginlist.cpp +++ b/src/pluginlist.cpp @@ -181,9 +181,17 @@ void PluginList::refresh(const QString &profileName try { FilesOrigin &origin = baseDirectory.getOriginByID(current->getOrigin(archive)); + QString iniPath = QFileInfo(filename).baseName() + ".ini"; bool hasIni = baseDirectory.findFile(ToWString(iniPath)).get() != nullptr; + std::set loadedArchives; + QString originPath = QString::fromWCharArray(origin.getPath().c_str()); + QDir dir(QDir::toNativeSeparators(originPath)); + for (QString filename : dir.entryList(QStringList() << QFileInfo(filename).baseName() + "*.bsa" << QFileInfo(filename).baseName() + "*.ba2")) { + loadedArchives.insert(filename); + } + QString originName = ToQString(origin.getName()); unsigned int modIndex = ModInfo::getIndex(originName); if (modIndex != UINT_MAX) { @@ -191,7 +199,7 @@ void PluginList::refresh(const QString &profileName originName = modInfo->name(); } - m_ESPs.push_back(ESPInfo(filename, forceEnabled, originName, ToQString(current->getFullPath()), hasIni)); + m_ESPs.push_back(ESPInfo(filename, forceEnabled, originName, ToQString(current->getFullPath()), hasIni, loadedArchives)); m_ESPs.rbegin()->m_Priority = -1; } catch (const std::exception &e) { reportError(tr("failed to update esp info for file %1 (source id: %2), error: %3").arg(filename).arg(current->getOrigin(archive)).arg(e.what())); @@ -888,8 +896,15 @@ QVariant PluginList::data(const QModelIndex &modelIndex, int role) const if (!enabledMasters.empty()) { text += "
" + tr("Enabled Masters") + ": " + SetJoin(enabledMasters, ", "); } + if (!m_ESPs[index].m_Archives.empty()) { + text += "
" + tr("Loads Archives") + ": " + SetJoin(m_ESPs[index].m_Archives, ", "); + text += "
" + tr("There are Archives connected to this plugin. " + "Their assets will be added to your game, overwriting in case of conflicts following the plugin order. " + "Loose files will always overwrite assets from Archives."); + } if (m_ESPs[index].m_HasIni) { - text += "
" + tr("There is an ini file connected to this esp. " + text += "
" + tr("Loads INI settings") + ": "; + text += "
" + tr("There is an ini file connected to this plugin. " "Its settings will be added to your game settings, overwriting in case of conflicts."); } if (m_ESPs[index].m_IsLightFlagged && !m_ESPs[index].m_IsLight) { @@ -917,6 +932,9 @@ QVariant PluginList::data(const QModelIndex &modelIndex, int role) const if (m_ESPs[index].m_HasIni) { result.append(":/MO/gui/attachment"); } + if (!m_ESPs[index].m_Archives.empty()) { + result.append(":/MO/gui/archive_conflict_neutral"); + } if (m_ESPs[index].m_IsLightFlagged && !m_ESPs[index].m_IsLight) { result.append(":/MO/gui/awaiting"); } @@ -1228,9 +1246,9 @@ bool PluginList::eventFilter(QObject *obj, QEvent *event) PluginList::ESPInfo::ESPInfo(const QString &name, bool enabled, const QString &originName, const QString &fullPath, - bool hasIni) + bool hasIni, std::set archives) : m_Name(name), m_FullPath(fullPath), m_Enabled(enabled), m_ForceEnabled(enabled), - m_Priority(0), m_LoadOrder(-1), m_OriginName(originName), m_HasIni(hasIni), m_ModSelected(false) + m_Priority(0), m_LoadOrder(-1), m_OriginName(originName), m_HasIni(hasIni), m_Archives(archives), m_ModSelected(false) { try { ESP::File file(ToWString(fullPath)); diff --git a/src/pluginlist.h b/src/pluginlist.h index f6745aa84..583c72726 100644 --- a/src/pluginlist.h +++ b/src/pluginlist.h @@ -277,7 +277,7 @@ public slots: struct ESPInfo { - ESPInfo(const QString &name, bool enabled, const QString &originName, const QString &fullPath, bool hasIni); + ESPInfo(const QString &name, bool enabled, const QString &originName, const QString &fullPath, bool hasIni, std::set archives); QString m_Name; QString m_FullPath; bool m_Enabled; @@ -294,6 +294,7 @@ public slots: QString m_Author; QString m_Description; bool m_HasIni; + std::set m_Archives; std::set m_Masters; mutable std::set m_MasterUnset; bool operator < (const ESPInfo& str) const From 023cacab7fc2a9c36749fe235255fce8c4edd65b Mon Sep 17 00:00:00 2001 From: Al12rs Date: Tue, 17 Jul 2018 16:26:17 +0200 Subject: [PATCH 45/67] Added ability to open mod information window by double cliking on plugins. --- src/mainwindow.cpp | 38 +++++++++++++++ src/mainwindow.h | 1 + src/mainwindow.ui | 115 ++++++++++++++------------------------------- src/pluginlist.cpp | 7 +++ src/pluginlist.h | 6 +++ 5 files changed, 88 insertions(+), 79 deletions(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 5999608cd..c4e888ac0 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2872,6 +2872,44 @@ void MainWindow::on_modList_doubleClicked(const QModelIndex &index) } } +void MainWindow::on_espList_doubleClicked(const QModelIndex &index) +{ + if (!index.isValid()) { + return; + } + + if (m_OrganizerCore.pluginList()->timeElapsedSinceLastChecked() <= QApplication::doubleClickInterval()) { + // don't interpret double click if we only just checked a plugin + return; + } + + QModelIndex sourceIdx = mapToModel(m_OrganizerCore.pluginList(), index); + if (!sourceIdx.isValid()) { + return; + } + try { + + QItemSelectionModel *selection = ui->espList->selectionModel(); + + if (selection->hasSelection() && selection->selectedRows().count() == 1) { + + QModelIndex idx = selection->currentIndex(); + QString fileName = idx.data().toString(); + + + displayModInformation(ModInfo::getIndex(m_OrganizerCore.pluginList()->origin(fileName))); + // workaround to cancel the editor that might have opened because of + // selection-click + ui->espList->closePersistentEditor(index); + + + } + } + catch (const std::exception &e) { + reportError(e.what()); + } +} + bool MainWindow::populateMenuCategories(QMenu *menu, int targetID) { ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow); diff --git a/src/mainwindow.h b/src/mainwindow.h index 737a15340..773bf2985 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -574,6 +574,7 @@ private slots: // ui slots void on_executablesListBox_currentIndexChanged(int index); void on_modList_customContextMenuRequested(const QPoint &pos); void on_modList_doubleClicked(const QModelIndex &index); + void on_espList_doubleClicked(const QModelIndex &index); void on_profileBox_currentIndexChanged(int index); void on_savegameList_customContextMenuRequested(const QPoint &pos); void on_startButton_clicked(); diff --git a/src/mainwindow.ui b/src/mainwindow.ui index e0aa6f36e..d8873d4fa 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -96,9 +96,9 @@
- - false - + + false + 0 @@ -194,7 +194,7 @@ - + Pick a module collection @@ -248,7 +248,7 @@ p, li { white-space: pre-wrap; } 16 - + @@ -265,16 +265,6 @@ p, li { white-space: pre-wrap; } - - - - - - - - - - Restore Backup... @@ -509,7 +499,7 @@ p, li { white-space: pre-wrap; } - + Qt::Horizontal @@ -521,7 +511,7 @@ p, li { white-space: pre-wrap; } - + @@ -536,12 +526,12 @@ p, li { white-space: pre-wrap; } 22 - + 95 0 - + false @@ -567,7 +557,7 @@ p, li { white-space: pre-wrap; } - + Qt::Horizontal @@ -579,14 +569,14 @@ p, li { white-space: pre-wrap; } - - - + + + 220 0 - + Qt::ClickFocus @@ -608,13 +598,13 @@ p, li { white-space: pre-wrap; } - - + + 220 0 - + Namefilter @@ -835,12 +825,12 @@ p, li { white-space: pre-wrap; } - - true - + + true + Sort - + :/MO/gui/sort:/MO/gui/sort @@ -954,6 +944,9 @@ p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This list contains the esps, esms, and esls contained in the active mods. These require their own load order. Use drag&amp;drop to modify this load order. Please note that MO will only save the load order for mods that are active/checked.<br />There is a great tool named &quot;BOSS&quot; to automatically sort these files.</span></p></body></html> + + QAbstractItemView::EditKeyPressed|QAbstractItemView::SelectedClicked + true @@ -987,6 +980,9 @@ p, li { white-space: pre-wrap; } true + + false + false @@ -1009,15 +1005,12 @@ p, li { white-space: pre-wrap; } - - - + + false + Archives - - true - 6 @@ -1032,17 +1025,7 @@ p, li { white-space: pre-wrap; } 6 - - - - - - - - - - - + @@ -1072,50 +1055,24 @@ p, li { white-space: pre-wrap; } BSAs checked here are loaded in such a way that your installation order is obeyed properly. - - QAbstractItemView::NoEditTriggers - false - + false - + false - - QAbstractItemView::NoDragDrop - - - Qt::IgnoreAction - - - QAbstractItemView::SingleSelection - - - QAbstractItemView::SelectRows - - + 20 - + true - + 1 - - false - - - 200 - - - - File - - diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp index 9153df720..c6cea045b 100644 --- a/src/pluginlist.cpp +++ b/src/pluginlist.cpp @@ -77,6 +77,7 @@ PluginList::PluginList(QObject *parent) , m_FontMetrics(QFont()) { connect(this, SIGNAL(writePluginsList()), this, SLOT(generatePluginIndexes())); + m_LastCheck.start(); } PluginList::~PluginList() @@ -583,6 +584,11 @@ void PluginList::disconnectSlots() { m_PluginStateChanged.disconnect_all_slots(); } +int PluginList::timeElapsedSinceLastChecked() const +{ + return m_LastCheck.elapsed(); +} + QStringList PluginList::pluginNames() const { QStringList result; @@ -954,6 +960,7 @@ bool PluginList::setData(const QModelIndex &modIndex, const QVariant &value, int if (role == Qt::CheckStateRole) { m_ESPs[modIndex.row()].m_Enabled = value.toInt() == Qt::Checked || m_ESPs[modIndex.row()].m_ForceEnabled; + m_LastCheck.restart(); emit dataChanged(modIndex, modIndex); refreshLoadOrder(); diff --git a/src/pluginlist.h b/src/pluginlist.h index 583c72726..b8e35c32d 100644 --- a/src/pluginlist.h +++ b/src/pluginlist.h @@ -28,6 +28,7 @@ namespace MOBase { class IPluginGame; } #include #include #include +#include #include #pragma warning(push) @@ -192,6 +193,8 @@ class PluginList : public QAbstractItemModel, public MOBase::IPluginList */ int enabledCount() const; + int timeElapsedSinceLastChecked() const; + QString getName(int index) const { return m_ESPs.at(index).m_Name; } int getPriority(int index) const { return m_ESPs.at(index).m_Priority; } QString getIndexPriority(int index) const; @@ -273,6 +276,7 @@ public slots: void writePluginsList(); + private: struct ESPInfo { @@ -347,6 +351,8 @@ public slots: QTemporaryFile m_TempFile; + QTime m_LastCheck; + const MOBase::IPluginGame *m_GamePlugin; }; From 344b83957e0f4491925055feab38091fdee4e751 Mon Sep 17 00:00:00 2001 From: Al12rs Date: Tue, 17 Jul 2018 18:35:32 +0200 Subject: [PATCH 46/67] Revert "Fix download index in downloadFinished" This reverts commit 942e656a71f452cd3cdbca2a4264db3cc2014ae9. --- src/downloadmanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index beb498c18..67c43d4b0 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -1671,7 +1671,7 @@ void DownloadManager::nxmRequestFailed(QString gameName, int modID, int fileID, void DownloadManager::downloadFinished(int index) { DownloadInfo *info; - if (index >= 0) + if (index) info = m_ActiveDownloads[index]; else info = findDownload(this->sender(), &index); From f824d255a2044e81208e0813c76f9d31e630267c Mon Sep 17 00:00:00 2001 From: Al12rs Date: Tue, 17 Jul 2018 19:51:30 +0200 Subject: [PATCH 47/67] Added clarification to loads archives tooltip. --- src/pluginlist.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp index c6cea045b..58364e78a 100644 --- a/src/pluginlist.cpp +++ b/src/pluginlist.cpp @@ -906,7 +906,7 @@ QVariant PluginList::data(const QModelIndex &modelIndex, int role) const text += "
" + tr("Loads Archives") + ": " + SetJoin(m_ESPs[index].m_Archives, ", "); text += "
" + tr("There are Archives connected to this plugin. " "Their assets will be added to your game, overwriting in case of conflicts following the plugin order. " - "Loose files will always overwrite assets from Archives."); + "Loose files will always overwrite assets from Archives. (This flag only checks for Archives from the same mod as the plugin)"); } if (m_ESPs[index].m_HasIni) { text += "
" + tr("Loads INI settings") + ": "; From b38957d853c2c3a62d243654d78597b52de38287 Mon Sep 17 00:00:00 2001 From: Al12rs Date: Wed, 18 Jul 2018 12:00:55 +0200 Subject: [PATCH 48/67] Small visual fix for size positioning in compact downloads. --- src/downloadlistwidgetcompact.ui | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/src/downloadlistwidgetcompact.ui b/src/downloadlistwidgetcompact.ui index a3cf3f029..a3fa958c4 100644 --- a/src/downloadlistwidgetcompact.ui +++ b/src/downloadlistwidgetcompact.ui @@ -19,7 +19,7 @@ true - + 2 @@ -77,19 +77,6 @@
- - - - Qt::Horizontal - - - - 40 - 20 - - - - From d5f3bee642f6ab25f10d0ba34d6f85d4876fcf2d Mon Sep 17 00:00:00 2001 From: Al12rs Date: Wed, 18 Jul 2018 12:01:50 +0200 Subject: [PATCH 49/67] Changed unit check. --- src/downloadmanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index 67c43d4b0..33899f465 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -1321,7 +1321,7 @@ void DownloadManager::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) if (speed < 1000) { unit = "Bytes/s"; } - else if (speed/1024 < 1000) { + else if (speed < 1000*1024) { speed /= 1024; unit = "KB/s"; } From ce2494ba200a6da56634aa3e6cdb6e22884a56e6 Mon Sep 17 00:00:00 2001 From: Al12rs Date: Wed, 18 Jul 2018 12:03:02 +0200 Subject: [PATCH 50/67] Don't show info dialog for unmanaged pluugins. --- src/mainwindow.cpp | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index c4e888ac0..2e14a2010 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2896,13 +2896,20 @@ void MainWindow::on_espList_doubleClicked(const QModelIndex &index) QModelIndex idx = selection->currentIndex(); QString fileName = idx.data().toString(); + if (ModInfo::getIndex(m_OrganizerCore.pluginList()->origin(fileName)) == UINT_MAX) + return; + + ModInfo::Ptr modInfo = ModInfo::getByIndex(ModInfo::getIndex(m_OrganizerCore.pluginList()->origin(fileName))); + std::vector flags = modInfo->getFlags(); - displayModInformation(ModInfo::getIndex(m_OrganizerCore.pluginList()->origin(fileName))); - // workaround to cancel the editor that might have opened because of - // selection-click - ui->espList->closePersistentEditor(index); - + if (modInfo->isRegular() || (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end())) { + displayModInformation(ModInfo::getIndex(m_OrganizerCore.pluginList()->origin(fileName))); + // workaround to cancel the editor that might have opened because of + // selection-click + ui->espList->closePersistentEditor(index); + + } } } catch (const std::exception &e) { From 4f14598cfc2ea2cf13cbf35fdd9d61338eb1f409 Mon Sep 17 00:00:00 2001 From: Al12rs Date: Wed, 18 Jul 2018 12:06:35 +0200 Subject: [PATCH 51/67] Changed size rappresentation in downloads compact view. --- src/downloadlistwidgetcompact.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/downloadlistwidgetcompact.cpp b/src/downloadlistwidgetcompact.cpp index 34e31006a..663a224ea 100644 --- a/src/downloadlistwidgetcompact.cpp +++ b/src/downloadlistwidgetcompact.cpp @@ -107,9 +107,9 @@ void DownloadListWidgetCompactDelegate::paintPendingDownload(int downloadIndex) { std::tuple nexusids = m_Manager->getPendingDownload(downloadIndex); m_NameLabel->setText(tr("< game %1 mod %2 file %3 >").arg(std::get<0>(nexusids)).arg(std::get<1>(nexusids)).arg(std::get<2>(nexusids))); - if (m_SizeLabel != nullptr) { - m_SizeLabel->setText("???"); - } + //if (m_SizeLabel != nullptr) { + // m_SizeLabel->setText("???"); + //} m_DoneLabel->setVisible(true); m_DoneLabel->setText(tr("Pending")); m_Progress->setVisible(false); @@ -127,9 +127,13 @@ void DownloadListWidgetCompactDelegate::paintRegularDownload(int downloadIndex) DownloadManager::DownloadState state = m_Manager->getState(downloadIndex); - if ((m_SizeLabel != nullptr)) { - m_SizeLabel->setText(sizeFormat(m_Manager->getFileSize(downloadIndex))); + if (m_SizeLabel != nullptr) { + m_SizeLabel->setText(sizeFormat(m_Manager->getFileSize(downloadIndex)) + " "); + m_SizeLabel->setVisible(true); } + //else { + // m_SizeLabel->setVisible(false); + //} if ((state == DownloadManager::STATE_PAUSED) || (state == DownloadManager::STATE_ERROR) || (state == DownloadManager::STATE_PAUSING)) { m_DoneLabel->setVisible(true); From f6ecb93d460b16c10933b35605f7ac1d60eac705 Mon Sep 17 00:00:00 2001 From: Al12rs Date: Wed, 18 Jul 2018 16:00:19 +0200 Subject: [PATCH 52/67] Set delete Key to recursively clear overwrite with confirmation message. --- src/modlist.cpp | 9 +++++++++ src/modlist.h | 5 +++++ src/organizercore.cpp | 2 ++ 3 files changed, 16 insertions(+) diff --git a/src/modlist.cpp b/src/modlist.cpp index aaa4ba797..87cbbfb2b 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -999,6 +999,15 @@ bool ModList::removeRows(int row, int count, const QModelIndex &parent) bool success = false; + if (count == 1) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(row); + std::vector flags = modInfo->getFlags(); + if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end()) { + emit clearOverwrite(); + success = true; + } + } + for (int i = 0; i < count; ++i) { ModInfo::Ptr modInfo = ModInfo::getByIndex(row + i); if (!modInfo->isRegular()) { diff --git a/src/modlist.h b/src/modlist.h index b5f18e988..2db98bd1d 100644 --- a/src/modlist.h +++ b/src/modlist.h @@ -244,6 +244,11 @@ public slots: */ void fileMoved(const QString &relativePath, const QString &oldOriginName, const QString &newOriginName); + /** + * @brief emitted to have the overwrite folder cleared + */ + void clearOverwrite(); + void aboutToChangeData(); void postDataChanged(); diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 142faabed..74ec752e7 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -552,6 +552,8 @@ void OrganizerCore::setUserInterface(IUserInterface *userInterface, SLOT(modRemoved(QString))); connect(&m_ModList, SIGNAL(removeSelectedMods()), widget, SLOT(removeMod_clicked())); + connect(&m_ModList, SIGNAL(clearOverwrite()), widget, + SLOT(clearOverwrite())); connect(&m_ModList, SIGNAL(requestColumnSelect(QPoint)), widget, SLOT(displayColumnSelection(QPoint))); connect(&m_ModList, SIGNAL(fileMoved(QString, QString, QString)), widget, From e80bb3a8370aeea37183d94f62e7bb3a843722b5 Mon Sep 17 00:00:00 2001 From: Al12rs Date: Wed, 18 Jul 2018 16:14:16 +0200 Subject: [PATCH 53/67] Check if there is actually something in overwrite to delete before asking confirmation. --- src/modlist.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modlist.cpp b/src/modlist.cpp index 87cbbfb2b..27bccf63e 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -1002,7 +1002,7 @@ bool ModList::removeRows(int row, int count, const QModelIndex &parent) if (count == 1) { ModInfo::Ptr modInfo = ModInfo::getByIndex(row); std::vector flags = modInfo->getFlags(); - if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end()) { + if ((std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end()) && (QDir(modInfo->absolutePath()).count() > 2)) { emit clearOverwrite(); success = true; } From 8f28e0af36d6246f1230a8aa296cfd5485b34242 Mon Sep 17 00:00:00 2001 From: Al12rs Date: Sat, 21 Jul 2018 20:01:40 +0200 Subject: [PATCH 54/67] Temporarely disable the BSA list refresh if it is hidden from view. this will need to be reversted once the Bsa changes are added I presume. --- src/mainwindow.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 2e14a2010..f2a17c173 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2544,7 +2544,10 @@ void MainWindow::displayModInformation(ModInfo::Ptr modInfo, unsigned int index, , modInfo->stealFiles() , modInfo->archives()); DirectoryRefresher::cleanStructure(m_OrganizerCore.directoryStructure()); - m_OrganizerCore.refreshLists(); + //TODO: change this to always work once the BSA parsing is back in place. + if (ui->bsaList->isVisible()) + m_OrganizerCore.refreshBSAList(); + m_OrganizerCore.refreshESPList(); } } } From cc63717060229ce40279f4ac1edd0907110fee87 Mon Sep 17 00:00:00 2001 From: Al12rs Date: Sat, 21 Jul 2018 20:09:25 +0200 Subject: [PATCH 55/67] Added "Open Mods folder" to "Open Folder" context menu. --- src/mainwindow.cpp | 7 +++++++ src/mainwindow.h | 1 + 2 files changed, 8 insertions(+) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index f2a17c173..17db79d69 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -3272,6 +3272,11 @@ void MainWindow::openDownloadsFolder() ::ShellExecuteW(nullptr, L"explore", ToWString(m_OrganizerCore.settings().getDownloadDirectory()).c_str(), nullptr, nullptr, SW_SHOWNORMAL); } +void MainWindow::openModsFolder() +{ + ::ShellExecuteW(nullptr, L"explore", ToWString(m_OrganizerCore.settings().getModDirectory()).c_str(), nullptr, nullptr, SW_SHOWNORMAL); +} + void MainWindow::openGameFolder() { ::ShellExecuteW(nullptr, L"explore", ToWString(m_OrganizerCore.managedGame()->gameDirectory().absolutePath()).c_str(), nullptr, nullptr, SW_SHOWNORMAL); @@ -3465,6 +3470,8 @@ QMenu *MainWindow::openFolderMenu() FolderMenu->addAction(tr("Open Instance folder"), this, SLOT(openInstanceFolder())); + FolderMenu->addAction(tr("Open Mods folder"), this, SLOT(openModsFolder())); + FolderMenu->addAction(tr("Open Profile folder"), this, SLOT(openProfileFolder())); FolderMenu->addAction(tr("Open Downloads folder"), this, SLOT(openDownloadsFolder())); diff --git a/src/mainwindow.h b/src/mainwindow.h index 773bf2985..6cf833011 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -502,6 +502,7 @@ private slots: void openInstallFolder(); void openPluginsFolder(); void openDownloadsFolder(); + void openModsFolder(); void openProfileFolder(); void openGameFolder(); void openMyGamesFolder(); From d02df7e4b4d6b539ed744135eee565530c24f006 Mon Sep 17 00:00:00 2001 From: Al12rs Date: Sat, 21 Jul 2018 23:43:04 +0200 Subject: [PATCH 56/67] Avoided downloads defaulting to "File Time" sorting at each startup. Improved downloads tab header sizes and resize policy. --- src/mainwindow.cpp | 4 ++-- src/mainwindow.ui | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 17db79d69..c9874b203 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -4345,8 +4345,8 @@ void MainWindow::updateDownloadListDelegate() connect(ui->downloadFilterEdit, SIGNAL(textChanged(QString)), this, SLOT(downloadFilterChanged(QString))); ui->downloadView->setModel(sortProxy); - ui->downloadView->sortByColumn(1, Qt::DescendingOrder); - ui->downloadView->header()->resizeSections(QHeaderView::Fixed); + //ui->downloadView->sortByColumn(1, Qt::DescendingOrder); + ui->downloadView->header()->resizeSections(QHeaderView::Stretch); connect(ui->downloadView->itemDelegate(), SIGNAL(installDownload(int)), &m_OrganizerCore, SLOT(installDownload(int))); connect(ui->downloadView->itemDelegate(), SIGNAL(queryInfo(int)), m_OrganizerCore.downloadManager(), SLOT(queryInfo(int))); diff --git a/src/mainwindow.ui b/src/mainwindow.ui index d8873d4fa..8de344853 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -1243,7 +1243,7 @@ p, li { white-space: pre-wrap; } Qt::ScrollBarAlwaysOn - Qt::ScrollBarAlwaysOff + Qt::ScrollBarAsNeeded true @@ -1276,7 +1276,10 @@ p, li { white-space: pre-wrap; } true - 100 + 50 + + + 15 true From edf608ab23e98b4d7dbfd09628cb64770d4bdb1a Mon Sep 17 00:00:00 2001 From: Al12rs Date: Sun, 22 Jul 2018 15:49:40 +0200 Subject: [PATCH 57/67] Allow normal QAbstractItemModel event handling for mainwindow. Somehow before it wouldn't work. --- src/modlist.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/modlist.cpp b/src/modlist.cpp index 27bccf63e..e077a5eec 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -1228,6 +1228,7 @@ bool ModList::eventFilter(QObject *obj, QEvent *event) } else if (keyEvent->key() == Qt::Key_Space) { return toggleSelection(itemView); } + return QAbstractItemModel::eventFilter(obj, event); } return QAbstractItemModel::eventFilter(obj, event); } From fc8a6b358fb7bda18979c7c5c23c13d7c2ae8dc8 Mon Sep 17 00:00:00 2001 From: LostDragonist Date: Fri, 20 Jul 2018 03:21:30 -0500 Subject: [PATCH 58/67] Improve robustness of endorsement checks At times, the voted_by_user item is returned as Invalid. toBool() causes this to be false, meaning the item is seen as not endorsed. Now, the endorsement state is not checked if the data is invalid. --- src/mainwindow.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index c9874b203..d8dd6e45d 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -4388,7 +4388,8 @@ void MainWindow::nxmUpdatesAvailable(const std::vector &modIDs, QVariant us if (game && result["id"].toInt() == game->nexusModOrganizerID() && result["game_id"].toInt() == game->nexusGameID()) { - if (!result["voted_by_user"].toBool()) { + if (result["voted_by_user"].type() != QVariant::Invalid && + !result["voted_by_user"].toBool()) { ui->actionEndorseMO->setVisible(true); } } else { @@ -4412,7 +4413,8 @@ void MainWindow::nxmUpdatesAvailable(const std::vector &modIDs, QVariant us (*iter)->setNewestVersion(result["version"].toString()); (*iter)->setNexusDescription(result["description"].toString()); if (NexusInterface::instance(&m_PluginContainer)->getAccessManager()->loggedIn() && - result.contains("voted_by_user")) { + result.contains("voted_by_user") && + result["voted_by_user"].type() != QVariant::Invalid) { // 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()); } From 447a2169fec88b3239d48a3583226f647798388e Mon Sep 17 00:00:00 2001 From: LostDragonist Date: Sat, 21 Jul 2018 19:58:19 -0500 Subject: [PATCH 59/67] Allow more characters in instance names and add an error message Previously allowed: "A-Za-z0-9 " Now allowed: "A-Za-z0-9 !@#$%^()_+-=[]{};'." --- src/instancemanager.cpp | 35 ++++++++++++++++++++++++++++++++--- src/instancemanager.h | 1 + 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/src/instancemanager.cpp b/src/instancemanager.cpp index 2b718cb2f..1c6542e88 100644 --- a/src/instancemanager.cpp +++ b/src/instancemanager.cpp @@ -131,11 +131,12 @@ QString InstanceManager::manageInstances(const QStringList &instanceList) const QString InstanceManager::queryInstanceName(const QStringList &instanceList) const { QString instanceId; + QString dialogText; while (instanceId.isEmpty()) { QInputDialog dialog; - dialog.setWindowTitle(QObject::tr("Enter a Name for the new Instance")); - dialog.setLabelText(QObject::tr("Enter a new name or select one from the sugested list (only letters and numbers allowed):")); + dialog.setWindowTitle(QObject::tr("Enter a Name for the new Instance")); + dialog.setLabelText(QObject::tr("Enter a new name or select one from the suggested list:")); // would be neat if we could take the names from the game plugins but // the required initialization order requires the ini file to be // available *before* we load plugins @@ -146,7 +147,17 @@ QString InstanceManager::queryInstanceName(const QStringList &instanceList) cons if (dialog.exec() == QDialog::Rejected) { throw MOBase::MyException(QObject::tr("Canceled")); } - instanceId = dialog.textValue().replace(QRegExp("[^0-9a-zA-Z ]"), "").remove(QRegExp("( )*$")); + dialogText = dialog.textValue(); + instanceId = sanitizeInstanceName(dialogText); + if (instanceId != dialogText) { + if (QMessageBox::question( nullptr, + QObject::tr("Invalid instance name"), + QObject::tr("The instance name \"%1\" is invalid. Use the name \"%2\" instead?").arg(dialogText,instanceId), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) { + instanceId=""; + continue; + } + } bool alreadyExists=false; for (const QString &instance : instanceList) { @@ -296,3 +307,21 @@ QString InstanceManager::determineDataPath() } } + +QString InstanceManager::sanitizeInstanceName(const QString &name) const +{ + QString new_name = name; + + // Restrict the allowed characters + new_name = new_name.remove(QRegExp("[^A-Za-z0-9 _=+;!@#$%^'\\-\\.\\[\\]\\{\\}\\(\\)]")); + + // Don't end in spaces and periods + new_name = new_name.remove(QRegExp("\\.*$")); + new_name = new_name.remove(QRegExp(" *$")); + + // Recurse until stuff stops changing + if (new_name != name) { + return sanitizeInstanceName(new_name); + } + return new_name; +} \ No newline at end of file diff --git a/src/instancemanager.h b/src/instancemanager.h index adedd78f9..4efa6f033 100644 --- a/src/instancemanager.h +++ b/src/instancemanager.h @@ -50,6 +50,7 @@ class InstanceManager { QString manageInstances(const QStringList &instanceList) const; + QString sanitizeInstanceName(const QString &name) const; void setCurrentInstance(const QString &name); QString queryInstanceName(const QStringList &instanceList) const; From 385765ecddbb1e9d7f38cf313c3c6db6dc07be52 Mon Sep 17 00:00:00 2001 From: LostDragonist Date: Mon, 23 Jul 2018 14:13:12 -0500 Subject: [PATCH 60/67] Truncate strings longer than 1024 characters in the plugin list tooltip --- src/pluginlist.cpp | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp index 58364e78a..76de436d9 100644 --- a/src/pluginlist.cpp +++ b/src/pluginlist.cpp @@ -72,6 +72,15 @@ static bool ByDate(const PluginList::ESPInfo& LHS, const PluginList::ESPInfo& RH return QFileInfo(LHS.m_FullPath).lastModified() < QFileInfo(RHS.m_FullPath).lastModified(); } +static QString TruncateString(const QString& text) { + QString new_text = text; + if (new_text.length() > 1024) { + new_text.truncate(1024); + new_text += "..."; + } + return new_text; +} + PluginList::PluginList(QObject *parent) : QAbstractItemModel(parent) , m_FontMetrics(QFont()) @@ -187,7 +196,7 @@ void PluginList::refresh(const QString &profileName bool hasIni = baseDirectory.findFile(ToWString(iniPath)).get() != nullptr; std::set loadedArchives; - QString originPath = QString::fromWCharArray(origin.getPath().c_str()); + QString originPath = QString::fromWCharArray(origin.getPath().c_str()); QDir dir(QDir::toNativeSeparators(originPath)); for (QString filename : dir.entryList(QStringList() << QFileInfo(filename).baseName() + "*.bsa" << QFileInfo(filename).baseName() + "*.ba2")) { loadedArchives.insert(filename); @@ -887,23 +896,23 @@ QVariant PluginList::data(const QModelIndex &modelIndex, int role) const } else { QString text = tr("Origin: %1").arg(m_ESPs[index].m_OriginName); if (m_ESPs[index].m_Author.size() > 0) { - text += "
" + tr("Author") + ": " + m_ESPs[index].m_Author; + text += "
" + tr("Author") + ": " + TruncateString(m_ESPs[index].m_Author); } if (m_ESPs[index].m_Description.size() > 0) { - text += "
" + tr("Description") + ": " + m_ESPs[index].m_Description; + text += "
" + tr("Description") + ": " + TruncateString(m_ESPs[index].m_Description); } if (m_ESPs[index].m_MasterUnset.size() > 0) { - text += "
" + tr("Missing Masters") + ": " + SetJoin(m_ESPs[index].m_MasterUnset, ", ") + ""; + text += "
" + tr("Missing Masters") + ": " + TruncateString(SetJoin(m_ESPs[index].m_MasterUnset, ", ")) + ""; } std::set enabledMasters; std::set_difference(m_ESPs[index].m_Masters.begin(), m_ESPs[index].m_Masters.end(), m_ESPs[index].m_MasterUnset.begin(), m_ESPs[index].m_MasterUnset.end(), std::inserter(enabledMasters, enabledMasters.end())); if (!enabledMasters.empty()) { - text += "
" + tr("Enabled Masters") + ": " + SetJoin(enabledMasters, ", "); + text += "
" + tr("Enabled Masters") + ": " + TruncateString(SetJoin(enabledMasters, ", ")); } if (!m_ESPs[index].m_Archives.empty()) { - text += "
" + tr("Loads Archives") + ": " + SetJoin(m_ESPs[index].m_Archives, ", "); + text += "
" + tr("Loads Archives") + ": " + TruncateString(SetJoin(m_ESPs[index].m_Archives, ", ")); text += "
" + tr("There are Archives connected to this plugin. " "Their assets will be added to your game, overwriting in case of conflicts following the plugin order. " "Loose files will always overwrite assets from Archives. (This flag only checks for Archives from the same mod as the plugin)"); From 2f8e5b4f690e6c6ad195a1120ce2ccafc66a3f23 Mon Sep 17 00:00:00 2001 From: LostDragonist Date: Tue, 24 Jul 2018 14:00:26 -0500 Subject: [PATCH 61/67] Update translation file --- src/organizer_en.ts | 1329 +++++++++++++++++++++++-------------------- 1 file changed, 725 insertions(+), 604 deletions(-) diff --git a/src/organizer_en.ts b/src/organizer_en.ts index 60fd66e73..01a4abcbf 100644 --- a/src/organizer_en.ts +++ b/src/organizer_en.ts @@ -303,16 +303,21 @@ p, li { white-space: pre-wrap; } + Size + + + + Done - + Information missing, please select "Query Info" from the context menu to re-retrieve. - + pending download @@ -360,7 +365,7 @@ p, li { white-space: pre-wrap; } - + Done @@ -368,151 +373,180 @@ p, li { white-space: pre-wrap; } DownloadListWidgetCompactDelegate - + < game %1 mod %2 file %3 > - + Pending - + Paused - + Fetching Info 1 - + Fetching Info 2 - + Installed - + Uninstalled - + Done - - - - - + + + + + + + Are you sure? - + This will permanently delete the selected download. - + This will remove all finished downloads from this list and from disk. - + This will remove all installed downloads from this list and from disk. - + + This will remove all uninstalled downloads from this list and from disk. + + + + This will permanently remove all finished downloads from this list (but NOT from disk). - + This will permanently remove all installed downloads from this list (but NOT from disk). - + + This will permanently remove all uninstalled downloads from this list (but NOT from disk). + + + + Install - + Query Info - + Visit on Nexus - + + + + Show in Folder + + + + Delete - + Un-Hide - + Hide - + Cancel - + Pause - + Remove - + Resume - + Delete Installed... - + + Delete Uninstalled... + + + + Delete All... - + Hide Installed... - + + Hide Uninstalled... + + + + Hide All... - + Un-Hide All... @@ -540,20 +574,22 @@ p, li { white-space: pre-wrap; } - - + + + Are you sure? - + This will remove all finished downloads from this list and from disk. - - + + + Delete Files? @@ -563,92 +599,115 @@ p, li { white-space: pre-wrap; } - + This will remove all installed downloads from this list and from disk. - + + This will remove all uninstalled downloads from this list and from disk. + + + + This will remove all finished downloads from this list (but NOT from disk). - + This will remove all installed downloads from this list (but NOT from disk). - + + This will remove all uninstalled downloads from this list (but NOT from disk). + + + + Install - + Query Info - + Visit on Nexus - + + + + Show in Folder + + + + + Delete - + Un-Hide - + Hide - + Cancel - + Pause - - Remove + + Resume - - Resume + + Delete Installed... - - Delete Installed... + + Delete Uninstalled... - + Delete All... - + Hide Installed... - + + Hide Uninstalled... + + + + Hide All... - + Un-Hide All... @@ -661,286 +720,287 @@ p, li { white-space: pre-wrap; } - + Memory allocation error (in refreshing directory). - + failed to download %1: could not open output file: %2 - + Download again? - + A file with the same name has already been downloaded. Do you want to download it again? The new file will receive a different name. - + Wrong Game - + The download link is for a mod for "%1" but this instance of MO has been set up for "%2". - - + + Already Started - + A download for this mod file has already been queued. - + There is already a download started for this file (mod: %1, file: %2). - - + + remove: invalid download index %1 - + failed to delete %1 - + failed to delete meta file for %1 - + restore: invalid download index: %1 - + cancel: invalid download index %1 - + pause: invalid download index %1 - + resume: invalid download index %1 - + resume (int): invalid download index %1 - + No known download urls. Sorry, this download can't be resumed. - + query: invalid download index %1 - + Please enter the nexus mod id - + Mod ID: - + Please select the source game code for %1 - + + VisitNexus: invalid download index %1 - + Nexus ID for this Mod is unknown - + get pending: invalid download index %1 - + get path: invalid download index %1 - + Main - + Update - + Optional - + Old - + Misc - + Unknown - + display name: invalid download index %1 - + file name: invalid download index %1 - + file time: invalid download index %1 - + file size: invalid download index %1 - + progress: invalid download index %1 - + state: invalid download index %1 - + infocomplete: invalid download index %1 - - + + mod id: invalid download index %1 - + ishidden: invalid download index %1 - + file info: invalid download index %1 - + mark installed: invalid download index %1 - + mark uninstalled: invalid download index %1 - + Memory allocation error (in processing progress event). - + Memory allocation error (in processing downloaded data). - + 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 @@ -1447,7 +1507,7 @@ This is likely due to a corrupted or incompatible download or unrecognized archi MainWindow - + Categories @@ -1512,61 +1572,61 @@ p, li { white-space: pre-wrap; } - - + + Restore Backup... - - + + Create Backup - + 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 - + Clear all Filters - + No groups - + Nexus IDs - - - + + + Namefilter - + 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; } @@ -1576,12 +1636,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; } @@ -1590,17 +1650,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; } @@ -1609,27 +1669,27 @@ p, li { white-space: pre-wrap; } - + Shortcut - + Plugins - + Sort - + 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; } @@ -1638,27 +1698,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! @@ -1666,61 +1726,60 @@ p, li { white-space: pre-wrap; } - - + File - + 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). - + Mod - - + + Filter the above list so that only conflicts are displayed. - + Show only conflicts - + 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; } @@ -1731,155 +1790,155 @@ p, li { white-space: pre-wrap; } - + Downloads - + 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 Problems - + This button will be highlighted if MO discovered potential problems in your setup and provide tips on how to fix them. !Work in progress! @@ -1887,44 +1946,44 @@ Right now this has very limited functionality - - + + Help - + Ctrl+H - + Endorse MO - - + + Endorse Mod Organizer - + Copy Log to Clipboard - + Ctrl+C - + Change Game - + Open the Instance selection dialog to manage a different Game @@ -2175,16 +2234,16 @@ Right now this has very limited functionality - - + + failed to rename "%1" to "%2" - - - + + + Confirm @@ -2231,391 +2290,396 @@ Right now this has very limited functionality - + Nexus ID for this Mod is unknown - + Web page for this mod is unknown - - - + + + Create Mod... - + This will create an empty mod. Please enter a name: - - + + A mod with this name already exists - + This will move all files from overwrite into a new, regular mod. Please enter a name: - - + + Are you sure? - + About to recursively delete: - + Not logged in, endorsement information will be wrong - + 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 - + 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 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 - + Enable all visible - + Disable all visible - + Check all for update - + Export to csv... - + All Mods - + Sync to Mods... - + Clear Overwrite... - - + + Open in explorer - + Restore Backup - + Remove Backup... - + Change Categories - + Primary Category - + Change versioning scheme - + Un-ignore update - + Ignore update - + Rename Mod... - + Reinstall Mod - + Remove Mod... - + Un-Endorse - - + + 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. @@ -2623,12 +2687,12 @@ You can also use online editors and converters instead. - + Enable Mods... - + Delete %n save(s) @@ -2636,319 +2700,319 @@ You can also use online editors and converters instead. - + failed to remove %1 - + failed to create %1 - + 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? - + 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? - + Thank you! - + Thank you for your endorsement! - + 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 - + 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 @@ -3016,16 +3080,21 @@ You can also use online editors and converters instead. + INI files + + + + invalid content type %1 - + invalid mod index %1 - + remove: invalid mod index %1 @@ -3661,12 +3730,12 @@ p, li { white-space: pre-wrap; } - + %1 contains no esp/esm/esl and no asset (textures, meshes, interface, ...) directory - + Categories: <br> @@ -3724,193 +3793,198 @@ p, li { white-space: pre-wrap; } - + + INI files + + + + This entry contains files that have been created inside the virtual data tree (i.e. by the construction kit) - + Backup - + No valid game data - + Not endorsed yet - + Overwrites files - + Overwritten files - + Overwrites & Overwritten - + Redundant - + Alternate game source - + 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". - + 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 - - + + 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></table> + + 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></table> - + Time this mod was installed @@ -3918,7 +3992,7 @@ p, li { white-space: pre-wrap; } ModListSortProxy - + Drag&Drop is only supported when sorting by priority @@ -4006,189 +4080,201 @@ 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 successful - - + + Configure Mod - - + + This mod contains ini tweaks. Do you want to configure them now? - - + + mod "%1" not found - - + + Installation cancelled - - + + The mod was not installed completely. - + Executable "%1" not found - + Start Steam? - + Steam is required to be running already to correctly start the game. Should MO try to start steam now? - + 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? + + + + 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. @@ -4201,7 +4287,12 @@ Continue? - + + Open in Explorer + + + + You can use drag&drop to move files and directories to regular mods. @@ -4289,120 +4380,135 @@ Continue? PluginList - + Name - + Priority - + Mod Index - + Flags - - + + unknown - + Name of your mods - + Load priority of your mod. The higher, the more "important" it is and thus overwrites data from plugins with lower priority. - + The modindex determines the formids of objects originating from this mods. - + failed to update esp info for file %1 (source id: %2), error: %3 - + esp not found: %1 - - + + Confirm - + Really enable all plugins? - + Really disable all plugins? - + The file containing locked plugin indices is broken - - + + <b>Origin</b>: %1 - + <br><b><i>This plugin can't be disabled (enforced by the game).</i></b> - + Author - + Description - + Missing Masters - + Enabled Masters - - There is an ini file connected to this esp. Its settings will be added to your game settings, overwriting in case of conflicts. + + Loads Archives + + + + + There are Archives connected to this plugin. Their assets will be added to your game, overwriting in case of conflicts following the plugin order. Loose files will always overwrite assets from Archives. (This flag only checks for Archives from the same mod as the plugin) - + + Loads INI settings + + + + + There is an ini file connected to this plugin. Its settings will be added to your game settings, overwriting in case of conflicts. + + + + This ESP is flagged as an ESL. It will adhere to the ESP load order but the records will be loaded in ESL space. - + failed to restore load order for %1 @@ -4758,7 +4864,7 @@ p, li { white-space: pre-wrap; } - + Error @@ -4920,84 +5026,94 @@ If the folder was still in use, restart MO and try again. - + Enter a Name for the new Instance - - Enter a new name or select one from the sugested list (only letters and numbers allowed): + + Enter a new name or select one from the suggested list: - - + + Canceled - + + Invalid instance name + + + + + The instance name "%1" is invalid. Use the name "%2" instead? + + + + The instance "%1" already exists. - + Please choose a different instance name, like: "%1 1" . - + Choose Instance - + Each Instance is a full set of MO data files (mods, downloads, profiles, configuration, ...). You can use multiple instances for different games. Instances are stored in Appdata and can be accessed by all MO installations. If your MO folder is writable, you can also store a single instance locally (called a Portable install, and all the MO data files will be inside the installation folder). - + New - + Create a new instance. - + Portable - + Use MO folder for data. - + Manage Instances - + Delete an Instance. - + failed to create %1 - + Data directory created - + New data directory created at %1. If you don't want to store a lot of data there, reconfigure the storage directories via settings. @@ -5067,7 +5183,7 @@ If the folder was still in use, restart MO and try again. - + Failed to create "%1". Your user account probably lacks permission. @@ -5109,7 +5225,7 @@ If the folder was still in use, restart MO and try again. - + Mod Organizer @@ -5130,7 +5246,7 @@ If the folder was still in use, restart MO and try again. - + <Manage...> @@ -5171,12 +5287,12 @@ If the folder was still in use, restart MO and try again. - + failed to access %1 - + failed to set file time %1 @@ -5186,12 +5302,12 @@ If the folder was still in use, restart MO and try again. - + Script Extender - + Proxy DLL @@ -5411,12 +5527,12 @@ Select Show Details option to see the full change-log. - + Error - + Failed to create "%1", you may not have the necessary permission. path remains unchanged. @@ -5543,81 +5659,86 @@ If you use pre-releases, never contact me directly by e-mail or via private mess - + Base Directory - - - + + + ... - + Overwrite - + Caches - + Downloads - + Directory where mods are stored. - + Directory where mods are stored. Please note that changing this will break all associations of profiles with mods that don't exist in the new location (with the same name). - - + + Directory where downloads are stored. - + Mods - + Profiles - + + Managed Game + + + + Use %BASE_DIR% to refer to the Base Directory. - + Important: All directories have to be writeable! - - + + Nexus - + Allows automatic log-in when the Nexus-Page for the game is clicked. - + <!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; } @@ -5626,144 +5747,144 @@ p, li { white-space: pre-wrap; } - + If checked and if correct credentials are entered below, log-in to Nexus (for browsing and downloading) is automatic. - + Automatically Log-In to Nexus - - + + Username - - + + Password - + Remove cache and cookies. Forces a new login. - + Clear Cache - + Disable automatic internet features - + Disable automatic internet features. This does not affect features that are explicitly invoked by the user (like checking mods for updates, endorsing, opening the web browser) - + Offline Mode - + Use a proxy for network connections. - + Use a proxy for network connections. This uses the system-wide settings which can be configured in Internet Explorer. Please note that MO will start up a few seconds slower on some systems when using a proxy. - + Use HTTP Proxy (Uses System Settings) - + Associate with "Download with manager" links - + Known Servers (updated on download) - + Preferred Servers (Drag & Drop) - + Steam - + 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; } @@ -5779,17 +5900,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. @@ -5800,17 +5921,17 @@ 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. @@ -5819,44 +5940,44 @@ tl;dr-version: If Nexus-features don't work, insert the current version num - + 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 - + 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 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. @@ -5864,44 +5985,44 @@ If you disable this feature, MO will only display official DLCs this way. Please - + Display mods installed outside MO - - + + 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 - + These are workarounds for problems with Mod Organizer. Please make sure you read the help text before changing anything here. - + Diagnostics - + 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. @@ -5909,37 +6030,37 @@ For the other games this is not a sufficient replacement for AI! - + Debug - + Info (recommended) - + Warning - + Error - + 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. @@ -5950,37 +6071,37 @@ For the other games this is not a sufficient replacement for AI! - + None - + Mini (recommended) - + Data - + Full - + 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. @@ -5988,7 +6109,7 @@ For the other games this is not a sufficient replacement for AI! - + 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. @@ -5998,7 +6119,7 @@ For the other games this is not a sufficient replacement for AI! - + Hint: right click link and copy link location From a02b920b19f4efcf0cefe6056ec7a91764e108bd Mon Sep 17 00:00:00 2001 From: LostDragonist Date: Fri, 27 Jul 2018 12:19:16 -0500 Subject: [PATCH 62/67] Force nxmhandler.exe registration when the associate button is clicked This allows the register dialog to appear when the user had previously selected "No, don't ask again". If a user is clicking this button, there's a good chance they want MO to be the primary nxm handler. --- src/settingsdialog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index 44fc9b5e5..058d082a6 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -241,7 +241,7 @@ void SettingsDialog::deleteBlacklistItem() void SettingsDialog::on_associateButton_clicked() { - Settings::instance().registerAsNXMHandler(false); + Settings::instance().registerAsNXMHandler(true); } void SettingsDialog::on_clearCacheButton_clicked() From 10be66a62a6d719355d6a494d365e4ea4dac1e9e Mon Sep 17 00:00:00 2001 From: Al12rs Date: Wed, 1 Aug 2018 17:07:35 +0200 Subject: [PATCH 63/67] Revert "Temporarely disable the BSA list refresh if it is hidden from view. this will need to be reversted once the Bsa changes are added I presume." This reverts commit 8f28e0af36d6246f1230a8aa296cfd5485b34242. --- src/mainwindow.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index d8dd6e45d..f587cc502 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2544,10 +2544,7 @@ void MainWindow::displayModInformation(ModInfo::Ptr modInfo, unsigned int index, , modInfo->stealFiles() , modInfo->archives()); DirectoryRefresher::cleanStructure(m_OrganizerCore.directoryStructure()); - //TODO: change this to always work once the BSA parsing is back in place. - if (ui->bsaList->isVisible()) - m_OrganizerCore.refreshBSAList(); - m_OrganizerCore.refreshESPList(); + m_OrganizerCore.refreshLists(); } } } From 696bb3bc47daf7779a0520dba64affaac3a5b4ad Mon Sep 17 00:00:00 2001 From: Al12rs Date: Wed, 1 Aug 2018 17:18:07 +0200 Subject: [PATCH 64/67] Added CTRL+Double Click on Modlist to open in Explorer --- src/mainwindow.cpp | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index f587cc502..d3e3c613b 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2861,14 +2861,30 @@ void MainWindow::on_modList_doubleClicked(const QModelIndex &index) return; } - try { - m_ContextRow = m_ModListSortProxy->mapToSource(index).row(); - displayModInformation(sourceIdx.row()); - // workaround to cancel the editor that might have opened because of - // selection-click - ui->modList->closePersistentEditor(index); - } catch (const std::exception &e) { - reportError(e.what()); + Qt::KeyboardModifiers modifiers = QApplication::queryKeyboardModifiers(); + if (modifiers.testFlag(Qt::ControlModifier)) { + try { + m_ContextRow = m_ModListSortProxy->mapToSource(index).row(); + openExplorer_clicked(); + // workaround to cancel the editor that might have opened because of + // selection-click + ui->modList->closePersistentEditor(index); + } + catch (const std::exception &e) { + reportError(e.what()); + } + } + else { + try { + m_ContextRow = m_ModListSortProxy->mapToSource(index).row(); + displayModInformation(sourceIdx.row()); + // workaround to cancel the editor that might have opened because of + // selection-click + ui->modList->closePersistentEditor(index); + } + catch (const std::exception &e) { + reportError(e.what()); + } } } From e1e367c7eae67471e760673a8149ccf30bac8685 Mon Sep 17 00:00:00 2001 From: Al12rs Date: Wed, 1 Aug 2018 17:55:07 +0200 Subject: [PATCH 65/67] Added CTRL+Doubleclick on plugin list as well --- src/mainwindow.cpp | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index d3e3c613b..f6cfbfbd2 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2920,11 +2920,20 @@ void MainWindow::on_espList_doubleClicked(const QModelIndex &index) if (modInfo->isRegular() || (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end())) { - displayModInformation(ModInfo::getIndex(m_OrganizerCore.pluginList()->origin(fileName))); - // workaround to cancel the editor that might have opened because of - // selection-click - ui->espList->closePersistentEditor(index); + Qt::KeyboardModifiers modifiers = QApplication::queryKeyboardModifiers(); + if (modifiers.testFlag(Qt::ControlModifier)) { + openExplorer_activated(); + // workaround to cancel the editor that might have opened because of + // selection-click + ui->espList->closePersistentEditor(index); + } + else { + displayModInformation(ModInfo::getIndex(m_OrganizerCore.pluginList()->origin(fileName))); + // workaround to cancel the editor that might have opened because of + // selection-click + ui->espList->closePersistentEditor(index); + } } } } From 7eb4b33214279aeb97541344e1c20da2b8dd79ff Mon Sep 17 00:00:00 2001 From: LostDragonist Date: Tue, 31 Jul 2018 03:52:55 -0500 Subject: [PATCH 66/67] Tweak shading of mods and plugins to better support dark themes --- src/modlist.cpp | 7 +++---- src/pluginlist.cpp | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/modlist.cpp b/src/modlist.cpp index e077a5eec..0d084c224 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -354,7 +354,6 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const 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 (highlight & ModInfo::HIGHLIGHT_PLUGIN) return QBrush(Qt::darkBlue); } else if (column == COL_VERSION) { if (!modInfo->getNewestVersion().isValid()) { return QVariant(); @@ -368,11 +367,11 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const } else if ((role == Qt::BackgroundRole) || (role == ViewMarkingScrollBar::DEFAULT_ROLE)) { if (modInfo->getHighlight() & ModInfo::HIGHLIGHT_PLUGIN) { - return QColor(0, 0, 255, 32); + return QColor(0, 0, 255, 64); } else if (m_Overwrite.find(modIndex) != m_Overwrite.end()) { - return QColor(0, 255, 0, 32); + return QColor(0, 255, 0, 64); } else if (m_Overwritten.find(modIndex) != m_Overwritten.end()) { - return QColor(255, 0, 0, 32); + return QColor(255, 0, 0, 64); } else { return QVariant(); } diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp index 76de436d9..323cd98fd 100644 --- a/src/pluginlist.cpp +++ b/src/pluginlist.cpp @@ -861,7 +861,7 @@ QVariant PluginList::data(const QModelIndex &modelIndex, int role) const } else if (role == Qt::BackgroundRole || (role == ViewMarkingScrollBar::DEFAULT_ROLE)) { if (m_ESPs[index].m_ModSelected) { - return QColor(0, 0, 255, 32); + return QColor(0, 0, 255, 64); } else { return QVariant(); } From fd51434e5ee9ef702eb904d0b77c937d6b147908 Mon Sep 17 00:00:00 2001 From: Al12rs Date: Wed, 1 Aug 2018 20:04:59 +0200 Subject: [PATCH 67/67] Enabled Ctrl+C to copy selected stuff instead of the entire log. No one used it before and now it's much more practical. --- src/mainwindow.ui | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 8de344853..cf0f2aa41 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -1561,9 +1561,6 @@ Right now this has very limited functionality Copy Log to Clipboard - - Ctrl+C -