From 500ab7f706bdc8d4eb94db22ab60f882b2670dde Mon Sep 17 00:00:00 2001 From: Tannin Date: Mon, 16 Sep 2013 22:32:42 +0200 Subject: - bugfix: testing for missing masters at the wrong time seems to have caused crashes - bugfix: mod list is now written to a temporary file first. Only on success is the original file overwritten - bugfix: moving a mod priority to just above the overwrite could cause a crash or error message - bugfix: versions with a release candidate number weren't sorted correctly (woops) - bugfix: staging script didn't include archive.dll and dlls.manifest - installation time on overwrite no longer updates constantly --- src/profile.cpp | 86 ++++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 54 insertions(+), 32 deletions(-) (limited to 'src/profile.cpp') diff --git a/src/profile.cpp b/src/profile.cpp index a6d8f49d..330bfa09 100644 --- a/src/profile.cpp +++ b/src/profile.cpp @@ -30,6 +30,8 @@ along with Mod Organizer. If not, see . #include #include #include +#include + #define WIN32_LEAN_AND_MEAN #include #include @@ -146,38 +148,56 @@ void Profile::writeModlistNow(bool onlyOnTimer) const if (!m_Directory.exists()) return; #pragma message("right now, this is doing unnecessary saves. Need a flag that says that mod priority, enabled-state or name of a mod has changed") - QString fileName = getModlistFileName(); - QFile file(fileName); - if (!file.open(QIODevice::WriteOnly)) { - reportError(tr("failed to open \"%1\" for writing").arg(fileName)); - return; - } + try { + QTemporaryFile file; - file.resize(0); - file.write(QString("# This file was automatically generated by Mod Organizer.\r\n").toUtf8()); - if (m_ModStatus.empty()) { - return; - } + if (!file.open()) { + reportError(tr("failed to open temporary file")); + return; + } - for (int i = m_ModStatus.size() - 1; i >= 0; --i) { - // the priority order was inverted on load so it has to be inverted again - unsigned int index = m_ModIndexByPriority[i]; - if (index != UINT_MAX) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(index); - if (modInfo->getFixedPriority() == INT_MIN) { - if (m_ModStatus[index].m_Enabled) { - file.write("+"); - } else { - file.write("-"); + file.resize(0); + file.write(QString("# This file was automatically generated by Mod Organizer.\r\n").toUtf8()); + if (m_ModStatus.empty()) { + return; + } + + for (int i = m_ModStatus.size() - 1; i >= 0; --i) { + // the priority order was inverted on load so it has to be inverted again + unsigned int index = m_ModIndexByPriority[i]; + if (index != UINT_MAX) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(index); + if (modInfo->getFixedPriority() == INT_MIN) { + if (m_ModStatus[index].m_Enabled) { + file.write("+"); + } else { + file.write("-"); + } + file.write(modInfo->name().toUtf8()); + file.write("\r\n"); } - file.write(modInfo->name().toUtf8()); - file.write("\r\n"); } } - } - file.close(); - qDebug("%s saved", QDir::toNativeSeparators(fileName).toUtf8().constData()); + file.close(); + + + QString fileName = getModlistFileName(); + + if (QFile::exists(fileName)) { + shellDelete(QStringList(fileName)); + } + + if (!file.copy(fileName)) { + reportError(tr("failed to open \"%1\" for writing").arg(fileName)); + return; + } + + qDebug("%s saved", QDir::toNativeSeparators(fileName).toUtf8().constData()); + } catch (const std::exception &e) { + reportError(tr("failed to write mod list: %1").arg(e.what())); + return; + } } @@ -331,6 +351,7 @@ void Profile::updateIndices() for (unsigned int i = 0; i < m_ModStatus.size(); ++i) { int priority = m_ModStatus[i].m_Priority; if (priority < 0) { + // don't assign this to mapping at all, it's probably the overwrite mod continue; } else if (priority >= static_cast(m_ModIndexByPriority.size())) { qCritical("invalid priority %d for mod", priority); @@ -418,7 +439,7 @@ int Profile::getModPriority(unsigned int index) const void Profile::setModPriority(unsigned int index, int &newPriority) { - if (m_ModStatus[index].m_Overwrite) { + if (m_ModStatus.at(index).m_Overwrite) { // can't change priority of the overwrite return; } @@ -426,24 +447,25 @@ void Profile::setModPriority(unsigned int index, int &newPriority) int newPriorityTemp = (std::max)(0, (std::min)(m_ModStatus.size() - 1, newPriority)); // don't try to place below overwrite - while (m_ModStatus[m_ModIndexByPriority[newPriorityTemp]].m_Overwrite) { + while ((m_ModIndexByPriority.at(newPriorityTemp) >= m_ModStatus.size()) || + m_ModStatus.at(m_ModIndexByPriority.at(newPriorityTemp)).m_Overwrite) { --newPriorityTemp; } - int oldPriority = m_ModStatus[index].m_Priority; + int oldPriority = m_ModStatus.at(index).m_Priority; if (newPriorityTemp > oldPriority) { // priority is higher than the old, so the gap we left is in lower priorities for (int i = oldPriority + 1; i <= newPriorityTemp; ++i) { - --m_ModStatus[m_ModIndexByPriority[i]].m_Priority; + --m_ModStatus.at(m_ModIndexByPriority.at(i)).m_Priority; } } else { for (int i = newPriorityTemp; i < oldPriority; ++i) { - ++m_ModStatus[m_ModIndexByPriority[i]].m_Priority; + ++m_ModStatus.at(m_ModIndexByPriority.at(i)).m_Priority; } ++newPriority; } - m_ModStatus[index].m_Priority = newPriorityTemp; + m_ModStatus.at(index).m_Priority = newPriorityTemp; updateIndices(); writeModlist(); -- cgit v1.3.1 From dfca8be71b15bc13997110cc8777dd5a84a1fde7 Mon Sep 17 00:00:00 2001 From: Tannin Date: Sat, 21 Sep 2013 19:25:33 +0200 Subject: - esp reader now handles invalid files more gracefully - files moved will now also be treated as "deleted" in the old location so a newly created file with that same name is not created in overwrite - introduced a mechanism by which MO can recognize if it crashed before when attempting to load a plugin. That plugin can be blacklisted so it doesn't get loaded again - plugins can now programaticaly change their settings - plugins can now store data persistently without exposing that data as settings - requesting an unset-setting from a plugin is no longer treated as a bug - clarified warning message for when files are in overwrite directory - the proxyPython plugin will now discover if python initialization crashed MO on a previous session and give the user a chance to fix it or disable the plugin - bugfix: GetModuleFileName modified the buffer past the zero termination. While this doesn't violate the API documentation it is different from the regular windows implementation - bugfix: proxy plugins couldn't access the parent widget - bugfix: when moving a file from overwrite to a mod the in-memory file structure wasn't updated - bugfix: name input dialog for profiles allowed names that weren't valid directory names - bugfix: profile dialog wasn't able to delete profiles if the name started or ended in whitespaces - bugfix: The name-cells for plugin settings could be changed (without effect) - removed a few obsolete files from the repository --- src/activatemodsdialog.cpp | 5 -- src/activatemodsdialog.h | 1 - src/archivetree.cpp | 98 ------------------------ src/archivetree.h | 55 ------------- src/executableslist.cpp | 10 --- src/finddialog.cpp | 48 ------------ src/finddialog.h | 73 ------------------ src/main.cpp | 2 +- src/mainwindow.cpp | 94 ++++++++++++++++++++--- src/mainwindow.h | 6 ++ src/modlist.cpp | 8 ++ src/modlist.h | 8 ++ src/noeditdelegate.cpp | 10 +++ src/noeditdelegate.h | 12 +++ src/organizer.pro | 15 ++-- src/overwriteinfodialog.cpp | 1 - src/pluginlist.cpp | 8 +- src/profile.cpp | 1 + src/profileinputdialog.cpp | 55 ++++++------- src/profilesdialog.cpp | 47 ++++++++---- src/settings.cpp | 80 ++++++++++++++++++- src/settings.h | 47 +++++++++++- src/settingsdialog.cpp | 12 +++ src/settingsdialog.h | 2 + src/settingsdialog.ui | 182 ++++++++++++++++++++++++-------------------- src/shared/gameinfo.cpp | 20 ++--- src/shared/skyriminfo.cpp | 4 +- src/shared/skyriminfo.h | 2 +- src/version.rc | 4 +- 29 files changed, 457 insertions(+), 453 deletions(-) delete mode 100644 src/archivetree.cpp delete mode 100644 src/archivetree.h delete mode 100644 src/finddialog.cpp delete mode 100644 src/finddialog.h create mode 100644 src/noeditdelegate.cpp create mode 100644 src/noeditdelegate.h (limited to 'src/profile.cpp') diff --git a/src/activatemodsdialog.cpp b/src/activatemodsdialog.cpp index cf798189..8d463fba 100644 --- a/src/activatemodsdialog.cpp +++ b/src/activatemodsdialog.cpp @@ -65,11 +65,6 @@ ActivateModsDialog::~ActivateModsDialog() } -void ActivateModsDialog::on_buttonBox_accepted() -{ -} - - std::set ActivateModsDialog::getModsToActivate() { std::set result; diff --git a/src/activatemodsdialog.h b/src/activatemodsdialog.h index db0bf7fa..845167aa 100644 --- a/src/activatemodsdialog.h +++ b/src/activatemodsdialog.h @@ -62,7 +62,6 @@ public: std::set getESPsToActivate(); private slots: - void on_buttonBox_accepted(); private: Ui::ActivateModsDialog *ui; diff --git a/src/archivetree.cpp b/src/archivetree.cpp deleted file mode 100644 index 0484b241..00000000 --- a/src/archivetree.cpp +++ /dev/null @@ -1,98 +0,0 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#include "archivetree.h" -#include - -ArchiveTree::ArchiveTree(QWidget *parent) : - QTreeWidget(parent) -{ -} - - -bool ArchiveTree::testMovePossible(QTreeWidgetItem *source, QTreeWidgetItem *target) -{ - if ((target == NULL) || - (source == NULL)) { - return false; - } - - if ((source == target) || - (source->parent() == target)) { - return false; - } - - return true; -} - -void ArchiveTree::dragEnterEvent(QDragEnterEvent *event) -{ - QTreeWidgetItem *source = this->currentItem(); - if ((source == NULL) || (source->parent() == NULL)) { - // can't change top level - event->ignore(); - return; - } else { - QTreeWidget::dragEnterEvent(event); - } - -} - -void ArchiveTree::dragMoveEvent(QDragMoveEvent *event) -{ - if (!testMovePossible(this->currentItem(), itemAt(event->pos()))) { - event->ignore(); - } else { - QTreeWidget::dragMoveEvent(event); - } -} - - -void ArchiveTree::dropEvent(QDropEvent *event) -{ - event->ignore(); - - QTreeWidgetItem *target = itemAt(event->pos()); - - QList sourceItems = this->selectedItems(); - for (QList::iterator iter = sourceItems.begin(); - iter != sourceItems.end(); ++iter) { - QTreeWidgetItem *source = *iter; - if ((source->parent() != NULL) && - testMovePossible(source, target)) { - source->parent()->removeChild(source); - if (target->data(0, Qt::UserRole).toInt() != 0) { - // target is a file - if (target->parent() == NULL) { - // this should really not happen, how should a - // file get to the top level? - return; - } - int index = target->parent()->indexOfChild(target); - target->parent()->insertChild(index, source); - emit changed(); - } else { - // target is a directory - target->insertChild(target->childCount(), source); - emit changed(); - } - } - } - -} diff --git a/src/archivetree.h b/src/archivetree.h deleted file mode 100644 index 775bf8b9..00000000 --- a/src/archivetree.h +++ /dev/null @@ -1,55 +0,0 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#ifndef ARCHIVETREE_H -#define ARCHIVETREE_H - -#include - -/** - * @brief QT tree widget used to display the content of an archive in the manual installation dialog - **/ -class ArchiveTree : public QTreeWidget -{ - - Q_OBJECT - -public: - - explicit ArchiveTree(QWidget *parent = 0); - -signals: - - void changed(); - -public slots: - -protected: - - virtual void dragEnterEvent(QDragEnterEvent *event); - virtual void dragMoveEvent(QDragMoveEvent *event); - virtual void dropEvent(QDropEvent *event); - -private: - - bool testMovePossible(QTreeWidgetItem *source, QTreeWidgetItem *target); - -}; - -#endif // ARCHIVETREE_H diff --git a/src/executableslist.cpp b/src/executableslist.cpp index 4e39c1a3..c486a4ca 100644 --- a/src/executableslist.cpp +++ b/src/executableslist.cpp @@ -172,16 +172,6 @@ void ExecutablesList::addExecutable(const QString &title, const QString &executa } } -/*void ExecutablesList::remove(const QString &executableName) -{ - for (std::vector::iterator iter = m_Executables.begin(); iter != m_Executables.end(); ++iter) { - if (iter->m_Custom && (iter->m_BinaryInfo.absoluteFilePath() == executableName)) { - m_Executables.erase(iter); - break; - } - } -}*/ - void ExecutablesList::remove(const QString &title) { diff --git a/src/finddialog.cpp b/src/finddialog.cpp deleted file mode 100644 index 58b70ade..00000000 --- a/src/finddialog.cpp +++ /dev/null @@ -1,48 +0,0 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#include "finddialog.h" -#include "ui_finddialog.h" - -FindDialog::FindDialog(QWidget *parent) : - QDialog(parent), - ui(new Ui::FindDialog) -{ - ui->setupUi(this); -} - -FindDialog::~FindDialog() -{ - delete ui; -} - -void FindDialog::on_nextBtn_clicked() -{ - emit findNext(); -} - -void FindDialog::on_patternEdit_textChanged(const QString &pattern) -{ - emit patternChanged(pattern); -} - -void FindDialog::on_closeBtn_clicked() -{ - this->close(); -} diff --git a/src/finddialog.h b/src/finddialog.h deleted file mode 100644 index 70493654..00000000 --- a/src/finddialog.h +++ /dev/null @@ -1,73 +0,0 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#ifndef FINDDIALOG_H -#define FINDDIALOG_H - -#include - -namespace Ui { - class FindDialog; -} - -/** - * @brief Find dialog used in the TextView dialog - **/ -class FindDialog : public QDialog -{ - - Q_OBJECT - -public: - - /** - * @brief constructor - * - * @param parent parent widget - **/ - explicit FindDialog(QWidget *parent = 0); - - ~FindDialog(); - -signals: - - /** - * @brief emitted when the user wants to jump to the next location matching the pattern - **/ - void findNext(); - - /** - * @brief emitted when the user changes the pattern to search for - * - * @param pattern the new search pattern - **/ - void patternChanged(const QString &pattern); - -private slots: - void on_nextBtn_clicked(); - - void on_patternEdit_textChanged(const QString &arg1); - - void on_closeBtn_clicked(); - -private: - Ui::FindDialog *ui; -}; - -#endif // FINDDIALOG_H diff --git a/src/main.cpp b/src/main.cpp index 1d40dfeb..5a694121 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -343,7 +343,7 @@ int main(int argc, char *argv[]) bool done = false; while (!done) { - if (!GameInfo::init(moPath, ToWString(gamePath))) { + if (!GameInfo::init(moPath, ToWString(QDir::toNativeSeparators(gamePath)))) { if (!gamePath.isEmpty()) { reportError(QObject::tr("No game identified in \"%1\". The directory is required to contain " "the game binary and its launcher.").arg(gamePath)); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 2c5e3707..ffec91a1 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -98,6 +98,7 @@ along with Mod Organizer. If not, see . #include #include #include +#include #ifdef TEST_MODELS @@ -233,6 +234,7 @@ MainWindow::MainWindow(const QString &exeName, QSettings &initSettings, QWidget connect(&m_ModList, SIGNAL(modlist_changed(QModelIndex, int)), this, SLOT(modlistChanged(QModelIndex, int))); connect(&m_ModList, SIGNAL(removeSelectedMods()), this, SLOT(removeMod_clicked())); connect(&m_ModList, SIGNAL(requestColumnSelect(QPoint)), this, SLOT(displayColumnSelection(QPoint))); + connect(&m_ModList, SIGNAL(fileMoved(QString, QString, QString)), this, SLOT(fileMoved(QString, QString, QString))); connect(ui->modList, SIGNAL(dropModeUpdate(bool)), &m_ModList, SLOT(dropModeUpdate(bool))); connect(m_ModListSortProxy, SIGNAL(filterActive(bool)), this, SLOT(modFilterActive(bool))); connect(ui->modFilterEdit, SIGNAL(textChanged(QString)), m_ModListSortProxy, SLOT(updateFilter(QString))); @@ -997,6 +999,7 @@ bool MainWindow::registerPlugin(QObject *plugin) { // proxy plugins IPluginProxy *proxy = qobject_cast(plugin); if (verifyPlugin(proxy)) { + proxy->setParentWidget(this); QStringList pluginNames = proxy->pluginList(QCoreApplication::applicationDirPath() + "/" + ToQString(AppConfig::pluginPath())); foreach (const QString &pluginName, pluginNames) { try { @@ -1040,11 +1043,38 @@ void MainWindow::loadPlugins() registerPlugin(plugin); } + QFile loadCheck(QCoreApplication::applicationDirPath() + "/plugin_loadcheck.tmp"); + if (loadCheck.exists() && loadCheck.open(QIODevice::ReadOnly)) { + // oh, there was a failed plugin load last time. Find out which plugin was loaded last + QString fileName; + while (!loadCheck.atEnd()) { + fileName = QString::fromUtf8(loadCheck.readLine().constData()).trimmed(); + } + if (QMessageBox::question(this, tr("Plugin error"), + tr("It appears the plugin \"%1\" failed to load last startup and caused MO to crash. Do you want to disable it?\n" + "(Please note: If this is the first time you see this message for this plugin you may want to give it another try. " + "The plugin may be able to recover from the problem)").arg(fileName), + QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes) { + m_Settings.addBlacklistPlugin(fileName); + } + loadCheck.close(); + } + + loadCheck.open(QIODevice::WriteOnly); + QString pluginPath = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getOrganizerDirectory())) + "/" + ToQString(AppConfig::pluginPath()); qDebug("looking for plugins in %s", QDir::toNativeSeparators(pluginPath).toUtf8().constData()); QDirIterator iter(pluginPath, QDir::Files | QDir::NoDotAndDotDot); + while (iter.hasNext()) { iter.next(); + if (m_Settings.pluginBlacklisted(iter.fileName())) { + qDebug("plugin \"%s\" blacklisted", qPrintable(iter.fileName())); + continue; + } + loadCheck.write(iter.fileName().toUtf8()); + loadCheck.write("\n"); + loadCheck.flush(); QString pluginName = iter.filePath(); if (QLibrary::isLibrary(pluginName)) { QPluginLoader pluginLoader(pluginName); @@ -1063,6 +1093,9 @@ void MainWindow::loadPlugins() } } + // remove the load check file on success + loadCheck.remove(); + m_DownloadManager.setSupportedExtensions(m_InstallationManager.getSupportedExtensions()); m_DiagnosisPlugins.push_back(this); @@ -1172,6 +1205,21 @@ QVariant MainWindow::pluginSetting(const QString &pluginName, const QString &key return m_Settings.pluginSetting(pluginName, key); } +void MainWindow::setPluginSetting(const QString &pluginName, const QString &key, const QVariant &value) +{ + m_Settings.setPluginSetting(pluginName, key, value); +} + +QVariant MainWindow::persistent(const QString &pluginName, const QString &key, const QVariant &def) const +{ + return m_Settings.pluginPersistent(pluginName, key, def); +} + +void MainWindow::setPersistent(const QString &pluginName, const QString &key, const QVariant &value, bool sync) +{ + m_Settings.setPluginPersistent(pluginName, key, value, sync); +} + QString MainWindow::pluginDataPath() const { QString pluginPath = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getOrganizerDirectory())) + "/" + ToQString(AppConfig::pluginPath()); @@ -1273,11 +1321,9 @@ void MainWindow::spawnBinary(const QFileInfo &binary, const QString &arguments, this->setEnabled(true); refreshDirectoryStructure(); - refreshDataTree(); if (GameInfo::instance().getLoadOrderMechanism() == GameInfo::TYPE_FILETIME) { QFile::remove(m_CurrentProfile->getLoadOrderFileName()); } - refreshLists(); dialog->hide(); } } @@ -1622,11 +1668,15 @@ void MainWindow::refreshESPList() m_CurrentProfile->writeModlist(); // clear list - m_PluginList.refresh(m_CurrentProfile->getName(), - *m_DirectoryStructure, - m_CurrentProfile->getPluginsFileName(), - m_CurrentProfile->getLoadOrderFileName(), - m_CurrentProfile->getLockedOrderFileName()); + try { + m_PluginList.refresh(m_CurrentProfile->getName(), + *m_DirectoryStructure, + m_CurrentProfile->getPluginsFileName(), + m_CurrentProfile->getLoadOrderFileName(), + m_CurrentProfile->getLockedOrderFileName()); + } catch (const std::exception &e) { + reportError(tr("Failed to refresh list of esps: %s").arg(e.what())); + } } @@ -1926,7 +1976,6 @@ void MainWindow::on_btnRefreshData_clicked() { if (!m_DirectoryUpdate) { refreshDirectoryStructure(); - refreshDataTree(); } else { qDebug("directory update"); } @@ -2331,8 +2380,12 @@ void MainWindow::directory_refreshed() { DirectoryEntry *newStructure = m_DirectoryRefresher.getDirectoryStructure(); if (newStructure != NULL) { - delete m_DirectoryStructure; + DirectoryEntry *oldStructure = m_DirectoryStructure; m_DirectoryStructure = newStructure; + delete oldStructure; + + refreshDataTree(); + refreshLists(); } else { // TODO: don't know why this happens, this slot seems to get called twice with only one emit return; @@ -2541,6 +2594,29 @@ void MainWindow::modlistChanged(int) } +void MainWindow::fileMoved(const QString &filePath, const QString &oldOriginName, const QString &newOriginName) +{ + const FileEntry::Ptr filePtr = m_DirectoryStructure->findFile(ToWString(filePath)); + if (filePtr.get() != NULL) { + try { + FilesOrigin &oldOrigin = m_DirectoryStructure->getOriginByName(ToWString(oldOriginName)); + FilesOrigin &newOrigin = m_DirectoryStructure->getOriginByName(ToWString(newOriginName)); + + QString fullNewPath = ToQString(newOrigin.getPath()) + "\\" + filePath; + WIN32_FIND_DATAW findData; + ::FindFirstFileW(ToWString(fullNewPath).c_str(), &findData); + + filePtr->addOrigin(newOrigin.getID(), findData.ftCreationTime, L""); + filePtr->removeOrigin(oldOrigin.getID()); + } catch (const std::exception &e) { + reportError(tr("Failed to move \"%1\" from mod \"%2\" to \"%3\": %4").arg(filePath).arg(oldOriginName).arg(newOriginName).arg(e.what())); + } + } else { + reportError(tr("Failed to relocate \"%1\"").arg(filePath)); + } +} + + QTreeWidgetItem *MainWindow::addFilterItem(QTreeWidgetItem *root, const QString &name, int categoryID) { QTreeWidgetItem *item = new QTreeWidgetItem(QStringList(name)); diff --git a/src/mainwindow.h b/src/mainwindow.h index 152c0a27..54ae0e96 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -105,6 +105,9 @@ public: virtual bool removeMod(MOBase::IModInterface *mod); virtual void modDataChanged(MOBase::IModInterface *mod); virtual QVariant pluginSetting(const QString &pluginName, const QString &key) const; + virtual void setPluginSetting(const QString &pluginName, const QString &key, const QVariant &value); + virtual QVariant persistent(const QString &pluginName, const QString &key, const QVariant &def = QVariant()) const; + virtual void setPersistent(const QString &pluginName, const QString &key, const QVariant &value, bool sync = true); virtual QString pluginDataPath() const; virtual void installMod(const QString &fileName); virtual QString resolvePath(const QString &fileName) const; @@ -315,6 +318,8 @@ private: std::vector m_DiagnosisPlugins; std::vector m_UnloadedPlugins; + QFile m_PluginsCheck; + private slots: void showMessage(const QString &message); @@ -435,6 +440,7 @@ private slots: void updateStyle(const QString &style); void modlistChanged(const QModelIndex &index, int role); + void fileMoved(const QString &filePath, const QString &oldOriginName, const QString &newOriginName); void savePluginList(); diff --git a/src/modlist.cpp b/src/modlist.cpp index 5aa8fbb9..b5a7182e 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -477,6 +477,7 @@ Qt::ItemFlags ModList::flags(const QModelIndex &modelIndex) const return result; } + QStringList ModList::mimeTypes() const { QStringList result = QAbstractItemModel::mimeTypes(); @@ -544,6 +545,12 @@ bool ModList::dropURLs(const QMimeData *mimeData, int row, const QModelIndex &pa QDir modDirectory(modInfo->absolutePath()); QDir gameDirectory(QDir::fromNativeSeparators(ToQString(MOShared::GameInfo::instance().getOverwriteDir()))); + unsigned int overwriteIndex = ModInfo::findMod([](ModInfo::Ptr mod) -> bool { + std::vector flags = mod->getFlags(); + return std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end(); }); + + QString overwriteName = ModInfo::getByIndex(overwriteIndex)->name(); + foreach (const QUrl &url, mimeData->urls()) { QString relativePath = gameDirectory.relativeFilePath(url.toLocalFile()); if (relativePath.startsWith("..")) { @@ -552,6 +559,7 @@ bool ModList::dropURLs(const QMimeData *mimeData, int row, const QModelIndex &pa } source.append(url.toLocalFile()); target.append(modDirectory.absoluteFilePath(relativePath)); + emit fileMoved(relativePath, overwriteName, modInfo->name()); } if (source.count() != 0) { diff --git a/src/modlist.h b/src/modlist.h index 069de40b..7e76d677 100644 --- a/src/modlist.h +++ b/src/modlist.h @@ -182,6 +182,14 @@ signals: */ void removeSelectedMods(); + /** + * @brief fileMoved emitted when a file is moved from one mod to another + * @param relativePath relative path of the file moved + * @param oldOriginName name of the origin that previously contained the file + * @param newOriginName name of the origin that now contains the file + */ + void fileMoved(const QString &relativePath, const QString &oldOriginName, const QString &newOriginName); + protected: // event filter, handles event from the header and the tree view itself diff --git a/src/noeditdelegate.cpp b/src/noeditdelegate.cpp new file mode 100644 index 00000000..ab8a4880 --- /dev/null +++ b/src/noeditdelegate.cpp @@ -0,0 +1,10 @@ +#include "noeditdelegate.h" + +NoEditDelegate::NoEditDelegate(QObject *parent) + : QStyledItemDelegate(parent) +{ +} + +QWidget *NoEditDelegate::createEditor(QWidget*, const QStyleOptionViewItem&, const QModelIndex&) const { + return NULL; +} diff --git a/src/noeditdelegate.h b/src/noeditdelegate.h new file mode 100644 index 00000000..d0ba2f68 --- /dev/null +++ b/src/noeditdelegate.h @@ -0,0 +1,12 @@ +#ifndef NOEDITDELEGATE_H +#define NOEDITDELEGATE_H + +#include + +class NoEditDelegate: public QStyledItemDelegate { +public: + NoEditDelegate(QObject *parent = NULL); + virtual QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const; +}; + +#endif // NOEDITDELEGATE_H diff --git a/src/organizer.pro b/src/organizer.pro index 339a50cd..e89b6e99 100644 --- a/src/organizer.pro +++ b/src/organizer.pro @@ -51,7 +51,6 @@ SOURCES += \ json.cpp \ installationmanager.cpp \ helper.cpp \ - finddialog.cpp \ filedialogmemory.cpp \ executableslist.cpp \ editexecutablesdialog.cpp \ @@ -66,7 +65,6 @@ SOURCES += \ categoriesdialog.cpp \ categories.cpp \ bbcode.cpp \ - archivetree.cpp \ activatemodsdialog.cpp \ moapplication.cpp \ profileinputdialog.cpp \ @@ -80,7 +78,8 @@ SOURCES += \ serverinfo.cpp \ ../esptk/record.cpp \ ../esptk/espfile.cpp \ - ../esptk/subrecord.cpp + ../esptk/subrecord.cpp \ + noeditdelegate.cpp HEADERS += \ transfersavesdialog.h \ @@ -119,7 +118,6 @@ HEADERS += \ json.h \ installationmanager.h \ helper.h \ - finddialog.h \ filedialogmemory.h \ executableslist.h \ editexecutablesdialog.h \ @@ -134,7 +132,6 @@ HEADERS += \ categoriesdialog.h \ categories.h \ bbcode.h \ - archivetree.h \ activatemodsdialog.h \ moapplication.h \ profileinputdialog.h \ @@ -148,7 +145,9 @@ HEADERS += \ serverinfo.h \ ../esptk/record.h \ ../esptk/espfile.h \ - ../esptk/subrecord.h + ../esptk/subrecord.h \ + ../esptk/espexceptions.h \ + noeditdelegate.h FORMS += \ transfersavesdialog.ui \ @@ -218,8 +217,8 @@ TRANSLATIONS = organizer_de.ts \ organizer_zh_CN.ts \ organizer_cs.ts \ organizer_tr.ts \ - organizer_ru.ts \ - organizer.en.ts +# organizer.en.ts \ + organizer_ru.ts !isEmpty(TRANSLATIONS) { isEmpty(QMAKE_LRELEASE) { diff --git a/src/overwriteinfodialog.cpp b/src/overwriteinfodialog.cpp index 11e7b552..e215146f 100644 --- a/src/overwriteinfodialog.cpp +++ b/src/overwriteinfodialog.cpp @@ -37,7 +37,6 @@ public: virtual int columnCount(const QModelIndex &parent) const { m_RegularColumnCount = QFileSystemModel::columnCount(parent); -// return m_RegularColumnCount + 1; return m_RegularColumnCount; } diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp index ea23fcdd..6cb9bdf7 100644 --- a/src/pluginlist.cpp +++ b/src/pluginlist.cpp @@ -140,8 +140,12 @@ void PluginList::refresh(const QString &profileName, const DirectoryEntry &baseD std::find(primaryPlugins.begin(), primaryPlugins.end(), ToWString(filename.toLower())) != primaryPlugins.end(); bool archive = false; - FilesOrigin &origin = baseDirectory.getOriginByID(current->getOrigin(archive)); - m_ESPs.push_back(ESPInfo(filename, forceEnabled, current->getFileTime(), ToQString(origin.getName()), ToQString(current->getFullPath()))); + try { + FilesOrigin &origin = baseDirectory.getOriginByID(current->getOrigin(archive)); + m_ESPs.push_back(ESPInfo(filename, forceEnabled, current->getFileTime(), ToQString(origin.getName()), ToQString(current->getFullPath()))); + } 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())); + } } } diff --git a/src/profile.cpp b/src/profile.cpp index 330bfa09..07f7010e 100644 --- a/src/profile.cpp +++ b/src/profile.cpp @@ -755,6 +755,7 @@ QString Profile::getIniFileName() const return m_Directory.absoluteFilePath(ToQString(primaryIniFile)); } + QString Profile::getPath() const { return QDir::cleanPath(m_Directory.absolutePath()); diff --git a/src/profileinputdialog.cpp b/src/profileinputdialog.cpp index 7402d4d5..af33dcdd 100644 --- a/src/profileinputdialog.cpp +++ b/src/profileinputdialog.cpp @@ -17,29 +17,32 @@ You should have received a copy of the GNU General Public License along with Mod Organizer. If not, see . */ -#include "profileinputdialog.h" -#include "ui_profileinputdialog.h" - -ProfileInputDialog::ProfileInputDialog(QWidget *parent) : - QDialog(parent), - ui(new Ui::ProfileInputDialog) -{ - ui->setupUi(this); -} - -ProfileInputDialog::~ProfileInputDialog() -{ - delete ui; -} - -QString ProfileInputDialog::getName() const -{ - return ui->nameEdit->text().trimmed(); -} - - -bool ProfileInputDialog::getPreferDefaultSettings() const -{ - return ui->defaultSettingsBox->checkState() == Qt::Checked; -} - +#include "profileinputdialog.h" +#include "ui_profileinputdialog.h" +#include + +ProfileInputDialog::ProfileInputDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::ProfileInputDialog) +{ + ui->setupUi(this); +} + +ProfileInputDialog::~ProfileInputDialog() +{ + delete ui; +} + +QString ProfileInputDialog::getName() const +{ + QString result = ui->nameEdit->text(); + MOBase::fixDirectoryName(result); + return result; +} + + +bool ProfileInputDialog::getPreferDefaultSettings() const +{ + return ui->defaultSettingsBox->checkState() == Qt::Checked; +} + diff --git a/src/profilesdialog.cpp b/src/profilesdialog.cpp index 9955ecd2..69872a24 100644 --- a/src/profilesdialog.cpp +++ b/src/profilesdialog.cpp @@ -150,36 +150,55 @@ void ProfilesDialog::on_copyProfileButton_clicked() { bool okClicked; QString name = QInputDialog::getText(this, tr("Name"), tr("Please enter a name for the new profile"), QLineEdit::Normal, QString(), &okClicked); - if (okClicked && (name.size() > 0)) { - QListWidget *profilesList = findChild("profilesList"); - - try { - const Profile::Ptr currentProfile = profilesList->currentItem()->data(Qt::UserRole).value(); - createProfile(name, *currentProfile); - } catch (const std::exception &e) { - reportError(tr("failed to copy profile: %1").arg(e.what())); + fixDirectoryName(name); + if (okClicked) { + if (name.size() > 0) { + QListWidget *profilesList = findChild("profilesList"); + + try { + const Profile::Ptr currentProfile = profilesList->currentItem()->data(Qt::UserRole).value(); + createProfile(name, *currentProfile); + } catch (const std::exception &e) { + reportError(tr("failed to copy profile: %1").arg(e.what())); + } + } else { + QMessageBox::warning(this, tr("Invalid name"), tr("Invalid profile name")); } } } void ProfilesDialog::on_removeProfileButton_clicked() { - QMessageBox confirmBox(QMessageBox::Question, tr("Confirm"), tr("Are you sure you want to remove this profile?"), + QMessageBox confirmBox(QMessageBox::Question, tr("Confirm"), tr("Are you sure you want to remove this profile (including local savegames if any)?"), QMessageBox::Yes | QMessageBox::No); if (confirmBox.exec() == QMessageBox::Yes) { QListWidget *profilesList = findChild("profilesList"); Profile::Ptr currentProfile = profilesList->currentItem()->data(Qt::UserRole).value(); - - // on destruction, the profile object would write the profile.ini file again, so - // we have to get rid of the it before deleting the directory - QString profilePath = currentProfile->getPath(); + QString profilePath; + if (currentProfile.get() == NULL) { + profilePath = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getProfilesDir())) + "/" + profilesList->currentItem()->text(); + if (QMessageBox::question(this, tr("Profile broken"), + tr("This profile you're about to delete seems to be broken or the path is invalid. " + "I'm about to delete the following folder: \"%1\". Proceed?").arg(profilePath), QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) { + return; + } + } else { + // on destruction, the profile object would write the profile.ini file again, so + // we have to get rid of the it before deleting the directory + profilePath = currentProfile->getPath(); + } QListWidgetItem* item = profilesList->takeItem(profilesList->currentRow()); if (item != NULL) { delete item; } - shellDelete(QStringList(profilePath)); + if (!shellDelete(QStringList(profilePath))) { + qWarning("Failed to shell-delete \"%s\" (errorcode %lu), trying regular delete", qPrintable(profilePath), ::GetLastError()); + if (!removeDir(profilePath)) { + qWarning("regular delete failed too"); + } + } } } diff --git a/src/settings.cpp b/src/settings.cpp index 39b6dba7..42a8d693 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -87,6 +87,19 @@ void Settings::clearPlugins() { m_Plugins.clear(); m_PluginSettings.clear(); + + m_PluginBlacklist.clear(); + int count = m_Settings.beginReadArray("pluginBlacklist"); + for (int i = 0; i < count; ++i) { + m_Settings.setArrayIndex(i); + m_PluginBlacklist.insert(m_Settings.value("name").toString()); + } + m_Settings.endArray(); +} + +bool Settings::pluginBlacklisted(const QString &fileName) const +{ + return m_PluginBlacklist.contains(fileName); } void Settings::registerPlugin(IPlugin *plugin) @@ -275,16 +288,47 @@ QVariant Settings::pluginSetting(const QString &pluginName, const QString &key) { auto iterPlugin = m_PluginSettings.find(pluginName); if (iterPlugin == m_PluginSettings.end()) { - throw MyException(tr("setting for invalid plugin \"%1\" requested").arg(key)); + return QVariant(); } auto iterSetting = iterPlugin->find(key); if (iterSetting == iterPlugin->end()) { - throw MyException(tr("invalid setting \"%1\" requested for plugin \"%2\"").arg(key).arg(pluginName)); + return QVariant(); } return *iterSetting; } +void Settings::setPluginSetting(const QString &pluginName, const QString &key, const QVariant &value) +{ + auto iterPlugin = m_PluginSettings.find(pluginName); + if (iterPlugin == m_PluginSettings.end()) { + throw MyException(tr("attempt to store setting for unknown plugin \"%1\"").arg(pluginName)); + } + + // store the new setting both in memory and in the ini + m_PluginSettings[pluginName][key] = value; + m_Settings.setValue("Plugins/" + pluginName + "/" + key, value); +} + +QVariant Settings::pluginPersistent(const QString &pluginName, const QString &key, const QVariant &def) const +{ + if (!m_PluginSettings.contains(pluginName)) { + return def; + } + return m_Settings.value("PluginPersistance/" + pluginName + "/" + key, def); +} + +void Settings::setPluginPersistent(const QString &pluginName, const QString &key, const QVariant &value, bool sync) +{ + if (!m_PluginSettings.contains(pluginName)) { + throw MyException(tr("attempt to store setting for unknown plugin \"%1\"").arg(pluginName)); + } + m_Settings.setValue("PluginPersistance/" + pluginName + "/" + key, value); + if (sync) { + m_Settings.sync(); + } +} + QString Settings::language() { QString result = m_Settings.value("Settings/language", "").toString(); @@ -330,6 +374,23 @@ void Settings::updateServers(const QList &servers) m_Settings.sync(); } +void Settings::addBlacklistPlugin(const QString &fileName) +{ + m_PluginBlacklist.insert(fileName); + writePluginBlacklist(); +} + +void Settings::writePluginBlacklist() +{ + m_Settings.beginWriteArray("pluginBlacklist"); + int idx = 0; + foreach (const QString &plugin, m_PluginBlacklist) { + m_Settings.setArrayIndex(idx++); + m_Settings.setValue("name", plugin); + } + + m_Settings.endArray(); +} void Settings::addLanguages(QComboBox *languageBox) { @@ -443,6 +504,7 @@ void Settings::query(QWidget *parent) // plugis page QListWidget *pluginsList = dialog.findChild("pluginsList"); + QListWidget *pluginBlacklistList = dialog.findChild("pluginBlacklist"); // workarounds page QCheckBox *forceEnableBox = dialog.findChild("forceEnableBox"); @@ -522,6 +584,7 @@ void Settings::query(QWidget *parent) nmmVersionEdit->setText(m_Settings.value("Settings/nmm_version", "0.33.1").toString()); logLevelBox->setCurrentIndex(logLevel()); + // display plugin settings foreach (IPlugin *plugin, m_Plugins) { QListWidgetItem *listItem = new QListWidgetItem(plugin->name(), pluginsList); listItem->setData(Qt::UserRole, QVariant::fromValue((void*)plugin)); @@ -529,6 +592,12 @@ void Settings::query(QWidget *parent) pluginsList->addItem(listItem); } + // display plugin blacklist + foreach (const QString &pluginName, m_PluginBlacklist) { + pluginBlacklistList->addItem(pluginName); + } + + // display server preferences m_Settings.beginGroup("Servers"); foreach (const QString &key, m_Settings.childKeys()) { QVariantMap val = m_Settings.value(key).toMap(); @@ -624,6 +693,13 @@ void Settings::query(QWidget *parent) } } + // store plugin blacklist + m_PluginBlacklist.clear(); + foreach (QListWidgetItem *item, pluginBlacklistList->findItems("*", Qt::MatchWildcard)) { + m_PluginBlacklist.insert(item->text()); + } + writePluginBlacklist(); + // store server preference m_Settings.beginGroup("Servers"); for (int i = 0; i < knownServersList->count(); ++i) { diff --git a/src/settings.h b/src/settings.h index bf17d162..51ee6b92 100644 --- a/src/settings.h +++ b/src/settings.h @@ -57,6 +57,7 @@ public: /** * @brief register plugin to be configurable * @param plugin the plugin to register + * @return true if the plugin may be registered, false if it is blacklisted */ void registerPlugin(MOBase::IPlugin *plugin); @@ -195,10 +196,37 @@ public: * @param pluginName name of the plugin * @param key name of the setting to retrieve * @return the requested value as a QVariant - * @throws an exception is thrown if this setting doesn't exist + * @note an invalid QVariant is returned if the the plugin/setting is not declared */ QVariant pluginSetting(const QString &pluginName, const QString &key) const; + /** + * @brief set a setting for one of the installed mods + * @param pluginName name of the plugin + * @param key name of the setting to change + * @param value the new value to set + * @throw an exception is thrown if pluginName is invalid + */ + void setPluginSetting(const QString &pluginName, const QString &key, const QVariant &value); + + /** + * @brief retrieve a persistent value for a plugin + * @param pluginName name of the plugin to store data for + * @param key id of the value to retrieve + * @param def default value to return if the value is not set + * @return the requested value + */ + QVariant pluginPersistent(const QString &pluginName, const QString &key, const QVariant &def) const; + + /** + * @brief set a persistent value for a plugin + * @param pluginName name of the plugin to store data for + * @param key id of the value to retrieve + * @param value value to set + * @throw an exception is thrown if pluginName is invalid + */ + void setPluginPersistent(const QString &pluginName, const QString &key, const QVariant &value, bool sync); + /** * @return short code of the configured language (corresponding to the translation files) */ @@ -210,6 +238,19 @@ public: */ void updateServers(const QList &servers); + /** + * @brief add a plugin that is to be blacklisted + * @param fileName name of the plugin to blacklist + */ + void addBlacklistPlugin(const QString &fileName); + + /** + * @brief test if a plugin is blacklisted and shouldn't be loaded + * @param fileName name of the plugin + * @return true if the file is blacklisted + */ + bool pluginBlacklisted(const QString &fileName) const; + private: QString obfuscate(const QString &password) const; @@ -219,6 +260,8 @@ private: void addStyles(QComboBox *styleBox); bool isNXMHandler(bool *modifyable); void setNXMHandlerActive(bool active, bool writable); + void readPluginBlacklist(); + void writePluginBlacklist(); private slots: @@ -241,6 +284,8 @@ private: QMap > m_PluginSettings; + QSet m_PluginBlacklist; + }; #endif // WORKAROUNDS_H diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index 9f268455..c8908ddb 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -21,10 +21,12 @@ along with Mod Organizer. If not, see . #include "ui_settingsdialog.h" #include "categoriesdialog.h" #include "helper.h" +#include "noeditdelegate.h" #include #include #include #include +#include #define WIN32_LEAN_AND_MEAN #include @@ -37,6 +39,9 @@ SettingsDialog::SettingsDialog(QWidget *parent) : TutorableDialog("SettingsDialog", parent), ui(new Ui::SettingsDialog) { ui->setupUi(this); + + QShortcut *delShortcut = new QShortcut(QKeySequence(Qt::Key_Delete), ui->pluginBlacklist); + connect(delShortcut, SIGNAL(activated()), this, SLOT(deleteBlacklistItem())); } SettingsDialog::~SettingsDialog() @@ -146,6 +151,8 @@ void SettingsDialog::on_pluginsList_currentItemChanged(QListWidgetItem *current, for (auto iter = settings.begin(); iter != settings.end(); ++iter) { QTreeWidgetItem *newItem = new QTreeWidgetItem(QStringList(iter.key())); QVariant value = *iter; + + ui->pluginSettingsList->setItemDelegateForColumn(0, new NoEditDelegate()); newItem->setData(1, Qt::DisplayRole, value); newItem->setData(1, Qt::EditRole, value); @@ -153,3 +160,8 @@ void SettingsDialog::on_pluginsList_currentItemChanged(QListWidgetItem *current, ui->pluginSettingsList->addTopLevelItem(newItem); } } + +void SettingsDialog::deleteBlacklistItem() +{ + ui->pluginBlacklist->takeItem(ui->pluginBlacklist->currentIndex().row()); +} diff --git a/src/settingsdialog.h b/src/settingsdialog.h index 3bfc1706..718574a0 100644 --- a/src/settingsdialog.h +++ b/src/settingsdialog.h @@ -73,6 +73,8 @@ private slots: void on_pluginsList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous); + void deleteBlacklistItem(); + private: Ui::SettingsDialog *ui; }; diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui index b63965f9..e7bd0fa3 100644 --- a/src/settingsdialog.ui +++ b/src/settingsdialog.ui @@ -406,102 +406,116 @@ p, li { white-space: pre-wrap; } Plugins - + - - - - 0 - 0 - - - - - - + - - - - - Author: - - - - - - - - - - - - - - Version: - - + + + + 0 + 0 + + + + + + + + + + + + Author: + + + + + + + + + + + + + + Version: + + + + + + + + + + + + + + Description: + + + + + + + + + + true + + + + - - - - + + + + 0 - - - - - - Description: + + false - - - - - - + + false - - true + + false + + false + + + 100 + + + + Key + + + + + Value + + - - - - 0 - - - false - - - false - - - false - - - false - - - 100 - - - - Key - - - - - Value - - - - + + + + Blacklisted Plugins (use <del> to remove): + + + + + + @@ -638,7 +652,7 @@ Please note that MO does identify itself as MO to the webserver, it's not lying tl;dr-version: If Nexus-features don't work, insert the current version number of NMM here and try again. - 009.009.009 + 009.009.009; Qt::AlignCenter diff --git a/src/shared/gameinfo.cpp b/src/shared/gameinfo.cpp index fd5072bf..6b53450d 100644 --- a/src/shared/gameinfo.cpp +++ b/src/shared/gameinfo.cpp @@ -62,36 +62,36 @@ void GameInfo::identifyMyGamesDirectory(const std::wstring &file) } -bool GameInfo::identifyGame(const std::wstring &omoDirectory, const std::wstring &searchPath) +bool GameInfo::identifyGame(const std::wstring &moDirectory, const std::wstring &searchPath) { if (OblivionInfo::identifyGame(searchPath)) { - s_Instance = new OblivionInfo(omoDirectory, searchPath); + s_Instance = new OblivionInfo(moDirectory, searchPath); } else if (Fallout3Info::identifyGame(searchPath)) { - s_Instance = new Fallout3Info(omoDirectory, searchPath); + s_Instance = new Fallout3Info(moDirectory, searchPath); } else if (FalloutNVInfo::identifyGame(searchPath)) { - s_Instance = new FalloutNVInfo(omoDirectory, searchPath); + s_Instance = new FalloutNVInfo(moDirectory, searchPath); } else if (SkyrimInfo::identifyGame(searchPath)) { - s_Instance = new SkyrimInfo(omoDirectory, searchPath); + s_Instance = new SkyrimInfo(moDirectory, searchPath); } return s_Instance != NULL; } -bool GameInfo::init(const std::wstring &omoDirectory, const std::wstring &gamePath) +bool GameInfo::init(const std::wstring &moDirectory, const std::wstring &gamePath) { if (s_Instance == NULL) { if (gamePath.length() == 0) { // search upward in the directory until a recognized game-binary is found - std::wstring searchPath(omoDirectory); - while (!identifyGame(omoDirectory, searchPath)) { + std::wstring searchPath(moDirectory); + while (!identifyGame(moDirectory, searchPath)) { size_t lastSep = searchPath.find_last_of(L"/\\"); if (lastSep == std::string::npos) { return false; } searchPath.erase(lastSep); } - } else if (!identifyGame(omoDirectory, gamePath)) { + } else if (!identifyGame(moDirectory, gamePath)) { return false; } } @@ -204,4 +204,4 @@ std::wstring GameInfo::getSpecialPath(LPCWSTR name) const return temp; } -} // namespace MOShared +} // namespace MOShared diff --git a/src/shared/skyriminfo.cpp b/src/shared/skyriminfo.cpp index 80cf4c80..8c6b79ef 100644 --- a/src/shared/skyriminfo.cpp +++ b/src/shared/skyriminfo.cpp @@ -33,8 +33,8 @@ along with Mod Organizer. If not, see . namespace MOShared { -SkyrimInfo::SkyrimInfo(const std::wstring &omoDirectory, const std::wstring &gameDirectory) - : GameInfo(omoDirectory, gameDirectory) +SkyrimInfo::SkyrimInfo(const std::wstring &moDirectory, const std::wstring &gameDirectory) + : GameInfo(moDirectory, gameDirectory) { identifyMyGamesDirectory(L"skyrim"); diff --git a/src/shared/skyriminfo.h b/src/shared/skyriminfo.h index 4760c38b..c5d31ff0 100644 --- a/src/shared/skyriminfo.h +++ b/src/shared/skyriminfo.h @@ -95,7 +95,7 @@ public: private: - SkyrimInfo(const std::wstring &omoDirectory, const std::wstring &gameDirectory); + SkyrimInfo(const std::wstring &moDirectory, const std::wstring &gameDirectory); static bool identifyGame(const std::wstring &searchPath); diff --git a/src/version.rc b/src/version.rc index 7e988897..16e006cf 100644 --- a/src/version.rc +++ b/src/version.rc @@ -1,7 +1,7 @@ #include "Winver.h" -#define VER_FILEVERSION 1,0,0,1 -#define VER_FILEVERSION_STR "1,0,0,1\0" +#define VER_FILEVERSION 1,0,2,0 +#define VER_FILEVERSION_STR "1,0,2,0\0" VS_VERSION_INFO VERSIONINFO FILEVERSION VER_FILEVERSION -- cgit v1.3.1 From cffd9eb4e21f0ddcddca68d8eb0f1c80c8ac6ae1 Mon Sep 17 00:00:00 2001 From: Tannin Date: Sat, 5 Oct 2013 14:20:48 +0200 Subject: - hook.dll now doesn't inject to certain applications (currently steam, chrome and firefox) - versioning system improved. Will now report "downgrades" for mods and support a different versioning system (requires manual switch) - updates can now be ignored until a new version is uploaded - new splash screen - bugfix: a few memory leaks (shouldn't account for much) - bugfix: result of GetModuleHandle wasn't zero-terminated in some cases --- src/categories.cpp | 7 ++++ src/categories.h | 4 ++ src/downloadmanager.cpp | 1 + src/installationmanager.cpp | 6 +-- src/main.cpp | 97 ++++++++++++++++++++++++-------------------- src/mainwindow.cpp | 87 +++++++++++++++++++++++++++------------ src/mainwindow.h | 4 ++ src/modinfo.cpp | 30 ++++++++++++-- src/modinfo.h | 49 ++++++++++++++++++++++ src/modinfodialog.cpp | 11 +---- src/modlist.cpp | 34 ++++++++++------ src/modlistsortproxy.cpp | 2 +- src/modlistview.cpp | 4 +- src/nexusinterface.cpp | 10 +++++ src/nexusinterface.h | 1 + src/nxmaccessmanager.cpp | 8 ++++ src/nxmaccessmanager.h | 2 + src/organizer.pro | 7 +++- src/pluginlist.cpp | 4 ++ src/pluginlist.h | 2 + src/profile.cpp | 3 +- src/selfupdater.cpp | 2 - src/shared/gameinfo.cpp | 7 ++++ src/shared/gameinfo.h | 4 +- src/shared/leaktrace.cpp | 1 + src/shared/shared.pro | 10 ++++- src/spawn.cpp | 4 +- src/spawn.h | 4 +- src/splash.png | Bin 103422 -> 260807 bytes 29 files changed, 296 insertions(+), 109 deletions(-) (limited to 'src/profile.cpp') diff --git a/src/categories.cpp b/src/categories.cpp index 62ba3ca5..c084c238 100644 --- a/src/categories.cpp +++ b/src/categories.cpp @@ -43,6 +43,7 @@ QString CategoryFactory::categoriesFilePath() CategoryFactory::CategoryFactory() { + atexit(&cleanup); reset(); QFile categoryFile(categoriesFilePath()); @@ -124,6 +125,12 @@ void CategoryFactory::setParents() } } +void CategoryFactory::cleanup() +{ + delete s_Instance; + s_Instance = NULL; +} + void CategoryFactory::saveCategories() { diff --git a/src/categories.h b/src/categories.h index 75698149..31fccd6d 100644 --- a/src/categories.h +++ b/src/categories.h @@ -180,6 +180,8 @@ private: void setParents(); + static void cleanup(); + private: static CategoryFactory *s_Instance; @@ -188,6 +190,8 @@ private: std::map m_IDMap; std::map m_NexusMap; +private: + }; diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index bbb34ccc..b965d598 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -162,6 +162,7 @@ DownloadManager::~DownloadManager() for (QVector::iterator iter = m_ActiveDownloads.begin(); iter != m_ActiveDownloads.end(); ++iter) { delete *iter; } + m_ActiveDownloads.clear(); } diff --git a/src/installationmanager.cpp b/src/installationmanager.cpp index 378eb38a..87efecf1 100644 --- a/src/installationmanager.cpp +++ b/src/installationmanager.cpp @@ -46,6 +46,7 @@ along with Mod Organizer. If not, see . #include #include #include +#include using namespace MOBase; @@ -678,8 +679,7 @@ bool InstallationManager::install(const QString &fileName, GuessedValue this->m_CurrentArchive->close(); }); - DirectoryTree *filesTree = archiveOpen ? createFilesTree() : NULL; - + QScopedPointer filesTree(archiveOpen ? createFilesTree() : NULL); IPluginInstaller::EInstallResult installResult = IPluginInstaller::RESULT_NOTATTEMPTED; std::sort(m_Installers.begin(), m_Installers.end(), [] (IPluginInstaller *LHS, IPluginInstaller *RHS) { @@ -704,7 +704,7 @@ bool InstallationManager::install(const QString &fileName, GuessedValue (filesTree != NULL) && (installer->isArchiveSupported(*filesTree))) { installResult = installerSimple->install(modName, *filesTree, version, modID); if (installResult == IPluginInstaller::RESULT_SUCCESS) { - mapToArchive(filesTree); + mapToArchive(filesTree.data()); // the simple installer only prepares the installation, the rest works the same for all installers if (!doInstall(modName, modID, version, newestVersion, categoryID)) { installResult = IPluginInstaller::RESULT_FAILED; diff --git a/src/main.cpp b/src/main.cpp index 5a694121..c4431ac4 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -18,6 +18,11 @@ along with Mod Organizer. If not, see . */ +#ifdef LEAK_CHECK_WITH_VLD +#include +#include +#endif // LEAK_CHECK_WITH_VLD + #include #include #include @@ -430,61 +435,65 @@ int main(int argc, char *argv[]) application.setStyleFile(settings.value("Settings/style", "").toString()); - // set up main window and its data structures - MainWindow mainWindow(argv[0], settings); - QObject::connect(&mainWindow, SIGNAL(styleChanged(QString)), &application, SLOT(setStyleFile(QString))); - QObject::connect(&instance, SIGNAL(messageSent(QString)), &mainWindow, SLOT(externalMessage(QString))); + int res = 1; + { // scope to control lifetime of mainwindow + // set up main window and its data structures + MainWindow mainWindow(argv[0], settings); + QObject::connect(&mainWindow, SIGNAL(styleChanged(QString)), &application, SLOT(setStyleFile(QString))); + QObject::connect(&instance, SIGNAL(messageSent(QString)), &mainWindow, SLOT(externalMessage(QString))); - mainWindow.setExecutablesList(executablesList); - mainWindow.readSettings(); + mainWindow.setExecutablesList(executablesList); + mainWindow.readSettings(); - QString selectedProfileName = QString::fromUtf8(settings.value("selected_profile", "").toByteArray()); + QString selectedProfileName = QString::fromUtf8(settings.value("selected_profile", "").toByteArray()); - { // see if there is a profile on the command line - int profileIndex = arguments.indexOf("-p", 1); - if ((profileIndex != -1) && (profileIndex < arguments.size() - 1)) { - qDebug("profile overwritten on command line"); - selectedProfileName = arguments.at(profileIndex + 1); + { // see if there is a profile on the command line + int profileIndex = arguments.indexOf("-p", 1); + if ((profileIndex != -1) && (profileIndex < arguments.size() - 1)) { + qDebug("profile overwritten on command line"); + selectedProfileName = arguments.at(profileIndex + 1); + } + arguments.removeAt(profileIndex); + arguments.removeAt(profileIndex); + } + qDebug("configured profile: %s", qPrintable(selectedProfileName)); + + // if we have a command line parameter, it is either a nxm link or + // a binary to start + if ((arguments.size() > 1) && (!isNxmLink(arguments.at(1)))) { + QString exeName = arguments.at(1); + qDebug("starting %s from command line", qPrintable(exeName)); + arguments.removeFirst(); // remove application name (ModOrganizer.exe) + arguments.removeFirst(); // remove binary name + // pass the remaining parameters to the binary + mainWindow.spawnProgram(exeName, arguments.join(" "), selectedProfileName, QDir()); + return 0; } - arguments.removeAt(profileIndex); - arguments.removeAt(profileIndex); - } - qDebug("configured profile: %s", qPrintable(selectedProfileName)); - - // if we have a command line parameter, it is either a nxm link or - // a binary to start - if ((arguments.size() > 1) && (!isNxmLink(arguments.at(1)))) { - QString exeName = arguments.at(1); - qDebug("starting %s from command line", qPrintable(exeName)); - arguments.removeFirst(); // remove application name (ModOrganizer.exe) - arguments.removeFirst(); // remove binary name - // pass the remaining parameters to the binary - mainWindow.spawnProgram(exeName, arguments.join(" "), selectedProfileName, QDir()); - return 0; - } - mainWindow.createFirstProfile(); + mainWindow.createFirstProfile(); - if (selectedProfileName.length() != 0) { - if (!mainWindow.setCurrentProfile(selectedProfileName)) { + if (selectedProfileName.length() != 0) { + if (!mainWindow.setCurrentProfile(selectedProfileName)) { + mainWindow.setCurrentProfile(1); + qWarning("failed to set profile: %s", + selectedProfileName.toUtf8().constData()); + } + } else { mainWindow.setCurrentProfile(1); - qWarning("failed to set profile: %s", - selectedProfileName.toUtf8().constData()); } - } else { - mainWindow.setCurrentProfile(1); - } - qDebug("displaying main window"); - mainWindow.show(); + qDebug("displaying main window"); + mainWindow.show(); - if ((arguments.size() > 1) && - (isNxmLink(arguments.at(1)))) { - qDebug("starting download from command line: %s", qPrintable(arguments.at(1))); - mainWindow.externalMessage(arguments.at(1)); + if ((arguments.size() > 1) && + (isNxmLink(arguments.at(1)))) { + qDebug("starting download from command line: %s", qPrintable(arguments.at(1))); + mainWindow.externalMessage(arguments.at(1)); + } + splash.finish(&mainWindow); + res = application.exec(); } - splash.finish(&mainWindow); - return application.exec(); + return res; } catch (const std::exception &e) { reportError(e.what()); return 1; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 24772db1..1ea35d74 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -139,8 +139,8 @@ MainWindow::MainWindow(const QString &exeName, QSettings &initSettings, QWidget : QMainWindow(parent), ui(new Ui::MainWindow), m_Tutorial(this, "MainWindow"), m_ExeName(exeName), m_OldProfileIndex(-1), m_DirectoryStructure(new DirectoryEntry(L"data", NULL, 0)), - m_ModList(NexusInterface::instance()), m_ModListGroupingProxy(NULL), m_ModListSortProxy(NULL), - m_OldExecutableIndex(-1), m_GamePath(ToQString(GameInfo::instance().getGameDirectory())), + m_ModList(this), m_ModListGroupingProxy(NULL), m_ModListSortProxy(NULL), + m_PluginList(this), m_OldExecutableIndex(-1), m_GamePath(ToQString(GameInfo::instance().getGameDirectory())), m_DownloadManager(NexusInterface::instance(), this), m_InstallationManager(this), m_Translator(NULL), m_TranslatorQt(NULL), m_Updater(NexusInterface::instance(), this), m_CategoryFactory(CategoryFactory::instance()), @@ -149,7 +149,7 @@ MainWindow::MainWindow(const QString &exeName, QSettings &initSettings, QWidget m_GameInfo(new GameInfoImpl()) { ui->setupUi(this); - this->setWindowTitle(ToQString(GameInfo::instance().getGameName()).append(" Mod Organizer v").append(m_Updater.getVersion().canonicalString())); + this->setWindowTitle(ToQString(GameInfo::instance().getGameName()) + " Mod Organizer v" + m_Updater.getVersion().displayString()); m_RefreshProgress = new QProgressBar(statusBar()); m_RefreshProgress->setTextVisible(true); @@ -255,7 +255,7 @@ MainWindow::MainWindow(const QString &exeName, QSettings &initSettings, QWidget connect(&m_Updater, SIGNAL(updateAvailable()), this, SLOT(updateAvailable())); connect(&m_Updater, SIGNAL(motdAvailable(QString)), this, SLOT(motdReceived(QString))); - connect(ExitProxy::instance(), SIGNAL(exit()), this, SLOT(close())); +// connect(ExitProxy::instance(), SIGNAL(exit()), this, SLOT(close())); connect(NexusInterface::instance()->getAccessManager(), SIGNAL(loginSuccessful(bool)), this, SLOT(loginSuccessful(bool))); connect(NexusInterface::instance()->getAccessManager(), SIGNAL(loginFailed(QString)), this, SLOT(loginFailed(QString))); @@ -412,7 +412,7 @@ void MainWindow::actionToToolButton(QAction *&sourceAction) button->setToolButtonStyle(ui->toolBar->toolButtonStyle()); button->setToolTip(sourceAction->toolTip()); button->setShortcut(sourceAction->shortcut()); - QMenu *buttonMenu = new QMenu(sourceAction->text()); + QMenu *buttonMenu = new QMenu(sourceAction->text(), button); button->setMenu(buttonMenu); QAction *newAction = ui->toolBar->insertWidget(sourceAction, button); newAction->setObjectName(sourceAction->objectName()); @@ -1499,6 +1499,8 @@ void MainWindow::updateTo(QTreeWidgetItem *subTree, const std::wstring &director updateTo(directoryChild, temp.str(), **current, conflictsOnly); if (directoryChild->childCount() != 0) { subTree->addChild(directoryChild); + } else { + delete directoryChild; } } } @@ -3069,7 +3071,6 @@ void MainWindow::openExplorer_clicked() ::ShellExecuteW(NULL, L"explore", ToWString(modInfo->absolutePath()).c_str(), NULL, NULL, SW_SHOWNORMAL); } - void MainWindow::information_clicked() { try { @@ -3079,7 +3080,6 @@ void MainWindow::information_clicked() } } - void MainWindow::syncOverwrite() { ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow); @@ -3091,7 +3091,6 @@ void MainWindow::syncOverwrite() } } - void MainWindow::createModFromOverwrite() { GuessedValue name; @@ -3125,14 +3124,12 @@ void MainWindow::createModFromOverwrite() refreshModList(); } - void MainWindow::cancelModListEditor() { ui->modList->setEnabled(false); ui->modList->setEnabled(true); } - void MainWindow::on_modList_doubleClicked(const QModelIndex &index) { if (!index.isValid()) { @@ -3155,7 +3152,6 @@ void MainWindow::on_modList_doubleClicked(const QModelIndex &index) } } - bool MainWindow::addCategories(QMenu *menu, int targetID) { ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow); @@ -3210,7 +3206,6 @@ void MainWindow::saveCategoriesFromMenu(QMenu *menu, int modRow) } } - void MainWindow::saveCategories() { QMenu *menu = qobject_cast(sender()); @@ -3254,8 +3249,6 @@ void MainWindow::saveCategories() refreshFilters(); } - - void MainWindow::savePrimaryCategory() { QMenu *menu = qobject_cast(sender()); @@ -3280,7 +3273,6 @@ void MainWindow::savePrimaryCategory() } } - void MainWindow::checkModsForUpdates() { statusBar()->show(); @@ -3298,6 +3290,45 @@ void MainWindow::checkModsForUpdates() } } +void MainWindow::changeVersioningScheme() { + if (QMessageBox::question(this, tr("Continue?"), + tr("This will try to change the versioning scheme so that the newest version is interpreted as an update to " + "the installed version."), + QMessageBox::Yes | QMessageBox::Cancel) == QMessageBox::Yes) { + + ModInfo::Ptr info = ModInfo::getByIndex(m_ContextRow); + + bool success = false; + + static VersionInfo::VersionScheme schemes[] = { VersionInfo::SCHEME_REGULAR, VersionInfo::SCHEME_DECIMALMARK, VersionInfo::SCHEME_NUMBERSANDLETTERS }; + + for (int i = 0; i < sizeof(schemes) / sizeof(VersionInfo::VersionScheme) && !success; ++i) { + VersionInfo verOld(info->getVersion().canonicalString(), schemes[i]); + VersionInfo verNew(info->getNewestVersion().canonicalString(), schemes[i]); + if (verOld < verNew) { + info->setVersion(verOld); + info->setNewestVersion(verNew); + success = true; + } + } + if (!success) { + QMessageBox::information(this, tr("Sorry"), + tr("I don't know a versioning scheme where %1 is newer than %2.").arg(info->getNewestVersion().canonicalString()).arg(info->getVersion().canonicalString()), + QMessageBox::Ok); + } + } +} + +void MainWindow::ignoreUpdate() { + ModInfo::Ptr info = ModInfo::getByIndex(m_ContextRow); + info->ignoreUpdate(true); +} + +void MainWindow::unignoreUpdate() +{ + ModInfo::Ptr info = ModInfo::getByIndex(m_ContextRow); + info->ignoreUpdate(false); +} void MainWindow::addPrimaryCategoryCandidates(QMenu *primaryCategoryMenu, ModInfo::Ptr info) { @@ -3319,7 +3350,6 @@ void MainWindow::addPrimaryCategoryCandidates(QMenu *primaryCategoryMenu, ModInf } } - void MainWindow::addPrimaryCategoryCandidates() { QMenu *menu = qobject_cast(sender()); @@ -3333,7 +3363,6 @@ void MainWindow::addPrimaryCategoryCandidates() addPrimaryCategoryCandidates(menu, modInfo); } - void MainWindow::enableVisibleMods() { if (QMessageBox::question(NULL, tr("Confirm"), tr("Really enable all visible mods?"), @@ -3342,7 +3371,6 @@ void MainWindow::enableVisibleMods() } } - void MainWindow::disableVisibleMods() { if (QMessageBox::question(NULL, tr("Confirm"), tr("Really disable all visible mods?"), @@ -3351,7 +3379,6 @@ void MainWindow::disableVisibleMods() } } - void MainWindow::exportModListCSV() { SelectionDialog selection(tr("Choose what to export")); @@ -3406,7 +3433,6 @@ void MainWindow::exportModListCSV() } } - void addMenuAsPushButton(QMenu *menu, QMenu *subMenu) { QPushButton *pushBtn = new QPushButton(subMenu->title()); @@ -3416,13 +3442,13 @@ void addMenuAsPushButton(QMenu *menu, QMenu *subMenu) menu->addAction(action); } - void MainWindow::on_modList_customContextMenuRequested(const QPoint &pos) { try { QTreeView *modList = findChild("modList"); - m_ContextRow = mapToModel(&m_ModList, modList->indexAt(pos)).row(); + QModelIndex index = mapToModel(&m_ModList, modList->indexAt(pos)); + m_ContextRow = index.row(); QMenu menu; @@ -3461,6 +3487,17 @@ void MainWindow::on_modList_customContextMenuRequested(const QPoint &pos) connect(primaryCategoryMenu, SIGNAL(aboutToHide()), this, SLOT(savePrimaryCategory())); addMenuAsPushButton(&menu, primaryCategoryMenu); + menu.addSeparator(); + if (info->downgradeAvailable()) { + menu.addAction(tr("Change versioning scheme"), this, SLOT(changeVersioningScheme())); + } + if (info->updateIgnored()) { + menu.addAction(tr("Un-ignore update"), this, SLOT(unignoreUpdate())); + } else { + menu.addAction(tr("Ignore update"), this, SLOT(ignoreUpdate())); + } + menu.addSeparator(); + menu.addAction(tr("Rename Mod..."), this, SLOT(renameMod_clicked())); menu.addAction(tr("Remove Mod..."), this, SLOT(removeMod_clicked())); menu.addAction(tr("Reinstall Mod"), this, SLOT(reinstallMod_clicked())); @@ -4207,9 +4244,9 @@ void MainWindow::on_actionEndorseMO_triggered() void MainWindow::updateDownloadListDelegate() { if (ui->compactBox->isChecked()) { - ui->downloadView->setItemDelegate(new DownloadListWidgetCompactDelegate(&m_DownloadManager, ui->downloadView)); + ui->downloadView->setItemDelegate(new DownloadListWidgetCompactDelegate(&m_DownloadManager, ui->downloadView, ui->downloadView)); } else { - ui->downloadView->setItemDelegate(new DownloadListWidgetDelegate(&m_DownloadManager, ui->downloadView)); + ui->downloadView->setItemDelegate(new DownloadListWidgetDelegate(&m_DownloadManager, ui->downloadView, ui->downloadView)); } DownloadListSortProxy *sortProxy = new DownloadListSortProxy(&m_DownloadManager, ui->downloadView); @@ -4267,7 +4304,7 @@ void MainWindow::nxmUpdatesAvailable(const std::vector &modIDs, QVariant us } else { std::vector info = ModInfo::getByModID(result["id"].toInt()); for (auto iter = info.begin(); iter != info.end(); ++iter) { - (*iter)->setNewestVersion(VersionInfo(result["version"].toString())); + (*iter)->setNewestVersion(result["version"].toString()); (*iter)->setNexusDescription(result["description"].toString()); if (NexusInterface::instance()->getAccessManager()->loggedIn() && result.contains("voted_by_user")) { diff --git a/src/mainwindow.h b/src/mainwindow.h index 80dca172..dfec5f49 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -466,6 +466,10 @@ private slots: void removeFromToolbar(); void overwriteClosed(int); + void changeVersioningScheme(); + void ignoreUpdate(); + void unignoreUpdate(); + private slots: // ui slots // actions void on_actionAdd_Profile_triggered(); diff --git a/src/modinfo.cpp b/src/modinfo.cpp index 7632a86e..1479f7b4 100644 --- a/src/modinfo.cpp +++ b/src/modinfo.cpp @@ -303,10 +303,11 @@ ModInfoRegular::ModInfoRegular(const QDir &path, DirectoryEntry **directoryStruc QString metaFileName = path.absoluteFilePath("meta.ini"); QSettings metaFile(metaFileName, QSettings::IniFormat); - m_Notes = metaFile.value("notes", "").toString(); - m_NexusID = metaFile.value("modid", -1).toInt(); + m_Notes = metaFile.value("notes", "").toString(); + m_NexusID = metaFile.value("modid", -1).toInt(); m_Version.parse(metaFile.value("version", "").toString()); - m_NewestVersion = metaFile.value("newestVersion", "").toString(); + m_NewestVersion = metaFile.value("newestVersion", "").toString(); + m_IgnoredVersion = metaFile.value("ignoredVersion", "").toString(); m_InstallationFile = metaFile.value("installationFile", "").toString(); m_NexusDescription = metaFile.value("nexusDescription", "").toString(); m_LastNexusQuery = QDateTime::fromString(metaFile.value("lastNexusQuery", "").toString(), Qt::ISODate); @@ -377,6 +378,7 @@ void ModInfoRegular::saveMeta() temp.erase(m_PrimaryCategory); metaFile.setValue("category", QString("%1").arg(m_PrimaryCategory) + "," + SetJoin(temp, ",")); metaFile.setValue("newestVersion", m_NewestVersion.canonicalString()); + metaFile.setValue("ignoredVersion", m_IgnoredVersion.canonicalString()); metaFile.setValue("version", m_Version.canonicalString()); metaFile.setValue("modid", m_NexusID); metaFile.setValue("notes", m_Notes); @@ -396,10 +398,22 @@ void ModInfoRegular::saveMeta() bool ModInfoRegular::updateAvailable() const { + if (m_IgnoredVersion.isValid() && (m_IgnoredVersion == m_NewestVersion)) { + return false; + } return m_NewestVersion.isValid() && (m_Version < m_NewestVersion); } +bool ModInfoRegular::downgradeAvailable() const +{ + if (m_IgnoredVersion.isValid() && (m_IgnoredVersion == m_NewestVersion)) { + return false; + } + return m_NewestVersion.isValid() && (m_NewestVersion < m_Version); +} + + void ModInfoRegular::nxmDescriptionAvailable(int, QVariant, QVariant resultData) { QVariantMap result = resultData.toMap(); @@ -576,6 +590,16 @@ QString ModInfoRegular::absolutePath() const return m_Path; } +void ModInfoRegular::ignoreUpdate(bool ignore) +{ + if (ignore) { + m_IgnoredVersion = m_NewestVersion; + } else { + m_IgnoredVersion.clear(); + } + m_MetaInfoChanged = true; +} + std::vector ModInfoRegular::getFlags() const { diff --git a/src/modinfo.h b/src/modinfo.h index 7e217de7..3b83d207 100644 --- a/src/modinfo.h +++ b/src/modinfo.h @@ -192,6 +192,22 @@ public: **/ virtual bool updateAvailable() const = 0; + /** + * @return true if the update currently available is ignored + */ + virtual bool updateIgnored() const = 0; + + /** + * @brief test if the "newest" version of the mod is older than the installed version + * + * test if there is a newer version of the mod. This does NOT cause + * information to be retrieved from the nexus, it will only test version information already + * available locally. Use checkAllForUpdate() to update this version information + * + * @return true if the newest version is older than the installed one + **/ + virtual bool downgradeAvailable() const = 0; + /** * @brief request an update of nexus description for this mod. * @@ -309,6 +325,11 @@ public: **/ virtual MOBase::VersionInfo getNewestVersion() const = 0; + /** + * @brief ignore the newest version for updates + */ + virtual void ignoreUpdate(bool ignore) = 0; + /** * @brief getter for the nexus mod id * @@ -494,6 +515,22 @@ public: **/ bool updateAvailable() const; + /** + * @return true if the current update is being ignored + */ + virtual bool updateIgnored() const { return m_IgnoredVersion == m_NewestVersion; } + + /** + * @brief test if there is a newer version of the mod + * + * test if there is a newer version of the mod. This does NOT cause + * information to be retrieved from the nexus, it will only test version information already + * available locally. Use checkAllForUpdate() to update this version information + * + * @return true if there is a newer version + **/ + bool downgradeAvailable() const; + /** * @brief request an update of nexus description for this mod. * @@ -620,6 +657,11 @@ public: **/ MOBase::VersionInfo getNewestVersion() const { return m_NewestVersion; } + /** + * @brief ignore the newest version for updates + */ + void ignoreUpdate(bool ignore); + /** * @brief getter for the installation file * @@ -746,6 +788,7 @@ private: bool m_MetaInfoChanged; MOBase::VersionInfo m_NewestVersion; + MOBase::VersionInfo m_IgnoredVersion; EEndorsedState m_EndorsedState; @@ -767,10 +810,13 @@ class ModInfoBackup : public ModInfoRegular public: virtual bool updateAvailable() const { return false; } + virtual bool updateIgnored() const { return false; } + virtual bool downgradeAvailable() const { return false; } virtual bool updateNXMInfo() { return false; } virtual void setNexusID(int) {} virtual void endorse(bool) {} virtual int getFixedPriority() const { return -1; } + virtual void ignoreUpdate(bool) {} virtual bool canBeUpdated() const { return false; } virtual bool canBeEnabled() const { return false; } virtual std::vector getIniTweaks() const { return std::vector(); } @@ -798,12 +844,15 @@ class ModInfoOverwrite : public ModInfo public: virtual bool updateAvailable() const { return false; } + virtual bool updateIgnored() const { return false; } + virtual bool downgradeAvailable() const { return false; } virtual bool updateNXMInfo() { return false; } virtual void setCategory(int, bool) {} virtual bool setName(const QString&) { return false; } virtual void setNotes(const QString&) {} virtual void setNexusID(int) {} virtual void setNewestVersion(const MOBase::VersionInfo&) {} + virtual void ignoreUpdate(bool) {} virtual void setNexusDescription(const QString&) {} virtual void setIsEndorsed(bool) {} virtual void setNeverEndorse() {} diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 75e7e499..b4ae57ea 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -76,7 +76,6 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo m_UTF8Codec = QTextCodec::codecForName("utf-8"); QListWidget *textFileList = findChild("textFileList"); - QListWidget *iniFileList = findChild("iniFileList"); QListWidget *iniTweaksList = findChild("iniTweaksList"); QListWidget *activeESPList = findChild("activeESPList"); QListWidget *inactiveESPList = findChild("inactiveESPList"); @@ -707,7 +706,7 @@ QString ModInfoDialog::getFileCategory(int categoryID) void ModInfoDialog::updateVersionColor() { // QPalette versionColor; - if (m_ModInfo->getVersion() < m_ModInfo->getNewestVersion()) { + if (m_ModInfo->getVersion() != m_ModInfo->getNewestVersion()) { ui->versionEdit->setStyleSheet("color: red"); // versionColor.setColor(QPalette::Text, Qt::red); ui->versionEdit->setToolTip(tr("Current Version: %1").arg(m_ModInfo->getNewestVersion().canonicalString())); @@ -758,13 +757,7 @@ void ModInfoDialog::modDetailsUpdated(bool success) ui->descriptionView->setHtml(tr("(description incomplete, please visit nexus)")); } - QString version = m_ModInfo->getNewestVersion().canonicalString(); - - if (!version.isEmpty()) { - m_ModInfo->setNewestVersion(version); - - updateVersionColor(); - } + updateVersionColor(); } } diff --git a/src/modlist.cpp b/src/modlist.cpp index b5a7182e..7367b2ae 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -143,13 +143,16 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const } else if (column == COL_NAME) { return modInfo->name(); } else if (column == COL_VERSION) { - QString version = modInfo->getVersion().canonicalString(); - if (version.isEmpty() && modInfo->canBeUpdated()) { - version = "?"; - } else if (version[0] == 'd') { - version.remove(0, 1); + VersionInfo verInfo = modInfo->getVersion(); + if (role == Qt::EditRole) { + return verInfo.canonicalString(); + } else { + QString version = verInfo.displayString(); + if (version.isEmpty() && modInfo->canBeUpdated()) { + version = "?"; + } + return version; } - return version; } else if (column == COL_PRIORITY) { int priority = modInfo->getFixedPriority(); if (priority != INT_MIN) { @@ -241,17 +244,18 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const result.setItalic(true); } } else if (column == COL_VERSION) { - if (modInfo->updateAvailable()) { + if (modInfo->updateAvailable() || modInfo->downgradeAvailable()) { result.setWeight(QFont::Bold); } } return result; } else if (role == Qt::DecorationRole) { if (column == COL_VERSION) { - if (modInfo->updateAvailable() && - modInfo->getNewestVersion().isValid()) { + if (modInfo->updateAvailable()) { return QIcon(":/MO/gui/update_available"); - } else if (modInfo->getVersion().isVersionDate()) { + } else if (modInfo->downgradeAvailable()) { + return QIcon(":/MO/gui/warning"); + } else if (modInfo->getVersion().scheme() == VersionInfo::SCHEME_DATE) { return QIcon(":/MO/gui/version_date"); } } @@ -264,7 +268,7 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const } else if (column == COL_VERSION) { if (!modInfo->getNewestVersion().isValid()) { return QVariant(); - } else if (modInfo->updateAvailable()) { + } else if (modInfo->updateAvailable() || modInfo->downgradeAvailable()) { return QBrush(Qt::red); } else { return QBrush(Qt::darkGreen); @@ -291,7 +295,13 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const return QString(); } } else if (column == COL_VERSION) { - return tr("installed version: %1, newest version: %2").arg(modInfo->getVersion().canonicalString()).arg(modInfo->getNewestVersion().canonicalString()); + QString text = tr("installed version: %1, newest version: %2").arg(modInfo->getVersion().displayString()).arg(modInfo->getNewestVersion().displayString()); + if (modInfo->downgradeAvailable()) { + text += "
" + tr("The newest version on Nexus seems to be older than the one you have installed. This could either mean the version you have has been withdrawn " + "(i.e. due to a bug) or the author uses a non-standard versioning scheme and that newest version is actually newer. " + "Either way you may want to \"upgrade\"."); + } + return text; } else if (column == COL_CATEGORY) { const std::set &categories = modInfo->getCategories(); std::wostringstream categoryString; diff --git a/src/modlistsortproxy.cpp b/src/modlistsortproxy.cpp index 53c03c6a..c7c3ab28 100644 --- a/src/modlistsortproxy.cpp +++ b/src/modlistsortproxy.cpp @@ -211,7 +211,7 @@ bool ModListSortProxy::filterMatches(ModInfo::Ptr info, bool enabled) const if (enabled) return false; } break; case CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE: { - if (!info->updateAvailable()) return false; + if (!info->updateAvailable() && !info->downgradeAvailable()) return false; } break; case CategoryFactory::CATEGORY_SPECIAL_NOCATEGORY: { if (info->getCategories().size() > 0) return false; diff --git a/src/modlistview.cpp b/src/modlistview.cpp index 44228d5b..1a4f6266 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -4,7 +4,7 @@ #include -class ModListViewStyle: public QProxyStyle{ +class ModListViewStyle: public QProxyStyle { public: ModListViewStyle(QStyle *style, int indentation); @@ -37,7 +37,7 @@ void ModListViewStyle::drawPrimitive(PrimitiveElement element, const QStyleOptio ModListView::ModListView(QWidget *parent) : QTreeView(parent) { - setStyle(new ModListViewStyle(style(), indentation())); +// setStyle(new ModListViewStyle(style(), indentation())); } void ModListView::dragEnterEvent(QDragEnterEvent *event) diff --git a/src/nexusinterface.cpp b/src/nexusinterface.cpp index cd78ca4c..bee0bb44 100644 --- a/src/nexusinterface.cpp +++ b/src/nexusinterface.cpp @@ -68,6 +68,7 @@ void NexusBridge::nxmDescriptionAvailable(int modID, QVariant userData, QVariant std::set::iterator iter = m_RequestIDs.find(requestID); if (iter != m_RequestIDs.end()) { m_RequestIDs.erase(iter); + emit descriptionAvailable(modID, userData, resultData); } } @@ -142,6 +143,7 @@ QAtomicInt NexusInterface::NXMRequestInfo::s_NextID(0); NexusInterface::NexusInterface() : m_NMMVersion() { + atexit(&cleanup); m_AccessManager = new NXMAccessManager(this); m_DiskCache = new QNetworkDiskCache(this); @@ -149,6 +151,13 @@ NexusInterface::NexusInterface() } +void NexusInterface::cleanup() +{ + delete NexusInterface::s_Instance; + NexusInterface::s_Instance = NULL; +} + + NXMAccessManager *NexusInterface::getAccessManager() { return m_AccessManager; @@ -405,6 +414,7 @@ void NexusInterface::downloadRequestedNXM(const QString &url) emit requestNXMDownload(url); } +#include void NexusInterface::requestFinished(std::list::iterator iter) { diff --git a/src/nexusinterface.h b/src/nexusinterface.h index 0c304a71..0b53f9d0 100644 --- a/src/nexusinterface.h +++ b/src/nexusinterface.h @@ -294,6 +294,7 @@ private: NexusInterface(); void nextRequest(); void requestFinished(std::list::iterator iter); + static void cleanup(); private: diff --git a/src/nxmaccessmanager.cpp b/src/nxmaccessmanager.cpp index d83ffa61..a05b8a6c 100644 --- a/src/nxmaccessmanager.cpp +++ b/src/nxmaccessmanager.cpp @@ -44,6 +44,14 @@ NXMAccessManager::NXMAccessManager(QObject *parent) { } +NXMAccessManager::~NXMAccessManager() +{ + if (m_LoginReply != NULL) { + m_LoginReply->deleteLater(); + m_LoginReply = NULL; + } +} + QNetworkReply *NXMAccessManager::createRequest( QNetworkAccessManager::Operation operation, const QNetworkRequest &request, diff --git a/src/nxmaccessmanager.h b/src/nxmaccessmanager.h index b0c43978..1d8d36ab 100644 --- a/src/nxmaccessmanager.h +++ b/src/nxmaccessmanager.h @@ -37,6 +37,8 @@ public: explicit NXMAccessManager(QObject *parent); + ~NXMAccessManager(); + bool loggedIn() const; void login(const QString &username, const QString &password); diff --git a/src/organizer.pro b/src/organizer.pro index e89b6e99..a9353361 100644 --- a/src/organizer.pro +++ b/src/organizer.pro @@ -183,7 +183,6 @@ INCLUDEPATH += ../shared ../archive ../uibase ../bsatk ../esptk "$(BOOSTPATH)" LIBS += -L"$(BOOSTPATH)/stage/lib" - CONFIG(debug, debug|release) { OUTDIR = $$OUT_PWD/debug DSTDIR = $$PWD/../../outputd @@ -294,6 +293,12 @@ OTHER_FILES += \ INCLUDEPATH += "$(ZLIBPATH)" "$(ZLIBPATH)/build" "$(BOOSTPATH)" LIBS += -L"$(ZLIBPATH)/build" -lzlibstatic + +# leak detection with vld +#INCLUDEPATH += "E:/Visual Leak Detector/include" +#LIBS += -L"E:/Visual Leak Detector/lib/Win32" +#DEFINES += LEAK_CHECK_WITH_VLD + #SOURCES += modeltest.cpp #HEADERS += modeltest.h #DEFINES += TEST_MODELS diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp index 39cca6af..e730d3f4 100644 --- a/src/pluginlist.cpp +++ b/src/pluginlist.cpp @@ -90,6 +90,10 @@ PluginList::PluginList(QObject *parent) } +PluginList::~PluginList() +{ +} + QString PluginList::getColumnName(int column) { diff --git a/src/pluginlist.h b/src/pluginlist.h index 3442a9f7..60c9be95 100644 --- a/src/pluginlist.h +++ b/src/pluginlist.h @@ -53,6 +53,8 @@ public: **/ PluginList(QObject *parent = NULL); + ~PluginList(); + /** * @brief does a complete refresh of the list * diff --git a/src/profile.cpp b/src/profile.cpp index 07f7010e..3da12d7d 100644 --- a/src/profile.cpp +++ b/src/profile.cpp @@ -185,7 +185,7 @@ void Profile::writeModlistNow(bool onlyOnTimer) const QString fileName = getModlistFileName(); if (QFile::exists(fileName)) { - shellDelete(QStringList(fileName)); + shellDelete(QStringList(fileName), false, QApplication::activeModalWidget()); } if (!file.copy(fileName)) { @@ -238,6 +238,7 @@ void Profile::createTweakedIniFile() if (error) { reportError(tr("failed to create tweaked ini: %1").arg(getCurrentErrorStringA().c_str())); } + qDebug("%s saved", qPrintable(QDir::toNativeSeparators(tweakedIni))); } diff --git a/src/selfupdater.cpp b/src/selfupdater.cpp index 421fbeab..bdace814 100644 --- a/src/selfupdater.cpp +++ b/src/selfupdater.cpp @@ -126,7 +126,6 @@ void SelfUpdater::startUpdate() void SelfUpdater::download(const QString &downloadLink, const QString &fileName) { - qDebug("download: %s", downloadLink.toUtf8().constData()); QNetworkAccessManager *accessManager = m_Interface->getAccessManager(); QUrl dlUrl(downloadLink); QNetworkRequest request(dlUrl); @@ -324,7 +323,6 @@ void SelfUpdater::nxmDescriptionAvailable(int, QVariant, QVariant resultData, in if (m_NewestVersion.isEmpty()) { QTimer::singleShot(5000, this, SLOT(testForUpdate())); } - VersionInfo currentVersion(m_MOVersion); VersionInfo newestVersion(m_NewestVersion); diff --git a/src/shared/gameinfo.cpp b/src/shared/gameinfo.cpp index 6b53450d..f74e52b4 100644 --- a/src/shared/gameinfo.cpp +++ b/src/shared/gameinfo.cpp @@ -40,6 +40,13 @@ GameInfo* GameInfo::s_Instance = NULL; GameInfo::GameInfo(const std::wstring &omoDirectory, const std::wstring &gameDirectory) : m_GameDirectory(gameDirectory), m_OrganizerDirectory(omoDirectory) { + atexit(&cleanup); +} + + +void GameInfo::cleanup() { + delete GameInfo::s_Instance; + GameInfo::s_Instance = NULL; } diff --git a/src/shared/gameinfo.h b/src/shared/gameinfo.h index 3e022ef4..14f52f05 100644 --- a/src/shared/gameinfo.h +++ b/src/shared/gameinfo.h @@ -176,9 +176,11 @@ private: static bool identifyGame(const std::wstring &omoDirectory, const std::wstring &searchPath); std::wstring getSpecialPath(LPCWSTR name) const; + static void cleanup(); + private: - static GameInfo* s_Instance; + static GameInfo *s_Instance; std::wstring m_MyGamesDirectory; diff --git a/src/shared/leaktrace.cpp b/src/shared/leaktrace.cpp index 83ed4fd0..c3721557 100644 --- a/src/shared/leaktrace.cpp +++ b/src/shared/leaktrace.cpp @@ -114,6 +114,7 @@ static struct __TraceData { } std::map m_Traces; + } __trace; diff --git a/src/shared/shared.pro b/src/shared/shared.pro index ab0bd8a0..992fd7f2 100644 --- a/src/shared/shared.pro +++ b/src/shared/shared.pro @@ -13,9 +13,17 @@ CONFIG += staticlib INCLUDEPATH += ../bsatk "$(BOOSTPATH)" + +# only for custom leak detection +#DEFINES += TRACE_LEAKS +#LIBS += -lDbgHelp + + CONFIG(debug, debug|release) { - LIBS += -L$$OUT_PWD/../bsatk/debug + LIBS += -L$$OUT_PWD/../bsatk/debug LIBS += -lDbgHelp + QMAKE_CXXFLAGS_DEBUG -= -Zi + QMAKE_CXXFLAGS += -Z7 } else { LIBS += -L$$OUT_PWD/../bsatk/release } diff --git a/src/spawn.cpp b/src/spawn.cpp index ab43b687..5639a78c 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -156,7 +156,7 @@ HANDLE startBinary(const QFileInfo &binary, const QString &arguments, const QStr return processHandle; } - +/* ExitProxy *ExitProxy::s_Instance = NULL; ExitProxy *ExitProxy::instance() @@ -170,4 +170,4 @@ ExitProxy *ExitProxy::instance() void ExitProxy::emitExit() { emit exit(); -} +}*/ diff --git a/src/spawn.h b/src/spawn.h index e0d1f958..48320fea 100644 --- a/src/spawn.h +++ b/src/spawn.h @@ -31,7 +31,7 @@ along with Mod Organizer. If not, see . * @brief a dirty little trick so we can issue a clean restart from startBinary * @note unused */ -class ExitProxy : public QObject { +/*class ExitProxy : public QObject { Q_OBJECT public: static ExitProxy *instance(); @@ -42,7 +42,7 @@ private: ExitProxy() {} private: static ExitProxy *s_Instance; -}; +};*/ /** diff --git a/src/splash.png b/src/splash.png index 45ace8f8..0137bf72 100644 Binary files a/src/splash.png and b/src/splash.png differ -- cgit v1.3.1 From 823dfd410b90407cf6a641226bc46291f23d0004 Mon Sep 17 00:00:00 2001 From: Tannin Date: Sun, 6 Oct 2013 13:34:34 +0200 Subject: - option to ignore/unignore updates is now only shown if there IS an update - window now shouldn't lose focus on win 8 2 seconds after mod information gets invalidated - small bugfix related to new version scheme parsing --- src/mainwindow.cpp | 14 ++++++++------ src/modlist.cpp | 11 +++++------ src/profile.cpp | 4 ++-- 3 files changed, 15 insertions(+), 14 deletions(-) (limited to 'src/profile.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 9516f818..10985b0b 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -3293,8 +3293,8 @@ void MainWindow::checkModsForUpdates() void MainWindow::changeVersioningScheme() { if (QMessageBox::question(this, tr("Continue?"), - tr("This will try to change the versioning scheme so that the newest version is interpreted as an update to " - "the installed version."), + tr("The versioning scheme decides which version is considered newer than another.\n" + "This function will guess the versioning scheme under the assumption that the installed version is outdated."), QMessageBox::Yes | QMessageBox::Cancel) == QMessageBox::Yes) { ModInfo::Ptr info = ModInfo::getByIndex(m_ContextRow); @@ -3492,10 +3492,12 @@ void MainWindow::on_modList_customContextMenuRequested(const QPoint &pos) if (info->downgradeAvailable()) { menu.addAction(tr("Change versioning scheme"), this, SLOT(changeVersioningScheme())); } - if (info->updateIgnored()) { - menu.addAction(tr("Un-ignore update"), this, SLOT(unignoreUpdate())); - } else { - menu.addAction(tr("Ignore update"), this, SLOT(ignoreUpdate())); + if (info->updateAvailable() || info->downgradeAvailable()) { + if (info->updateIgnored()) { + menu.addAction(tr("Un-ignore update"), this, SLOT(unignoreUpdate())); + } else { + menu.addAction(tr("Ignore update"), this, SLOT(ignoreUpdate())); + } } menu.addSeparator(); diff --git a/src/modlist.cpp b/src/modlist.cpp index 7367b2ae..786f6f17 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -144,15 +144,13 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const return modInfo->name(); } else if (column == COL_VERSION) { VersionInfo verInfo = modInfo->getVersion(); - if (role == Qt::EditRole) { - return verInfo.canonicalString(); - } else { - QString version = verInfo.displayString(); + QString version = verInfo.displayString(); + if (role != Qt::EditRole) { if (version.isEmpty() && modInfo->canBeUpdated()) { version = "?"; } - return version; } + return version; } else if (column == COL_PRIORITY) { int priority = modInfo->getFixedPriority(); if (priority != INT_MIN) { @@ -405,7 +403,8 @@ bool ModList::setData(const QModelIndex &index, const QVariant &value, int role) } break; case COL_VERSION: { ModInfo::Ptr info = ModInfo::getByIndex(modID); - VersionInfo version(value.toString()); + VersionInfo::VersionScheme scheme = info->getVersion().scheme(); + VersionInfo version(value.toString(), scheme); if (version.isValid()) { info->setVersion(version); return true; diff --git a/src/profile.cpp b/src/profile.cpp index 3da12d7d..e075a4e6 100644 --- a/src/profile.cpp +++ b/src/profile.cpp @@ -185,7 +185,7 @@ void Profile::writeModlistNow(bool onlyOnTimer) const QString fileName = getModlistFileName(); if (QFile::exists(fileName)) { - shellDelete(QStringList(fileName), false, QApplication::activeModalWidget()); + shellDeleteQuiet(fileName); } if (!file.copy(fileName)) { @@ -205,7 +205,7 @@ void Profile::createTweakedIniFile() { QString tweakedIni = m_Directory.absoluteFilePath("initweaks.ini"); - if (QFile::exists(tweakedIni) && !shellDelete(QStringList(tweakedIni))) { + if (QFile::exists(tweakedIni) && !shellDeleteQuiet(tweakedIni)) { reportError(tr("failed to update tweaked ini file, wrong settings may be used: %1").arg(windowsErrorString(::GetLastError()))); return; } -- cgit v1.3.1 From e597823337c858f2985c615ee5147f47567991ee Mon Sep 17 00:00:00 2001 From: Tannin Date: Thu, 23 Jan 2014 00:05:46 +0100 Subject: - boss integration - plugin list can now also display multiple flags for a file (like the mod list) - changed some compiler&linker settings to produce smaller binaries --- src/ModOrganizer.pro | 3 +- src/aboutdialog.cpp | 2 + src/icondelegate.cpp | 30 +-- src/icondelegate.h | 5 +- src/lockeddialog.cpp | 104 ++++++----- src/lockeddialog.h | 96 +++++----- src/logbuffer.cpp | 3 +- src/main.cpp | 5 +- src/mainwindow.cpp | 26 ++- src/mainwindow.h | 1 + src/mainwindow.ui | 82 +++++++-- src/modflagicondelegate.cpp | 50 +++++ src/modflagicondelegate.h | 18 ++ src/organizer.pro | 32 ++-- src/pdll.h | 402 +++++++++++++++++++++++++++++++++++++++++ src/pluginflagicondelegate.cpp | 24 +++ src/pluginflagicondelegate.h | 17 ++ src/pluginlist.cpp | 270 +++++++++++++++++++++++---- src/pluginlist.h | 95 +++++++++- src/pluginlistsortproxy.cpp | 5 - src/profile.cpp | 34 +--- src/resources.qrc | 2 +- src/safewritefile.cpp | 48 +++++ src/safewritefile.h | 51 ++++++ 24 files changed, 1179 insertions(+), 226 deletions(-) create mode 100644 src/modflagicondelegate.cpp create mode 100644 src/modflagicondelegate.h create mode 100644 src/pdll.h create mode 100644 src/pluginflagicondelegate.cpp create mode 100644 src/pluginflagicondelegate.h create mode 100644 src/safewritefile.cpp create mode 100644 src/safewritefile.h (limited to 'src/profile.cpp') diff --git a/src/ModOrganizer.pro b/src/ModOrganizer.pro index dbf6ab6e..05b05855 100644 --- a/src/ModOrganizer.pro +++ b/src/ModOrganizer.pro @@ -13,11 +13,12 @@ SUBDIRS = bsatk \ nxmhandler \ BossDummy \ pythonRunner \ + boss_modified \ esptk plugins.depends = pythonRunner hookdll.depends = shared -organizer.depends = shared uibase plugins +organizer.depends = shared uibase plugins boss_modified CONFIG(debug, debug|release) { DESTDIR = outputd diff --git a/src/aboutdialog.cpp b/src/aboutdialog.cpp index c7f95640..a8f9b4db 100644 --- a/src/aboutdialog.cpp +++ b/src/aboutdialog.cpp @@ -44,6 +44,8 @@ AboutDialog::AboutDialog(const QString &version, QWidget *parent) addLicense("7-zip", LICENSE_LGPL3); addLicense("ZLib", LICENSE_ZLIB); addLicense("NIF File Format Library", LICENSE_BSD3); + addLicense("BOSS (modified)", LICENSE_GPL3); + addLicense("Alphanum Algorithm", LICENSE_ZLIB); ui->nameLabel->setText(QString("%1 %2").arg(ui->nameLabel->text()).arg(version)); #ifdef HGID diff --git a/src/icondelegate.cpp b/src/icondelegate.cpp index 2540e1d5..048b09f9 100644 --- a/src/icondelegate.cpp +++ b/src/icondelegate.cpp @@ -29,38 +29,17 @@ IconDelegate::IconDelegate(QObject *parent) } -QIcon IconDelegate::getFlagIcon(ModInfo::EFlag flag) const -{ - switch (flag) { - case ModInfo::FLAG_BACKUP: return QIcon(":/MO/gui/emblem_backup"); - case ModInfo::FLAG_INVALID: return QIcon(":/MO/gui/emblem_problem"); - case ModInfo::FLAG_NOTENDORSED: return QIcon(":/MO/gui/emblem_notendorsed"); - case ModInfo::FLAG_NOTES: return QIcon(":/MO/gui/emblem_notes"); - case ModInfo::FLAG_CONFLICT_OVERWRITE: return QIcon(":/MO/gui/emblem_conflict_overwrite"); - case ModInfo::FLAG_CONFLICT_OVERWRITTEN: return QIcon(":/MO/gui/emblem_conflict_overwritten"); - case ModInfo::FLAG_CONFLICT_MIXED: return QIcon(":/MO/gui/emblem_conflict_mixed"); - case ModInfo::FLAG_CONFLICT_REDUNDANT: return QIcon(":MO/gui/emblem_conflict_redundant"); - default: return QIcon(); - } -} - - void IconDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { QStyledItemDelegate::paint(painter, option, index); - QVariant modid = index.data(Qt::UserRole + 1); - if (!modid.isValid()) { - return; - } - ModInfo::Ptr info = ModInfo::getByIndex(modid.toInt()); - std::vector flags = info->getFlags(); + + QList icons = getIcons(index); int x = 4; painter->save(); painter->translate(option.rect.topLeft()); - for (auto iter = flags.begin(); iter != flags.end(); ++iter) { - QIcon temp = getFlagIcon(*iter); - painter->drawPixmap(x, 2, 16, 16, temp.pixmap(QSize(16, 16))); + for (auto iter = icons.begin(); iter != icons.end(); ++iter) { + painter->drawPixmap(x, 2, 16, 16, iter->pixmap(QSize(16, 16))); x += 20; } @@ -70,6 +49,7 @@ void IconDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, QSize IconDelegate::sizeHint(const QStyleOptionViewItem&, const QModelIndex &modelIndex) const { + int count = getNumIcons(modelIndex); unsigned int index = modelIndex.data(Qt::UserRole + 1).toInt(); if (index < ModInfo::getNumMods()) { ModInfo::Ptr info = ModInfo::getByIndex(index); diff --git a/src/icondelegate.h b/src/icondelegate.h index dd9f9dfc..cf292443 100644 --- a/src/icondelegate.h +++ b/src/icondelegate.h @@ -34,13 +34,16 @@ public: virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; virtual QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const; + signals: public slots: private: - QIcon getFlagIcon(ModInfo::EFlag flag) const; + virtual QList getIcons(const QModelIndex &index) const = 0; + virtual size_t getNumIcons(const QModelIndex &index) const = 0; + private: diff --git a/src/lockeddialog.cpp b/src/lockeddialog.cpp index 4c19c615..065d9270 100644 --- a/src/lockeddialog.cpp +++ b/src/lockeddialog.cpp @@ -17,51 +17,59 @@ You should have received a copy of the GNU General Public License along with Mod Organizer. If not, see . */ -#include "lockeddialog.h" -#include "ui_lockeddialog.h" -#include - - -LockedDialog::LockedDialog(QWidget *parent) : - QDialog(parent), - ui(new Ui::LockedDialog), m_UnlockClicked(false) -{ - ui->setupUi(this); - - this->setWindowFlags(this->windowFlags() | Qt::ToolTip | Qt::FramelessWindowHint); - - if (parent != NULL) { - QPoint position = parent->mapToGlobal(QPoint(parent->width() / 2, parent->height() / 2)); - position.rx() -= this->width() / 2; - position.ry() -= this->height() / 2; - move(position); - } -} - -LockedDialog::~LockedDialog() -{ - delete ui; -} - - -void LockedDialog::setProcessName(const QString &name) -{ - ui->processLabel->setText(name); -} - - -void LockedDialog::resizeEvent(QResizeEvent *event) -{ - QWidget *par = parentWidget(); - if (par != NULL) { - QPoint position = par->mapToGlobal(QPoint(par->width() / 2, par->height() / 2)); - position.rx() -= event->size().width() / 2; - position.ry() -= event->size().height() / 2; - move(position); - } -} - -void LockedDialog::on_unlockButton_clicked() -{ - m_UnlockClicked = true; -} +#include "lockeddialog.h" +#include "ui_lockeddialog.h" +#include + + +LockedDialog::LockedDialog(QWidget *parent, const QString &text, bool unlockButton) + : QDialog(parent) + , ui(new Ui::LockedDialog) + , m_UnlockClicked(false) +{ + ui->setupUi(this); + + this->setWindowFlags(this->windowFlags() | Qt::ToolTip | Qt::FramelessWindowHint); + + if (parent != NULL) { + QPoint position = parent->mapToGlobal(QPoint(parent->width() / 2, parent->height() / 2)); + position.rx() -= this->width() / 2; + position.ry() -= this->height() / 2; + move(position); + } + + if (text.length() > 0) { + ui->label->setText(text); + } + if (!unlockButton) { + ui->unlockButton->hide(); + } +} + +LockedDialog::~LockedDialog() +{ + delete ui; +} + + +void LockedDialog::setProcessName(const QString &name) +{ + ui->processLabel->setText(name); +} + + +void LockedDialog::resizeEvent(QResizeEvent *event) +{ + QWidget *par = parentWidget(); + if (par != NULL) { + QPoint position = par->mapToGlobal(QPoint(par->width() / 2, par->height() / 2)); + position.rx() -= event->size().width() / 2; + position.ry() -= event->size().height() / 2; + move(position); + } +} + +void LockedDialog::on_unlockButton_clicked() +{ + m_UnlockClicked = true; +} diff --git a/src/lockeddialog.h b/src/lockeddialog.h index 5be398bc..0f3b29b0 100644 --- a/src/lockeddialog.h +++ b/src/lockeddialog.h @@ -17,51 +17,51 @@ You should have received a copy of the GNU General Public License along with Mod Organizer. If not, see . */ -#ifndef LOCKEDDIALOG_H -#define LOCKEDDIALOG_H - -#include - -namespace Ui { - class LockedDialog; -} - -/** - * a small borderless dialog displayed while the Mod Organizer UI is locked - * The dialog contains only a label and a button to force the UI to be unlocked - * - * The UI gets locked while running external applications since they may modify the - * data on which Mod Organizer works. After the UI is unlocked (manually or after the - * external application closed) MO will refresh all of its data sources - **/ -class LockedDialog : public QDialog -{ - Q_OBJECT - -public: - explicit LockedDialog(QWidget *parent = 0); - ~LockedDialog(); - - /** - * @brief see if the user clicked the unlock-button - * - * @return true if the user clicked the unlock button - **/ - bool unlockClicked() const { return m_UnlockClicked; } - - void setProcessName(const QString &name); - -protected: - - virtual void resizeEvent(QResizeEvent *event); - -private slots: - - void on_unlockButton_clicked(); - -private: - Ui::LockedDialog *ui; - bool m_UnlockClicked; -}; - -#endif // LOCKEDDIALOG_H +#ifndef LOCKEDDIALOG_H +#define LOCKEDDIALOG_H + +#include + +namespace Ui { + class LockedDialog; +} + +/** + * a small borderless dialog displayed while the Mod Organizer UI is locked + * The dialog contains only a label and a button to force the UI to be unlocked + * + * The UI gets locked while running external applications since they may modify the + * data on which Mod Organizer works. After the UI is unlocked (manually or after the + * external application closed) MO will refresh all of its data sources + **/ +class LockedDialog : public QDialog +{ + Q_OBJECT + +public: + explicit LockedDialog(QWidget *parent = 0, const QString &text = "", bool unlockButton = true); + ~LockedDialog(); + + /** + * @brief see if the user clicked the unlock-button + * + * @return true if the user clicked the unlock button + **/ + bool unlockClicked() const { return m_UnlockClicked; } + + void setProcessName(const QString &name); + +protected: + + virtual void resizeEvent(QResizeEvent *event); + +private slots: + + void on_unlockButton_clicked(); + +private: + Ui::LockedDialog *ui; + bool m_UnlockClicked; +}; + +#endif // LOCKEDDIALOG_H diff --git a/src/logbuffer.cpp b/src/logbuffer.cpp index 4f72e551..689d2b55 100644 --- a/src/logbuffer.cpp +++ b/src/logbuffer.cpp @@ -21,6 +21,7 @@ along with Mod Organizer. If not, see . #include "report.h" #include #include +#include #include QScopedPointer LogBuffer::s_Instance; @@ -124,8 +125,6 @@ char LogBuffer::msgTypeID(QtMsgType type) } } -#include - void LogBuffer::log(QtMsgType type, const char *message) { QMutexLocker guard(&s_Mutex); diff --git a/src/main.cpp b/src/main.cpp index 8b74ba83..05d68ec7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -119,7 +119,8 @@ bool bootstrap() QDir::fromNativeSeparators(ToQString(gameInfo.getDownloadDir())), QDir::fromNativeSeparators(ToQString(gameInfo.getOverwriteDir())), QDir::fromNativeSeparators(ToQString(gameInfo.getLogDir())), - QDir::fromNativeSeparators(ToQString(gameInfo.getTutorialDir())) + QDir::fromNativeSeparators(ToQString(gameInfo.getTutorialDir())), + QDir::fromNativeSeparators(ToQString(gameInfo.getOrganizerDirectory()) + "/boss") }; static const int NUM_DIRECTORIES = sizeof(dirNames) / sizeof(QString); @@ -284,7 +285,7 @@ int main(int argc, char *argv[]) SetUnhandledExceptionFilter(MyUnhandledExceptionFilter); - LogBuffer::init(20, QtDebugMsg, application.applicationDirPath() + "/logs/mo_interface.log"); + LogBuffer::init(200, QtDebugMsg, application.applicationDirPath() + "/logs/mo_interface.log"); qDebug("Working directory: %s", qPrintable(QDir::toNativeSeparators(QDir::currentPath()))); qDebug("MO at: %s", qPrintable(QDir::toNativeSeparators(application.applicationDirPath()))); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 582bc283..0f98989c 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -48,7 +48,8 @@ along with Mod Organizer. If not, see . #include "filedialogmemory.h" #include "questionboxmemory.h" #include "tutorialmanager.h" -#include "icondelegate.h" +#include "modflagicondelegate.h" +#include "pluginflagicondelegate.h" #include "credentialsdialog.h" #include "selectiondialog.h" #include "csvbuilder.h" @@ -104,6 +105,7 @@ along with Mod Organizer. If not, see . #include #include #include +#include #ifdef TEST_MODELS @@ -186,7 +188,7 @@ MainWindow::MainWindow(const QString &exeName, QSettings &initSettings, QWidget ui->modList->setModel(m_ModListSortProxy); ui->modList->sortByColumn(ModList::COL_PRIORITY, Qt::AscendingOrder); - ui->modList->setItemDelegateForColumn(ModList::COL_FLAGS, new IconDelegate(ui->modList)); + ui->modList->setItemDelegateForColumn(ModList::COL_FLAGS, new ModFlagIconDelegate(ui->modList)); //ui->modList->setAcceptDrops(true); ui->modList->header()->installEventFilter(&m_ModList); if (initSettings.contains("mod_list_state")) { @@ -206,6 +208,7 @@ MainWindow::MainWindow(const QString &exeName, QSettings &initSettings, QWidget ui->espList->setModel(m_PluginListSortProxy); ui->espList->sortByColumn(PluginList::COL_PRIORITY, Qt::AscendingOrder); + ui->espList->setItemDelegateForColumn(PluginList::COL_FLAGS, new PluginFlagIconDelegate(ui->espList)); if (initSettings.contains("plugin_list_state")) { ui->espList->header()->restoreState(initSettings.value("plugin_list_state").toByteArray()); } @@ -1314,6 +1317,8 @@ HANDLE MainWindow::spawnBinaryDirect(const QFileInfo &binary, const QString &arg QCoreApplication::processEvents(); } + m_CurrentProfile->writeModlistNow(true); + // TODO: should also pass arguments if (m_AboutToRun(binary.absoluteFilePath())) { return startBinary(binary, arguments, profileName, m_Settings.logLevel(), currentDirectory, true); @@ -1778,7 +1783,7 @@ void MainWindow::refreshESPList() m_CurrentProfile->getLoadOrderFileName(), m_CurrentProfile->getLockedOrderFileName()); } catch (const std::exception &e) { - reportError(tr("Failed to refresh list of esps: %s").arg(e.what())); + reportError(tr("Failed to refresh list of esps: %1").arg(e.what())); } } @@ -5123,3 +5128,18 @@ void MainWindow::on_showHiddenBox_toggled(bool checked) m_DownloadManager.setShowHidden(checked); } +void MainWindow::on_bossButton_clicked() +{ + try { + LockedDialog dialog(this, tr("BOSS working"), false); + dialog.show(); + qApp->processEvents(); + m_PluginList.bossSort(); + + savePluginList(); + dialog.hide(); + } catch (const std::exception &e) { + reportError(tr("failed to run boss: %1").arg(e.what())); + ui->bossButton->setEnabled(false); + } +} diff --git a/src/mainwindow.h b/src/mainwindow.h index c01ce767..b247b924 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -564,6 +564,7 @@ private slots: // ui slots void on_linkButton_pressed(); void on_showHiddenBox_toggled(bool checked); void on_bsaList_itemChanged(QTreeWidgetItem *item, int column); + void on_bossButton_clicked(); }; #endif // MAINWINDOW_H diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 5c89a7e0..f3bb425c 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -31,7 +31,16 @@ 4 - + + 6 + + + 6 + + + 6 + + 6 @@ -705,14 +714,25 @@ p, li { white-space: pre-wrap; }
- - - - - - Namefilter - - + + + + + + + + Namefilter + + + + + + + Sort + + + +
@@ -721,7 +741,16 @@ p, li { white-space: pre-wrap; } Archives - + + 6 + + + 6 + + + 6 + + 6 @@ -801,7 +830,16 @@ BSAs checked here are loaded in such a way that your installation order is obeye Data - + + 6 + + + 6 + + + 6 + + 6 @@ -871,7 +909,16 @@ BSAs checked here are loaded in such a way that your installation order is obeye Saves - + + 6 + + + 6 + + + 6 + + 6 @@ -900,7 +947,16 @@ p, li { white-space: pre-wrap; } Downloads - + + 2 + + + 2 + + + 2 + + 2 diff --git a/src/modflagicondelegate.cpp b/src/modflagicondelegate.cpp new file mode 100644 index 00000000..db69eb52 --- /dev/null +++ b/src/modflagicondelegate.cpp @@ -0,0 +1,50 @@ +#include "modflagicondelegate.h" +#include + + +ModFlagIconDelegate::ModFlagIconDelegate(QObject *parent) + : IconDelegate(parent) +{ +} + +QList ModFlagIconDelegate::getIcons(const QModelIndex &index) const +{ + QList result; + QVariant modid = index.data(Qt::UserRole + 1); + if (modid.isValid()) { + ModInfo::Ptr info = ModInfo::getByIndex(modid.toInt()); + std::vector flags = info->getFlags(); + + for (auto iter = flags.begin(); iter != flags.end(); ++iter) { + result.append(getFlagIcon(*iter)); + } + } + return result; +} + +QIcon ModFlagIconDelegate::getFlagIcon(ModInfo::EFlag flag) const +{ + switch (flag) { + case ModInfo::FLAG_BACKUP: return QIcon(":/MO/gui/emblem_backup"); + case ModInfo::FLAG_INVALID: return QIcon(":/MO/gui/emblem_problem"); + case ModInfo::FLAG_NOTENDORSED: return QIcon(":/MO/gui/emblem_notendorsed"); + case ModInfo::FLAG_NOTES: return QIcon(":/MO/gui/emblem_notes"); + case ModInfo::FLAG_CONFLICT_OVERWRITE: return QIcon(":/MO/gui/emblem_conflict_overwrite"); + case ModInfo::FLAG_CONFLICT_OVERWRITTEN: return QIcon(":/MO/gui/emblem_conflict_overwritten"); + case ModInfo::FLAG_CONFLICT_MIXED: return QIcon(":/MO/gui/emblem_conflict_mixed"); + case ModInfo::FLAG_CONFLICT_REDUNDANT: return QIcon(":MO/gui/emblem_conflict_redundant"); + default: return QIcon(); + } +} + +size_t ModFlagIconDelegate::getNumIcons(const QModelIndex &index) const +{ + unsigned int modIdx = index.data(Qt::UserRole + 1).toInt(); + if (modIdx < ModInfo::getNumMods()) { + ModInfo::Ptr info = ModInfo::getByIndex(modIdx); + return info->getFlags().size(); + } else { + return 0; + } +} + diff --git a/src/modflagicondelegate.h b/src/modflagicondelegate.h new file mode 100644 index 00000000..800e2741 --- /dev/null +++ b/src/modflagicondelegate.h @@ -0,0 +1,18 @@ +#ifndef MODFLAGICONDELEGATE_H +#define MODFLAGICONDELEGATE_H + +#include "icondelegate.h" + +class ModFlagIconDelegate : public IconDelegate +{ +public: + explicit ModFlagIconDelegate(QObject *parent = 0); +private: + virtual QList getIcons(const QModelIndex &index) const; + virtual size_t getNumIcons(const QModelIndex &index) const; + + QIcon getFlagIcon(ModInfo::EFlag flag) const; + +}; + +#endif // MODFLAGICONDELEGATE_H diff --git a/src/organizer.pro b/src/organizer.pro index fd72dea3..9a9d0da3 100644 --- a/src/organizer.pro +++ b/src/organizer.pro @@ -81,7 +81,10 @@ SOURCES += \ previewgenerator.cpp \ previewdialog.cpp \ aboutdialog.cpp \ - json.cpp + json.cpp \ + safewritefile.cpp \ + modflagicondelegate.cpp \ + pluginflagicondelegate.cpp HEADERS += \ @@ -152,7 +155,11 @@ HEADERS += \ previewgenerator.h \ previewdialog.h \ aboutdialog.h \ - json.h + json.h \ + safewritefile.h\ + pdll.h \ + modflagicondelegate.h \ + pluginflagicondelegate.h FORMS += \ transfersavesdialog.ui \ @@ -185,24 +192,28 @@ FORMS += \ previewdialog.ui \ aboutdialog.ui -INCLUDEPATH += ../shared ../archive ../uibase ../bsatk ../esptk "$(BOOSTPATH)" +INCLUDEPATH += ../shared ../archive ../uibase ../bsatk ../esptk ../boss_modified/boss-api "$(BOOSTPATH)" LIBS += -L"$(BOOSTPATH)/stage/lib" CONFIG(debug, debug|release) { OUTDIR = $$OUT_PWD/debug DSTDIR = $$PWD/../../outputd - LIBS += -L$$OUT_PWD/../shared/debug -L$$OUT_PWD/../bsatk/debug - LIBS += -L$$OUT_PWD/../uibase/debug - LIBS += -lDbgHelp + LIBS += -L$$OUT_PWD/../shared/debug + LIBS += -L$$OUT_PWD/../bsatk/debug + LIBS += -L$$OUT_PWD/../uibase/debug + LIBS += -L$$OUT_PWD/../boss_modified/debug + LIBS += -lDbgHelp } else { OUTDIR = $$OUT_PWD/release DSTDIR = $$PWD/../../output - LIBS += -L$$OUT_PWD/../shared/release -L$$OUT_PWD/../bsatk/release + LIBS += -L$$OUT_PWD/../shared/release + LIBS += -L$$OUT_PWD/../bsatk/release LIBS += -L$$OUT_PWD/../uibase/release - QMAKE_CXXFLAGS += /Zi + LIBS += -L$$OUT_PWD/../boss_modified/release + QMAKE_CXXFLAGS += /Zi /GL # QMAKE_CXXFLAGS -= -O2 - QMAKE_LFLAGS += /DEBUG + QMAKE_LFLAGS += /DEBUG /LTCG /OPT:REF /OPT:ICF } #QMAKE_CXXFLAGS_WARN_ON -= -W3 @@ -299,9 +310,6 @@ OTHER_FILES += \ tutorials/tutorial_window_installer.js \ tutorials/tutorials_installdialog.qml -INCLUDEPATH += "$(ZLIBPATH)" "$(ZLIBPATH)/build" "$(BOOSTPATH)" -LIBS += -L"$(ZLIBPATH)/build" -lzlibstatic - # leak detection with vld #INCLUDEPATH += "E:/Visual Leak Detector/include" diff --git a/src/pdll.h b/src/pdll.h new file mode 100644 index 00000000..6e1d06f3 --- /dev/null +++ b/src/pdll.h @@ -0,0 +1,402 @@ +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Class: PDll // +// Authors: MicHael Galkovsky // +// Date: April 14, 1998 // +// Company: Pervasive Software // +// Purpose: Base class to wrap dynamic use of dll // +////////////////////////////////////////////////////////////////////////////////////////////// + +#if !defined (_PDLL_H_) +#define _PDLL_H_ + +#include +#include + +#include +#include +#include + + +#define FUNC_LOADED 3456 + +//function declarations according to the number of parameters +//define the type +//declare a variable of that type +//declare a member function by the same name as the dll function +//check for dll handle +//if this is the first call to the function then try to load it +//if not then if the function was loaded successfully make a call to it +//otherwise return a NULL cast to the return parameter. + +#define DECLARE_FUNCTION0(CallType, retVal, FuncName) \ + typedef retVal (CallType* TYPE_##FuncName)(); \ + TYPE_##FuncName m_##FuncName; \ + short m_is##FuncName; \ + retVal FuncName() \ + { \ + if (m_dllHandle) \ + { \ + if (FUNC_LOADED != m_is##FuncName) \ + {\ + m_##FuncName = NULL; \ + m_##FuncName = (TYPE_##FuncName)GetProcAddress(m_dllHandle, #FuncName); \ + m_is##FuncName = FUNC_LOADED;\ + }\ + if (NULL != m_##FuncName) \ + return m_##FuncName(); \ + else \ + return (retVal)NULL; \ + } \ + else \ + return (retVal)NULL; \ + } + +#define DECLARE_FUNCTION1(CallType,retVal, FuncName, Param1) \ + typedef retVal (CallType* TYPE_##FuncName)(Param1); \ + TYPE_##FuncName m_##FuncName; \ + short m_is##FuncName;\ + retVal FuncName(Param1 p1) \ + { \ + if (m_dllHandle) \ + { \ + if (FUNC_LOADED != m_is##FuncName) \ + {\ + m_##FuncName = NULL; \ + m_##FuncName = (TYPE_##FuncName)GetProcAddress(m_dllHandle, #FuncName); \ + m_is##FuncName = FUNC_LOADED;\ + }\ + if (NULL != m_##FuncName) \ + return m_##FuncName(p1); \ + else \ + return (retVal)NULL; \ + } \ + else \ + return (retVal)NULL; \ + } + +#define DECLARE_FUNCTION2(CallType,retVal, FuncName, Param1, Param2) \ + typedef retVal (CallType* TYPE_##FuncName)(Param1, Param2); \ + TYPE_##FuncName m_##FuncName; \ + short m_is##FuncName;\ + retVal FuncName (Param1 p1, Param2 p2) \ + {\ + if (m_dllHandle)\ + {\ + if (FUNC_LOADED != m_is##FuncName) \ + {\ + m_##FuncName = NULL; \ + m_##FuncName = (TYPE_##FuncName)GetProcAddress(m_dllHandle, #FuncName); \ + m_is##FuncName = FUNC_LOADED;\ + }\ + if (NULL != m_##FuncName) \ + return m_##FuncName(p1, p2); \ + else \ + return (retVal)NULL; \ + } \ + else\ + return (retVal)NULL; \ + } + +#define DECLARE_FUNCTION3(CallType,retVal, FuncName, Param1, Param2, Param3) \ + typedef retVal (CallType* TYPE_##FuncName)(Param1, Param2, Param3); \ + TYPE_##FuncName m_##FuncName; \ + short m_is##FuncName;\ + retVal FuncName (Param1 p1, Param2 p2, Param3 p3) \ + {\ + if (m_dllHandle)\ + {\ + if (FUNC_LOADED != m_is##FuncName) \ + {\ + m_##FuncName = NULL; \ + m_##FuncName = (TYPE_##FuncName)GetProcAddress(m_dllHandle, #FuncName); \ + m_is##FuncName = FUNC_LOADED; \ + }\ + if (NULL != m_##FuncName) \ + return m_##FuncName(p1, p2, p3);\ + else \ + return (retVal)NULL; \ + } \ + else\ + return (retVal)NULL; \ + } + +#define DECLARE_FUNCTION4(CallType,retVal, FuncName, Param1, Param2, Param3, Param4) \ + typedef retVal (CallType* TYPE_##FuncName)(Param1, Param2, Param3, Param4); \ + TYPE_##FuncName m_##FuncName; \ + short m_is##FuncName;\ + retVal FuncName (Param1 p1, Param2 p2, Param3 p3, Param4 p4) \ + {\ + if (m_dllHandle)\ + {\ + if (FUNC_LOADED != m_is##FuncName) \ + {\ + m_##FuncName = NULL; \ + m_##FuncName = (TYPE_##FuncName)GetProcAddress(m_dllHandle, #FuncName); \ + m_is##FuncName = FUNC_LOADED;\ + }\ + if (NULL != m_##FuncName) \ + return m_##FuncName(p1, p2, p3, p4);\ + else \ + return (retVal)NULL; \ + } \ + else\ + return (retVal)NULL; \ + } + +#define DECLARE_FUNCTION5(CallType,retVal, FuncName, Param1, Param2, Param3, Param4, Param5) \ + typedef retVal (CallType* TYPE_##FuncName)(Param1, Param2, Param3, Param4, Param5); \ + TYPE_##FuncName m_##FuncName; \ + short m_is##FuncName; \ + retVal FuncName (Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5) \ + {\ + if (m_dllHandle)\ + {\ + if (FUNC_LOADED != m_is##FuncName) \ + {\ + m_##FuncName = NULL; \ + m_##FuncName = (TYPE_##FuncName)GetProcAddress(m_dllHandle, #FuncName); \ + m_is##FuncName = FUNC_LOADED;\ + }\ + if (NULL != m_##FuncName) \ + return m_##FuncName(p1, p2, p3, p4, p5);\ + else \ + return (retVal)NULL; \ + } \ + else\ + return (retVal)NULL; \ + } + +#define DECLARE_FUNCTION6(CallType,retVal, FuncName, Param1, Param2, Param3, Param4, Param5, Param6) \ + typedef retVal (CallType* TYPE_##FuncName)(Param1, Param2, Param3, Param4, Param5, Param6); \ + TYPE_##FuncName m_##FuncName; \ + short m_is##FuncName;\ + retVal FuncName (Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6) \ + {\ + if (m_dllHandle)\ + {\ + if (FUNC_LOADED != m_is##FuncName) \ + {\ + m_##FuncName = NULL; \ + m_##FuncName = (TYPE_##FuncName)GetProcAddress(m_dllHandle, #FuncName); \ + m_is##FuncName = FUNC_LOADED;\ + }\ + if (NULL != m_##FuncName) \ + return m_##FuncName(p1, p2, p3, p4, p5, p6);\ + else \ + return (retVal)NULL; \ + } \ + else\ + return (retVal)NULL; \ + } + +#define DECLARE_FUNCTION7(CallType,retVal, FuncName, Param1, Param2, Param3, Param4, Param5, Param6, Param7) \ + typedef retVal (CallType* TYPE_##FuncName)(Param1, Param2, Param3, Param4, Param5, Param6, Param7); \ + TYPE_##FuncName m_##FuncName; \ + short m_is##FuncName;\ + retVal FuncName (Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7) \ + {\ + if (m_dllHandle)\ + {\ + if (FUNC_LOADED != m_is##FuncName) \ + {\ + m_##FuncName = NULL; \ + m_##FuncName = (TYPE_##FuncName)GetProcAddress(m_dllHandle, #FuncName); \ + m_is##FuncName = FUNC_LOADED;\ + }\ + if (NULL != m_##FuncName) \ + return m_##FuncName(p1, p2, p3, p4, p5, p6, p7);\ + else \ + return (retVal)NULL; \ + } \ + else\ + return (retVal)NULL; \ + } + +#define DECLARE_FUNCTION8(CallType,retVal, FuncName, Param1, Param2, Param3, Param4, Param5, Param6, Param7, Param8) \ + typedef retVal (CallType* TYPE_##FuncName)(Param1, Param2, Param3, Param4, Param5, Param6, Param7, Param8); \ + TYPE_##FuncName m_##FuncName; \ + short m_is##FuncName;\ + retVal FuncName (Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7, Param8 p8) \ + {\ + if (m_dllHandle)\ + {\ + if (FUNC_LOADED != m_is##FuncName) \ + {\ + m_##FuncName = NULL; \ + m_##FuncName = (TYPE_##FuncName)GetProcAddress(m_dllHandle, #FuncName); \ + m_is##FuncName = FUNC_LOADED;\ + }\ + if (NULL != m_##FuncName) \ + return m_##FuncName(p1, p2, p3, p4, p5, p6, p7, p8);\ + else \ + return (retVal)NULL; \ + }\ + else\ + return (retVal)NULL; \ + } + +#define DECLARE_FUNCTION9(CallType,retVal, FuncName, Param1, Param2, Param3, Param4, Param5, Param6, Param7, Param8, Param9) \ + typedef retVal (CallType* TYPE_##FuncName)(Param1, Param2, Param3, Param4, Param5, Param6, Param7, Param8, Param9); \ + TYPE_##FuncName m_##FuncName; \ + short m_is##FuncName; \ + retVal FuncName (Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7, Param8 p8, Param9 p9) \ + {\ + if (m_dllHandle)\ + {\ + if (FUNC_NAME != m_is##FuncName) \ + {\ + m_##FuncName = NULL; \ + m_##FuncName = (TYPE_##FuncName)GetProcAddress(m_dllHandle, #FuncName); \ + m_is##FuncName = FUNC_LOADED;\ + }\ + if (NULL != m_##FuncName) \ + return m_##FuncName(p1, p2, p3, p4, p5, p6, p7, p8, p9);\ + else \ + return (retVal)NULL; \ + }\ + else\ + return (retVal)NULL; \ + } + +#define DECLARE_FUNCTION10(CallType,retVal, FuncName, Param1, Param2, Param3, Param4, Param5, Param6, Param7, Param8, Param9, Param10) \ + typedef retVal (CallType* TYPE_##FuncName)FuncName(Param1, Param2, Param3, Param4, Param5, Param6, Param7, Param8, Param9, Param10); \ + TYPE_##FuncName m_##FuncName; \ + short m_is##FuncName;\ + retVal FuncName (Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7, Param8 p8, Param9 p9, Param10 p10) \ + {\ + if (m_dllHandle)\ + {\ + if (FUNC_LOADED != m_is##FuncName) \ + {\ + m_##FuncName = NULL; \ + m_##FuncName = (TYPE_##FuncName)GetProcAddress(m_dllHandle, #FuncName); \ + m_is##FuncName = FUNC_LOADED;\ + }\ + if (NULL != m_##FuncName) \ + return m_##FuncName(p1, p2, p3, p4, p5, p6, p7, p8, p9, p10);\ + else \ + return (retVal)NULL; \ + }\ + else \ + return (retVal)NULL;\ + } + +//declare constructors and LoadFunctions +#define DECLARE_CLASS(ClassName) \ + public: \ + ClassName (LPCTSTR name){LoadDll(name);} \ + ClassName () {PDLL();} + +class PDLL +{ +protected: + HINSTANCE m_dllHandle; +private: + LPTSTR m_dllName; + int m_refCount; + +public: + + PDLL() + { + m_dllHandle = NULL; + m_dllName = NULL; + m_refCount = 0; + } + + //A NULL here means the name has already been set + void LoadDll(LPCTSTR name, bool showMsg = true) + { + if (name) + SetDllName(name); + + //try to load + m_dllHandle = LoadLibrary(m_dllName); + + if (m_dllHandle == NULL && showMsg) + { + std::ostringstream message; + message << "failed to load dll: " << ::GetLastError(); + throw std::runtime_error(message.str().c_str()); + } + } + + bool SetDllName(LPCTSTR newName) + { + bool retVal = false; + + //we allow name resets only if the current DLL handle is invalid + //once they've hooked into a DLL, the name cannot be changed + if (!m_dllHandle) + { + if (m_dllName) + { + delete []m_dllName; + m_dllName = NULL; + } + + //They may be setting this null (e.g., uninitialize) + if (newName) + { + m_dllName = new TCHAR[_tcslen(newName) + 1]; + _tcscpy(m_dllName, newName); + } + retVal = true; + } + return retVal; + } + + virtual bool Initialize(short showMsg = 1) + { + + bool retVal = false; + + //Add one to our internal reference counter + m_refCount++; + + if (m_refCount == 1 && m_dllName) //if this is first time, load the DLL + { + //we are assuming the name is already set + LoadDll(NULL, showMsg); + retVal = (m_dllHandle != NULL); + } + return retVal; + } + + virtual void Uninitialize(void) + { + //If we're already completely unintialized, early exit + if (!m_refCount) + return; + //if this is the last time this instance has been unitialized, + //then do a full uninitialization + m_refCount--; + + if (m_refCount < 1) + { + if (m_dllHandle) + { + FreeLibrary(m_dllHandle); + m_dllHandle = NULL; + } + + SetDllName(NULL); //clear out the name & free memory + } + } + + virtual ~PDLL() + { + //force this to be a true uninitialize + m_refCount = 1; + Uninitialize(); + + //free name + if (m_dllName) + { + delete [] m_dllName; + m_dllName = NULL; + } + } + +}; +#endif diff --git a/src/pluginflagicondelegate.cpp b/src/pluginflagicondelegate.cpp new file mode 100644 index 00000000..6c0bb29e --- /dev/null +++ b/src/pluginflagicondelegate.cpp @@ -0,0 +1,24 @@ +#include "pluginflagicondelegate.h" +#include "pluginlist.h" +#include + + +PluginFlagIconDelegate::PluginFlagIconDelegate(QObject *parent) + : IconDelegate(parent) +{ +} + +QList PluginFlagIconDelegate::getIcons(const QModelIndex &index) const +{ + QList result; + foreach (const QVariant &var, index.data(Qt::UserRole + 1).toList()) { + result.append(var.value()); + } + return result; +} + +size_t PluginFlagIconDelegate::getNumIcons(const QModelIndex &index) const +{ + return index.data(Qt::UserRole + 1).toList().count(); +} + diff --git a/src/pluginflagicondelegate.h b/src/pluginflagicondelegate.h new file mode 100644 index 00000000..554e968b --- /dev/null +++ b/src/pluginflagicondelegate.h @@ -0,0 +1,17 @@ +#ifndef PLUGINFLAGICONDELEGATE_H +#define PLUGINFLAGICONDELEGATE_H + +#include "icondelegate.h" + +class PluginFlagIconDelegate : public IconDelegate +{ +public: + PluginFlagIconDelegate(QObject *parent = NULL); + + // IconDelegate interface +private: + virtual QList getIcons(const QModelIndex &index) const; + virtual size_t getNumIcons(const QModelIndex &index) const; +}; + +#endif // PLUGINFLAGICONDELEGATE_H diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp index 85137390..c2e14182 100644 --- a/src/pluginlist.cpp +++ b/src/pluginlist.cpp @@ -20,8 +20,10 @@ along with Mod Organizer. If not, see . #include "pluginlist.h" #include "report.h" #include "inject.h" -#include #include "settings.h" +#include "safewritefile.h" +#include "scopeguard.h" +#include #include #include #include @@ -39,7 +41,6 @@ along with Mod Organizer. If not, see . #include #include -#include #include #include #include @@ -74,8 +75,10 @@ bool ByDate(const PluginList::ESPInfo& LHS, const PluginList::ESPInfo& RHS) { } PluginList::PluginList(QObject *parent) - : QAbstractTableModel(parent), - m_FontMetrics(QFont()), m_SaveTimer(this) + : QAbstractTableModel(parent) + , m_FontMetrics(QFont()) + , m_SaveTimer(this) + , m_BOSS(NULL) { m_SaveTimer.setSingleShot(true); connect(&m_SaveTimer, SIGNAL(timeout()), this, SIGNAL(saveTimer())); @@ -92,6 +95,12 @@ PluginList::PluginList(QObject *parent) PluginList::~PluginList() { + if (m_BOSS != NULL) { + m_BOSS->DestroyBossDb(m_BOSSDB); + m_BOSS->CleanUpAPI(); + delete m_BOSS; + m_BOSS = NULL; + } } @@ -101,6 +110,7 @@ QString PluginList::getColumnName(int column) case COL_NAME: return tr("Name"); case COL_PRIORITY: return tr("Priority"); case COL_MODINDEX: return tr("Mod Index"); + case COL_FLAGS: return tr("Flags"); default: return tr("unknown"); } } @@ -205,7 +215,7 @@ void PluginList::refresh(const QString &profileName, const DirectoryEntry &baseD emit layoutChanged(); refreshLoadOrder(); - emit dataChanged(this->index(0, 0), this->index(m_ESPs.size(), 1)); + emit dataChanged(this->index(0, 0), this->index(m_ESPs.size(), columnCount())); m_Refreshed(); } @@ -375,18 +385,16 @@ void PluginList::readLockedOrderFrom(const QString &fileName) } + void PluginList::writePlugins(const QString &fileName, bool writeUnchecked) const { - QFile file(fileName); - if (!file.open(QIODevice::WriteOnly)) { - throw MyException(tr("failed to open output file: %1").arg(fileName)); - } + SafeWriteFile file(fileName); QTextCodec *textCodec = writeUnchecked ? m_Utf8Codec : m_LocalCodec; - file.resize(0); + file->resize(0); - file.write(textCodec->fromUnicode("# This file was automatically generated by Mod Organizer.\r\n")); + file->write(textCodec->fromUnicode("# This file was automatically generated by Mod Organizer.\r\n")); bool invalidFileNames = false; int writtenCount = 0; @@ -398,36 +406,34 @@ void PluginList::writePlugins(const QString &fileName, bool writeUnchecked) cons invalidFileNames = true; qCritical("invalid plugin name %s", m_ESPs[priority].m_Name.toUtf8().constData()); } else { - file.write(textCodec->fromUnicode(m_ESPs[priority].m_Name)); + file->write(textCodec->fromUnicode(m_ESPs[priority].m_Name)); } - file.write("\r\n"); + file->write("\r\n"); ++writtenCount; } } - file.close(); if (invalidFileNames) { reportError(tr("Some of your plugins have invalid names! These plugins can not be loaded by the game. " "Please see mo_interface.log for a list of affected plugins and rename them.")); } + file.commit(); + qDebug("%s saved", QDir::toNativeSeparators(fileName).toUtf8().constData()); } void PluginList::writeLockedOrder(const QString &fileName) const { - QFile file(fileName); - if (!file.open(QIODevice::WriteOnly)) { - throw MyException(tr("failed to open output file: %1").arg(fileName)); - } + SafeWriteFile file(fileName); - file.resize(0); - file.write(QString("# This file was automatically generated by Mod Organizer.\r\n").toUtf8()); + file->resize(0); + file->write(QString("# This file was automatically generated by Mod Organizer.\r\n").toUtf8()); for (auto iter = m_LockedOrder.begin(); iter != m_LockedOrder.end(); ++iter) { - file.write(QString("%1|%2\r\n").arg(iter->first).arg(iter->second).toUtf8()); + file->write(QString("%1|%2\r\n").arg(iter->first).arg(iter->second).toUtf8()); } - file.close(); + file.commit(); } @@ -590,6 +596,175 @@ void PluginList::refreshLoadOrder() emit layoutChanged(); } + +class boss_exception : public std::runtime_error { +public: + boss_exception(const std::string &message) : std::runtime_error(message) {} +}; + +#define THROW_BOSS_ERROR(obj) \ + uint8_t *message; \ + obj->GetLastErrorDetails(&message); \ + throw boss_exception(std::string(reinterpret_cast(message))); + +#define U8(text) reinterpret_cast(text) + + +void outputBossLog(const QString &filename) +{ + QFile file(filename); + if (file.open(QIODevice::ReadOnly)) { + while (!file.atEnd()) { + QByteArray line = file.readLine().trimmed(); + if (line.length() < 1) + continue; + + int endFirstWord = line.indexOf(':'); + if (endFirstWord < 0) { + endFirstWord = 0; + } + QString firstWord = line.mid(0, endFirstWord); + if (firstWord == "DEBUG") { + qDebug("(boss) %s", line.mid(endFirstWord + 2).constData()); + } else { + qWarning("(boss) %s", line.mid(endFirstWord + 2).constData()); + } + } + } + file.resize(0); +} + + +void PluginList::initBoss() +{ + m_BOSS = new BossDLL(TEXT("dlls\\boss.dll")); + + if (!m_BOSS->IsCompatibleVersion(2,1,1)) { + throw MyException(tr("BOSS dll incompatible")); + } + uint8_t *versionString; + if (m_BOSS->GetVersionString(&versionString) != BossDLL::RESULT_OK) { + THROW_BOSS_ERROR(m_BOSS) + } + qDebug("using boss version %s", versionString); + + m_TempFile.open(); m_TempFile.close(); // yeah, stupid, but open is required to generate the name + m_BOSS->SetLoggerOutput(m_TempFile.fileName().toLocal8Bit().constData(), 4); + + if (m_BOSS->CreateBossDb(&m_BOSSDB, BossDLL::SKYRIM, NULL) != BossDLL::RESULT_OK) { + uint8_t *message; + m_BOSS->GetLastErrorDetails(&message); + std::string messageCopy(reinterpret_cast(message)); + delete m_BOSS; + m_BOSS = NULL; + throw boss_exception(messageCopy); + } + qApp->processEvents(); + + QString masterlistName = QDir::toNativeSeparators(qApp->applicationDirPath() + "/boss/masterlist.txt"); + + uint32_t res = m_BOSS->UpdateMasterlist(m_BOSSDB, U8(masterlistName.toUtf8().constData())); + qApp->processEvents(); + if (res == BossDLL::RESULT_OK) { + qDebug("boss masterlist updated"); + } else if (res == BossDLL::RESULT_NO_UPDATE_NECESSARY) { + qDebug("boss masterlist already up-to-date"); + } else { + THROW_BOSS_ERROR(m_BOSS) + } + if (m_BOSS->Load(m_BOSSDB, + U8(masterlistName.toUtf8().constData()), + U8(QDir::toNativeSeparators(qApp->applicationDirPath() + "/boss/userlist.txt").toUtf8().constData())) != BossDLL::RESULT_OK) { + THROW_BOSS_ERROR(m_BOSS) + } +} + +void PluginList::convertPluginListForBoss(boost::ptr_vector &inputPlugins, std::vector &activePlugins) +{ + foreach (int idx, m_ESPsByPriority) { + QString fileName = m_ESPs[idx].m_Name; + QByteArray name = fileName.toUtf8(); + + uint8_t *nameU8 = new uint8_t[name.length() + 1]; + memcpy(nameU8, name.constData(), name.length() + 1); + if (m_ESPs[idx].m_Enabled) { + activePlugins.push_back(nameU8); + } + inputPlugins.push_back(nameU8); + } + if (m_BOSS->SetActivePluginsDumb(m_BOSSDB, &activePlugins[0], activePlugins.size()) != BossDLL::RESULT_OK) { + THROW_BOSS_ERROR(m_BOSS) + } +} + +void PluginList::applyBOSSSorting(uint8_t **pluginList, size_t size, int &priority, bool recognized, const char *extension) +{ + for (size_t i= 0; i < size; ++i) { + QString name = QString::fromUtf8(reinterpret_cast(pluginList[i])).toLower(); + if (name.endsWith(extension)) { + auto iter = m_ESPsByName.find(name); + if (iter == m_ESPsByName.end()) { + throw MyException("boss returned invalid data"); + } + BossMessage *message; + size_t numMessages = 0; + m_BOSS->GetPluginMessages(m_BOSSDB, pluginList[i], &message, &numMessages); + m_ESPs[iter->second].m_BOSSMessages.clear(); + for (size_t im = 0; im < numMessages; ++im) { + m_ESPs[iter->second].m_BOSSMessages.append(QString::fromUtf8(reinterpret_cast(message[im].message))); + } + m_ESPs[iter->second].m_Priority = priority++; + m_ESPs[iter->second].m_BOSSUnrecognized = !recognized; + } + } +} + +void PluginList::bossSort() +{ + if (m_BOSS == NULL) { + // first run, check boss compatibility and update + initBoss(); + } + + // create a boss-compatible representation of our current mod list. + boost::ptr_vector inputPlugins; + std::vector activePlugins; + convertPluginListForBoss(inputPlugins, activePlugins); + + // sort mods in-memory + uint8_t **sortedPlugins; + uint8_t **unrecognizedPlugins; + size_t sizeSorted, sizeUnrecognized; + + if (m_BOSS->SortCustomMods(m_BOSSDB, + inputPlugins.c_array(), inputPlugins.size(), + &sortedPlugins, &sizeSorted, + &unrecognizedPlugins, &sizeUnrecognized) != BossDLL::RESULT_OK) { + THROW_BOSS_ERROR(m_BOSS) + } + + // output the log from boss to our own log to make it visible + outputBossLog(m_TempFile.fileName()); + + qDebug("%d sorted, %d unrecognized", sizeSorted, sizeUnrecognized); + + emit layoutAboutToBeChanged(); + + int priority = 0; + applyBOSSSorting(sortedPlugins, sizeSorted, priority, true, "esm"); + applyBOSSSorting(unrecognizedPlugins, sizeUnrecognized, priority, false, "esm"); + applyBOSSSorting(sortedPlugins, sizeSorted, priority, true, "esp"); + applyBOSSSorting(unrecognizedPlugins, sizeUnrecognized, priority, false, "esp"); + + // inform view of the changed data + updateIndices(); + emit layoutChanged(); + syncLoadOrder(); + + emit dataChanged(this->index(0, 0), this->index(m_ESPs.size(), columnCount())); + m_Refreshed(); +} + IPluginList::PluginState PluginList::state(const QString &name) const { auto iter = m_ESPsByName.find(name.toLower()); @@ -670,7 +845,7 @@ int PluginList::rowCount(const QModelIndex &parent) const int PluginList::columnCount(const QModelIndex &) const { - return 3; + return COL_LASTCOLUMN + 1; } @@ -724,18 +899,6 @@ QVariant PluginList::data(const QModelIndex &modelIndex, int role) const return QVariant(); } break; } - } else if ((role == Qt::DecorationRole) && (modelIndex.column() == 0)) { - if (m_ESPs[index].m_MasterUnset.size() > 0) { - return QIcon(":/MO/gui/warning"); - } else if (m_LockedOrder.find(m_ESPs[index].m_Name.toLower()) != m_LockedOrder.end()) { - return QIcon(":/MO/gui/locked"); - } else if (m_ESPs[index].m_HasIni) { - return QIcon(":/MO/gui/attachment"); - } else if (m_ESPs[index].m_IsDummy && m_ESPs[index].m_Enabled && !m_ESPs[index].m_HasIni) { - return QIcon(":/MO/gui/edit_clear"); - } else { - return QVariant(); - } } else if ((role == Qt::CheckStateRole) && (modelIndex.column() == 0)) { return m_ESPs[index].m_Enabled ? Qt::Checked : Qt::Unchecked; } else if (role == Qt::FontRole) { @@ -754,8 +917,15 @@ QVariant PluginList::data(const QModelIndex &modelIndex, int role) const return QVariant(Qt::AlignHCenter | Qt::AlignVCenter); } } else if (role == Qt::ToolTipRole) { + QString toolTip; + if (!m_ESPs[index].m_BOSSMessages.isEmpty()) { + toolTip += m_ESPs[index].m_BOSSMessages.join("
") + "

"; + } + if (m_ESPs[index].m_BOSSUnrecognized) { + toolTip += "Not recognized by BOSS
"; + } if (m_ESPs[index].m_ForceEnabled) { - return tr("This plugin can't be disabled (enforced by the game)"); + toolTip += tr("This plugin can't be disabled (enforced by the game)"); } else { QString text = tr("Origin: %1").arg(m_ESPs[index].m_OriginName); if (m_ESPs[index].m_MasterUnset.size() > 0) { @@ -773,8 +943,30 @@ QVariant PluginList::data(const QModelIndex &modelIndex, int role) const text += "
This file is a dummy! It exists only so the bsa with the same name gets loaded. With MO you don't need this: " "If you enable the archive with the same name in the \"Archive\" tab you can disable this plugin."; } - return text; + toolTip += text; + } + return toolTip; + } else if (role == Qt::UserRole + 1) { + QVariantList result; + if (m_ESPs[index].m_MasterUnset.size() > 0) { + result.append(QIcon(":/MO/gui/warning")); + } + if (m_LockedOrder.find(m_ESPs[index].m_Name.toLower()) != m_LockedOrder.end()) { + result.append(QIcon(":/MO/gui/locked")); } + if (!m_ESPs[index].m_BOSSMessages.isEmpty()) { + result.append(QIcon(":/MO/gui/information")); + } + if (m_ESPs[index].m_BOSSUnrecognized) { + result.append(QIcon(":/MO/gui/help")); + } + if (m_ESPs[index].m_HasIni) { + result.append(QIcon(":/MO/gui/attachment")); + } + if (m_ESPs[index].m_IsDummy && m_ESPs[index].m_Enabled && !m_ESPs[index].m_HasIni) { + result.append(QIcon(":/MO/gui/edit_clear")); + } + return result; } else { return QVariant(); } @@ -784,7 +976,6 @@ QVariant PluginList::data(const QModelIndex &modelIndex, int role) const bool PluginList::setData(const QModelIndex &modIndex, const QVariant &value, int role) { if (role == Qt::CheckStateRole) { -qDebug("uncheck plugin"); m_ESPs[modIndex.row()].m_Enabled = value.toInt() == Qt::Checked; emit dataChanged(modIndex, modIndex); @@ -1036,7 +1227,8 @@ PluginList::ESPInfo::ESPInfo(const QString &name, bool enabled, FILETIME time, const QString &originName, const QString &fullPath, bool hasIni) : m_Name(name), m_Enabled(enabled), m_ForceEnabled(enabled), m_Removed(false), - m_Priority(0), m_LoadOrder(-1), m_Time(time), m_OriginName(originName), m_HasIni(hasIni) + m_Priority(0), m_LoadOrder(-1), m_Time(time), m_OriginName(originName), m_HasIni(hasIni), + m_BOSSUnrecognized(false) { try { ESP::File file(ToWString(fullPath)); diff --git a/src/pluginlist.h b/src/pluginlist.h index 64c914df..bb7428d0 100644 --- a/src/pluginlist.h +++ b/src/pluginlist.h @@ -25,8 +25,12 @@ along with Mod Organizer. If not, see . #include #include #include +#include #include +#include #include +#include +#include /** @@ -40,6 +44,7 @@ public: enum EColumn { COL_NAME, + COL_FLAGS, COL_PRIORITY, COL_MODINDEX, @@ -143,6 +148,8 @@ public: void refreshLoadOrder(); + void bossSort(); + public: virtual PluginState state(const QString &name) const; virtual int priority(const QString &name) const; @@ -162,6 +169,7 @@ public: // implementation of the QAbstractTableModel interface virtual Qt::DropActions supportedDropActions() const { return Qt::MoveAction; } virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent); + void applyBOSSSorting(uint8_t **pluginList, size_t size, int &priority, bool recognized, const char *extension); public slots: /** @@ -194,7 +202,6 @@ private: ESPInfo(const QString &name, bool enabled, FILETIME time, const QString &originName, const QString &fullPath, bool hasIni); QString m_Name; - QString m_FullPath; bool m_Enabled; bool m_ForceEnabled; bool m_Removed; @@ -207,12 +214,91 @@ private: bool m_HasIni; std::set m_Masters; mutable std::set m_MasterUnset; + QStringList m_BOSSMessages; + bool m_BOSSUnrecognized; }; friend bool ByName(const ESPInfo& LHS, const ESPInfo& RHS); friend bool ByDate(const ESPInfo& LHS, const ESPInfo& RHS); friend bool ByPriority(const ESPInfo& LHS, const ESPInfo& RHS); + + class BossDLL : public PDLL { + DECLARE_CLASS(BossDLL) + + DECLARE_FUNCTION3(__cdecl, uint32_t, CreateBossDb, boss_db*, const uint32_t, const uint8_t*) + DECLARE_FUNCTION1(__cdecl, void, DestroyBossDb, boss_db) + DECLARE_FUNCTION0(__cdecl, void, CleanUpAPI) + + DECLARE_FUNCTION7(__cdecl, uint32_t, SortCustomMods, boss_db, uint8_t**, size_t, uint8_t***, size_t*, uint8_t***, size_t*) + DECLARE_FUNCTION3(__cdecl, uint32_t, SetActivePluginsDumb, boss_db, uint8_t**, const size_t) + + DECLARE_FUNCTION3(__cdecl, uint32_t, GetActivePluginsDumb , boss_db, uint8_t***, size_t*) + + DECLARE_FUNCTION2(__cdecl, uint32_t, UpdateMasterlist, boss_db, const uint8_t*) + DECLARE_FUNCTION3(__cdecl, uint32_t, Load, boss_db, const uint8_t*, const uint8_t*) + + DECLARE_FUNCTION2(__cdecl, void, SetLoggerOutput, const char*, uint8_t) + DECLARE_FUNCTION1(__cdecl, uint32_t, GetLastErrorDetails, uint8_t**) + + DECLARE_FUNCTION1(__cdecl, uint32_t, GetVersionString, uint8_t**) + DECLARE_FUNCTION3(__cdecl, bool, IsCompatibleVersion, const uint32_t, const uint32_t, const uint32_t) + + DECLARE_FUNCTION4(__cdecl, uint32_t, GetPluginMessages, boss_db, const uint8_t*, BossMessage**, size_t*) + + enum ResultCode { + RESULT_OK = 0, + RESULT_NO_MASTER_FILE = 1, + RESULT_FILE_READ_FAIL = 2, + RESULT_FILE_WRITE_FAIL = 3, + RESULT_FILE_NOT_UTF8 = 4, + RESULT_FILE_NOT_FOUND = 5, + RESULT_FILE_PARSE_FAIL = 6, + RESULT_CONDITION_EVAL_FAIL = 7, + RESULT_REGEX_EVAL_FAIL = 8, + RESULT_NO_GAME_DETECTED = 9, + RESULT_ENCODING_CONVERSION_FAIL = 10, + RESULT_FIND_ONLINE_MASTERLIST_REVISION_FAIL = 11, + RESULT_FIND_ONLINE_MASTERLIST_DATE_FAIL = 12, + RESULT_READ_UPDATE_FILE_LIST_FAIL = 13, + RESULT_FILE_CRC_MISMATCH = 14, + RESULT_FS_FILE_MOD_TIME_READ_FAIL = 15, + RESULT_FS_FILE_MOD_TIME_WRITE_FAIL = 16, + RESULT_FS_FILE_RENAME_FAIL = 17, + RESULT_FS_FILE_DELETE_FAIL = 18, + RESULT_FS_CREATE_DIRECTORY_FAIL = 19, + RESULT_FS_ITER_DIRECTORY_FAIL = 20, + RESULT_CURL_INIT_FAIL = 21, + RESULT_CURL_SET_ERRBUFF_FAIL = 22, + RESULT_CURL_SET_OPTION_FAIL = 23, + RESULT_CURL_SET_PROXY_FAIL = 24, + RESULT_CURL_SET_PROXY_TYPE_FAIL = 25, + RESULT_CURL_SET_PROXY_AUTH_FAIL = 26, + RESULT_CURL_SET_PROXY_AUTH_TYPE_FAIL = 27, + RESULT_CURL_PERFORM_FAIL = 28, + RESULT_CURL_USER_CANCEL = 29, + RESULT_GUI_WINDOW_INIT_FAIL = 30, + RESULT_NO_UPDATE_NECESSARY = 31, + RESULT_LO_MISMATCH = 32, + RESULT_NO_MEM = 33, + RESULT_INVALID_ARGS = 34, + RESULT_NETWORK_FAIL = 35, + RESULT_NO_INTERNET_CONNECTION = 36, + RESULT_NO_TAG_MAP = 37, + RESULT_PLUGINS_FULL = 38, + RESULT_PLUGIN_BEFORE_MASTER = 39, + RESULT_INVALID_SYNTAX = 40 + }; + + enum GameIDs { + AUTODETECT = 0, + OBLIVION = 1, + SKYRIM = 3, + FALLOUT3 = 4, + FALLOUTNV = 5 + }; + }; + private: void syncLoadOrder(); @@ -230,6 +316,9 @@ private: void testMasters(); + void initBoss(); + void convertPluginListForBoss(boost::ptr_vector &inputPlugins, std::vector &activePlugins); + private: std::vector m_ESPs; @@ -253,6 +342,10 @@ private: SignalRefreshed m_Refreshed; + BossDLL *m_BOSS; + boss_db m_BOSSDB; + QTemporaryFile m_TempFile; + }; #endif // PLUGINLIST_H diff --git a/src/pluginlistsortproxy.cpp b/src/pluginlistsortproxy.cpp index 5bff24f5..8412fa27 100644 --- a/src/pluginlistsortproxy.cpp +++ b/src/pluginlistsortproxy.cpp @@ -125,11 +125,6 @@ bool PluginListSortProxy::lessThan(const QModelIndex &left, return plugins->getPriority(left.row()) < plugins->getPriority(right.row()); } break; default: { - static bool first = true; - if (first) { - qCritical("invalid sort column %d", left.column()); - first = false; - } return plugins->getPriority(left.row()) < plugins->getPriority(right.row()); } break; } diff --git a/src/profile.cpp b/src/profile.cpp index e075a4e6..c1f1025e 100644 --- a/src/profile.cpp +++ b/src/profile.cpp @@ -23,6 +23,7 @@ along with Mod Organizer. If not, see . #include "windows_error.h" #include "dummybsa.h" #include "modinfo.h" +#include "safewritefile.h" #include #include #include @@ -149,15 +150,10 @@ void Profile::writeModlistNow(bool onlyOnTimer) const #pragma message("right now, this is doing unnecessary saves. Need a flag that says that mod priority, enabled-state or name of a mod has changed") try { - QTemporaryFile file; - - if (!file.open()) { - reportError(tr("failed to open temporary file")); - return; - } + QString fileName = getModlistFileName(); + SafeWriteFile file(fileName); - file.resize(0); - file.write(QString("# This file was automatically generated by Mod Organizer.\r\n").toUtf8()); + file->write(QString("# This file was automatically generated by Mod Organizer.\r\n").toUtf8()); if (m_ModStatus.empty()) { return; } @@ -169,29 +165,17 @@ void Profile::writeModlistNow(bool onlyOnTimer) const ModInfo::Ptr modInfo = ModInfo::getByIndex(index); if (modInfo->getFixedPriority() == INT_MIN) { if (m_ModStatus[index].m_Enabled) { - file.write("+"); + file->write("+"); } else { - file.write("-"); + file->write("-"); } - file.write(modInfo->name().toUtf8()); - file.write("\r\n"); + file->write(modInfo->name().toUtf8()); + file->write("\r\n"); } } } - file.close(); - - - QString fileName = getModlistFileName(); - - if (QFile::exists(fileName)) { - shellDeleteQuiet(fileName); - } - - if (!file.copy(fileName)) { - reportError(tr("failed to open \"%1\" for writing").arg(fileName)); - return; - } + file.commit(); qDebug("%s saved", QDir::toNativeSeparators(fileName).toUtf8().constData()); } catch (const std::exception &e) { diff --git a/src/resources.qrc b/src/resources.qrc index 1582a3f2..73921f64 100644 --- a/src/resources.qrc +++ b/src/resources.qrc @@ -18,7 +18,7 @@ resources/contact-new.png resources/preferences-system.png resources/application-x-executable.png - resources/dialog-information.png + resources/dialog-information.png resources/emblem-readonly.png resources/go-next_16.png resources/go-previous_16.png diff --git a/src/safewritefile.cpp b/src/safewritefile.cpp new file mode 100644 index 00000000..6df8c2b8 --- /dev/null +++ b/src/safewritefile.cpp @@ -0,0 +1,48 @@ +/* +Copyright (C) 2014 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + + +#include "safewritefile.h" +#include + + +using namespace MOBase; + + +SafeWriteFile::SafeWriteFile(const QString &fileName) +: m_FileName(fileName) +{ + if (!m_TempFile.open()) { + throw MyException(QObject::tr("failed to open temporary file")); + } +} + + +QFile *SafeWriteFile::operator->() { + Q_ASSERT(m_TempFile.isOpen()); + return &m_TempFile; +} + + +void SafeWriteFile::commit() { + shellDeleteQuiet(m_FileName); + m_TempFile.rename(m_FileName); + m_TempFile.setAutoRemove(false); + m_TempFile.close(); +} diff --git a/src/safewritefile.h b/src/safewritefile.h new file mode 100644 index 00000000..56bd7744 --- /dev/null +++ b/src/safewritefile.h @@ -0,0 +1,51 @@ +/* +Copyright (C) 2014 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + + +#ifndef SAFEWRITEFILE_H +#define SAFEWRITEFILE_H + + +#include +#include +#include + +/** + * @brief a wrapper for QFile that ensures the file is only actually (over-)written if writing was successful + */ +class SafeWriteFile { +public: + SafeWriteFile(const QString &fileName); + + QFile *operator->(); + + void commit(); + +private: + QString m_FileName; + QTemporaryFile m_TempFile; +}; + + +#endif // SAFEWRITEFILE_H + + + + + -- cgit v1.3.1 From f9eba9dc15ec655cc16efbb1ddccac3a562741d5 Mon Sep 17 00:00:00 2001 From: Tannin Date: Mon, 17 Feb 2014 21:15:20 +0100 Subject: - ini file changes that would have to overwrite ini tweaks are now stored in a separate file and actually get used. - a warning message advices users to clean out that separate file - slightly improved default main window layout - bugfix: newly created ini tweaks were added to the list with incorrect properties --- src/mainwindow.cpp | 4 ++ src/mainwindow.ui | 4 +- src/modinfo.cpp | 120 +++++++++++++++++++++-------------------------- src/modinfo.h | 6 +++ src/modinfodialog.cpp | 17 +++---- src/profile.cpp | 6 +++ src/profile.h | 5 ++ src/shared/appconfig.inc | 1 + 8 files changed, 85 insertions(+), 78 deletions(-) (limited to 'src/profile.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 12db5e2b..556c33f3 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -217,6 +217,9 @@ MainWindow::MainWindow(const QString &exeName, QSettings &initSettings, QWidget ui->bsaList->setLocalMoveOnly(true); + ui->splitter->setStretchFactor(0, 3); + ui->splitter->setStretchFactor(1, 2); + resizeLists(initSettings.contains("mod_list_state"), initSettings.contains("plugin_list_state")); QMenu *linkMenu = new QMenu(this); @@ -3158,6 +3161,7 @@ void MainWindow::displayModInformation(ModInfo::Ptr modInfo, unsigned int index, dialog->activateWindow(); connect(dialog, SIGNAL(finished(int)), this, SLOT(overwriteClosed(int))); } else { + modInfo->saveMeta(); ModInfoDialog dialog(modInfo, m_DirectoryStructure, this); connect(&dialog, SIGNAL(nexusLinkActivated(QString)), this, SLOT(nexusLinkActivated(QString))); connect(&dialog, SIGNAL(downloadRequest(QString)), this, SLOT(downloadRequestedNXM(QString))); diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 110c14c6..26cbbf83 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -6,8 +6,8 @@ 0 0 - 868 - 701 + 926 + 710
diff --git a/src/modinfo.cpp b/src/modinfo.cpp index ff44b392..ed13deae 100644 --- a/src/modinfo.cpp +++ b/src/modinfo.cpp @@ -300,8 +300,37 @@ ModInfoRegular::ModInfoRegular(const QDir &path, DirectoryEntry **directoryStruc 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); + readMeta(); + + connect(&m_NexusBridge, SIGNAL(descriptionAvailable(int,QVariant,QVariant)), this, SLOT(nxmDescriptionAvailable(int,QVariant,QVariant))); + connect(&m_NexusBridge, SIGNAL(endorsementToggled(int,QVariant,QVariant)), this, SLOT(nxmEndorsementToggled(int,QVariant,QVariant))); + connect(&m_NexusBridge, SIGNAL(requestFailed(int,int,QVariant,QString)), this, SLOT(nxmRequestFailed(int,int,QVariant,QString))); +} + + +ModInfoRegular::~ModInfoRegular() +{ + try { + saveMeta(); + } catch (const std::exception &e) { + qCritical("failed to save meta information for \"%s\": %s", + m_Name.toUtf8().constData(), e.what()); + } +} + +bool ModInfoRegular::isEmpty() const +{ + QDirIterator iter(m_Path, QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs); + if (!iter.hasNext()) return true; + iter.next(); + if ((iter.fileName() == "meta.ini") && !iter.hasNext()) return true; + return false; +} + + +void ModInfoRegular::readMeta() +{ + QSettings metaFile(m_Path + "/meta.ini", QSettings::IniFormat); m_Notes = metaFile.value("notes", "").toString(); m_NexusID = metaFile.value("modid", -1).toInt(); @@ -324,7 +353,6 @@ ModInfoRegular::ModInfoRegular(const QDir &path, DirectoryEntry **directoryStruc } } - QString categoriesString = metaFile.value("category", "").toString(); QStringList categories = categoriesString.split(',', QString::SkipEmptyParts); @@ -343,77 +371,39 @@ ModInfoRegular::ModInfoRegular(const QDir &path, DirectoryEntry **directoryStruc } } - connect(&m_NexusBridge, SIGNAL(descriptionAvailable(int,QVariant,QVariant)), this, SLOT(nxmDescriptionAvailable(int,QVariant,QVariant))); - connect(&m_NexusBridge, SIGNAL(endorsementToggled(int,QVariant,QVariant)), this, SLOT(nxmEndorsementToggled(int,QVariant,QVariant))); - connect(&m_NexusBridge, SIGNAL(requestFailed(int,int,QVariant,QString)), this, SLOT(nxmRequestFailed(int,int,QVariant,QString))); -} - - -ModInfoRegular::~ModInfoRegular() -{ - try { - saveMeta(); - } catch (const std::exception &e) { - qCritical("failed to save meta information for \"%s\": %s", - m_Name.toUtf8().constData(), e.what()); - } -} - -bool ModInfoRegular::isEmpty() const -{ - QDirIterator iter(m_Path, QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs); - if (!iter.hasNext()) return true; - iter.next(); - if ((iter.fileName() == "meta.ini") && !iter.hasNext()) return true; - return false; + m_MetaInfoChanged = false; } - void ModInfoRegular::saveMeta() { // only write meta data if the mod directory exists if (m_MetaInfoChanged && QFile::exists(absolutePath())) { - bool success = false; - { - QSettings metaFile(absolutePath().append("/meta.ini.new"), QSettings::IniFormat); - if (metaFile.status() == QSettings::NoError) { - std::set temp = m_Categories; - temp.erase(m_PrimaryCategory); - metaFile.setValue("category", QString("%1").arg(m_PrimaryCategory) + "," + SetJoin(temp, ",")); - metaFile.setValue("newestVersion", m_NewestVersion.canonicalString()); - metaFile.setValue("ignoredVersion", m_IgnoredVersion.canonicalString()); - metaFile.setValue("version", m_Version.canonicalString()); - if (m_NexusID != -1) { - metaFile.setValue("modid", m_NexusID); - } - metaFile.setValue("notes", m_Notes); - metaFile.setValue("nexusDescription", m_NexusDescription); - metaFile.setValue("lastNexusQuery", m_LastNexusQuery.toString(Qt::ISODate)); - if (m_EndorsedState != ENDORSED_UNKNOWN) { - metaFile.setValue("endorsed", m_EndorsedState); - } - metaFile.sync(); // sync needs to be called to ensure the file is created - - if (metaFile.status() == QSettings::NoError) { - success = true; - m_MetaInfoChanged = false; - } else { - reportError(tr("failed to write %1/meta.ini: error %2").arg(absolutePath()).arg(metaFile.status())); - } - } else { - reportError(tr("failed to write %1/meta.ini: error %2").arg(absolutePath()).arg(metaFile.status())); + QSettings metaFile(absolutePath().append("/meta.ini"), QSettings::IniFormat); + if (metaFile.status() == QSettings::NoError) { + std::set temp = m_Categories; + temp.erase(m_PrimaryCategory); + metaFile.setValue("category", QString("%1").arg(m_PrimaryCategory) + "," + SetJoin(temp, ",")); + metaFile.setValue("newestVersion", m_NewestVersion.canonicalString()); + metaFile.setValue("ignoredVersion", m_IgnoredVersion.canonicalString()); + metaFile.setValue("version", m_Version.canonicalString()); + if (m_NexusID != -1) { + metaFile.setValue("modid", m_NexusID); } - } - if (success) { - if (!QFile::remove(absolutePath().append("/meta.ini"))) { - qCritical("failed to remove %s", qPrintable(absolutePath().append("/meta.ini"))); - return; + metaFile.setValue("notes", m_Notes); + metaFile.setValue("nexusDescription", m_NexusDescription); + metaFile.setValue("lastNexusQuery", m_LastNexusQuery.toString(Qt::ISODate)); + if (m_EndorsedState != ENDORSED_UNKNOWN) { + metaFile.setValue("endorsed", m_EndorsedState); } - if (QFile::rename(absolutePath().append("/meta.ini.new"), absolutePath().append("/meta.ini"))) { - qDebug("%s written", qPrintable(absolutePath().append("/meta.ini"))); + metaFile.sync(); // sync needs to be called to ensure the file is created + + if (metaFile.status() == QSettings::NoError) { + m_MetaInfoChanged = false; } else { - qCritical("writing %s failed", qPrintable(absolutePath().append("/meta.ini"))); + reportError(tr("failed to write %1/meta.ini: error %2").arg(absolutePath()).arg(metaFile.status())); } + } else { + reportError(tr("failed to write %1/meta.ini: error %2").arg(absolutePath()).arg(metaFile.status())); } } } @@ -717,13 +707,11 @@ QString ModInfoRegular::getNexusDescription() const return m_NexusDescription; } - ModInfoRegular::EEndorsedState ModInfoRegular::endorsedState() const { return m_EndorsedState; } - ModInfoRegular::EConflictType ModInfoRegular::isConflicted() const { // this is costy so cache the result diff --git a/src/modinfo.h b/src/modinfo.h index b7d72584..8b37b915 100644 --- a/src/modinfo.h +++ b/src/modinfo.h @@ -431,6 +431,11 @@ public: */ void testValid(); + /** + * @brief reads meta information from disk + */ + virtual void readMeta() {} + /** * @brief stores meta information back to disk */ @@ -748,6 +753,7 @@ public: */ virtual void saveMeta(); + void readMeta(); private: enum EConflictType { diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 117b2726..dae4cc18 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -158,9 +158,8 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo ModInfoDialog::~ModInfoDialog() { m_ModInfo->setNotes(ui->notesEdit->toPlainText()); - saveIniTweaks(); saveCategories(ui->categoriesTree->invisibleRootItem()); - + saveIniTweaks(); // ini tweaks are written to the ini file directly. This is the only information not managed by ModInfo delete ui; delete m_Settings; } @@ -216,7 +215,6 @@ QByteArray ModInfoDialog::saveTabState() const stream << ui->tabWidget->count(); for (int i = 0; i < ui->tabWidget->count(); ++i) { stream << ui->tabWidget->widget(i)->objectName(); - } return result; @@ -475,18 +473,15 @@ void ModInfoDialog::openIniFile(const QString &fileName) void ModInfoDialog::saveIniTweaks() { - QListWidget *iniTweaksList = findChild("iniTweaksList"); - m_Settings->beginWriteArray("INI Tweaks"); int countEnabled = 0; - for (int i = 0; i < iniTweaksList->count(); ++i) { - if (iniTweaksList->item(i)->checkState() == Qt::Checked) { + for (int i = 0; i < ui->iniTweaksList->count(); ++i) { + if (ui->iniTweaksList->item(i)->checkState() == Qt::Checked) { m_Settings->setArrayIndex(countEnabled++); - m_Settings->setValue("name", iniTweaksList->item(i)->text()); + m_Settings->setValue("name", ui->iniTweaksList->item(i)->text()); } } - m_Settings->endArray(); } @@ -1229,8 +1224,10 @@ void ModInfoDialog::createTweak() return; } - QListWidgetItem *newTweak = new QListWidgetItem(name); + QListWidgetItem *newTweak = new QListWidgetItem(name + ".ini"); newTweak->setData(Qt::UserRole, "INI Tweaks/" + name + ".ini"); + newTweak->setFlags(newTweak->flags() | Qt::ItemIsUserCheckable); + newTweak->setCheckState(Qt::Unchecked); ui->iniTweaksList->addItem(newTweak); } diff --git a/src/profile.cpp b/src/profile.cpp index c1f1025e..41a867c5 100644 --- a/src/profile.cpp +++ b/src/profile.cpp @@ -201,6 +201,7 @@ void Profile::createTweakedIniFile() } } + mergeTweak(getProfileTweaks(), tweakedIni); bool error = false; if (!::WritePrivateProfileStringW(L"Archive", L"bInvalidateOlderFiles", L"1", ToWString(tweakedIni).c_str())) { @@ -740,6 +741,11 @@ QString Profile::getIniFileName() const return m_Directory.absoluteFilePath(ToQString(primaryIniFile)); } +QString Profile::getProfileTweaks() const +{ + return QDir::cleanPath(m_Directory.absoluteFilePath(ToQString(AppConfig::profileTweakIni()))); +} + QString Profile::getPath() const { diff --git a/src/profile.h b/src/profile.h index 9c678a23..4960671a 100644 --- a/src/profile.h +++ b/src/profile.h @@ -177,6 +177,11 @@ public: **/ QString getIniFileName() const; + /** + * @return the path of the tweak ini in this profile + */ + QString getProfileTweaks() const; + /** * @return path to this profile **/ diff --git a/src/shared/appconfig.inc b/src/shared/appconfig.inc index 7710925a..080d3655 100644 --- a/src/shared/appconfig.inc +++ b/src/shared/appconfig.inc @@ -1,6 +1,7 @@ APPPARAM(std::wstring, translationPrefix, L"organizer") APPPARAM(std::wstring, pluginPath, L"plugins") APPPARAM(std::wstring, stylesheetsPath, L"stylesheets") +APPPARAM(std::wstring, profileTweakIni, L"profile_tweaks.ini") APPPARAM(std::wstring, logFile, L"ModOrganizer.log") APPPARAM(std::wstring, proxyDLLTarget, L"steam_api.dll") APPPARAM(std::wstring, proxyDLLOrig, L"steam_api_orig.dll") // needs to be identical to the value used in proxydll-project -- cgit v1.3.1 From 28301e7486423a2ca0b5434d1538d36b05f4ac86 Mon Sep 17 00:00:00 2001 From: Tannin Date: Wed, 26 Mar 2014 15:35:59 +0100 Subject: - improved NCC compatibility - crude support for multi-volume archives - updated imageformats plugins - nxmhandler now puts the exe to the top of the list when registering an MO instance, even if it is already in the list - bugfix: WritePrivateProfileString hook attempted to access lpKeyName even when it is null --- src/installationmanager.cpp | 17 ++++++++++++----- src/pluginlist.cpp | 3 +-- src/profile.cpp | 4 ++-- 3 files changed, 15 insertions(+), 9 deletions(-) (limited to 'src/profile.cpp') diff --git a/src/installationmanager.cpp b/src/installationmanager.cpp index e6e660e6..7b8ed176 100644 --- a/src/installationmanager.cpp +++ b/src/installationmanager.cpp @@ -69,7 +69,7 @@ template T resolveFunction(QLibrary &lib, const char *name) InstallationManager::InstallationManager(QWidget *parent) : QObject(parent), m_ParentWidget(parent), - m_InstallationProgress(parent), m_SupportedExtensions(boost::assign::list_of("zip")("rar")("7z")("fomod")) + m_InstallationProgress(parent), m_SupportedExtensions(boost::assign::list_of("zip")("rar")("7z")("fomod")("001")) { QLibrary archiveLib("dlls\\archive.dll"); if (!archiveLib.load()) { @@ -659,7 +659,9 @@ bool InstallationManager::install(const QString &fileName, GuessedValue // open the archive and construct the directory tree the installers work on bool archiveOpen = m_CurrentArchive->open(ToWString(QDir::toNativeSeparators(fileName)).c_str(), new MethodCallback(this, &InstallationManager::queryPassword)); - + if (!archiveOpen) { + qDebug("integrated archiver can't open %s. errorcode %d", qPrintable(fileName), m_CurrentArchive->getLastError()); + } ON_BLOCK_EXIT(std::bind(&InstallationManager::postInstallCleanup, this)); QScopedPointer filesTree(archiveOpen ? createFilesTree() : NULL); @@ -676,8 +678,12 @@ bool InstallationManager::install(const QString &fileName, GuessedValue } // try only manual installers if that was requested - if ((installResult == IPluginInstaller::RESULT_MANUALREQUESTED) && !installer->isManualInstaller()) { - continue; + if (installResult == IPluginInstaller::RESULT_MANUALREQUESTED) { + if (!installer->isManualInstaller()) { + continue; + } + } else if (installResult != IPluginInstaller::RESULT_NOTATTEMPTED) { + break; } try { @@ -719,7 +725,8 @@ bool InstallationManager::install(const QString &fileName, GuessedValue case IPluginInstaller::RESULT_FAILED: { return false; } break; - case IPluginInstaller::RESULT_SUCCESS: { + case IPluginInstaller::RESULT_SUCCESS: + case IPluginInstaller::RESULT_SUCCESSCANCEL: { if (filesTree != NULL) { DirectoryTree::node_iterator iniTweakNode = filesTree->nodeFind(DirectoryTreeInformation("INI Tweaks")); hasIniTweaks = (iniTweakNode != filesTree->nodesEnd()) && diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp index 289032bb..af6f42da 100644 --- a/src/pluginlist.cpp +++ b/src/pluginlist.cpp @@ -1035,9 +1035,8 @@ QVariant PluginList::data(const QModelIndex &modelIndex, int role) const result.append(QIcon(":/MO/gui/edit_clear")); } return result; - } else { - return QVariant(); } + return QVariant(); } diff --git a/src/profile.cpp b/src/profile.cpp index 41a867c5..42358b7d 100644 --- a/src/profile.cpp +++ b/src/profile.cpp @@ -93,7 +93,7 @@ Profile::Profile(const QDir& directory) GameInfo::instance().repairProfile(ToWString(m_Directory.absolutePath())); if (!QFile::exists(getIniFileName())) { - reportError(QObject::tr("\"%1\" is missing").arg(getIniFileName())); + reportError(QObject::tr("\"%1\" is missing or inaccessible").arg(getIniFileName())); } refreshModStatus(); } @@ -231,7 +231,7 @@ void Profile::refreshModStatus() { QFile file(getModlistFileName()); if (!file.exists()) { - throw MyException(QObject::tr("failed to find \"%1\"").arg(getModlistFileName())); + throw MyException(tr("\"%1\" is missing or inaccessible").arg(getModlistFileName())); } bool modStatusModified = false; -- cgit v1.3.1