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') 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