From 7d79e3ad1e8a710e836e1cd44106a610e6d2a049 Mon Sep 17 00:00:00 2001 From: Tannin Date: Thu, 15 Aug 2013 22:13:18 +0200 Subject: - bugfix: nameprefix-flag incorrectly interpreted on oblivion-style bsas - bugfix: optimization for missing inis fixed - bugfix: fixmods didn't search for esps in overwrite - bugfix: configurator didn't work for fallout new vegas because of a missing cast - bugfix: configurator attempted parameter formatting on ini files instead of using the raw data - bugfix: searching for a path in the directory structure could cause a crash when not using the directory output - optimization to findfirstfile for cases where a single file is searched - display of bsas changed. hopefully it is more understandable now - cache for the test whether a mod is in conflict. Should speed ab scrolling in mod list - mod list has now an additional column for the installation time - nmm importer no longer cancels in cases where a data file references an undeclared mod. - integrated improved settings.json for the configurator contributed by "delta534" --- src/mainwindow.cpp | 85 +++++++++++++++++++++++++++++---------- src/mainwindow.h | 2 + src/mainwindow.ui | 86 ++++++++++++++++++++++++++++++--------- src/modinfo.cpp | 93 ++++++++++++++++++++++++------------------- src/modinfo.h | 17 +++++++- src/modlist.cpp | 4 ++ src/modlist.h | 1 + src/shared/directoryentry.cpp | 16 ++++---- 8 files changed, 216 insertions(+), 88 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 99b37feb..84e5b094 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -256,6 +256,8 @@ MainWindow::MainWindow(const QString &exeName, QSettings &initSettings, QWidget connect(ui->espFilterEdit, SIGNAL(textChanged(QString)), this, SLOT(espFilterChanged(QString))); connect(&m_PluginList, SIGNAL(saveTimer()), this, SLOT(savePluginList())); + connect(ui->bsaWarning, SIGNAL(linkActivated(QString)), this, SLOT(linkClicked(QString))); + connect(&m_DirectoryRefresher, SIGNAL(refreshed()), this, SLOT(directory_refreshed())); connect(&m_DirectoryRefresher, SIGNAL(progress(int)), this, SLOT(refresher_progress(int))); connect(&m_DirectoryRefresher, SIGNAL(error(QString)), this, SLOT(showError(QString))); @@ -1648,7 +1650,8 @@ void MainWindow::refreshBSAList() QTreeWidgetItem *newItem = new QTreeWidgetItem(strings); newItem->setData(0, Qt::UserRole, index); newItem->setData(1, Qt::UserRole, origin); - newItem->setFlags(newItem->flags() & ~Qt::ItemIsDropEnabled | Qt::ItemIsUserCheckable); +// newItem->setFlags(newItem->flags() & ~Qt::ItemIsDropEnabled | Qt::ItemIsUserCheckable); + newItem->setFlags(newItem->flags() | Qt::ItemIsUserCheckable); newItem->setCheckState(0, (index != -1) ? Qt::Checked : Qt::Unchecked); if (m_Settings.forceEnableCoreFiles() && m_DefaultArchives.contains(filename)) { newItem->setCheckState(0, Qt::Checked); @@ -1658,6 +1661,7 @@ void MainWindow::refreshBSAList() } if (index < 0) index = 0; + UINT32 sortValue = ((m_DirectoryStructure->getOriginByID(origin).getPriority() & 0xFFFF) << 16) | (index & 0xFFFF); items.push_back(std::make_pair(sortValue, newItem)); } @@ -1666,7 +1670,18 @@ void MainWindow::refreshBSAList() std::sort(items.begin(), items.end(), BySortValue); for (std::vector >::iterator iter = items.begin(); iter != items.end(); ++iter) { - ui->bsaList->addTopLevelItem(iter->second); + int originID = iter->second->data(1, Qt::UserRole).toInt(); + FilesOrigin origin = m_DirectoryStructure->getOriginByID(originID); + QList items = ui->bsaList->findItems(ToQString(origin.getName()), Qt::MatchFixedString); + QTreeWidgetItem *subItem = NULL; + if (items.length() > 0) { + subItem = items.at(0); + } else { + subItem = new QTreeWidgetItem(QStringList(ToQString(origin.getName()))); + ui->bsaList->addTopLevelItem(subItem); + } + subItem->addChild(iter->second); + //ui->bsaList->addTopLevelItem(iter->second); } checkBSAList(); @@ -1681,27 +1696,35 @@ void MainWindow::checkBSAList() bool warning = false; for (int i = 0; i < ui->bsaList->topLevelItemCount(); ++i) { - QTreeWidgetItem *item = ui->bsaList->topLevelItem(i); - QString filename = item->text(0); - item->setIcon(0, QIcon()); - item->setToolTip(0, QString()); - - if (item->checkState(0) == Qt::Unchecked) { - if (m_DefaultArchives.contains(filename)) { - item->setIcon(0, QIcon(":/MO/gui/resources/dialog-warning.png")); - item->setToolTip(0, tr("This bsa is enabled in the ini file so it may be required!")); - warning = true; - } else { - QString espName = filename.mid(0, filename.length() - 3).append("esp").toLower(); - QString esmName = filename.mid(0, filename.length() - 3).append("esm").toLower(); - if (m_PluginList.isEnabled(espName) || m_PluginList.isEnabled(esmName)) { + bool modWarning = false; + QTreeWidgetItem *tlItem = ui->bsaList->topLevelItem(i); + for (int j = 0; j < tlItem->childCount(); ++j) { + QTreeWidgetItem *item = tlItem->child(j); + QString filename = item->text(0); + item->setIcon(0, QIcon()); + item->setToolTip(0, QString()); + + if (item->checkState(0) == Qt::Unchecked) { + if (m_DefaultArchives.contains(filename)) { item->setIcon(0, QIcon(":/MO/gui/resources/dialog-warning.png")); - item->setToolTip(0, tr("This archive will still be loaded since there is a plugin of the same name but " - "its files will not follow installation order!")); - warning = true; + item->setToolTip(0, tr("This bsa is enabled in the ini file so it may be required!")); + modWarning = true; + } else { + QString espName = filename.mid(0, filename.length() - 3).append("esp").toLower(); + QString esmName = filename.mid(0, filename.length() - 3).append("esm").toLower(); + if (m_PluginList.isEnabled(espName) || m_PluginList.isEnabled(esmName)) { + item->setIcon(0, QIcon(":/MO/gui/resources/dialog-warning.png")); + item->setToolTip(0, tr("This archive will still be loaded since there is a plugin of the same name but " + "its files will not follow installation order!")); + modWarning = true; + } } } } + if (modWarning) { + ui->bsaList->expandItem(ui->bsaList->topLevelItem(i)); + warning = true; + } } if (warning) { @@ -2072,7 +2095,6 @@ void MainWindow::issueTriggered() ::ShellExecuteW(NULL, L"open", L"http://issue.tannin.eu/tbg", NULL, NULL, SW_SHOWNORMAL); } - void MainWindow::tutorialTriggered() { QAction *tutorialAction = qobject_cast(sender()); @@ -3312,6 +3334,7 @@ void MainWindow::fixMods_clicked() QStringList espFilter("*.esp"); espFilter.append("*.esm"); + // search in data { QDir dataDir(m_GamePath + "/data"); QStringList esps = dataDir.entryList(espFilter); @@ -3323,6 +3346,7 @@ void MainWindow::fixMods_clicked() } } + // search in mods for (unsigned int i = 0; i < m_CurrentProfile->numRegularMods(); ++i) { int modIndex = m_CurrentProfile->modIndexByPriority(i); ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); @@ -3336,12 +3360,25 @@ void MainWindow::fixMods_clicked() } } + // search in overwrite + { + QDir overwriteDir(ToQString(GameInfo::instance().getOverwriteDir())); + QStringList esps = overwriteDir.entryList(espFilter); + foreach (const QString &esp, esps) { + std::map >::iterator iter = missingPlugins.find(esp); + if (iter != missingPlugins.end()) { + iter->second.push_back(""); + } + } + } + + ActivateModsDialog dialog(missingPlugins, this); if (dialog.exec() == QDialog::Accepted) { // activate the required mods, then enable all esps std::set modsToActivate = dialog.getModsToActivate(); for (std::set::iterator iter = modsToActivate.begin(); iter != modsToActivate.end(); ++iter) { - if (*iter != "") { + if ((*iter != "") && (*iter != "")) { unsigned int modIndex = ModInfo::getIndex(*iter); m_CurrentProfile->setModEnabled(modIndex, true); } @@ -3496,6 +3533,12 @@ void MainWindow::nexusLinkActivated(const QString &link) } +void MainWindow::linkClicked(const QString &url) +{ + ::ShellExecuteW(NULL, L"open", ToWString(url).c_str(), NULL, NULL, SW_SHOWNORMAL); +} + + void MainWindow::downloadRequestedNXM(const QString &url) { QString username, password; diff --git a/src/mainwindow.h b/src/mainwindow.h index 23d5f0da..d7d0a8e7 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -362,6 +362,8 @@ private slots: void checkModsForUpdates(); void nexusLinkActivated(const QString &link); + void linkClicked(const QString &url); + void loginSuccessful(bool necessary); void loginSuccessfulUpdate(bool necessary); void loginFailed(const QString &message); diff --git a/src/mainwindow.ui b/src/mainwindow.ui index c019ccd9..fcdbd63c 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -31,7 +31,16 @@ 4 - + + 6 + + + 6 + + + 6 + + 6 @@ -719,19 +728,18 @@ p, li { white-space: pre-wrap; } Archives - + + 6 + + + 6 + + + 6 + + 6 - - - - IMPORTANT: You can change the order of BSAs here but installation order of mods has priority over the order specified here! - - - true - - - @@ -749,14 +757,17 @@ BSAs checked here are loaded in such a way that your installation order is obeye QAbstractItemView::NoEditTriggers + + false + - true + false false - QAbstractItemView::InternalMove + QAbstractItemView::NoDragDrop Qt::IgnoreAction @@ -768,10 +779,10 @@ BSAs checked here are loaded in such a way that your installation order is obeye QAbstractItemView::SelectRows - 0 + 20 - false + true 200 @@ -788,6 +799,16 @@ BSAs checked here are loaded in such a way that your installation order is obeye + + + + <html><head/><body><p>Marked Archives (<img src=":/MO/gui/resources/dialog-warning_16.png"/>) are still loaded on Skyrim but the <a href="http://forums.bethsoft.com/topic/1354395-update-bsas-and-you/"><span style=" text-decoration: underline; color:#0000ff;">regular file override</span></a> mechanism will apply: Loose files override BSAs, no matter the mod/plugin priority.</p></body></html> + + + true + + + @@ -795,7 +816,16 @@ BSAs checked here are loaded in such a way that your installation order is obeye Data - + + 6 + + + 6 + + + 6 + + 6 @@ -865,7 +895,16 @@ BSAs checked here are loaded in such a way that your installation order is obeye Saves - + + 6 + + + 6 + + + 6 + + 6 @@ -894,7 +933,16 @@ p, li { white-space: pre-wrap; } Downloads - + + 2 + + + 2 + + + 2 + + 2 diff --git a/src/modinfo.cpp b/src/modinfo.cpp index 1bb2daad..f6043d97 100644 --- a/src/modinfo.cpp +++ b/src/modinfo.cpp @@ -298,7 +298,7 @@ ModInfoRegular::ModInfoRegular(const QDir &path, DirectoryEntry **directoryStruc m_EndorsedState(ENDORSED_UNKNOWN), m_DirectoryStructure(directoryStructure) { testValid(); - + m_CreationTime = QFileInfo(path.absolutePath()).created(); // read out the meta-file for information QString metaFileName = path.absoluteFilePath("meta.ini"); QSettings metaFile(metaFileName, QSettings::IniFormat); @@ -645,6 +645,11 @@ QString ModInfoRegular::notes() const return m_Notes; } +QDateTime ModInfoRegular::creationTime() const +{ + return m_CreationTime; +} + QString ModInfoRegular::getNexusDescription() const { return m_NexusDescription; @@ -659,55 +664,63 @@ ModInfoRegular::EEndorsedState ModInfoRegular::endorsedState() const ModInfoRegular::EConflictType ModInfoRegular::isConflicted() const { - bool overwrite = false; - bool overwritten = false; - bool regular = false; + // this is costy so cache the result + QTime now = QTime::currentTime(); + if (abs(m_LastConflictCheck.secsTo(now)) > 10) { + bool overwrite = false; + bool overwritten = false; + bool regular = false; - int dataID = 0; - if ((*m_DirectoryStructure)->originExists(L"data")) { - dataID = (*m_DirectoryStructure)->getOriginByName(L"data").getID(); - } + int dataID = 0; + if ((*m_DirectoryStructure)->originExists(L"data")) { + dataID = (*m_DirectoryStructure)->getOriginByName(L"data").getID(); + } - std::wstring name = ToWString(m_Name); - if ((*m_DirectoryStructure)->originExists(name)) { - FilesOrigin &origin = (*m_DirectoryStructure)->getOriginByName(name); - std::vector files = origin.getFiles(); - for (auto iter = files.begin(); iter != files.end() && (!overwrite || !overwritten || !regular); ++iter) { - const std::vector &alternatives = (*iter)->getAlternatives(); - if (alternatives.size() == 0) { - // no alternatives -> no conflict - regular = true; - } else { - for (auto altIter = alternatives.begin(); altIter != alternatives.end(); ++altIter) { - // don't treat files overwritten in data as "conflict" - if (*altIter != dataID) { - bool ignore = false; - if ((*iter)->getOrigin(ignore) == origin.getID()) { - overwrite = true; - break; - } else { - overwritten = true; - break; + std::wstring name = ToWString(m_Name); + if ((*m_DirectoryStructure)->originExists(name)) { + FilesOrigin &origin = (*m_DirectoryStructure)->getOriginByName(name); + std::vector files = origin.getFiles(); + for (auto iter = files.begin(); iter != files.end() && (!overwrite || !overwritten || !regular); ++iter) { + const std::vector &alternatives = (*iter)->getAlternatives(); + if (alternatives.size() == 0) { + // no alternatives -> no conflict + regular = true; + } else { + for (auto altIter = alternatives.begin(); altIter != alternatives.end(); ++altIter) { + // don't treat files overwritten in data as "conflict" + if (*altIter != dataID) { + bool ignore = false; + if ((*iter)->getOrigin(ignore) == origin.getID()) { + overwrite = true; + break; + } else { + overwritten = true; + break; + } + } else if (alternatives.size() == 1) { + // only alternative is data -> no conflict + regular = true; } - } else if (alternatives.size() == 1) { - // only alternative is data -> no conflict - regular = true; } } } } - } - if (overwrite && overwritten) return CONFLICT_MIXED; - else if (overwrite) return CONFLICT_OVERWRITE; - else if (overwritten) { - if (!regular) { - return CONFLICT_REDUNDANT; - } else { - return CONFLICT_OVERWRITTEN; + m_LastConflictCheck = QTime::currentTime(); + + if (overwrite && overwritten) m_CurrentConflictState = CONFLICT_MIXED; + else if (overwrite) m_CurrentConflictState = CONFLICT_OVERWRITE; + else if (overwritten) { + if (!regular) { + m_CurrentConflictState = CONFLICT_REDUNDANT; + } else { + m_CurrentConflictState = CONFLICT_OVERWRITTEN; + } } + else m_CurrentConflictState = CONFLICT_NONE; } - else return CONFLICT_NONE; + + return m_CurrentConflictState; } diff --git a/src/modinfo.h b/src/modinfo.h index 74a77cd2..e6c6e6ac 100644 --- a/src/modinfo.h +++ b/src/modinfo.h @@ -354,10 +354,15 @@ public: virtual QString getDescription() const = 0; /** - * @return manually set notes for this mod + * @return notes for this mod */ virtual QString notes() const = 0; + /** + * @return creation time of this mod + */ + virtual QDateTime creationTime() const = 0; + /** * @return nexus description of the mod (html) */ @@ -669,6 +674,11 @@ public: */ virtual QString notes() const; + /** + * @return time this mod was created (file time of the directory) + */ + virtual QDateTime creationTime() const; + /** * @return nexus description of the mod (html) */ @@ -729,6 +739,7 @@ private: QString m_Notes; QString m_NexusDescription; + QDateTime m_CreationTime; QDateTime m_LastNexusQuery; int m_NexusID; @@ -742,6 +753,9 @@ private: MOShared::DirectoryEntry **m_DirectoryStructure; + mutable EConflictType m_CurrentConflictState; + mutable QTime m_LastConflictCheck; + }; @@ -797,6 +811,7 @@ public: virtual void endorse(bool) {} virtual QString name() const { return tr("Overwrite"); } virtual QString notes() const { return ""; } + virtual QDateTime creationTime() const { return QDateTime::currentDateTime(); } virtual QString absolutePath() const; virtual MOBase::VersionInfo getNewestVersion() const { return ""; } virtual QString getInstallationFile() const { return ""; } diff --git a/src/modlist.cpp b/src/modlist.cpp index 45bed00a..9c537300 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -178,6 +178,8 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const //return tr("None"); return QVariant(); } + } else if (column == COL_INSTALLTIME) { + return modInfo->creationTime(); } else { return tr("invalid"); } @@ -718,6 +720,7 @@ QString ModList::getColumnName(int column) case COL_PRIORITY: return tr("Priority"); case COL_CATEGORY: return tr("Category"); case COL_MODID: return tr("Nexus ID"); + case COL_INSTALLTIME: return tr("Installation"); default: return tr("unknown"); } } @@ -733,6 +736,7 @@ QString ModList::getColumnToolTip(int column) case COL_CATEGORY: return tr("Category of the mod."); case COL_MODID: return tr("Id of the mod as used on Nexus."); case COL_FLAGS: return tr("Emblemes to highlight things that might require attention."); + case COL_INSTALLTIME: return tr("Time this mod was installed"); default: return tr("unknown"); } } diff --git a/src/modlist.h b/src/modlist.h index 4f1bf85e..069de40b 100644 --- a/src/modlist.h +++ b/src/modlist.h @@ -53,6 +53,7 @@ public: COL_CATEGORY, COL_MODID, COL_VERSION, + COL_INSTALLTIME, COL_PRIORITY, COL_LASTCOLUMN = COL_PRIORITY diff --git a/src/shared/directoryentry.cpp b/src/shared/directoryentry.cpp index ceba113d..7897b6aa 100644 --- a/src/shared/directoryentry.cpp +++ b/src/shared/directoryentry.cpp @@ -699,11 +699,12 @@ const FileEntry::Ptr DirectoryEntry::searchFile(const std::wstring &path, const *directory = NULL; } - if ((path.length() == 0) || - (path == L"*")) { + if ((path.length() == 0) || (path == L"*")) { // no file name -> the path ended on a (back-)slash - *directory = this; - return NULL; + if (directory != NULL) { + *directory = this; + } + return FileEntry::Ptr(); } size_t len = path.find_first_of(L"\\/"); @@ -727,9 +728,10 @@ const FileEntry::Ptr DirectoryEntry::searchFile(const std::wstring &path, const return temp->searchFile(path.substr(len + 1), directory); } } - return NULL; + return FileEntry::Ptr(); } + DirectoryEntry *DirectoryEntry::findSubDirectory(const std::wstring &name) const { for (std::vector::const_iterator iter = m_SubDirectories.begin(); iter != m_SubDirectories.end(); ++iter) { @@ -747,7 +749,7 @@ const FileEntry::Ptr DirectoryEntry::findFile(const std::wstring &name) if (iter != m_Files.end()) { return m_FileRegister->getFile(iter->second); } else { - return NULL; + return FileEntry::Ptr(); } } @@ -828,7 +830,7 @@ FileEntry::Ptr FileRegister::getFile(FileEntry::Index index) if (iter != m_Files.end()) { return iter->second; } - return NULL; + return FileEntry::Ptr(); } -- cgit v1.3.1