diff --git a/.clang-format b/.clang-format new file mode 100644 index 000000000..6098e1f5f --- /dev/null +++ b/.clang-format @@ -0,0 +1,41 @@ +--- +# We'll use defaults from the LLVM style, but with 4 columns indentation. +BasedOnStyle: LLVM +IndentWidth: 2 +--- +Language: Cpp +DeriveLineEnding: false +UseCRLF: true +DerivePointerAlignment: false +PointerAlignment: Left +AlignConsecutiveAssignments: true +AllowShortFunctionsOnASingleLine: Inline +AllowShortIfStatementsOnASingleLine: Never +AllowShortLambdasOnASingleLine: Empty +AlwaysBreakTemplateDeclarations: Yes +AccessModifierOffset: -2 +AlignTrailingComments: true +SpacesBeforeTrailingComments: 2 +NamespaceIndentation: Inner +MaxEmptyLinesToKeep: 1 +BreakBeforeBraces: Custom +BraceWrapping: + AfterCaseLabel: false + AfterClass: true + AfterControlStatement: false + AfterEnum: true + AfterFunction: true + AfterNamespace: true + AfterStruct: true + AfterUnion: true + AfterExternBlock: true + BeforeCatch: false + BeforeElse: false + BeforeLambdaBody: false + BeforeWhile: false + IndentBraces: false + SplitEmptyFunction: false + SplitEmptyRecord: false + SplitEmptyNamespace: true +ColumnLimit: 88 +ForEachMacros: ['Q_FOREACH', 'foreach'] diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 000000000..cf5f6c126 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,2 @@ +d13f6bb870cdda71257f665367be8ef9fca86255 +86bb01ba9eac879d3685c439ac9da0028bc4bc80 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..f86971274 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,7 @@ +# Set the default behavior, in case people don't have core.autocrlf set. +* text=auto + +# Explicitly declare text files you want to always be normalized and converted +# to native line endings on checkout. +*.cpp text eol=crlf +*.h text eol=crlf diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 000000000..098a8d852 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,20 @@ +name: Build ModOrganizer 2 + +on: + push: + branches: master + pull_request: + types: [opened, synchronize, reopened] + +jobs: + build: + runs-on: windows-2022 + steps: + - name: Build ModOrganizer 2 + uses: ModOrganizer2/build-with-mob-action@master + with: + qt-modules: qtpositioning qtwebchannel qtwebengine qtwebsockets + mo2-third-parties: + 7z zlib fmt gtest libbsarch libloot openssl libffi bzip2 python lz4 spdlog + boost boost-di sip pyqt pybind11 ss licenses explorerpp usvfs + mo2-dependencies: cmake_common uibase githubpp bsatk esptk archive lootcli game_gamebryo diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml new file mode 100644 index 000000000..43cdeeaed --- /dev/null +++ b/.github/workflows/linting.yml @@ -0,0 +1,17 @@ +name: Lint ModOrganizer 2 + +on: + push: + pull_request: + types: [opened, synchronize, reopened] + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Run clang-format + uses: jidicula/clang-format-action@v4.11.0 + with: + clang-format-version: "15" + check-path: "." diff --git a/src/aboutdialog.cpp b/src/aboutdialog.cpp index 781848735..1c70d91e4 100644 --- a/src/aboutdialog.cpp +++ b/src/aboutdialog.cpp @@ -1,126 +1,126 @@ -/* -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 "aboutdialog.h" -#include "ui_aboutdialog.h" -#include "shared/util.h" -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -AboutDialog::AboutDialog(const QString &version, QWidget *parent) - : QDialog(parent) - , ui(new Ui::AboutDialog) -{ - ui->setupUi(this); - - m_LicenseFiles[LICENSE_LGPL3] = "LGPL-v3.0.txt"; - m_LicenseFiles[LICENSE_LGPL21] = "GNU-LGPL-v2.1.txt"; - m_LicenseFiles[LICENSE_GPL3] = "GPL-v3.0.txt"; - m_LicenseFiles[LICENSE_GPL2] = "GPL-v2.0.txt"; - m_LicenseFiles[LICENSE_BOOST] = "boost.txt"; - m_LicenseFiles[LICENSE_7ZIP] = "7zip.txt"; - m_LicenseFiles[LICENSE_CCBY3] = "BY-SA-v3.0.txt"; - m_LicenseFiles[LICENSE_ZLIB] = "zlib.txt"; - m_LicenseFiles[LICENSE_PYTHON] = "python.txt"; - m_LicenseFiles[LICENSE_SSL] = "openssl.txt"; - m_LicenseFiles[LICENSE_CPPTOML] = "cpptoml.txt"; - m_LicenseFiles[LICENSE_UDIS] = "udis86.txt"; - m_LicenseFiles[LICENSE_SPDLOG] = "spdlog.txt"; - m_LicenseFiles[LICENSE_FMT] = "fmt.txt"; - m_LicenseFiles[LICENSE_SIP] = "sip.txt"; - m_LicenseFiles[LICENSE_CASTLE] = "Castle.txt"; - m_LicenseFiles[LICENSE_ANTLR] = "AntlrBuildTask.txt"; - m_LicenseFiles[LICENSE_DXTEX] = "DXTex.txt"; - m_LicenseFiles[LICENSE_VDF] = "ValveFileVDF.txt"; - - addLicense("Qt", LICENSE_LGPL3); - addLicense("Qt Json", LICENSE_GPL3); - addLicense("Boost Library", LICENSE_BOOST); - addLicense("7-zip", LICENSE_7ZIP); - addLicense("ZLib", LICENSE_NONE); - addLicense("Tango Icon Theme", LICENSE_NONE); - addLicense("RRZE Icon Set", LICENSE_CCBY3); - addLicense("Icons by Lorc, Delapouite and sbed available on http://game-icons.net", LICENSE_CCBY3); - addLicense("Castle Core", LICENSE_CASTLE); - addLicense("ANTLR", LICENSE_ANTLR); - addLicense("LOOT", LICENSE_GPL3); - addLicense("Python", LICENSE_PYTHON); - addLicense("OpenSSL", LICENSE_SSL); - addLicense("cpptoml", LICENSE_CPPTOML); - addLicense("Udis86", LICENSE_UDIS); - addLicense("spdlog", LICENSE_SPDLOG); - addLicense("{fmt}", LICENSE_FMT); - addLicense("SIP", LICENSE_SIP); - addLicense("DXTex Headers", LICENSE_DXTEX); - addLicense("Valve File VDF Reader", LICENSE_VDF); - - ui->nameLabel->setText(QString("%1 %2").arg(ui->nameLabel->text()).arg(version)); -#if defined(HGID) - ui->revisionLabel->setText(ui->revisionLabel->text() + " " + HGID); -#elif defined(GITID) - ui->revisionLabel->setText(ui->revisionLabel->text() + " " + GITID); -#else - ui->revisionLabel->setText(ui->revisionLabel->text() + " unknown"); -#endif - - - ui->usvfsLabel->setText(ui->usvfsLabel->text() + " " + MOShared::getUsvfsVersionString()); - ui->licenseText->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); -} - - -AboutDialog::~AboutDialog() -{ - delete ui; -} - - -void AboutDialog::addLicense(const QString &name, Licenses license) -{ - QListWidgetItem *item = new QListWidgetItem(name); - item->setData(Qt::UserRole, license); - ui->creditsList->addItem(item); -} - - -void AboutDialog::on_creditsList_currentItemChanged(QListWidgetItem *current, QListWidgetItem*) -{ - auto iter = m_LicenseFiles.find(current->data(Qt::UserRole).toInt()); - if (iter != m_LicenseFiles.end()) { - QString filePath = qApp->applicationDirPath() + "/licenses/" + iter->second; - QString text = MOBase::readFileText(filePath); - ui->licenseText->setText(text); - } else { - ui->licenseText->setText(tr("No license")); - } -} - -void AboutDialog::on_sourceText_linkActivated(const QString &link) -{ - MOBase::shell::Open(QUrl(link)); -} +/* +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 "aboutdialog.h" +#include "shared/util.h" +#include "ui_aboutdialog.h" +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +AboutDialog::AboutDialog(const QString& version, QWidget* parent) + : QDialog(parent), ui(new Ui::AboutDialog) +{ + ui->setupUi(this); + + m_LicenseFiles[LICENSE_LGPL3] = "LGPL-v3.0.txt"; + m_LicenseFiles[LICENSE_LGPL21] = "GNU-LGPL-v2.1.txt"; + m_LicenseFiles[LICENSE_GPL3] = "GPL-v3.0.txt"; + m_LicenseFiles[LICENSE_GPL2] = "GPL-v2.0.txt"; + m_LicenseFiles[LICENSE_BOOST] = "boost.txt"; + m_LicenseFiles[LICENSE_7ZIP] = "7zip.txt"; + m_LicenseFiles[LICENSE_CCBY3] = "BY-SA-v3.0.txt"; + m_LicenseFiles[LICENSE_ZLIB] = "zlib.txt"; + m_LicenseFiles[LICENSE_PYTHON] = "python.txt"; + m_LicenseFiles[LICENSE_SSL] = "openssl.txt"; + m_LicenseFiles[LICENSE_CPPTOML] = "cpptoml.txt"; + m_LicenseFiles[LICENSE_UDIS] = "udis86.txt"; + m_LicenseFiles[LICENSE_SPDLOG] = "spdlog.txt"; + m_LicenseFiles[LICENSE_FMT] = "fmt.txt"; + m_LicenseFiles[LICENSE_SIP] = "sip.txt"; + m_LicenseFiles[LICENSE_CASTLE] = "Castle.txt"; + m_LicenseFiles[LICENSE_ANTLR] = "AntlrBuildTask.txt"; + m_LicenseFiles[LICENSE_DXTEX] = "DXTex.txt"; + m_LicenseFiles[LICENSE_VDF] = "ValveFileVDF.txt"; + + addLicense("Qt", LICENSE_LGPL3); + addLicense("Qt Json", LICENSE_GPL3); + addLicense("Boost Library", LICENSE_BOOST); + addLicense("7-zip", LICENSE_7ZIP); + addLicense("ZLib", LICENSE_NONE); + addLicense("Tango Icon Theme", LICENSE_NONE); + addLicense("RRZE Icon Set", LICENSE_CCBY3); + addLicense("Icons by Lorc, Delapouite and sbed available on http://game-icons.net", + LICENSE_CCBY3); + addLicense("Castle Core", LICENSE_CASTLE); + addLicense("ANTLR", LICENSE_ANTLR); + addLicense("LOOT", LICENSE_GPL3); + addLicense("Python", LICENSE_PYTHON); + addLicense("OpenSSL", LICENSE_SSL); + addLicense("cpptoml", LICENSE_CPPTOML); + addLicense("Udis86", LICENSE_UDIS); + addLicense("spdlog", LICENSE_SPDLOG); + addLicense("{fmt}", LICENSE_FMT); + addLicense("SIP", LICENSE_SIP); + addLicense("DXTex Headers", LICENSE_DXTEX); + addLicense("Valve File VDF Reader", LICENSE_VDF); + + ui->nameLabel->setText( + QString("%1 %2") + .arg(ui->nameLabel->text()) + .arg(version)); +#if defined(HGID) + ui->revisionLabel->setText(ui->revisionLabel->text() + " " + HGID); +#elif defined(GITID) + ui->revisionLabel->setText(ui->revisionLabel->text() + " " + GITID); +#else + ui->revisionLabel->setText(ui->revisionLabel->text() + " unknown"); +#endif + + ui->usvfsLabel->setText(ui->usvfsLabel->text() + " " + + MOShared::getUsvfsVersionString()); + ui->licenseText->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); +} + +AboutDialog::~AboutDialog() +{ + delete ui; +} + +void AboutDialog::addLicense(const QString& name, Licenses license) +{ + QListWidgetItem* item = new QListWidgetItem(name); + item->setData(Qt::UserRole, license); + ui->creditsList->addItem(item); +} + +void AboutDialog::on_creditsList_currentItemChanged(QListWidgetItem* current, + QListWidgetItem*) +{ + auto iter = m_LicenseFiles.find(current->data(Qt::UserRole).toInt()); + if (iter != m_LicenseFiles.end()) { + QString filePath = qApp->applicationDirPath() + "/licenses/" + iter->second; + QString text = MOBase::readFileText(filePath); + ui->licenseText->setText(text); + } else { + ui->licenseText->setText(tr("No license")); + } +} + +void AboutDialog::on_sourceText_linkActivated(const QString& link) +{ + MOBase::shell::Open(QUrl(link)); +} diff --git a/src/aboutdialog.h b/src/aboutdialog.h index ec1bb9d59..f76cdd3f2 100644 --- a/src/aboutdialog.h +++ b/src/aboutdialog.h @@ -1,84 +1,82 @@ -#ifndef ABOUTDIALOG_H -/* -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 . -*/ - - -#define ABOUTDIALOG_H - -#include -class QListWidgetItem; -#include -#include - -#include - -namespace Ui { - class AboutDialog; -} - -class AboutDialog : public QDialog -{ - Q_OBJECT - -public: - explicit AboutDialog(const QString &version, QWidget *parent = 0); - ~AboutDialog(); - -private: - - enum Licenses { - LICENSE_NONE, - LICENSE_LGPL3, - LICENSE_LGPL21, - LICENSE_GPL3, - LICENSE_GPL2, - LICENSE_BOOST, - LICENSE_CCBY3, - LICENSE_PYTHON, - LICENSE_SSL, - LICENSE_CPPTOML, - LICENSE_7ZIP, - LICENSE_ZLIB, - LICENSE_UDIS, - LICENSE_SPDLOG, - LICENSE_FMT, - LICENSE_SIP, - LICENSE_CASTLE, - LICENSE_ANTLR, - LICENSE_DXTEX, - LICENSE_VDF, - }; - -private: - - void addLicense(const QString &name, Licenses license); - -private slots: - void on_creditsList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous); - void on_sourceText_linkActivated(const QString &link); - -private: - - Ui::AboutDialog *ui; - - std::map m_LicenseFiles; - -}; - -#endif // ABOUTDIALOG_H +#ifndef ABOUTDIALOG_H +/* +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 . +*/ + +#define ABOUTDIALOG_H + +#include +class QListWidgetItem; +#include +#include + +#include + +namespace Ui +{ +class AboutDialog; +} + +class AboutDialog : public QDialog +{ + Q_OBJECT + +public: + explicit AboutDialog(const QString& version, QWidget* parent = 0); + ~AboutDialog(); + +private: + enum Licenses + { + LICENSE_NONE, + LICENSE_LGPL3, + LICENSE_LGPL21, + LICENSE_GPL3, + LICENSE_GPL2, + LICENSE_BOOST, + LICENSE_CCBY3, + LICENSE_PYTHON, + LICENSE_SSL, + LICENSE_CPPTOML, + LICENSE_7ZIP, + LICENSE_ZLIB, + LICENSE_UDIS, + LICENSE_SPDLOG, + LICENSE_FMT, + LICENSE_SIP, + LICENSE_CASTLE, + LICENSE_ANTLR, + LICENSE_DXTEX, + LICENSE_VDF, + }; + +private: + void addLicense(const QString& name, Licenses license); + +private slots: + void on_creditsList_currentItemChanged(QListWidgetItem* current, + QListWidgetItem* previous); + void on_sourceText_linkActivated(const QString& link); + +private: + Ui::AboutDialog* ui; + + std::map m_LicenseFiles; +}; + +#endif // ABOUTDIALOG_H diff --git a/src/activatemodsdialog.cpp b/src/activatemodsdialog.cpp index c7e3dca2e..67ad5b483 100644 --- a/src/activatemodsdialog.cpp +++ b/src/activatemodsdialog.cpp @@ -1,96 +1,94 @@ -/* -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 "activatemodsdialog.h" -#include "ui_activatemodsdialog.h" - -#include -#include -#include -#include -#include - -#include - -ActivateModsDialog::ActivateModsDialog(SaveGameInfo::MissingAssets const &missingAssets, QWidget *parent) - : TutorableDialog("ActivateMods", parent), ui(new Ui::ActivateModsDialog) -{ - ui->setupUi(this); - - QTableWidget *modsTable = findChild("modsTable"); - QHeaderView *headerView = modsTable->horizontalHeader(); - headerView->setSectionResizeMode(0, QHeaderView::Stretch); - headerView->setSectionResizeMode(1, QHeaderView::Interactive); - - int row = 0; - - modsTable->setRowCount(missingAssets.size()); - - for (SaveGameInfo::MissingAssets::const_iterator espIter = missingAssets.begin(); - espIter != missingAssets.end(); ++espIter, ++row) { - modsTable->setCellWidget(row, 0, new QLabel(espIter.key())); - if (espIter->size() == 0) { - modsTable->setCellWidget(row, 1, new QLabel(tr("not found"))); - } else { - QComboBox* combo = new QComboBox(); - for (QString const &mod : espIter.value()) { - combo->addItem(mod); - } - modsTable->setCellWidget(row, 1, combo); - } - } -} - - -ActivateModsDialog::~ActivateModsDialog() -{ - delete ui; -} - - -std::set ActivateModsDialog::getModsToActivate() -{ - std::set result; - QTableWidget *modsTable = findChild("modsTable"); - - for (int row = 0; row < modsTable->rowCount(); ++row) { - QComboBox *comboBox = dynamic_cast(modsTable->cellWidget(row, 1)); - if (comboBox != nullptr) { - result.insert(comboBox->currentText()); - } - } - return result; -} - - -std::set ActivateModsDialog::getESPsToActivate() -{ - std::set result; - QTableWidget *modsTable = findChild("modsTable"); - - for (int row = 0; row < modsTable->rowCount(); ++row) { - QComboBox *comboBox = dynamic_cast(modsTable->cellWidget(row, 1)); - if (comboBox != nullptr) { - QLabel *espName = dynamic_cast(modsTable->cellWidget(row, 0)); - - result.insert(espName->text()); - } - } - return result; -} +/* +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 "activatemodsdialog.h" +#include "ui_activatemodsdialog.h" + +#include +#include +#include +#include +#include + +#include + +ActivateModsDialog::ActivateModsDialog(SaveGameInfo::MissingAssets const& missingAssets, + QWidget* parent) + : TutorableDialog("ActivateMods", parent), ui(new Ui::ActivateModsDialog) +{ + ui->setupUi(this); + + QTableWidget* modsTable = findChild("modsTable"); + QHeaderView* headerView = modsTable->horizontalHeader(); + headerView->setSectionResizeMode(0, QHeaderView::Stretch); + headerView->setSectionResizeMode(1, QHeaderView::Interactive); + + int row = 0; + + modsTable->setRowCount(missingAssets.size()); + + for (SaveGameInfo::MissingAssets::const_iterator espIter = missingAssets.begin(); + espIter != missingAssets.end(); ++espIter, ++row) { + modsTable->setCellWidget(row, 0, new QLabel(espIter.key())); + if (espIter->size() == 0) { + modsTable->setCellWidget(row, 1, new QLabel(tr("not found"))); + } else { + QComboBox* combo = new QComboBox(); + for (QString const& mod : espIter.value()) { + combo->addItem(mod); + } + modsTable->setCellWidget(row, 1, combo); + } + } +} + +ActivateModsDialog::~ActivateModsDialog() +{ + delete ui; +} + +std::set ActivateModsDialog::getModsToActivate() +{ + std::set result; + QTableWidget* modsTable = findChild("modsTable"); + + for (int row = 0; row < modsTable->rowCount(); ++row) { + QComboBox* comboBox = dynamic_cast(modsTable->cellWidget(row, 1)); + if (comboBox != nullptr) { + result.insert(comboBox->currentText()); + } + } + return result; +} + +std::set ActivateModsDialog::getESPsToActivate() +{ + std::set result; + QTableWidget* modsTable = findChild("modsTable"); + + for (int row = 0; row < modsTable->rowCount(); ++row) { + QComboBox* comboBox = dynamic_cast(modsTable->cellWidget(row, 1)); + if (comboBox != nullptr) { + QLabel* espName = dynamic_cast(modsTable->cellWidget(row, 0)); + + result.insert(espName->text()); + } + } + return result; +} diff --git a/src/activatemodsdialog.h b/src/activatemodsdialog.h index 56dda237a..f7fd719f5 100644 --- a/src/activatemodsdialog.h +++ b/src/activatemodsdialog.h @@ -1,76 +1,79 @@ -/* -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 ACTIVATEMODSDIALOG_H -#define ACTIVATEMODSDIALOG_H - -#include "savegameinfo.h" -#include "tutorabledialog.h" - -#include - -class QString; -class QWidget; - -#include - -namespace Ui { - class ActivateModsDialog; -} - -/** - * @brief Dialog that is used to batch activate/deactivate mods and plugins - **/ -class ActivateModsDialog : public MOBase::TutorableDialog -{ - Q_OBJECT - -public: - /** - * @brief constructor - * - * @param missingPlugins a map containing missing plugins that need to be activated - * @param parent ... Defaults to 0. - **/ - explicit ActivateModsDialog(SaveGameInfo::MissingAssets const &missingAssets, QWidget *parent = 0); - ~ActivateModsDialog(); - - /** - * @brief get a list of mods that the user chose to activate - * - * @note This can of ocurse only be called after the dialog has been displayed - * - * @return set< QString > the mods to activate - **/ - std::set getModsToActivate(); - - /** - * @brief get a list of plugins that should be activated - * - * @return set< QString > the plugins to activate. This contains only plugins that become available after enabling the mods retrieved with getModsToActivate - **/ - std::set getESPsToActivate(); - -private slots: - -private: - Ui::ActivateModsDialog *ui; -}; - -#endif // ACTIVATEMODSDIALOG_H +/* +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 ACTIVATEMODSDIALOG_H +#define ACTIVATEMODSDIALOG_H + +#include "savegameinfo.h" +#include "tutorabledialog.h" + +#include + +class QString; +class QWidget; + +#include + +namespace Ui +{ +class ActivateModsDialog; +} + +/** + * @brief Dialog that is used to batch activate/deactivate mods and plugins + **/ +class ActivateModsDialog : public MOBase::TutorableDialog +{ + Q_OBJECT + +public: + /** + * @brief constructor + * + * @param missingPlugins a map containing missing plugins that need to be activated + * @param parent ... Defaults to 0. + **/ + explicit ActivateModsDialog(SaveGameInfo::MissingAssets const& missingAssets, + QWidget* parent = 0); + ~ActivateModsDialog(); + + /** + * @brief get a list of mods that the user chose to activate + * + * @note This can of ocurse only be called after the dialog has been displayed + * + * @return set< QString > the mods to activate + **/ + std::set getModsToActivate(); + + /** + * @brief get a list of plugins that should be activated + * + * @return set< QString > the plugins to activate. This contains only plugins that + *become available after enabling the mods retrieved with getModsToActivate + **/ + std::set getESPsToActivate(); + +private slots: + +private: + Ui::ActivateModsDialog* ui; +}; + +#endif // ACTIVATEMODSDIALOG_H diff --git a/src/apiuseraccount.cpp b/src/apiuseraccount.cpp index 976dd4880..4b908df0c 100644 --- a/src/apiuseraccount.cpp +++ b/src/apiuseraccount.cpp @@ -2,25 +2,20 @@ QString localizedUserAccountType(APIUserAccountTypes t) { - switch (t) - { - case APIUserAccountTypes::Regular: - return QObject::tr("Regular"); + switch (t) { + case APIUserAccountTypes::Regular: + return QObject::tr("Regular"); - case APIUserAccountTypes::Premium: - return QObject::tr("Premium"); + case APIUserAccountTypes::Premium: + return QObject::tr("Premium"); - case APIUserAccountTypes::None: // fall-through - default: - return QObject::tr("None"); + case APIUserAccountTypes::None: // fall-through + default: + return QObject::tr("None"); } } - -APIUserAccount::APIUserAccount() - : m_type(APIUserAccountTypes::None) -{ -} +APIUserAccount::APIUserAccount() : m_type(APIUserAccountTypes::None) {} bool APIUserAccount::isValid() const { @@ -84,9 +79,7 @@ APIUserAccount& APIUserAccount::limits(const APILimits& limits) int APIUserAccount::remainingRequests() const { - return std::max( - m_limits.remainingDailyRequests, - m_limits.remainingHourlyRequests); + return std::max(m_limits.remainingDailyRequests, m_limits.remainingHourlyRequests); } bool APIUserAccount::shouldThrottle() const diff --git a/src/apiuseraccount.h b/src/apiuseraccount.h index ac8931cf6..829654a54 100644 --- a/src/apiuseraccount.h +++ b/src/apiuseraccount.h @@ -4,8 +4,8 @@ #include /** -* represents user account types on a mod provider website such as nexus -*/ + * represents user account types on a mod provider website such as nexus + */ enum class APIUserAccountTypes { // not logged in @@ -20,10 +20,9 @@ enum class APIUserAccountTypes QString localizedUserAccountType(APIUserAccountTypes t); - /** -* current limits imposed on the user account -**/ + * current limits imposed on the user account + **/ struct APILimits { // maximum number of requests per day @@ -39,20 +38,18 @@ struct APILimits int remainingHourlyRequests = 0; }; - /** -* API statistics -*/ + * API statistics + */ struct APIStats { // number of API requests currently queued int requestsQueued = 0; }; - /** -* represents a user account on the mod provider website -*/ + * represents a user account on the mod provider website + */ class APIUserAccount { public: @@ -62,79 +59,76 @@ class APIUserAccount APIUserAccount(); - /** * whether the user is logged in */ bool isValid() const; /** - * api key - */ + * api key + */ const QString& apiKey() const; /** - * user id - */ + * user id + */ const QString& id() const; /** - * user name - */ + * user name + */ const QString& name() const; /** - * account type - */ + * account type + */ APIUserAccountTypes type() const; /** - * current API limits - */ + * current API limits + */ const APILimits& limits() const; - /** * sets the api key */ APIUserAccount& apiKey(const QString& key); /** - * sets the user id - */ + * sets the user id + */ APIUserAccount& id(const QString& id); /** - * sets the user name - **/ + * sets the user name + **/ APIUserAccount& name(const QString& name); /** - * sets the account type - */ + * sets the account type + */ APIUserAccount& type(APIUserAccountTypes type); /** - * sets the current limits - */ + * sets the current limits + */ APIUserAccount& limits(const APILimits& limits); - /** - * returns the number of remaining requests - */ + * returns the number of remaining requests + */ int remainingRequests() const; /** - * whether the number of remaining requests is low enough that further - * requests should be throttled - */ + * whether the number of remaining requests is low enough that further + * requests should be throttled + */ bool shouldThrottle() const; /** - * true if all the remaining requests have been used and the API will refuse - * further requests - */ + * true if all the remaining requests have been used and the API will refuse + * further requests + */ bool exhausted() const; private: @@ -143,4 +137,4 @@ class APIUserAccount APILimits m_limits; }; -#endif // APIUSERACCOUNT_H +#endif // APIUSERACCOUNT_H diff --git a/src/archivefiletree.cpp b/src/archivefiletree.cpp index 11e165b86..b90c4798f 100644 --- a/src/archivefiletree.cpp +++ b/src/archivefiletree.cpp @@ -29,9 +29,9 @@ using namespace MOBase; /** * We use custom file entries to store the index. */ -class ArchiveFileEntry : public virtual FileTreeEntry { +class ArchiveFileEntry : public virtual FileTreeEntry +{ public: - /** * @brief Create a new entry. * @@ -39,11 +39,12 @@ class ArchiveFileEntry : public virtual FileTreeEntry { * @param name The name of this entry. * @param index The index of the entry in the archive. */ - ArchiveFileEntry(std::shared_ptr parent, QString name, int index) : - FileTreeEntry(parent, name), m_Index(index) { - } + ArchiveFileEntry(std::shared_ptr parent, QString name, int index) + : FileTreeEntry(parent, name), m_Index(index) + {} - virtual std::shared_ptr clone() const override { + virtual std::shared_ptr clone() const override + { return std::make_shared(nullptr, name(), m_Index); } @@ -51,67 +52,70 @@ class ArchiveFileEntry : public virtual FileTreeEntry { const int m_Index; }; - /** * */ -class ArchiveFileTreeImpl: public virtual ArchiveFileTree, public virtual ArchiveFileEntry { +class ArchiveFileTreeImpl : public virtual ArchiveFileTree, + public virtual ArchiveFileEntry +{ public: - using File = std::tuple; -public: // Public for make_shared (but not accessible by other since not exposed in .h): +public + : // Public for make_shared (but not accessible by other since not exposed in .h): + ArchiveFileTreeImpl(std::shared_ptr parent, QString name, int index, + std::vector files) + : FileTreeEntry(parent, name), ArchiveFileEntry(parent, name, index), IFileTree(), + m_Files(std::move(files)) + {} - ArchiveFileTreeImpl(std::shared_ptr parent, QString name, int index, std::vector files) - : FileTreeEntry(parent, name), ArchiveFileEntry(parent, name, index), IFileTree(), m_Files(std::move(files)) { } +public: // Override to avoid VS warnings: + virtual std::shared_ptr astree() override { return IFileTree::astree(); } -public: // Override to avoid VS warnings: - - virtual std::shared_ptr astree() override { - return IFileTree::astree(); - } - - virtual std::shared_ptr astree() const override { + virtual std::shared_ptr astree() const override + { return IFileTree::astree(); } protected: - - virtual std::shared_ptr clone() const override { + virtual std::shared_ptr clone() const override + { return IFileTree::clone(); } -public: // Overrides: - +public: // Overrides: /** * */ - static void mapToArchive(IFileTree const& tree, QString path, std::vector const& data) + static void mapToArchive(IFileTree const& tree, QString path, + std::vector const& data) { if (path.length() > 0) { - // when using a long windows path (starting with \\?\) we apparently can have redundant - // . components in the path. This wasn't a problem with "regular" path names. + // when using a long windows path (starting with \\?\) we apparently can have + // redundant . components in the path. This wasn't a problem with "regular" path + // names. if (path == ".") { path.clear(); - } - else { + } else { path.append("\\"); } } for (auto const& entry : tree) { if (entry->isDir()) { - const ArchiveFileTreeImpl& archiveEntry = dynamic_cast(*entry); + const ArchiveFileTreeImpl& archiveEntry = + dynamic_cast(*entry); QString tmp = path + archiveEntry.name(); if (archiveEntry.m_Index != -1) { data[archiveEntry.m_Index]->addOutputFilePath(tmp.toStdWString()); } mapToArchive(*archiveEntry.astree(), tmp, data); - } - else { - const ArchiveFileEntry& archiveFileEntry = dynamic_cast(*entry); + } else { + const ArchiveFileEntry& archiveFileEntry = + dynamic_cast(*entry); if (archiveFileEntry.m_Index != -1) { - data[archiveFileEntry.m_Index]->addOutputFilePath((path + archiveFileEntry.name()).toStdWString()); + data[archiveFileEntry.m_Index]->addOutputFilePath( + (path + archiveFileEntry.name()).toStdWString()); } } } @@ -120,36 +124,42 @@ class ArchiveFileTreeImpl: public virtual ArchiveFileTree, public virtual Archiv /** * */ - void mapToArchive(Archive &archive) const override { + void mapToArchive(Archive& archive) const override + { mapToArchive(*this, "", archive.getFileList()); } protected: - /** - * Overriding makeDirectory and makeFile to create file tree or file entry with index -1. + * Overriding makeDirectory and makeFile to create file tree or file entry with index + * -1. * */ - virtual std::shared_ptr makeDirectory( - std::shared_ptr parent, QString name) const override { + virtual std::shared_ptr + makeDirectory(std::shared_ptr parent, QString name) const override + { return std::make_shared(parent, name, -1, std::vector{}); } - virtual std::shared_ptr makeFile( - std::shared_ptr parent, QString name) const override { + virtual std::shared_ptr + makeFile(std::shared_ptr parent, QString name) const override + { return std::make_shared(parent, name, -1); } - virtual bool doPopulate(std::shared_ptr parent, std::vector>& entries) const override { + virtual bool + doPopulate(std::shared_ptr parent, + std::vector>& entries) const override + { // Sort by name: - std::sort(std::begin(m_Files), std::end(m_Files), - [](const auto& a, const auto& b) { - return std::get<0>(a)[0].compare(std::get<0>(b)[0], Qt::CaseInsensitive) < 0; }); + std::sort(std::begin(m_Files), std::end(m_Files), [](const auto& a, const auto& b) { + return std::get<0>(a)[0].compare(std::get<0>(b)[0], Qt::CaseInsensitive) < 0; + }); // We know that the files are sorted: QString currentName = ""; - int currentIndex = -1; + int currentIndex = -1; std::vector currentFiles; for (auto& p : m_Files) { @@ -162,11 +172,12 @@ class ArchiveFileTreeImpl: public virtual ArchiveFileTree, public virtual Archiv // accumulated: if (currentName != std::get<0>(p)[0]) { - // We may or may not have an index here, it depends on the type of archive (some archives list - // intermediate non-empty folders, some don't): - entries.push_back(std::make_shared(parent, currentName, currentIndex, std::move(currentFiles))); + // We may or may not have an index here, it depends on the type of archive (some + // archives list intermediate non-empty folders, some don't): + entries.push_back(std::make_shared( + parent, currentName, currentIndex, std::move(currentFiles))); - currentFiles.clear(); // Back to a valid state. + currentFiles.clear(); // Back to a valid state. // Reset the index: currentIndex = -1; @@ -181,36 +192,35 @@ class ArchiveFileTreeImpl: public virtual ArchiveFileTree, public virtual Archiv // If it is not a directory, then it is a file in directly under this tree: if (!std::get<1>(p)) { entries.push_back( - std::make_shared(parent, currentName, std::get<2>(p))); + std::make_shared(parent, currentName, std::get<2>(p))); currentName = ""; - } - else { - // Otherwize, it is the actual "file" corresponding to the directory we are listing, so we can retrieve - // the index here: + } else { + // Otherwize, it is the actual "file" corresponding to the directory we are + // listing, so we can retrieve the index here: currentIndex = std::get<2>(p); } - } - else { - currentFiles.push_back({ - QStringList(std::get<0>(p).begin() + 1, std::get<0>(p).end()), std::get<1>(p), std::get<2>(p) - }); + } else { + currentFiles.push_back( + {QStringList(std::get<0>(p).begin() + 1, std::get<0>(p).end()), + std::get<1>(p), std::get<2>(p)}); } } if (currentName != "") { - entries.push_back(std::make_shared(parent, currentName, currentIndex, std::move(currentFiles))); + entries.push_back(std::make_shared( + parent, currentName, currentIndex, std::move(currentFiles))); } // Let the parent class sort the entries: return false; } - virtual std::shared_ptr doClone() const override { + virtual std::shared_ptr doClone() const override + { return std::make_shared(nullptr, name(), m_Index, m_Files); } private: - mutable std::vector m_Files; }; @@ -224,15 +234,15 @@ std::shared_ptr ArchiveFileTree::makeTree(Archive const& archiv for (size_t i = 0; i < data.size(); ++i) { // Ignore "." and ".." as they're useless and muck things up if (data[i]->getArchiveFilePath().compare(L".") == 0 || - data[i]->getArchiveFilePath().compare(L"..") == 0) - { + data[i]->getArchiveFilePath().compare(L"..") == 0) { continue; } - files.push_back(std::make_tuple( - QString::fromStdWString(data[i]->getArchiveFilePath()).replace("\\", "/").split("/", Qt::SkipEmptyParts), - data[i]->isDirectory(), - (int) i)); + files.push_back( + std::make_tuple(QString::fromStdWString(data[i]->getArchiveFilePath()) + .replace("\\", "/") + .split("/", Qt::SkipEmptyParts), + data[i]->isDirectory(), (int)i)); } auto tree = std::make_shared(nullptr, "", -1, std::move(files)); @@ -240,15 +250,16 @@ std::shared_ptr ArchiveFileTree::makeTree(Archive const& archiv } /** - * @brief Recursive function for the ArchiveFileTree::mapToArchive method. Need a template - * here because iterators from a vector of entries are not exactly the same as the iterators - * returned by a IFileTree. + * @brief Recursive function for the ArchiveFileTree::mapToArchive method. Need a + * template here because iterators from a vector of entries are not exactly the same as + * the iterators returned by a IFileTree. * */ template -void mapToArchive(std::vector const& data, It begin, It end) { +void mapToArchive(std::vector const& data, It begin, It end) +{ for (auto it = begin; it != end; ++it) { - auto entry = *it; + auto entry = *it; auto* aentry = dynamic_cast(entry.get()); if (aentry->m_Index != -1) { @@ -262,6 +273,8 @@ void mapToArchive(std::vector const& data, It begin, It end) { } } -void ArchiveFileTree::mapToArchive(Archive &archive, std::vector> const& entries) { +void ArchiveFileTree::mapToArchive( + Archive& archive, std::vector> const& entries) +{ ::mapToArchive(archive.getFileList(), entries.cbegin(), entries.cend()); } diff --git a/src/archivefiletree.h b/src/archivefiletree.h index e4a5cbdd2..3b95769d0 100644 --- a/src/archivefiletree.h +++ b/src/archivefiletree.h @@ -23,13 +23,12 @@ along with Mod Organizer. If not, see . #include "archive.h" #include "ifiletree.h" - /** * */ -class ArchiveFileTree: public virtual MOBase::IFileTree { +class ArchiveFileTree : public virtual MOBase::IFileTree +{ public: - /** * @brief Create a new file tree representing the given archive. * @@ -48,7 +47,7 @@ class ArchiveFileTree: public virtual MOBase::IFileTree { * @param archive The archive to update. Must be the one used to * create the tree. */ - virtual void mapToArchive(Archive &archive) const = 0; + virtual void mapToArchive(Archive& archive) const = 0; /** * @brief Update the given archive to prepare for the extraction @@ -61,12 +60,12 @@ class ArchiveFileTree: public virtual MOBase::IFileTree { * @param entries List of entries to mark for extraction. All the entries must * come from a tree created with the given archive. */ - static void mapToArchive(Archive &archive, std::vector> const& entries); + static void + mapToArchive(Archive& archive, + std::vector> const& entries); protected: - using IFileTree::IFileTree; - }; #endif diff --git a/src/bbcode.cpp b/src/bbcode.cpp index 36b60a668..f1a3d375b 100644 --- a/src/bbcode.cpp +++ b/src/bbcode.cpp @@ -1,292 +1,318 @@ -/* -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 "bbcode.h" -#include -#include -#include - -namespace BBCode { - -namespace log = MOBase::log; - -class BBCodeMap { - - typedef std::map > TagMap; - -public: - - static BBCodeMap &instance() { - static BBCodeMap s_Instance; - return s_Instance; - } - - QString convertTag(QString input, int &length) - { - // extract the tag name - auto match = m_TagNameExp.match(input, 1, QRegularExpression::NormalMatch, QRegularExpression::AnchoredMatchOption); - QString tagName = match.captured(0).toLower(); - TagMap::iterator tagIter = m_TagMap.find(tagName); - if (tagIter != m_TagMap.end()) { - // recognized tag - if (tagName.endsWith('=')) { - tagName.chop(1); - } - - int closeTagPos = 0; - int nextTagPos = 0; - int nextTagSearchIndex = input.indexOf("]"); - int closeTagLength = 0; - if (tagName == "*") { - // ends at the next bullet point - closeTagPos = input.indexOf(QRegularExpression("(\\[\\*\\]|)", QRegularExpression::CaseInsensitiveOption), 3); - // leave closeTagLength at 0 because we don't want to "eat" the next bullet point - } else if (tagName == "line") { - // ends immediately after the tag - closeTagPos = 6; - // leave closeTagLength at 0 because there is no close tag to skip over - } else { - QRegularExpression nextTag(QString("\\[%1[=\\]]?").arg(tagName), QRegularExpression::CaseInsensitiveOption); - QString closeTag = QString("[/%1]").arg(tagName); - closeTagPos = input.indexOf(closeTag, 0, Qt::CaseInsensitive); - nextTagPos = nextTag.match(input, nextTagSearchIndex).capturedStart(0); - while (nextTagPos != -1 && closeTagPos != -1 && nextTagPos < closeTagPos) { - closeTagPos = input.indexOf(closeTag, closeTagPos + closeTag.size(), Qt::CaseInsensitive); - nextTagSearchIndex = input.indexOf("]", nextTagPos); - nextTagPos = nextTag.match(input, nextTagSearchIndex).capturedStart(0); - } - if (closeTagPos == -1) { - // workaround to improve compatibility: add fake closing tag - input.append(closeTag); - closeTagPos = input.size() - closeTag.size(); - } - closeTagLength = closeTag.size(); - } - - if (closeTagPos > -1) { - length = closeTagPos + closeTagLength; - QString temp = input.mid(0, length); - tagIter->second.first.setPatternOptions(QRegularExpression::PatternOption::DotMatchesEverythingOption); - auto match = tagIter->second.first.match(temp); - if (match.hasMatch()) { - if (tagIter->second.second.isEmpty()) { - if (tagName == "color") { - QString color = match.captured(1); - QString content = match.captured(2); - if (color.at(0) == '#') { - return temp.replace(tagIter->second.first, QString("%2").arg(color, content)); - } else { - auto colIter = m_ColorMap.find(color.toLower()); - if (colIter != m_ColorMap.end()) { - color = colIter->second; - } - return temp.replace(tagIter->second.first, QString("%2").arg(color, content)); - } - } else { - log::warn("don't know how to deal with tag {}", tagName); - } - } else { - if (tagName == "*") { - temp.remove(QRegularExpression("(\\[/\\*\\])?(
)?$")); - } - return temp.replace(tagIter->second.first, tagIter->second.second); - } - } else { - // expression doesn't match. either the input string is invalid - // or the expression is - log::warn("{} doesn't match the expression for {}", temp, tagName); - length = 0; - return QString(); - } - } - } - - // not a recognized tag or tag invalid - length = 0; - return QString(); - } - -private: - BBCodeMap() - : m_TagNameExp("[a-zA-Z*]*=?") - { - m_TagMap["b"] = std::make_pair(QRegularExpression("\\[b\\](.*)\\[/b\\]"), - "\\1"); - m_TagMap["i"] = std::make_pair(QRegularExpression("\\[i\\](.*)\\[/i\\]"), - "\\1"); - m_TagMap["u"] = std::make_pair(QRegularExpression("\\[u\\](.*)\\[/u\\]"), - "\\1"); - m_TagMap["s"] = std::make_pair(QRegularExpression("\\[s\\](.*)\\[/s\\]"), - "\\1"); - m_TagMap["sub"] = std::make_pair(QRegularExpression("\\[sub\\](.*)\\[/sub\\]"), - "\\1"); - m_TagMap["sup"] = std::make_pair(QRegularExpression("\\[sup\\](.*)\\[/sup\\]"), - "\\1"); - m_TagMap["size="] = std::make_pair(QRegularExpression("\\[size=([^\\]]*)\\](.*)\\[/size\\]"), - "\\2"); - m_TagMap["color="] = std::make_pair(QRegularExpression("\\[color=([^\\]]*)\\](.*)\\[/color\\]"), - ""); - m_TagMap["font="] = std::make_pair(QRegularExpression("\\[font=([^\\]]*)\\](.*)\\[/font\\]"), - "\\2"); - m_TagMap["center"] = std::make_pair(QRegularExpression("\\[center\\](.*)\\[/center\\]"), - "
\\1
"); - m_TagMap["right"] = std::make_pair(QRegularExpression("\\[right\\](.*)\\[/right\\]"), - "
\\1
"); - m_TagMap["quote"] = std::make_pair(QRegularExpression("\\[quote\\](.*)\\[/quote\\]"), - "
\\1
"); - m_TagMap["quote="] = std::make_pair(QRegularExpression("\\[quote=([^\\]]*)\\](.*)\\[/quote\\]"), - "
\\2
"); - m_TagMap["spoiler"] = std::make_pair(QRegularExpression("\\[spoiler\\](.*)\\[/spoiler\\]"), - "
Spoiler:
Show
\\1
"); - m_TagMap["code"] = std::make_pair(QRegularExpression("\\[code\\](.*)\\[/code\\]"), - "\\1"); - m_TagMap["heading"]= std::make_pair(QRegularExpression("\\[heading\\](.*)\\[/heading\\]"), - "

\\1

"); - m_TagMap["line"] = std::make_pair(QRegularExpression("\\[line\\]"), - "
"); - - // lists - m_TagMap["list"] = std::make_pair(QRegularExpression("\\[list\\](.*)\\[/list\\]"), - "
    \\1
"); - m_TagMap["list="] = std::make_pair(QRegularExpression("\\[list.*\\](.*)\\[/list\\]"), - "
    \\1
"); - m_TagMap["ul"] = std::make_pair(QRegularExpression("\\[ul\\](.*)\\[/ul\\]"), - "
    \\1
"); - m_TagMap["ol"] = std::make_pair(QRegularExpression("\\[ol\\](.*)\\[/ol\\]"), - "
    \\1
"); - m_TagMap["li"] = std::make_pair(QRegularExpression("\\[li\\](.*)\\[/li\\]"), - "
  • \\1
  • "); - - // tables - m_TagMap["table"] = std::make_pair(QRegularExpression("\\[table\\](.*)\\[/table\\]"), - "\\1
    "); - m_TagMap["tr"] = std::make_pair(QRegularExpression("\\[tr\\](.*)\\[/tr\\]"), - "\\1"); - m_TagMap["th"] = std::make_pair(QRegularExpression("\\[th\\](.*)\\[/th\\]"), - "\\1"); - m_TagMap["td"] = std::make_pair(QRegularExpression("\\[td\\](.*)\\[/td\\]"), - "\\1"); - - // web content - m_TagMap["url"] = std::make_pair(QRegularExpression("\\[url\\](.*)\\[/url\\]"), - "\\1"); - m_TagMap["url="] = std::make_pair(QRegularExpression("\\[url=([^\\]]*)\\](.*)\\[/url\\]"), - "\\2"); - m_TagMap["img"] = std::make_pair(QRegularExpression("\\[img(?:\\s*width=\\d+\\s*,?\\s*height=\\d+)?\\](.*)\\[/img\\]"), - ""); - m_TagMap["img="] = std::make_pair(QRegularExpression("\\[img=([^\\]]*)\\](.*)\\[/img\\]"), - "\"\\1\""); - m_TagMap["email="] = std::make_pair(QRegularExpression("\\[email=\"?([^\\]]*)\"?\\](.*)\\[/email\\]"), - "\\2"); - m_TagMap["youtube"] = std::make_pair(QRegularExpression("\\[youtube\\](.*)\\[/youtube\\]"), - "https://www.youtube.com/watch?v=\\1"); - - - // make all patterns non-greedy and case-insensitive - for (TagMap::iterator iter = m_TagMap.begin(); iter != m_TagMap.end(); ++iter) { - iter->second.first.setPatternOptions(QRegularExpression::CaseInsensitiveOption | QRegularExpression::InvertedGreedinessOption); - } - - // this tag is in fact greedy - m_TagMap["*"] = std::make_pair(QRegularExpression("\\[\\*\\](.*)"), - "
  • \\1
  • "); - - m_ColorMap.insert(std::make_pair("red", "FF0000")); - m_ColorMap.insert(std::make_pair("green", "00FF00")); - m_ColorMap.insert(std::make_pair("blue", "0000FF")); - m_ColorMap.insert(std::make_pair("black", "000000")); - m_ColorMap.insert(std::make_pair("gray", "7F7F7F")); - m_ColorMap.insert(std::make_pair("white", "FFFFFF")); - m_ColorMap.insert(std::make_pair("yellow", "FFFF00")); - m_ColorMap.insert(std::make_pair("cyan", "00FFFF")); - m_ColorMap.insert(std::make_pair("magenta", "FF00FF")); - m_ColorMap.insert(std::make_pair("brown", "A52A2A")); - m_ColorMap.insert(std::make_pair("orange", "FFA500")); - m_ColorMap.insert(std::make_pair("gold", "FFD700")); - m_ColorMap.insert(std::make_pair("deepskyblue", "00BFFF")); - m_ColorMap.insert(std::make_pair("salmon", "FA8072")); - m_ColorMap.insert(std::make_pair("dodgerblue", "1E90FF")); - m_ColorMap.insert(std::make_pair("greenyellow", "ADFF2F")); - m_ColorMap.insert(std::make_pair("peru", "CD853F")); - } - -private: - - QRegularExpression m_TagNameExp; - TagMap m_TagMap; - std::map m_ColorMap; -}; - - -QString convertToHTML(const QString &inputParam) -{ - // this code goes over the input string once and replaces all bbtags - // it encounters. This function is called recursively for every replaced - // string to convert nested tags. - // - // This could be implemented simpler by applying a set of regular expressions - // for each recognized bb-tag one after the other but that would probably be - // very inefficient (O(n^2)). - - QString input = inputParam.mid(0).replace("\r\n", "
    "); - input.replace("\\\"", "\"").replace("\\'", "'"); - QString result; - int lastBlock = 0; - int pos = 0; - - // iterate over the input buffer - while ((pos = input.indexOf('[', lastBlock)) != -1) { - // append everything between the previous tag-block and the current one - result.append(input.mid(lastBlock, pos - lastBlock)); - - if ((pos < (input.size() - 1)) && (input.at(pos + 1) == '/')) { - // skip invalid end tag - int tagEnd = input.indexOf(']', pos) + 1; - if (tagEnd == 0) { - //no closing tag found - //move the pos up one so that the opening bracket is ignored next iteration - pos++; - } - else { - pos = tagEnd; - } - } else { - // convert the tag and content if necessary - int length = -1; - QString replacement = BBCodeMap::instance().convertTag(input.mid(pos), length); - if (length != 0) { - result.append(convertToHTML(replacement)); - // length contains the number of characters in the original tag - pos += length; - } else { - // nothing replaced - result.append('['); - ++pos; - } - } - lastBlock = pos; - } - - // append the remainder (everything after the last tag) - result.append(input.mid(lastBlock)); - return result; -} - -} // namespace BBCode - +/* +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 "bbcode.h" +#include +#include +#include + +namespace BBCode +{ + +namespace log = MOBase::log; + +class BBCodeMap +{ + + typedef std::map> TagMap; + +public: + static BBCodeMap& instance() + { + static BBCodeMap s_Instance; + return s_Instance; + } + + QString convertTag(QString input, int& length) + { + // extract the tag name + auto match = m_TagNameExp.match(input, 1, QRegularExpression::NormalMatch, + QRegularExpression::AnchoredMatchOption); + QString tagName = match.captured(0).toLower(); + TagMap::iterator tagIter = m_TagMap.find(tagName); + if (tagIter != m_TagMap.end()) { + // recognized tag + if (tagName.endsWith('=')) { + tagName.chop(1); + } + + int closeTagPos = 0; + int nextTagPos = 0; + int nextTagSearchIndex = input.indexOf("]"); + int closeTagLength = 0; + if (tagName == "*") { + // ends at the next bullet point + closeTagPos = + input.indexOf(QRegularExpression("(\\[\\*\\]|)", + QRegularExpression::CaseInsensitiveOption), + 3); + // leave closeTagLength at 0 because we don't want to "eat" the next bullet + // point + } else if (tagName == "line") { + // ends immediately after the tag + closeTagPos = 6; + // leave closeTagLength at 0 because there is no close tag to skip over + } else { + QRegularExpression nextTag(QString("\\[%1[=\\]]?").arg(tagName), + QRegularExpression::CaseInsensitiveOption); + QString closeTag = QString("[/%1]").arg(tagName); + closeTagPos = input.indexOf(closeTag, 0, Qt::CaseInsensitive); + nextTagPos = nextTag.match(input, nextTagSearchIndex).capturedStart(0); + while (nextTagPos != -1 && closeTagPos != -1 && nextTagPos < closeTagPos) { + closeTagPos = input.indexOf(closeTag, closeTagPos + closeTag.size(), + Qt::CaseInsensitive); + nextTagSearchIndex = input.indexOf("]", nextTagPos); + nextTagPos = nextTag.match(input, nextTagSearchIndex).capturedStart(0); + } + if (closeTagPos == -1) { + // workaround to improve compatibility: add fake closing tag + input.append(closeTag); + closeTagPos = input.size() - closeTag.size(); + } + closeTagLength = closeTag.size(); + } + + if (closeTagPos > -1) { + length = closeTagPos + closeTagLength; + QString temp = input.mid(0, length); + tagIter->second.first.setPatternOptions( + QRegularExpression::PatternOption::DotMatchesEverythingOption); + auto match = tagIter->second.first.match(temp); + if (match.hasMatch()) { + if (tagIter->second.second.isEmpty()) { + if (tagName == "color") { + QString color = match.captured(1); + QString content = match.captured(2); + if (color.at(0) == '#') { + return temp.replace(tagIter->second.first, + QString("%2") + .arg(color, content)); + } else { + auto colIter = m_ColorMap.find(color.toLower()); + if (colIter != m_ColorMap.end()) { + color = colIter->second; + } + return temp.replace(tagIter->second.first, + QString("%2") + .arg(color, content)); + } + } else { + log::warn("don't know how to deal with tag {}", tagName); + } + } else { + if (tagName == "*") { + temp.remove(QRegularExpression("(\\[/\\*\\])?(
    )?$")); + } + return temp.replace(tagIter->second.first, tagIter->second.second); + } + } else { + // expression doesn't match. either the input string is invalid + // or the expression is + log::warn("{} doesn't match the expression for {}", temp, tagName); + length = 0; + return QString(); + } + } + } + + // not a recognized tag or tag invalid + length = 0; + return QString(); + } + +private: + BBCodeMap() : m_TagNameExp("[a-zA-Z*]*=?") + { + m_TagMap["b"] = + std::make_pair(QRegularExpression("\\[b\\](.*)\\[/b\\]"), "\\1"); + m_TagMap["i"] = + std::make_pair(QRegularExpression("\\[i\\](.*)\\[/i\\]"), "\\1"); + m_TagMap["u"] = + std::make_pair(QRegularExpression("\\[u\\](.*)\\[/u\\]"), "\\1"); + m_TagMap["s"] = + std::make_pair(QRegularExpression("\\[s\\](.*)\\[/s\\]"), "\\1"); + m_TagMap["sub"] = + std::make_pair(QRegularExpression("\\[sub\\](.*)\\[/sub\\]"), "\\1"); + m_TagMap["sup"] = + std::make_pair(QRegularExpression("\\[sup\\](.*)\\[/sup\\]"), "\\1"); + m_TagMap["size="] = + std::make_pair(QRegularExpression("\\[size=([^\\]]*)\\](.*)\\[/size\\]"), + "\\2"); + m_TagMap["color="] = + std::make_pair(QRegularExpression("\\[color=([^\\]]*)\\](.*)\\[/color\\]"), ""); + m_TagMap["font="] = + std::make_pair(QRegularExpression("\\[font=([^\\]]*)\\](.*)\\[/font\\]"), + "\\2"); + m_TagMap["center"] = + std::make_pair(QRegularExpression("\\[center\\](.*)\\[/center\\]"), + "
    \\1
    "); + m_TagMap["right"] = + std::make_pair(QRegularExpression("\\[right\\](.*)\\[/right\\]"), + "
    \\1
    "); + m_TagMap["quote"] = + std::make_pair(QRegularExpression("\\[quote\\](.*)\\[/quote\\]"), + "
    \\1
    "); + m_TagMap["quote="] = + std::make_pair(QRegularExpression("\\[quote=([^\\]]*)\\](.*)\\[/quote\\]"), + "
    \\2
    "); + m_TagMap["spoiler"] = + std::make_pair(QRegularExpression("\\[spoiler\\](.*)\\[/spoiler\\]"), + "
    Spoiler:
    Show
    \\1
    "); + m_TagMap["code"] = std::make_pair(QRegularExpression("\\[code\\](.*)\\[/code\\]"), + "\\1"); + m_TagMap["heading"] = + std::make_pair(QRegularExpression("\\[heading\\](.*)\\[/heading\\]"), + "

    \\1

    "); + m_TagMap["line"] = std::make_pair(QRegularExpression("\\[line\\]"), "
    "); + + // lists + m_TagMap["list"] = + std::make_pair(QRegularExpression("\\[list\\](.*)\\[/list\\]"), "
      \\1
    "); + m_TagMap["list="] = std::make_pair( + QRegularExpression("\\[list.*\\](.*)\\[/list\\]"), "
      \\1
    "); + m_TagMap["ul"] = + std::make_pair(QRegularExpression("\\[ul\\](.*)\\[/ul\\]"), "
      \\1
    "); + m_TagMap["ol"] = + std::make_pair(QRegularExpression("\\[ol\\](.*)\\[/ol\\]"), "
      \\1
    "); + m_TagMap["li"] = + std::make_pair(QRegularExpression("\\[li\\](.*)\\[/li\\]"), "
  • \\1
  • "); + + // tables + m_TagMap["table"] = std::make_pair( + QRegularExpression("\\[table\\](.*)\\[/table\\]"), "\\1
    "); + m_TagMap["tr"] = + std::make_pair(QRegularExpression("\\[tr\\](.*)\\[/tr\\]"), "\\1"); + m_TagMap["th"] = + std::make_pair(QRegularExpression("\\[th\\](.*)\\[/th\\]"), "\\1"); + m_TagMap["td"] = + std::make_pair(QRegularExpression("\\[td\\](.*)\\[/td\\]"), "\\1"); + + // web content + m_TagMap["url"] = std::make_pair(QRegularExpression("\\[url\\](.*)\\[/url\\]"), + "\\1"); + m_TagMap["url="] = + std::make_pair(QRegularExpression("\\[url=([^\\]]*)\\](.*)\\[/url\\]"), + "\\2"); + m_TagMap["img"] = std::make_pair( + QRegularExpression( + "\\[img(?:\\s*width=\\d+\\s*,?\\s*height=\\d+)?\\](.*)\\[/img\\]"), + ""); + m_TagMap["img="] = + std::make_pair(QRegularExpression("\\[img=([^\\]]*)\\](.*)\\[/img\\]"), + "\"\\1\""); + m_TagMap["email="] = std::make_pair( + QRegularExpression("\\[email=\"?([^\\]]*)\"?\\](.*)\\[/email\\]"), + "\\2"); + m_TagMap["youtube"] = + std::make_pair(QRegularExpression("\\[youtube\\](.*)\\[/youtube\\]"), + "https://" + "www.youtube.com/watch?v=\\1"); + + // make all patterns non-greedy and case-insensitive + for (TagMap::iterator iter = m_TagMap.begin(); iter != m_TagMap.end(); ++iter) { + iter->second.first.setPatternOptions( + QRegularExpression::CaseInsensitiveOption | + QRegularExpression::InvertedGreedinessOption); + } + + // this tag is in fact greedy + m_TagMap["*"] = std::make_pair(QRegularExpression("\\[\\*\\](.*)"), "
  • \\1
  • "); + + m_ColorMap.insert(std::make_pair("red", "FF0000")); + m_ColorMap.insert(std::make_pair("green", "00FF00")); + m_ColorMap.insert(std::make_pair("blue", "0000FF")); + m_ColorMap.insert(std::make_pair("black", "000000")); + m_ColorMap.insert(std::make_pair("gray", "7F7F7F")); + m_ColorMap.insert(std::make_pair("white", "FFFFFF")); + m_ColorMap.insert(std::make_pair("yellow", "FFFF00")); + m_ColorMap.insert(std::make_pair("cyan", "00FFFF")); + m_ColorMap.insert(std::make_pair("magenta", "FF00FF")); + m_ColorMap.insert(std::make_pair("brown", "A52A2A")); + m_ColorMap.insert(std::make_pair("orange", "FFA500")); + m_ColorMap.insert(std::make_pair("gold", "FFD700")); + m_ColorMap.insert(std::make_pair("deepskyblue", "00BFFF")); + m_ColorMap.insert(std::make_pair("salmon", "FA8072")); + m_ColorMap.insert(std::make_pair("dodgerblue", "1E90FF")); + m_ColorMap.insert(std::make_pair("greenyellow", "ADFF2F")); + m_ColorMap.insert(std::make_pair("peru", "CD853F")); + } + +private: + QRegularExpression m_TagNameExp; + TagMap m_TagMap; + std::map m_ColorMap; +}; + +QString convertToHTML(const QString& inputParam) +{ + // this code goes over the input string once and replaces all bbtags + // it encounters. This function is called recursively for every replaced + // string to convert nested tags. + // + // This could be implemented simpler by applying a set of regular expressions + // for each recognized bb-tag one after the other but that would probably be + // very inefficient (O(n^2)). + + QString input = inputParam.mid(0).replace("\r\n", "
    "); + input.replace("\\\"", "\"").replace("\\'", "'"); + QString result; + int lastBlock = 0; + int pos = 0; + + // iterate over the input buffer + while ((pos = input.indexOf('[', lastBlock)) != -1) { + // append everything between the previous tag-block and the current one + result.append(input.mid(lastBlock, pos - lastBlock)); + + if ((pos < (input.size() - 1)) && (input.at(pos + 1) == '/')) { + // skip invalid end tag + int tagEnd = input.indexOf(']', pos) + 1; + if (tagEnd == 0) { + // no closing tag found + // move the pos up one so that the opening bracket is ignored next iteration + pos++; + } else { + pos = tagEnd; + } + } else { + // convert the tag and content if necessary + int length = -1; + QString replacement = BBCodeMap::instance().convertTag(input.mid(pos), length); + if (length != 0) { + result.append(convertToHTML(replacement)); + // length contains the number of characters in the original tag + pos += length; + } else { + // nothing replaced + result.append('['); + ++pos; + } + } + lastBlock = pos; + } + + // append the remainder (everything after the last tag) + result.append(input.mid(lastBlock)); + return result; +} + +} // namespace BBCode diff --git a/src/bbcode.h b/src/bbcode.h index 708dfe715..00e31da6e 100644 --- a/src/bbcode.h +++ b/src/bbcode.h @@ -1,40 +1,39 @@ -/* -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 BBCODE_H -#define BBCODE_H - - -#include - - -namespace BBCode { - -/** - * @brief convert a string with BB Code-Tags to HTML - * @param input the input string with BB tags - * @param replaceOccured if not nullptr, this parameter will be set to true if any bb tags were replaced - * @return the same string in html representation - **/ -QString convertToHTML(const QString &input); - -} - - -#endif // BBCODE_H +/* +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 BBCODE_H +#define BBCODE_H + +#include + +namespace BBCode +{ + +/** + * @brief convert a string with BB Code-Tags to HTML + * @param input the input string with BB tags + * @param replaceOccured if not nullptr, this parameter will be set to true if any bb + *tags were replaced + * @return the same string in html representation + **/ +QString convertToHTML(const QString& input); + +} // namespace BBCode + +#endif // BBCODE_H diff --git a/src/browserdialog.cpp b/src/browserdialog.cpp index 8e3413635..f9396db95 100644 --- a/src/browserdialog.cpp +++ b/src/browserdialog.cpp @@ -1,297 +1,289 @@ -/* -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 "browserdialog.h" - -#include "ui_browserdialog.h" -#include "browserview.h" -#include "messagedialog.h" -#include "report.h" -#include "persistentcookiejar.h" -#include "settings.h" - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace MOBase; - - -BrowserDialog::BrowserDialog(QWidget *parent) - : QDialog(parent) - , ui(new Ui::BrowserDialog) - , m_AccessManager(new QNetworkAccessManager(this)) -{ - ui->setupUi(this); - - m_AccessManager->setCookieJar(new PersistentCookieJar( - QDir::fromNativeSeparators(Settings::instance().paths().cache() + "/cookies.dat"))); - - Qt::WindowFlags flags = windowFlags() | Qt::WindowMaximizeButtonHint | Qt::WindowMinimizeButtonHint; - Qt::WindowFlags helpFlag = Qt::WindowContextHelpButtonHint; - flags = flags & (~helpFlag); - setWindowFlags(flags); - - m_Tabs = this->findChild("browserTabWidget"); - - installEventFilter(this); - - connect(m_Tabs, SIGNAL(tabCloseRequested(int)), this, SLOT(tabCloseRequested(int))); - - ui->urlEdit->setVisible(false); -} - -BrowserDialog::~BrowserDialog() -{ - delete ui; -} - -void BrowserDialog::closeEvent(QCloseEvent *event) -{ - Settings::instance().geometry().saveGeometry(this); - QDialog::closeEvent(event); -} - -void BrowserDialog::initTab(BrowserView *newView) -{ - //newView->page()->setNetworkAccessManager(m_AccessManager); - //newView->page()->setForwardUnsupportedContent(true); - - connect(newView, SIGNAL(loadProgress(int)), this, SLOT(progress(int))); - connect(newView, SIGNAL(titleChanged(QString)), this, SLOT(titleChanged(QString))); - connect(newView, SIGNAL(initTab(BrowserView*)), this, SLOT(initTab(BrowserView*))); - connect(newView, SIGNAL(startFind()), this, SLOT(startSearch())); - connect(newView, SIGNAL(urlChanged(QUrl)), this, SLOT(urlChanged(QUrl))); - connect(newView, SIGNAL(openUrlInNewTab(QUrl)), this, SLOT(openInNewTab(QUrl))); - connect(newView, SIGNAL(downloadRequested(QNetworkRequest)), this, SLOT(downloadRequested(QNetworkRequest))); - connect(newView, SIGNAL(unsupportedContent(QNetworkReply*)), this, SLOT(unsupportedContent(QNetworkReply*))); - - ui->backBtn->setEnabled(false); - ui->fwdBtn->setEnabled(false); - m_Tabs->addTab(newView, tr("new")); - newView->settings()->setAttribute(QWebEngineSettings::PluginsEnabled, true); - newView->settings()->setAttribute(QWebEngineSettings::AutoLoadImages, true); -} - - -void BrowserDialog::openInNewTab(const QUrl &url) -{ - BrowserView *newView = new BrowserView(this); - initTab(newView); - newView->setUrl(url); -} - - -BrowserView *BrowserDialog::getCurrentView() -{ - return qobject_cast(m_Tabs->currentWidget()); -} - - -void BrowserDialog::urlChanged(const QUrl &url) -{ - BrowserView *currentView = getCurrentView(); - if (currentView != nullptr) { - ui->backBtn->setEnabled(currentView->history()->canGoBack()); - ui->fwdBtn->setEnabled(currentView->history()->canGoForward()); - } - ui->urlEdit->setText(url.toString()); -} - - -void BrowserDialog::openUrl(const QUrl &url) -{ - if (isHidden()) { - Settings::instance().geometry().restoreGeometry(this); - show(); - } - openInNewTab(url); -} - - -void BrowserDialog::maximizeWidth() -{ - int viewportWidth = getCurrentView()->page()->contentsSize ().width(); - int frameWidth = width() - viewportWidth; - - int contentWidth = getCurrentView()->page()->contentsSize().width(); - - QScreen* screen = this->window()->windowHandle()->screen(); - int screenWidth = screen->geometry().size().width(); - - int targetWidth = std::min(std::max(viewportWidth, contentWidth) + frameWidth, screenWidth); - this->resize(targetWidth, height()); -} - - -void BrowserDialog::progress(int value) -{ - ui->loadProgress->setValue(value); - if (value == 100) { - maximizeWidth(); - ui->loadProgress->setVisible(false); - } else { - ui->loadProgress->setVisible(true); - } -} - - -void BrowserDialog::titleChanged(const QString &title) -{ - BrowserView *view = qobject_cast(sender()); - for (int i = 0; i < m_Tabs->count(); ++i) { - if (m_Tabs->widget(i) == view) { - m_Tabs->setTabText(i, title.mid(0, 15)); - m_Tabs->setTabToolTip(i, title); - } - } -} - - -QString BrowserDialog::guessFileName(const QString &url) -{ - QRegularExpression uploadsExp(QString("https://.+/uploads/([^/]+)$")); - auto match = uploadsExp.match(url); - if (match.hasMatch()) { - // these seem to be premium downloads - return match.captured(1); - } - - QRegularExpression filesExp(QString("https://.+\\?file=([^&]+)")); - match = filesExp.match(url); - if (match.hasMatch()) { - // a regular manual download? - return match.captured(1); - } - return "unknown"; -} - -void BrowserDialog::unsupportedContent(QNetworkReply *reply) -{ - try { - QWebEnginePage *page = qobject_cast(sender()); - if (page == nullptr) { - log::error("sender not a page"); - return; - } - /*browserview *view = qobject_cast(page->view()); - if (view == nullptr) { - log::error("no view?"); - return; - }*/ - - emit requestDownload(page->url(), reply); - } catch (const std::exception &e) { - if (isVisible()) { - MessageDialog::showMessage(tr("failed to start download"), this); - } - log::error("exception downloading unsupported content: {}", e.what()); - } -} - - -void BrowserDialog::downloadRequested(const QNetworkRequest &request) -{ - log::error("download request {} ignored", request.url().toString()); -} - - -void BrowserDialog::tabCloseRequested(int index) -{ - if (m_Tabs->count() == 1) { - this->close(); - } else { - m_Tabs->widget(index)->deleteLater(); - m_Tabs->removeTab(index); - } -} - -void BrowserDialog::on_backBtn_clicked() -{ - BrowserView *currentView = getCurrentView(); - if (currentView != nullptr) { - currentView->back(); - } -} - -void BrowserDialog::on_fwdBtn_clicked() -{ - BrowserView *currentView = getCurrentView(); - if (currentView != nullptr) { - currentView->forward(); - } -} - - -void BrowserDialog::startSearch() -{ - ui->searchEdit->setFocus(); -} - - -void BrowserDialog::on_searchEdit_returnPressed() -{ -// BrowserView *currentView = getCurrentView(); -// if (currentView != nullptr) { -// currentView->findText(ui->searchEdit->text(), QWebEnginePage::FindWrapsAroundDocument); -// } -} - -void BrowserDialog::on_refreshBtn_clicked() -{ - getCurrentView()->reload(); -} - -void BrowserDialog::on_browserTabWidget_currentChanged(int index) -{ - BrowserView *currentView = qobject_cast(ui->browserTabWidget->widget(index)); - if (currentView != nullptr) { - ui->backBtn->setEnabled(currentView->history()->canGoBack()); - ui->fwdBtn->setEnabled(currentView->history()->canGoForward()); - } -} - -void BrowserDialog::on_urlEdit_returnPressed() -{ - QWebEngineView *currentView = getCurrentView(); - if (currentView != nullptr) { - currentView->setUrl(QUrl(ui->urlEdit->text())); - } -} - -bool BrowserDialog::eventFilter(QObject *object, QEvent *event) -{ - if (event->type() == QEvent::KeyPress) { - QKeyEvent *keyEvent = reinterpret_cast(event); - if ((keyEvent->modifiers() & Qt::ControlModifier) - && (keyEvent->key() == Qt::Key_U)) { - ui->urlEdit->setVisible(!ui->urlEdit->isVisible()); - return true; - } - } - return QDialog::eventFilter(object, event); -} +/* +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 "browserdialog.h" + +#include "browserview.h" +#include "messagedialog.h" +#include "persistentcookiejar.h" +#include "report.h" +#include "settings.h" +#include "ui_browserdialog.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace MOBase; + +BrowserDialog::BrowserDialog(QWidget* parent) + : QDialog(parent), ui(new Ui::BrowserDialog), + m_AccessManager(new QNetworkAccessManager(this)) +{ + ui->setupUi(this); + + m_AccessManager->setCookieJar(new PersistentCookieJar(QDir::fromNativeSeparators( + Settings::instance().paths().cache() + "/cookies.dat"))); + + Qt::WindowFlags flags = + windowFlags() | Qt::WindowMaximizeButtonHint | Qt::WindowMinimizeButtonHint; + Qt::WindowFlags helpFlag = Qt::WindowContextHelpButtonHint; + flags = flags & (~helpFlag); + setWindowFlags(flags); + + m_Tabs = this->findChild("browserTabWidget"); + + installEventFilter(this); + + connect(m_Tabs, SIGNAL(tabCloseRequested(int)), this, SLOT(tabCloseRequested(int))); + + ui->urlEdit->setVisible(false); +} + +BrowserDialog::~BrowserDialog() +{ + delete ui; +} + +void BrowserDialog::closeEvent(QCloseEvent* event) +{ + Settings::instance().geometry().saveGeometry(this); + QDialog::closeEvent(event); +} + +void BrowserDialog::initTab(BrowserView* newView) +{ + // newView->page()->setNetworkAccessManager(m_AccessManager); + // newView->page()->setForwardUnsupportedContent(true); + + connect(newView, SIGNAL(loadProgress(int)), this, SLOT(progress(int))); + connect(newView, SIGNAL(titleChanged(QString)), this, SLOT(titleChanged(QString))); + connect(newView, SIGNAL(initTab(BrowserView*)), this, SLOT(initTab(BrowserView*))); + connect(newView, SIGNAL(startFind()), this, SLOT(startSearch())); + connect(newView, SIGNAL(urlChanged(QUrl)), this, SLOT(urlChanged(QUrl))); + connect(newView, SIGNAL(openUrlInNewTab(QUrl)), this, SLOT(openInNewTab(QUrl))); + connect(newView, SIGNAL(downloadRequested(QNetworkRequest)), this, + SLOT(downloadRequested(QNetworkRequest))); + connect(newView, SIGNAL(unsupportedContent(QNetworkReply*)), this, + SLOT(unsupportedContent(QNetworkReply*))); + + ui->backBtn->setEnabled(false); + ui->fwdBtn->setEnabled(false); + m_Tabs->addTab(newView, tr("new")); + newView->settings()->setAttribute(QWebEngineSettings::PluginsEnabled, true); + newView->settings()->setAttribute(QWebEngineSettings::AutoLoadImages, true); +} + +void BrowserDialog::openInNewTab(const QUrl& url) +{ + BrowserView* newView = new BrowserView(this); + initTab(newView); + newView->setUrl(url); +} + +BrowserView* BrowserDialog::getCurrentView() +{ + return qobject_cast(m_Tabs->currentWidget()); +} + +void BrowserDialog::urlChanged(const QUrl& url) +{ + BrowserView* currentView = getCurrentView(); + if (currentView != nullptr) { + ui->backBtn->setEnabled(currentView->history()->canGoBack()); + ui->fwdBtn->setEnabled(currentView->history()->canGoForward()); + } + ui->urlEdit->setText(url.toString()); +} + +void BrowserDialog::openUrl(const QUrl& url) +{ + if (isHidden()) { + Settings::instance().geometry().restoreGeometry(this); + show(); + } + openInNewTab(url); +} + +void BrowserDialog::maximizeWidth() +{ + int viewportWidth = getCurrentView()->page()->contentsSize().width(); + int frameWidth = width() - viewportWidth; + + int contentWidth = getCurrentView()->page()->contentsSize().width(); + + QScreen* screen = this->window()->windowHandle()->screen(); + int screenWidth = screen->geometry().size().width(); + + int targetWidth = std::min( + std::max(viewportWidth, contentWidth) + frameWidth, screenWidth); + this->resize(targetWidth, height()); +} + +void BrowserDialog::progress(int value) +{ + ui->loadProgress->setValue(value); + if (value == 100) { + maximizeWidth(); + ui->loadProgress->setVisible(false); + } else { + ui->loadProgress->setVisible(true); + } +} + +void BrowserDialog::titleChanged(const QString& title) +{ + BrowserView* view = qobject_cast(sender()); + for (int i = 0; i < m_Tabs->count(); ++i) { + if (m_Tabs->widget(i) == view) { + m_Tabs->setTabText(i, title.mid(0, 15)); + m_Tabs->setTabToolTip(i, title); + } + } +} + +QString BrowserDialog::guessFileName(const QString& url) +{ + QRegularExpression uploadsExp(QString("https://.+/uploads/([^/]+)$")); + auto match = uploadsExp.match(url); + if (match.hasMatch()) { + // these seem to be premium downloads + return match.captured(1); + } + + QRegularExpression filesExp(QString("https://.+\\?file=([^&]+)")); + match = filesExp.match(url); + if (match.hasMatch()) { + // a regular manual download? + return match.captured(1); + } + return "unknown"; +} + +void BrowserDialog::unsupportedContent(QNetworkReply* reply) +{ + try { + QWebEnginePage* page = qobject_cast(sender()); + if (page == nullptr) { + log::error("sender not a page"); + return; + } + /*browserview *view = qobject_cast(page->view()); + if (view == nullptr) { + log::error("no view?"); + return; + }*/ + + emit requestDownload(page->url(), reply); + } catch (const std::exception& e) { + if (isVisible()) { + MessageDialog::showMessage(tr("failed to start download"), this); + } + log::error("exception downloading unsupported content: {}", e.what()); + } +} + +void BrowserDialog::downloadRequested(const QNetworkRequest& request) +{ + log::error("download request {} ignored", request.url().toString()); +} + +void BrowserDialog::tabCloseRequested(int index) +{ + if (m_Tabs->count() == 1) { + this->close(); + } else { + m_Tabs->widget(index)->deleteLater(); + m_Tabs->removeTab(index); + } +} + +void BrowserDialog::on_backBtn_clicked() +{ + BrowserView* currentView = getCurrentView(); + if (currentView != nullptr) { + currentView->back(); + } +} + +void BrowserDialog::on_fwdBtn_clicked() +{ + BrowserView* currentView = getCurrentView(); + if (currentView != nullptr) { + currentView->forward(); + } +} + +void BrowserDialog::startSearch() +{ + ui->searchEdit->setFocus(); +} + +void BrowserDialog::on_searchEdit_returnPressed() +{ + // BrowserView *currentView = getCurrentView(); + // if (currentView != nullptr) { + // currentView->findText(ui->searchEdit->text(), + // QWebEnginePage::FindWrapsAroundDocument); + // } +} + +void BrowserDialog::on_refreshBtn_clicked() +{ + getCurrentView()->reload(); +} + +void BrowserDialog::on_browserTabWidget_currentChanged(int index) +{ + BrowserView* currentView = + qobject_cast(ui->browserTabWidget->widget(index)); + if (currentView != nullptr) { + ui->backBtn->setEnabled(currentView->history()->canGoBack()); + ui->fwdBtn->setEnabled(currentView->history()->canGoForward()); + } +} + +void BrowserDialog::on_urlEdit_returnPressed() +{ + QWebEngineView* currentView = getCurrentView(); + if (currentView != nullptr) { + currentView->setUrl(QUrl(ui->urlEdit->text())); + } +} + +bool BrowserDialog::eventFilter(QObject* object, QEvent* event) +{ + if (event->type() == QEvent::KeyPress) { + QKeyEvent* keyEvent = reinterpret_cast(event); + if ((keyEvent->modifiers() & Qt::ControlModifier) && + (keyEvent->key() == Qt::Key_U)) { + ui->urlEdit->setVisible(!ui->urlEdit->isVisible()); + return true; + } + } + return QDialog::eventFilter(object, event); +} diff --git a/src/browserdialog.h b/src/browserdialog.h index 354a377b4..2c9781b26 100644 --- a/src/browserdialog.h +++ b/src/browserdialog.h @@ -1,126 +1,121 @@ -/* -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 BROWSERDIALOG_H -#define BROWSERDIALOG_H - -#include -#include -#include -#include -#include -#include -#include -#include - - -namespace Ui { - class BrowserDialog; -} - -class BrowserView; - -/** - * @brief a dialog containing a webbrowser that is intended to browse the nexus network - **/ -class BrowserDialog : public QDialog -{ - Q_OBJECT - -public: - - /** - * @brief constructor - * - * @param accessManager the access manager to use for network requests - * @param parent parent widget - **/ - explicit BrowserDialog(QWidget *parent = 0); - ~BrowserDialog(); - - /** - * @brief set the url to open. If automatic login is enabled, the url is opened after login - * - * @param url the url to open - **/ - void openUrl(const QUrl &url); - - virtual bool eventFilter(QObject *object, QEvent *event); -signals: - - /** - * @brief emitted when the user starts a download - * @param pageUrl url of the current web site from which the download was started - * @param reply network reply of the started download - */ - void requestDownload(const QUrl &pageUrl, QNetworkReply *reply); - -protected: - - virtual void closeEvent(QCloseEvent *); - -private slots: - - void initTab(BrowserView *newView); - void openInNewTab(const QUrl &url); - - void progress(int value); - - void titleChanged(const QString &title); - void unsupportedContent(QNetworkReply *reply); - void downloadRequested(const QNetworkRequest &request); - - void tabCloseRequested(int index); - - void urlChanged(const QUrl &url); - - void on_backBtn_clicked(); - - void on_fwdBtn_clicked(); - - void on_searchEdit_returnPressed(); - - void startSearch(); - - void on_refreshBtn_clicked(); - - void on_browserTabWidget_currentChanged(int index); - - void on_urlEdit_returnPressed(); - -private: - - QString guessFileName(const QString &url); - - BrowserView *getCurrentView(); - - void maximizeWidth(); - -private: - - Ui::BrowserDialog *ui; - - QNetworkAccessManager *m_AccessManager; - - QTabWidget *m_Tabs; - - -}; - -#endif // BROWSERDIALOG_H +/* +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 BROWSERDIALOG_H +#define BROWSERDIALOG_H + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Ui +{ +class BrowserDialog; +} + +class BrowserView; + +/** + * @brief a dialog containing a webbrowser that is intended to browse the nexus network + **/ +class BrowserDialog : public QDialog +{ + Q_OBJECT + +public: + /** + * @brief constructor + * + * @param accessManager the access manager to use for network requests + * @param parent parent widget + **/ + explicit BrowserDialog(QWidget* parent = 0); + ~BrowserDialog(); + + /** + * @brief set the url to open. If automatic login is enabled, the url is opened after + *login + * + * @param url the url to open + **/ + void openUrl(const QUrl& url); + + virtual bool eventFilter(QObject* object, QEvent* event); +signals: + + /** + * @brief emitted when the user starts a download + * @param pageUrl url of the current web site from which the download was started + * @param reply network reply of the started download + */ + void requestDownload(const QUrl& pageUrl, QNetworkReply* reply); + +protected: + virtual void closeEvent(QCloseEvent*); + +private slots: + + void initTab(BrowserView* newView); + void openInNewTab(const QUrl& url); + + void progress(int value); + + void titleChanged(const QString& title); + void unsupportedContent(QNetworkReply* reply); + void downloadRequested(const QNetworkRequest& request); + + void tabCloseRequested(int index); + + void urlChanged(const QUrl& url); + + void on_backBtn_clicked(); + + void on_fwdBtn_clicked(); + + void on_searchEdit_returnPressed(); + + void startSearch(); + + void on_refreshBtn_clicked(); + + void on_browserTabWidget_currentChanged(int index); + + void on_urlEdit_returnPressed(); + +private: + QString guessFileName(const QString& url); + + BrowserView* getCurrentView(); + + void maximizeWidth(); + +private: + Ui::BrowserDialog* ui; + + QNetworkAccessManager* m_AccessManager; + + QTabWidget* m_Tabs; +}; + +#endif // BROWSERDIALOG_H diff --git a/src/browserview.cpp b/src/browserview.cpp index 81fd8a740..55ed1f7ba 100644 --- a/src/browserview.cpp +++ b/src/browserview.cpp @@ -1,76 +1,75 @@ -/* -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 "browserview.h" - -#include -#include -#include -#include -#include -#include -#include -#include "utility.h" - - -BrowserView::BrowserView(QWidget *parent) - : QWebEngineView(parent) -{ - installEventFilter(this); - - //page()->settings()->setMaximumPagesInCache(10); -} - -QWebEngineView *BrowserView::createWindow(QWebEnginePage::WebWindowType) -{ - BrowserView *newView = new BrowserView(parentWidget()); - emit initTab(newView); - return newView; -} - -bool BrowserView::eventFilter(QObject *obj, QEvent *event) -{ - if (event->type() == QEvent::ShortcutOverride) { - QKeyEvent *keyEvent = static_cast(event); - if (keyEvent->matches(QKeySequence::Find)) { - emit startFind(); - } else if (keyEvent->matches(QKeySequence::FindNext)) { - emit findAgain(); - } - } else if (event->type() == QEvent::MouseButtonPress) { - QMouseEvent *mouseEvent = static_cast(event); - if (mouseEvent->button() == Qt::MouseButton::MiddleButton) { - mouseEvent->ignore(); - return true; - } -// TODO This is due to that QTWebEnginePage doesn't support QWebFrame anymore -// } else if (event->type() == QEvent::MouseButtonRelease) { -// QMouseEvent *mouseEvent = static_cast(event); -// if (mouseEvent->button() == Qt::MidButton) { -// QWebEngineContextMenuData hitTest = page()->hitTestContent(mouseEvent->pos()); -// if (hitTest.linkUrl().isValid()) { -// emit openUrlInNewTab(hitTest.linkUrl()); -// } -// mouseEvent->ignore(); -// -// return true; -// } - } - return QWebEngineView::eventFilter(obj, event); -} +/* +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 "browserview.h" + +#include "utility.h" +#include +#include +#include +#include +#include +#include +#include + +BrowserView::BrowserView(QWidget* parent) : QWebEngineView(parent) +{ + installEventFilter(this); + + // page()->settings()->setMaximumPagesInCache(10); +} + +QWebEngineView* BrowserView::createWindow(QWebEnginePage::WebWindowType) +{ + BrowserView* newView = new BrowserView(parentWidget()); + emit initTab(newView); + return newView; +} + +bool BrowserView::eventFilter(QObject* obj, QEvent* event) +{ + if (event->type() == QEvent::ShortcutOverride) { + QKeyEvent* keyEvent = static_cast(event); + if (keyEvent->matches(QKeySequence::Find)) { + emit startFind(); + } else if (keyEvent->matches(QKeySequence::FindNext)) { + emit findAgain(); + } + } else if (event->type() == QEvent::MouseButtonPress) { + QMouseEvent* mouseEvent = static_cast(event); + if (mouseEvent->button() == Qt::MouseButton::MiddleButton) { + mouseEvent->ignore(); + return true; + } + // TODO This is due to that QTWebEnginePage doesn't support QWebFrame anymore + // } else if (event->type() == QEvent::MouseButtonRelease) { + // QMouseEvent *mouseEvent = static_cast(event); + // if (mouseEvent->button() == Qt::MidButton) { + // QWebEngineContextMenuData hitTest = + // page()->hitTestContent(mouseEvent->pos()); if (hitTest.linkUrl().isValid()) + // { + // emit openUrlInNewTab(hitTest.linkUrl()); + // } + // mouseEvent->ignore(); + // + // return true; + // } + } + return QWebEngineView::eventFilter(obj, event); +} diff --git a/src/browserview.h b/src/browserview.h index 24be21c18..b07f0ad08 100644 --- a/src/browserview.h +++ b/src/browserview.h @@ -1,81 +1,76 @@ -/* -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 NEXUSVIEW_H -#define NEXUSVIEW_H - - -class QEvent; -class QUrl; -class QWidget; -#include -#include - -/** - * @brief web view used to display a nexus page - **/ -class BrowserView : public QWebEngineView -{ - Q_OBJECT - -public: - - explicit BrowserView(QWidget *parent = 0); - -signals: - - /** - * @brief emitted when the user opens a new window to be displayed in another tab - * - * @param newView the view for the newly opened window - **/ - void initTab(BrowserView *newView); - - /** - * @brief emitted when the user requests a link to be opened in a new tab by middle-clicking - * - * @param url the url to open - */ - void openUrlInNewTab(const QUrl &url); - - /** - * @brief Ctrl-f was clicked. The containing dialog should activate its find-facility - */ - void startFind(); - - /** - * @brief F3 was pressed. The containing dialog should search again - */ - void findAgain(); - -protected: - - virtual QWebEngineView *createWindow(QWebEnginePage::WebWindowType type); - - virtual bool eventFilter(QObject *obj, QEvent *event); - - -private: - - QString m_FindPattern; - bool m_MiddleClick; - -}; - -#endif // NEXUSVIEW_H +/* +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 NEXUSVIEW_H +#define NEXUSVIEW_H + +class QEvent; +class QUrl; +class QWidget; +#include +#include + +/** + * @brief web view used to display a nexus page + **/ +class BrowserView : public QWebEngineView +{ + Q_OBJECT + +public: + explicit BrowserView(QWidget* parent = 0); + +signals: + + /** + * @brief emitted when the user opens a new window to be displayed in another tab + * + * @param newView the view for the newly opened window + **/ + void initTab(BrowserView* newView); + + /** + * @brief emitted when the user requests a link to be opened in a new tab by + * middle-clicking + * + * @param url the url to open + */ + void openUrlInNewTab(const QUrl& url); + + /** + * @brief Ctrl-f was clicked. The containing dialog should activate its find-facility + */ + void startFind(); + + /** + * @brief F3 was pressed. The containing dialog should search again + */ + void findAgain(); + +protected: + virtual QWebEngineView* createWindow(QWebEnginePage::WebWindowType type); + + virtual bool eventFilter(QObject* obj, QEvent* event); + +private: + QString m_FindPattern; + bool m_MiddleClick; +}; + +#endif // NEXUSVIEW_H diff --git a/src/categories.cpp b/src/categories.cpp index 35d3e9963..081391d97 100644 --- a/src/categories.cpp +++ b/src/categories.cpp @@ -1,494 +1,513 @@ -/* -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 "categories.h" - -#include -#include -#include - -#include -#include -#include -#include -#include - -#include "nexusinterface.h" - -using namespace MOBase; - - -QString CategoryFactory::categoriesFilePath() -{ - return qApp->property("dataPath").toString() + "/categories.dat"; -} - - -QString CategoryFactory::nexusMappingFilePath() -{ - return qApp->property("dataPath").toString() + "/nexuscatmap.dat"; -} - - -CategoryFactory::CategoryFactory() : QObject() -{ -} - -void CategoryFactory::loadCategories() -{ - reset(); - - QFile categoryFile(categoriesFilePath()); - bool needLoad = false; - - if (!categoryFile.open(QIODevice::ReadOnly)) { - needLoad = true; - } else { - int lineNum = 0; - while (!categoryFile.atEnd()) { - QByteArray line = categoryFile.readLine(); - ++lineNum; - QList cells = line.split('|'); - if (cells.count() == 4) { - std::vector nexusCats; - if (cells[2].length() > 0) { - QList nexusIDStrings = cells[2].split(','); - for (QList::iterator iter = nexusIDStrings.begin(); - iter != nexusIDStrings.end(); ++iter) { - bool ok = false; - int temp = iter->toInt(&ok); - if (!ok) { - log::error(tr("invalid category id {}").toStdString(), iter->constData()); - } - nexusCats.push_back(NexusCategory("Unknown", temp)); - } - } - bool cell0Ok = true; - bool cell3Ok = true; - int id = cells[0].toInt(&cell0Ok); - int parentID = cells[3].trimmed().toInt(&cell3Ok); - if (!cell0Ok || !cell3Ok) { - log::error(tr("invalid category line {}: {}").toStdString(), lineNum, line.constData()); - } - addCategory(id, QString::fromUtf8(cells[1].constData()), nexusCats, parentID); - } else if (cells.count() == 3) { - bool cell0Ok = true; - bool cell3Ok = true; - int id = cells[0].toInt(&cell0Ok); - int parentID = cells[2].trimmed().toInt(&cell3Ok); - if (!cell0Ok || !cell3Ok) { - log::error(tr("invalid category line {}: {}").toStdString(), lineNum, line.constData()); - } - - addCategory(id, QString::fromUtf8(cells[1].constData()), std::vector(), parentID); - } else { - log::error( - tr("invalid category line {}: {} ({} cells)").toStdString(), - lineNum, line.constData(), cells.count()); - } - } - categoryFile.close(); - - QFile nexusMapFile(nexusMappingFilePath()); - if (!nexusMapFile.open(QIODevice::ReadOnly)) { - needLoad = true; - } else { - int nexLineNum = 0; - while (!nexusMapFile.atEnd()) { - QByteArray nexLine = nexusMapFile.readLine(); - ++nexLineNum; - QList nexCells = nexLine.split('|'); - if (nexCells.count() == 3) { - std::vector nexusCats; - QString nexName = nexCells[1]; - bool ok = false; - int nexID = nexCells[2].toInt(&ok); - if (!ok) { - log::error(tr("invalid nexus ID {}").toStdString(), nexCells[2].constData()); - } - int catID = nexCells[0].toInt(&ok); - if (!ok) { - log::error(tr("invalid category id {}").toStdString(), nexCells[0].constData()); - } - m_NexusMap.insert_or_assign(nexID, NexusCategory(nexName, nexID)); - m_NexusMap.at(nexID).m_CategoryID = catID; - } else { - log::error( - tr("invalid nexus category line {}: {} ({} cells)").toStdString(), - lineNum, nexLine.constData(), nexCells.count()); - } - } - } - nexusMapFile.close(); - } - std::sort(m_Categories.begin(), m_Categories.end()); - setParents(); - if (needLoad) loadDefaultCategories(); -} - - -CategoryFactory *CategoryFactory::instance() -{ - static CategoryFactory s_Instance; - return &s_Instance; -} - - -void CategoryFactory::reset() -{ - m_Categories.clear(); - m_NexusMap.clear(); - m_IDMap.clear(); - // 28 = - // 43 = Savegames (makes no sense to install them through MO) - // 45 = Videos and trailers - // 87 = Miscelanous - addCategory(0, "None", std::vector(), 0); -} - - -void CategoryFactory::setParents() -{ - for (std::vector::iterator iter = m_Categories.begin(); - iter != m_Categories.end(); ++iter) { - iter->m_HasChildren = false; - } - - for (std::vector::const_iterator categoryIter = m_Categories.begin(); - categoryIter != m_Categories.end(); ++categoryIter) { - if (categoryIter->m_ParentID != 0) { - std::map::const_iterator iter = m_IDMap.find(categoryIter->m_ParentID); - if (iter != m_IDMap.end()) { - m_Categories[iter->second].m_HasChildren = true; - } - } - } -} - - -void CategoryFactory::saveCategories() -{ - QFile categoryFile(categoriesFilePath()); - - if (!categoryFile.open(QIODevice::WriteOnly)) { - reportError(tr("Failed to save custom categories")); - return; - } - - categoryFile.resize(0); - for (std::vector::const_iterator iter = m_Categories.begin(); - iter != m_Categories.end(); ++iter) { - if (iter->m_ID == 0) { - continue; - } - QByteArray line; - line.append(QByteArray::number(iter->m_ID)).append("|") - .append(iter->m_Name.toUtf8()).append("|") - .append(QByteArray::number(iter->m_ParentID)).append("\n"); - categoryFile.write(line); - } - categoryFile.close(); - - QFile nexusMapFile(nexusMappingFilePath()); - - if (!nexusMapFile.open(QIODevice::WriteOnly)) { - reportError(tr("Failed to save nexus category mappings")); - return; - } - - nexusMapFile.resize(0); - for (auto iter = m_NexusMap.begin(); iter != m_NexusMap.end(); ++iter) { - QByteArray line; - line.append(QByteArray::number(iter->second.m_CategoryID)).append("|"); - line.append(iter->second.m_Name.toUtf8()).append("|"); - line.append(QByteArray::number(iter->second.m_ID)).append("\n"); - nexusMapFile.write(line); - } - nexusMapFile.close(); -} - - -unsigned int CategoryFactory::countCategories(std::function filter) -{ - unsigned int result = 0; - for (const Category &cat : m_Categories) { - if (filter(cat)) { - ++result; - } - } - return result; -} - -int CategoryFactory::addCategory(const QString &name, const std::vector& nexusCats, int parentID) -{ - int id = 1; - while (m_IDMap.find(id) != m_IDMap.end()) { - ++id; - } - addCategory(id, name, nexusCats, parentID); - - saveCategories(); - return id; -} - -void CategoryFactory::addCategory(int id, const QString &name, int parentID) -{ - int index = static_cast(m_Categories.size()); - m_Categories.push_back(Category(index, id, name, parentID, std::vector())); - m_IDMap[id] = index; -} - -void CategoryFactory::addCategory(int id, const QString& name, const std::vector& nexusCats, int parentID) -{ - for (auto nexusCat : nexusCats) { - m_NexusMap.insert_or_assign(nexusCat.m_ID, nexusCat); - m_NexusMap.at(nexusCat.m_ID).m_CategoryID = id; - } - int index = static_cast(m_Categories.size()); - m_Categories.push_back(Category(index, id, name, parentID, nexusCats)); - m_IDMap[id] = index; -} - -void CategoryFactory::setNexusCategories(std::vector& nexusCats) -{ - m_NexusMap.empty(); - for (auto nexusCat : nexusCats) { - m_NexusMap.emplace(nexusCat.m_ID, nexusCat); - } - - saveCategories(); -} - - -void CategoryFactory::loadDefaultCategories() -{ - // the order here is relevant as it defines the order in which the - // mods appear in the combo box - addCategory(1, "Animations", 0); - addCategory(52, "Poses", 1); - addCategory(2, "Armour", 0); - addCategory(53, "Power Armor", 2); - addCategory(3, "Audio", 0); - addCategory(38, "Music", 0); - addCategory(39, "Voice", 0); - addCategory(5, "Clothing", 0); - addCategory(41, "Jewelry", 5); - addCategory(42, "Backpacks", 5); - addCategory(6, "Collectables", 0); - addCategory(28, "Companions", 0); - addCategory(7, "Creatures, Mounts, & Vehicles", 0); - addCategory(8, "Factions", 0); - addCategory(9, "Gameplay", 0); - addCategory(27, "Combat", 9); - addCategory(43, "Crafting", 9); - addCategory(48, "Overhauls", 9); - addCategory(49, "Perks", 9); - addCategory(54, "Radio", 9); - addCategory(55, "Shouts", 9); - addCategory(22, "Skills & Levelling", 9); - addCategory(58, "Weather & Lighting", 9); - addCategory(44, "Equipment", 43); - addCategory(45, "Home/Settlement", 43); - addCategory(10, "Body, Face, & Hair", 0); - addCategory(39, "Tattoos", 10); - addCategory(40, "Character Presets", 0); - addCategory(11, "Items", 0); - addCategory(32, "Mercantile", 0); - addCategory(37, "Ammo", 11); - addCategory(19, "Weapons", 11); - addCategory(36, "Weapon & Armour Sets", 11); - addCategory(23, "Player Homes", 0); - addCategory(25, "Castles & Mansions", 23); - addCategory(51, "Settlements", 23); - addCategory(12, "Locations", 0); - addCategory(4, "Cities", 12); - addCategory(31, "Landscape Changes", 0); - addCategory(29, "Environment", 0); - addCategory(30, "Immersion", 0); - addCategory(20, "Magic", 0); - addCategory(21, "Models & Textures", 0); - addCategory(33, "Modders resources", 0); - addCategory(13, "NPCs", 0); - addCategory(24, "Bugfixes", 0); - addCategory(14, "Patches", 24); - addCategory(35, "Utilities", 0); - addCategory(26, "Cheats", 0); - addCategory(15, "Quests", 0); - addCategory(16, "Races & Classes", 0); - addCategory(34, "Stealth", 0); - addCategory(17, "UI", 0); - addCategory(18, "Visuals", 0); - addCategory(50, "Pip-Boy", 18); - addCategory(46, "Shader Presets", 0); - addCategory(47, "Miscellaneous", 0); -} - - -int CategoryFactory::getParentID(unsigned int index) const -{ - if (index >= m_Categories.size()) { - throw MyException(tr("invalid category index: %1").arg(index)); - } - - return m_Categories[index].m_ParentID; -} - - -bool CategoryFactory::categoryExists(int id) const -{ - return m_IDMap.find(id) != m_IDMap.end(); -} - - -bool CategoryFactory::isDescendantOf(int id, int parentID) const -{ - // handles cycles - std::set seen; - return isDescendantOfImpl(id, parentID, seen); -} - -bool CategoryFactory::isDescendantOfImpl( - int id, int parentID, std::set& seen) const -{ - if (!seen.insert(id).second) { - log::error("cycle in category: {}", id); - return false; - } - - std::map::const_iterator iter = m_IDMap.find(id); - - if (iter != m_IDMap.end()) { - unsigned int index = iter->second; - if (m_Categories[index].m_ParentID == 0) { - return false; - } else if (m_Categories[index].m_ParentID == parentID) { - return true; - } else { - return isDescendantOfImpl(m_Categories[index].m_ParentID, parentID, seen); - } - } else { - log::warn(tr("{} is no valid category id").toStdString(), id); - return false; - } -} - - -bool CategoryFactory::hasChildren(unsigned int index) const -{ - if (index >= m_Categories.size()) { - throw MyException(tr("invalid category index: %1").arg(index)); - } - - return m_Categories[index].m_HasChildren; -} - - -QString CategoryFactory::getCategoryName(unsigned int index) const -{ - if (index >= m_Categories.size()) { - throw MyException(tr("invalid category index: %1").arg(index)); - } - - return m_Categories[index].m_Name; -} - -QString CategoryFactory::getSpecialCategoryName(SpecialCategories type) const -{ - QString label; - switch (type) - { - case Checked: label = QObject::tr("Active"); break; - case UpdateAvailable: label = QObject::tr("Update available"); break; - case HasCategory: label = QObject::tr("Has category"); break; - case Conflict: label = QObject::tr("Conflicted"); break; - case HasHiddenFiles: label = QObject::tr("Has hidden files"); break; - case Endorsed: label = QObject::tr("Endorsed"); break; - case Backup: label = QObject::tr("Has backup"); break; - case Managed: label = QObject::tr("Managed"); break; - case HasGameData: label = QObject::tr("Has valid game data"); break; - case HasNexusID: label = QObject::tr("Has Nexus ID"); break; - case Tracked: label = QObject::tr("Tracked on Nexus"); break; - default: return {}; - } - return QString("<%1>").arg(label); -} - -QString CategoryFactory::getCategoryNameByID(int id) const -{ - auto itor = m_IDMap.find(id); - - if (itor == m_IDMap.end()) { - return getSpecialCategoryName(static_cast(id)); - } else { - const auto index = itor->second; - if (index >= m_Categories.size()) { - return {}; - } - - return m_Categories[index].m_Name; - } -} - -int CategoryFactory::getCategoryID(unsigned int index) const -{ - if (index >= m_Categories.size()) { - throw MyException(tr("invalid category index: %1").arg(index)); - } - - return m_Categories[index].m_ID; -} - - -int CategoryFactory::getCategoryIndex(int ID) const -{ - std::map::const_iterator iter = m_IDMap.find(ID); - if (iter == m_IDMap.end()) { - throw MyException(tr("invalid category id: %1").arg(ID)); - } - return iter->second; -} - - -int CategoryFactory::getCategoryID(const QString &name) const -{ - auto iter = std::find_if(m_Categories.begin(), m_Categories.end(), - [name] (const Category &cat) -> bool { - return cat.m_Name == name; - }); - - if (iter != m_Categories.end()) { - return iter->m_ID; - } else { - return -1; - } -} - - -unsigned int CategoryFactory::resolveNexusID(int nexusID) const -{ - auto result = m_NexusMap.find(nexusID); - if (result != m_NexusMap.end()) { - if (m_IDMap.count(result->second.m_CategoryID)) { - log::debug(tr("nexus category id {} maps to internal {}").toStdString(), nexusID, m_IDMap.at(result->second.m_CategoryID)); - return m_IDMap.at(result->second.m_CategoryID); - } - } - log::debug(tr("nexus category id {} not mapped").toStdString(), nexusID); - return 0U; -} +/* +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 "categories.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "nexusinterface.h" + +using namespace MOBase; + +QString CategoryFactory::categoriesFilePath() +{ + return qApp->property("dataPath").toString() + "/categories.dat"; +} + +CategoryFactory::CategoryFactory() + +QString CategoryFactory::nexusMappingFilePath() +{ + return qApp->property("dataPath").toString() + "/nexuscatmap.dat"; +} + +CategoryFactory::CategoryFactory() : QObject() +{ + atexit(&cleanup); +} + +void CategoryFactory::loadCategories() +{ + reset(); + + QFile categoryFile(categoriesFilePath()); + bool needLoad = false; + + if (!categoryFile.open(QIODevice::ReadOnly)) { + needLoad = true; + } else { + int lineNum = 0; + while (!categoryFile.atEnd()) { + QByteArray line = categoryFile.readLine(); + ++lineNum; + QList cells = line.split('|'); + if (cells.count() == 4) { + std::vector nexusCats; + if (cells[2].length() > 0) { + QList nexusIDStrings = cells[2].split(','); + for (QList::iterator iter = nexusIDStrings.begin(); + iter != nexusIDStrings.end(); ++iter) { + bool ok = false; + int temp = iter->toInt(&ok); + if (!ok) { + log::error(tr("invalid category id {}").toStdString(), iter->constData()); + } + nexusCats.push_back(NexusCategory("Unknown", temp)); + } + } + bool cell0Ok = true; + bool cell3Ok = true; + int id = cells[0].toInt(&cell0Ok); + int parentID = cells[3].trimmed().toInt(&cell3Ok); + if (!cell0Ok || !cell3Ok) { + log::error(tr("invalid category line {}: {}").toStdString(), lineNum, line.constData()); + } + addCategory(id, QString::fromUtf8(cells[1].constData()), nexusCats, parentID); + } else if (cells.count() == 3) { + bool cell0Ok = true; + bool cell3Ok = true; + int id = cells[0].toInt(&cell0Ok); + int parentID = cells[2].trimmed().toInt(&cell3Ok); + if (!cell0Ok || !cell3Ok) { + log::error(tr("invalid category line {}: {}").toStdString(), lineNum, line.constData()); + } + + addCategory(id, QString::fromUtf8(cells[1].constData()), std::vector(), parentID); + } else { + log::error( + tr("invalid category line {}: {} ({} cells)").toStdString(), + lineNum, line.constData(), cells.count()); + } + } + categoryFile.close(); + + QFile nexusMapFile(nexusMappingFilePath()); + if (!nexusMapFile.open(QIODevice::ReadOnly)) { + needLoad = true; + } else { + int nexLineNum = 0; + while (!nexusMapFile.atEnd()) { + QByteArray nexLine = nexusMapFile.readLine(); + ++nexLineNum; + QList nexCells = nexLine.split('|'); + if (nexCells.count() == 3) { + std::vector nexusCats; + QString nexName = nexCells[1]; + bool ok = false; + int nexID = nexCells[2].toInt(&ok); + if (!ok) { + log::error(tr("invalid nexus ID {}").toStdString(), nexCells[2].constData()); + } + int catID = nexCells[0].toInt(&ok); + if (!ok) { + log::error(tr("invalid category id {}").toStdString(), nexCells[0].constData()); + } + m_NexusMap.insert_or_assign(nexID, NexusCategory(nexName, nexID)); + m_NexusMap.at(nexID).m_CategoryID = catID; + } else { + log::error( + tr("invalid nexus category line {}: {} ({} cells)").toStdString(), + lineNum, nexLine.constData(), nexCells.count()); + } + } + } + nexusMapFile.close(); + } + std::sort(m_Categories.begin(), m_Categories.end()); + setParents(); + if (needLoad) loadDefaultCategories(); +} + + +CategoryFactory* CategoryFactory::instance() +{ + static CategoryFactory s_Instance; + return &s_Instance; +} + +void CategoryFactory::reset() +{ + m_Categories.clear(); + m_NexusMap.clear(); + m_IDMap.clear(); + // 28 = + // 43 = Savegames (makes no sense to install them through MO) + // 45 = Videos and trailers + // 87 = Miscelanous + addCategory(0, "None", std::vector(), 0); +} + +void CategoryFactory::setParents() +{ + for (std::vector::iterator iter = m_Categories.begin(); + iter != m_Categories.end(); ++iter) { + iter->m_HasChildren = false; + } + + for (std::vector::const_iterator categoryIter = m_Categories.begin(); + categoryIter != m_Categories.end(); ++categoryIter) { + if (categoryIter->m_ParentID != 0) { + std::map::const_iterator iter = + m_IDMap.find(categoryIter->m_ParentID); + if (iter != m_IDMap.end()) { + m_Categories[iter->second].m_HasChildren = true; + } + } + } +} + + +void CategoryFactory::saveCategories() +{ + QFile categoryFile(categoriesFilePath()); + + if (!categoryFile.open(QIODevice::WriteOnly)) { + reportError(tr("Failed to save custom categories")); + return; + } + + categoryFile.resize(0); + for (std::vector::const_iterator iter = m_Categories.begin(); + iter != m_Categories.end(); ++iter) { + if (iter->m_ID == 0) { + continue; + } + QByteArray line; + line.append(QByteArray::number(iter->m_ID)) + .append("|") + .append(iter->m_Name.toUtf8()) + .append("|") + .append(QByteArray::number(iter->m_ParentID)) + .append("\n"); + categoryFile.write(line); + } + categoryFile.close(); + + QFile nexusMapFile(nexusMappingFilePath()); + + if (!nexusMapFile.open(QIODevice::WriteOnly)) { + reportError(tr("Failed to save nexus category mappings")); + return; + } + + nexusMapFile.resize(0); + for (auto iter = m_NexusMap.begin(); iter != m_NexusMap.end(); ++iter) { + QByteArray line; + line.append(QByteArray::number(iter->second.m_CategoryID)).append("|"); + line.append(iter->second.m_Name.toUtf8()).append("|"); + line.append(QByteArray::number(iter->second.m_ID)).append("\n"); + nexusMapFile.write(line); + } + nexusMapFile.close(); +} + +unsigned int +CategoryFactory::countCategories(std::function filter) +{ + unsigned int result = 0; + for (const Category& cat : m_Categories) { + if (filter(cat)) { + ++result; + } + } + return result; +} + +int CategoryFactory::addCategory(const QString& name, const std::vector& nexusCats, + int parentID) +{ + int id = 1; + while (m_IDMap.find(id) != m_IDMap.end()) { + ++id; + } + addCategory(id, name, nexusCats, parentID); + + saveCategories(); + return id; +} + +void CategoryFactory::addCategory(int id, const QString& name, + int parentID) +{ + int index = static_cast(m_Categories.size()); + m_Categories.push_back(Category(index, id, name, parentID, std::vector())); + m_IDMap[id] = index; +} + +void CategoryFactory::addCategory(int id, const QString& name, const std::vector& nexusCats, int parentID) +{ + for (auto nexusCat : nexusCats) { + m_NexusMap.insert_or_assign(nexusCat.m_ID, nexusCat); + m_NexusMap.at(nexusCat.m_ID).m_CategoryID = id; + } + int index = static_cast(m_Categories.size()); + m_Categories.push_back(Category(index, id, name, parentID, nexusCats)); + m_IDMap[id] = index; +} + +void CategoryFactory::setNexusCategories(std::vector& nexusCats) +{ + m_NexusMap.empty(); + for (auto nexusCat : nexusCats) { + m_NexusMap.emplace(nexusCat.m_ID, nexusCat); + } + + saveCategories(); +} + + +void CategoryFactory::loadDefaultCategories() +{ + // the order here is relevant as it defines the order in which the + // mods appear in the combo box + addCategory(1, "Animations", 0); + addCategory(52, "Poses", 1); + addCategory(2, "Armour", 0); + addCategory(53, "Power Armor", 2); + addCategory(3, "Audio", 0); + addCategory(38, "Music", 0); + addCategory(39, "Voice", 0); + addCategory(5, "Clothing", 0); + addCategory(41, "Jewelry", 5); + addCategory(42, "Backpacks", 5); + addCategory(6, "Collectables", 0); + addCategory(28, "Companions", 0); + addCategory(7, "Creatures, Mounts, & Vehicles", 0); + addCategory(8, "Factions", 0); + addCategory(9, "Gameplay", 0); + addCategory(27, "Combat", 9); + addCategory(43, "Crafting", 9); + addCategory(48, "Overhauls", 9); + addCategory(49, "Perks", 9); + addCategory(54, "Radio", 9); + addCategory(55, "Shouts", 9); + addCategory(22, "Skills & Levelling", 9); + addCategory(58, "Weather & Lighting", 9); + addCategory(44, "Equipment", 43); + addCategory(45, "Home/Settlement", 43); + addCategory(10, "Body, Face, & Hair", 0); + addCategory(39, "Tattoos", 10); + addCategory(40, "Character Presets", 0); + addCategory(11, "Items", 0); + addCategory(32, "Mercantile", 0); + addCategory(37, "Ammo", 11); + addCategory(19, "Weapons", 11); + addCategory(36, "Weapon & Armour Sets", 11); + addCategory(23, "Player Homes", 0); + addCategory(25, "Castles & Mansions", 23); + addCategory(51, "Settlements", 23); + addCategory(12, "Locations", 0); + addCategory(4, "Cities", 12); + addCategory(31, "Landscape Changes", 0); + addCategory(29, "Environment", 0); + addCategory(30, "Immersion", 0); + addCategory(20, "Magic", 0); + addCategory(21, "Models & Textures", 0); + addCategory(33, "Modders resources", 0); + addCategory(13, "NPCs", 0); + addCategory(24, "Bugfixes", 0); + addCategory(14, "Patches", 24); + addCategory(35, "Utilities", 0); + addCategory(26, "Cheats", 0); + addCategory(15, "Quests", 0); + addCategory(16, "Races & Classes", 0); + addCategory(34, "Stealth", 0); + addCategory(17, "UI", 0); + addCategory(18, "Visuals", 0); + addCategory(50, "Pip-Boy", 18); + addCategory(46, "Shader Presets", 0); + addCategory(47, "Miscellaneous", 0); +} + +int CategoryFactory::getParentID(unsigned int index) const +{ + if (index >= m_Categories.size()) { + throw MyException(tr("invalid category index: %1").arg(index)); + } + + return m_Categories[index].m_ParentID; +} + +bool CategoryFactory::categoryExists(int id) const +{ + return m_IDMap.find(id) != m_IDMap.end(); +} + +bool CategoryFactory::isDescendantOf(int id, int parentID) const +{ + // handles cycles + std::set seen; + return isDescendantOfImpl(id, parentID, seen); +} + +bool CategoryFactory::isDescendantOfImpl(int id, int parentID, + std::set& seen) const +{ + if (!seen.insert(id).second) { + log::error("cycle in category: {}", id); + return false; + } + + std::map::const_iterator iter = m_IDMap.find(id); + + if (iter != m_IDMap.end()) { + unsigned int index = iter->second; + if (m_Categories[index].m_ParentID == 0) { + return false; + } else if (m_Categories[index].m_ParentID == parentID) { + return true; + } else { + return isDescendantOfImpl(m_Categories[index].m_ParentID, parentID, seen); + } + } else { + log::warn(tr("{} is no valid category id").toStdString(), id); + return false; + } +} + +bool CategoryFactory::hasChildren(unsigned int index) const +{ + if (index >= m_Categories.size()) { + throw MyException(tr("invalid category index: %1").arg(index)); + } + + return m_Categories[index].m_HasChildren; +} + +QString CategoryFactory::getCategoryName(unsigned int index) const +{ + if (index >= m_Categories.size()) { + throw MyException(tr("invalid category index: %1").arg(index)); + } + + return m_Categories[index].m_Name; +} + +QString CategoryFactory::getSpecialCategoryName(SpecialCategories type) const +{ + QString label; + switch (type) { + case Checked: + label = QObject::tr("Active"); + break; + case UpdateAvailable: + label = QObject::tr("Update available"); + break; + case HasCategory: + label = QObject::tr("Has category"); + break; + case Conflict: + label = QObject::tr("Conflicted"); + break; + case HasHiddenFiles: + label = QObject::tr("Has hidden files"); + break; + case Endorsed: + label = QObject::tr("Endorsed"); + break; + case Backup: + label = QObject::tr("Has backup"); + break; + case Managed: + label = QObject::tr("Managed"); + break; + case HasGameData: + label = QObject::tr("Has valid game data"); + break; + case HasNexusID: + label = QObject::tr("Has Nexus ID"); + break; + case Tracked: + label = QObject::tr("Tracked on Nexus"); + break; + default: + return {}; + } + return QString("<%1>").arg(label); +} + +QString CategoryFactory::getCategoryNameByID(int id) const +{ + auto itor = m_IDMap.find(id); + + if (itor == m_IDMap.end()) { + return getSpecialCategoryName(static_cast(id)); + } else { + const auto index = itor->second; + if (index >= m_Categories.size()) { + return {}; + } + + return m_Categories[index].m_Name; + } +} + +int CategoryFactory::getCategoryID(unsigned int index) const +{ + if (index >= m_Categories.size()) { + throw MyException(tr("invalid category index: %1").arg(index)); + } + + return m_Categories[index].m_ID; +} + +int CategoryFactory::getCategoryIndex(int ID) const +{ + std::map::const_iterator iter = m_IDMap.find(ID); + if (iter == m_IDMap.end()) { + throw MyException(tr("invalid category id: %1").arg(ID)); + } + return iter->second; +} + +int CategoryFactory::getCategoryID(const QString& name) const +{ + auto iter = std::find_if(m_Categories.begin(), m_Categories.end(), + [name](const Category& cat) -> bool { + return cat.m_Name == name; + }); + + if (iter != m_Categories.end()) { + return iter->m_ID; + } else { + return -1; + } +} + +unsigned int CategoryFactory::resolveNexusID(int nexusID) const +{ + auto result = m_NexusMap.find(nexusID); + if (result != m_NexusMap.end()) { + if (m_IDMap.count(result->second.m_CategoryID)) { + log::debug(tr("nexus category id {} maps to internal {}").toStdString(), nexusID, m_IDMap.at(result->second.m_CategoryID)); + return m_IDMap.at(result->second.m_CategoryID); + } + } + log::debug(tr("nexus category id {} not mapped").toStdString(), nexusID); + return 0U; +} diff --git a/src/categories.h b/src/categories.h index b9066faa9..b0d7dc221 100644 --- a/src/categories.h +++ b/src/categories.h @@ -1,240 +1,239 @@ -/* -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 CATEGORIES_H -#define CATEGORIES_H - - -#include -#include -#include -#include - - -/** - * @brief Manage the available mod categories - * @warning member functions of this class currently use a wild mix of ids and indexes to look up categories, - * optimized to where the request comes from. Therefore be very careful which of the two you have available - **/ -class CategoryFactory : public QObject { - Q_OBJECT; - - friend class CategoriesDialog; - -public: - enum SpecialCategories - { - Checked = 10000, - UpdateAvailable, - HasCategory, - Conflict, - HasHiddenFiles, - Endorsed, - Backup, - Managed, - HasGameData, - HasNexusID, - Tracked - }; - -public: - struct NexusCategory { - NexusCategory(const QString& name, const int nexusID) - : m_Name(name), m_ID(nexusID) {} - QString m_Name; - int m_ID; - int m_CategoryID = -1; - - friend bool operator==(const NexusCategory& LHS, const NexusCategory& RHS) { - return LHS.m_ID == RHS.m_ID; - } - - friend bool operator==(const NexusCategory& LHS, const int RHS) { - return LHS.m_ID == RHS; - } - - friend bool operator<(const NexusCategory& LHS, const NexusCategory& RHS) { - return LHS.m_ID < RHS.m_ID; - } - }; - - struct Category { - Category(int sortValue, int id, const QString &name, int parentID, std::vector nexusCats) - : m_SortValue(sortValue), m_ID(id), m_Name(name), m_HasChildren(false), m_ParentID(parentID) - , m_NexusCats(nexusCats) {} - int m_SortValue; - int m_ID; - int m_ParentID; - bool m_HasChildren; - QString m_Name; - std::vector m_NexusCats; - - friend bool operator<(const Category &LHS, const Category &RHS) { - return LHS.m_SortValue < RHS.m_SortValue; - } - }; - -public: - - /** - * @brief reset the list of categories - **/ - void reset(); - - /** - * @brief read categories from file - */ - void loadCategories(); - - /** - * @brief save the categories to the categories.dat file - **/ - void saveCategories(); - - void setNexusCategories(std::vector& nexusCats); - - int addCategory(const QString &name, const std::vector &nexusCats, int parentID); - - /** - * @brief retrieve the number of available categories - * - * @return unsigned int number of categories - **/ - size_t numCategories() const { return m_Categories.size(); } - - /** - * @brief count all categories that match a specified filter - * @param filter the filter to test - * @return number of matching categories - */ - unsigned int countCategories(std::function filter); - - /** - * @brief get the id of the parent category - * - * @param index the index to look up - * @return int id of the parent category - **/ - int getParentID(unsigned int index) const; - - /** - * @brief determine if a category exists (by id) - * - * @param id the id to check for existance - * @return true if the category exists, false otherwise - **/ - bool categoryExists(int id) const; - - /** - * @brief test if a category is child of a second one - * @param id the presumed child id - * @param parentID the parent id to test for - * @return true if id is a child of parentID - **/ - bool isDescendantOf(int id, int parentID) const; - - /** - * @brief test if the specified category has child categories - * - * @param index index of the category to look up - * @return bool true if the category has child categories - **/ - bool hasChildren(unsigned int index) const; - - /** - * @brief retrieve the name of a category - * - * @param index index of the category to look up - * @return QString name of the category - **/ - QString getCategoryName(unsigned int index) const; - QString getSpecialCategoryName(SpecialCategories type) const; - QString getCategoryNameByID(int id) const; - - /** - * @brief look up the id of a category by its index - * - * @param index index of the category to look up - * @return int id of the category - **/ - int getCategoryID(unsigned int index) const; - - /** - * @brief look up the id of a category by its name - * @note O(n) - */ - int getCategoryID(const QString &name) const; - - /** - * @brief look up the index of a category by its id - * - * @param id index of the category to look up - * @return unsigned int index of the category - **/ - int getCategoryIndex(int ID) const; - - /** - * @brief retrieve the index of a category by its nexus id - * - * @param nexusID nexus id of the category to look up - * @return unsigned int index of the category or 0 if no category matches - **/ - unsigned int resolveNexusID(int nexusID) const; - -public: - - /** - * @brief retrieve a reference to the singleton instance - * - * @return the reference to the singleton - **/ - static CategoryFactory *instance(); - - /** - * @return path to the file that contains the categories list - */ - static QString categoriesFilePath(); - - /** - * @return path to the file that contains the nexus category mappings - */ - static QString nexusMappingFilePath(); - -private: - explicit CategoryFactory(); - - void loadDefaultCategories(); - - void addCategory(int id, const QString &name, const std::vector &nexusCats, int parentID); - void addCategory(int id, const QString& name, int parentID); - - void setParents(); - -private: - std::vector m_Categories; - std::map m_IDMap; - std::map m_NexusMap; - -private: - // called by isDescendantOf() - bool isDescendantOfImpl(int id, int parentID, std::set& seen) const; -}; - - -#endif // CATEGORIES_H +/* +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 CATEGORIES_H +#define CATEGORIES_H + +#include +#include +#include +#include + +/** + * @brief Manage the available mod categories + * @warning member functions of this class currently use a wild mix of ids and indexes + *to look up categories, optimized to where the request comes from. Therefore be very + *careful which of the two you have available + **/ +class CategoryFactory : public QObject { + Q_OBJECT; + + friend class CategoriesDialog; + +public: + enum SpecialCategories + { + Checked = 10000, + UpdateAvailable, + HasCategory, + Conflict, + HasHiddenFiles, + Endorsed, + Backup, + Managed, + HasGameData, + HasNexusID, + Tracked + }; + +public: + struct NexusCategory { + NexusCategory(const QString& name, const int nexusID) + : m_Name(name), m_ID(nexusID) {} + QString m_Name; + int m_ID; + int m_CategoryID = -1; + + friend bool operator==(const NexusCategory& LHS, const NexusCategory& RHS) { + return LHS.m_ID == RHS.m_ID; + } + + friend bool operator==(const NexusCategory& LHS, const int RHS) { + return LHS.m_ID == RHS; + } + + friend bool operator<(const NexusCategory& LHS, const NexusCategory& RHS) { + return LHS.m_ID < RHS.m_ID; + } + }; + + struct Category { + Category(int sortValue, int id, const QString& name, + int parentID, std::vector nexusCats) + : m_SortValue(sortValue), m_ID(id), m_Name(name), m_HasChildren(false), m_ParentID(parentID) + , m_NexusCats(nexusCats) {} + int m_SortValue; + int m_ID; + int m_ParentID; + bool m_HasChildren; + QString m_Name; + std::vector m_NexusCats; + + friend bool operator<(const Category& LHS, const Category& RHS) + { + return LHS.m_SortValue < RHS.m_SortValue; + } + }; + +public: + /** + * @brief reset the list of categories + **/ + void reset(); + + /** + * @brief read categories from file + */ + void loadCategories(); + + /** + * @brief save the categories to the categories.dat file + **/ + void saveCategories(); + + void setNexusCategories(std::vector& nexusCats); + + int addCategory(const QString& name, const std::vector& nexusCats, int parentID); + + /** + * @brief retrieve the number of available categories + * + * @return unsigned int number of categories + **/ + size_t numCategories() const { return m_Categories.size(); } + + /** + * @brief count all categories that match a specified filter + * @param filter the filter to test + * @return number of matching categories + */ + unsigned int countCategories(std::function filter); + + /** + * @brief get the id of the parent category + * + * @param index the index to look up + * @return int id of the parent category + **/ + int getParentID(unsigned int index) const; + + /** + * @brief determine if a category exists (by id) + * + * @param id the id to check for existance + * @return true if the category exists, false otherwise + **/ + bool categoryExists(int id) const; + + /** + * @brief test if a category is child of a second one + * @param id the presumed child id + * @param parentID the parent id to test for + * @return true if id is a child of parentID + **/ + bool isDescendantOf(int id, int parentID) const; + + /** + * @brief test if the specified category has child categories + * + * @param index index of the category to look up + * @return bool true if the category has child categories + **/ + bool hasChildren(unsigned int index) const; + + /** + * @brief retrieve the name of a category + * + * @param index index of the category to look up + * @return QString name of the category + **/ + QString getCategoryName(unsigned int index) const; + QString getSpecialCategoryName(SpecialCategories type) const; + QString getCategoryNameByID(int id) const; + + /** + * @brief look up the id of a category by its index + * + * @param index index of the category to look up + * @return int id of the category + **/ + int getCategoryID(unsigned int index) const; + + /** + * @brief look up the id of a category by its name + * @note O(n) + */ + int getCategoryID(const QString& name) const; + + /** + * @brief look up the index of a category by its id + * + * @param id index of the category to look up + * @return unsigned int index of the category + **/ + int getCategoryIndex(int ID) const; + + /** + * @brief retrieve the index of a category by its nexus id + * + * @param nexusID nexus id of the category to look up + * @return unsigned int index of the category or 0 if no category matches + **/ + unsigned int resolveNexusID(int nexusID) const; + +public: + /** + * @brief retrieve a reference to the singleton instance + * + * @return the reference to the singleton + **/ + static CategoryFactory* instance(); + + /** + * @return path to the file that contains the categories list + */ + static QString categoriesFilePath(); + + /** + * @return path to the file that contains the nexus category mappings + */ + static QString nexusMappingFilePath(); + +private: + explicit CategoryFactory(); + + void loadDefaultCategories(); + + void addCategory(int id, const QString& name, const std::vector& nexusCats, + int parentID); + void addCategory(int id, const QString& name, int parentID); + + void setParents(); + +private: + std::vector m_Categories; + std::map m_IDMap; + std::map m_NexusMap; + +private: + // called by isDescendantOf() + bool isDescendantOfImpl(int id, int parentID, std::set& seen) const; +}; + +#endif // CATEGORIES_H diff --git a/src/categoriesdialog.cpp b/src/categoriesdialog.cpp index 2be8eab60..03bc6180f 100644 --- a/src/categoriesdialog.cpp +++ b/src/categoriesdialog.cpp @@ -1,350 +1,352 @@ -/* -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 "categoriesdialog.h" -#include "ui_categoriesdialog.h" -#include "categories.h" -#include "utility.h" -#include "settings.h" -#include "nexusinterface.h" -#include "messagedialog.h" -#include -#include -#include -#include - - -class NewIDValidator : public QIntValidator { -public: - NewIDValidator(const std::set &ids) - : m_UsedIDs(ids) {} - virtual State validate(QString &input, int &pos) const { - State intRes = QIntValidator::validate(input, pos); - if (intRes == Acceptable) { - bool ok = false; - int id = input.toInt(&ok); - if (m_UsedIDs.find(id) != m_UsedIDs.end()) { - return QValidator::Intermediate; - } - } - return intRes; - } -private: - const std::set &m_UsedIDs; -}; - - -class ExistingIDValidator : public QIntValidator { -public: - ExistingIDValidator(const std::set &ids) - : m_UsedIDs(ids) {} - virtual State validate(QString &input, int &pos) const { - State intRes = QIntValidator::validate(input, pos); - if (intRes == Acceptable) { - bool ok = false; - int id = input.toInt(&ok); - if ((id == 0) || (m_UsedIDs.find(id) != m_UsedIDs.end())) { - return QValidator::Acceptable; - } else { - return QValidator::Intermediate; - } - } else { - return intRes; - } - } -private: - const std::set &m_UsedIDs; -}; - - -class ValidatingDelegate : public QItemDelegate { - -public: - ValidatingDelegate(QObject *parent, QValidator *validator) - : QItemDelegate(parent), m_Validator(validator) {} - - QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem&, const QModelIndex&) const - { - QLineEdit *edit = new QLineEdit(parent); - edit->setValidator(m_Validator); - return edit; - } - virtual void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const - { - QLineEdit *edit = qobject_cast(editor); - int pos = 0; - QString editText = edit->text(); - if (m_Validator->validate(editText, pos) == QValidator::Acceptable) { - QItemDelegate::setModelData(editor, model, index); - } - } -private: - QValidator *m_Validator; -}; - - -CategoriesDialog::CategoriesDialog(PluginContainer *pluginContainer, QWidget *parent) - : TutorableDialog("Categories", parent), ui(new Ui::CategoriesDialog), m_PluginContainer(pluginContainer) -{ - ui->setupUi(this); - fillTable(); - connect(ui->categoriesTable, SIGNAL(cellChanged(int,int)), this, SLOT(cellChanged(int,int))); - connect(ui->nexusRefresh, SIGNAL(clicked()), this, SLOT(nexusRefresh_clicked())); - connect(ui->nexusImportButton, SIGNAL(clicked()), this, SLOT(nexusImport_clicked())); -} - -CategoriesDialog::~CategoriesDialog() -{ - delete ui; -} - -int CategoriesDialog::exec() -{ - GeometrySaver gs(Settings::instance(), this); - return QDialog::exec(); -} - - -void CategoriesDialog::cellChanged(int row, int) -{ - int currentID = ui->categoriesTable->item(row, 0)->text().toInt(); - if (currentID > m_HighestID) { - m_HighestID = currentID; - } -} - - -void CategoriesDialog::commitChanges() -{ - CategoryFactory *categories = CategoryFactory::instance(); - categories->reset(); - - for (int i = 0; i < ui->categoriesTable->rowCount(); ++i) { - int index = ui->categoriesTable->verticalHeader()->logicalIndex(i); - QVariantList nexusData = ui->categoriesTable->item(index, 3)->data(Qt::UserRole).toList(); - std::vector nexusCats; - for (auto nexusCat : nexusData) { - nexusCats.push_back(CategoryFactory::NexusCategory(nexusCat.toList()[0].toString(), nexusCat.toList()[1].toInt())); - } - - categories->addCategory( - ui->categoriesTable->item(index, 0)->text().toInt(), - ui->categoriesTable->item(index, 1)->text(), - nexusCats, - ui->categoriesTable->item(index, 2)->text().toInt()); - } - - categories->setParents(); - - std::vector nexusCats; - for (int i = 0; i < ui->nexusCategoryList->count(); ++i) { - nexusCats.push_back( - CategoryFactory::NexusCategory( - ui->nexusCategoryList->item(i)->data(Qt::DisplayRole).toString(), - ui->nexusCategoryList->item(i)->data(Qt::UserRole).toInt() - ) - ); - } - - categories->setNexusCategories(nexusCats); - - categories->saveCategories(); -} - - -void CategoriesDialog::refreshIDs() -{ - m_HighestID = 0; - for (int i = 0; i < ui->categoriesTable->rowCount(); ++i) { - int id = ui->categoriesTable->item(i, 0)->text().toInt(); - if (id > m_HighestID) { - m_HighestID = id; - } - m_IDs.insert(id); - } -} - - -void CategoriesDialog::fillTable() -{ - CategoryFactory *categories = CategoryFactory::instance(); - QTableWidget *table = ui->categoriesTable; - QListWidget *list = ui->nexusCategoryList; - - table->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Fixed); - table->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch); - table->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Fixed); - table->horizontalHeader()->setSectionResizeMode(3, QHeaderView::Stretch); - table->verticalHeader()->setSectionsMovable(true); - table->verticalHeader()->setSectionResizeMode(QHeaderView::Fixed); - table->setItemDelegateForColumn(0, new ValidatingDelegate(this, new NewIDValidator(m_IDs))); - table->setItemDelegateForColumn(2, new ValidatingDelegate(this, new ExistingIDValidator(m_IDs))); - table->setItemDelegateForColumn(3, new ValidatingDelegate(this, new QRegularExpressionValidator(QRegularExpression("([0-9]+)?(,[0-9]+)*"), this))); - - int row = 0; - for (std::vector::const_iterator iter = categories->m_Categories.begin(); - iter != categories->m_Categories.end(); ++iter, ++row) { - const CategoryFactory::Category &category = *iter; - if (category.m_ID == 0) { - --row; - continue; - } - table->insertRow(row); -// table->setVerticalHeaderItem(row, new QTableWidgetItem(" ")); - - QScopedPointer idItem(new QTableWidgetItem()); - idItem->setData(Qt::DisplayRole, category.m_ID); - - QScopedPointer nameItem(new QTableWidgetItem(category.m_Name)); - QScopedPointer parentIDItem(new QTableWidgetItem()); - parentIDItem->setData(Qt::DisplayRole, category.m_ParentID); - QScopedPointer nexusCatItem(new QTableWidgetItem()); - - table->setItem(row, 0, idItem.take()); - table->setItem(row, 1, nameItem.take()); - table->setItem(row, 2, parentIDItem.take()); - table->setItem(row, 3, nexusCatItem.take()); - } - - for (auto nexusCat : categories->m_NexusMap) - { - QScopedPointer nexusItem(new QListWidgetItem()); - nexusItem->setData(Qt::DisplayRole, nexusCat.second.m_Name); - nexusItem->setData(Qt::UserRole, nexusCat.second.m_ID); - list->addItem(nexusItem.take()); - auto item = table->item(categories->resolveNexusID(nexusCat.first)-1, 3); - if (item != nullptr) { - auto itemData = item->data(Qt::UserRole).toList(); - QVariantList newData; - newData.append(nexusCat.second.m_Name); - newData.append(nexusCat.second.m_ID); - itemData.insert(itemData.length(), newData); - QStringList names; - for (auto cat : itemData) { - names.append(cat.toList()[0].toString()); - } - item->setData(Qt::UserRole, itemData); - item->setData(Qt::DisplayRole, names.join(", ")); - } - } - - refreshIDs(); -} - - -void CategoriesDialog::addCategory_clicked() -{ - int row = m_ContextRow >= 0 ? m_ContextRow : 0; - ui->categoriesTable->insertRow(row); - ui->categoriesTable->setVerticalHeaderItem(row, new QTableWidgetItem(" ")); - ui->categoriesTable->setItem(row, 0, new QTableWidgetItem(QString::number(++m_HighestID))); - ui->categoriesTable->setItem(row, 1, new QTableWidgetItem("new")); - ui->categoriesTable->setItem(row, 2, new QTableWidgetItem("0")); - ui->categoriesTable->setItem(row, 3, new QTableWidgetItem("")); -} - - -void CategoriesDialog::removeCategory_clicked() -{ - ui->categoriesTable->removeRow(m_ContextRow); -} - - -void CategoriesDialog::nexusRefresh_clicked() -{ - NexusInterface &nexus = NexusInterface::instance(); - nexus.setPluginContainer(m_PluginContainer); - nexus.requestGameInfo(Settings::instance().game().plugin()->gameShortName(), this, QVariant(), QString()); -} - - -void CategoriesDialog::nexusImport_clicked() -{ - if (QMessageBox::question(nullptr, tr("Import Nexus Categories?"), - tr("This will overwrite your existing categories with the loaded Nexus categories."), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - QTableWidget* table = ui->categoriesTable; - QListWidget* list = ui->nexusCategoryList; - - table->setRowCount(0); - refreshIDs(); - int row = 0; - for (int i = 0; i < list->count(); ++i) { - row = table->rowCount(); - table->insertRow(table->rowCount()); - // table->setVerticalHeaderItem(row, new QTableWidgetItem(" ")); - - QScopedPointer idItem(new QTableWidgetItem()); - idItem->setData(Qt::DisplayRole, ++m_HighestID); - - QScopedPointer nameItem(new QTableWidgetItem(list->item(i)->data(Qt::DisplayRole).toString())); - QStringList nexusLabel; - QVariantList nexusData; - nexusLabel.append(list->item(i)->data(Qt::DisplayRole).toString()); - QVariantList data; - data.append(QVariant(list->item(i)->data(Qt::DisplayRole).toString())); - data.append(QVariant(list->item(i)->data(Qt::UserRole).toInt())); - nexusData.insert(nexusData.size(), data); - QScopedPointer nexusCatItem(new QTableWidgetItem(nexusLabel.join(", "))); - nexusCatItem->setData(Qt::UserRole, nexusData); - QScopedPointer parentIDItem(new QTableWidgetItem()); - parentIDItem->setData(Qt::DisplayRole, 0); - - table->setItem(row, 0, idItem.take()); - table->setItem(row, 1, nameItem.take()); - table->setItem(row, 2, parentIDItem.take()); - table->setItem(row, 3, nexusCatItem.take()); - } - refreshIDs(); - } -} - - -void CategoriesDialog::nxmGameInfoAvailable(QString gameName, QVariant, QVariant resultData, int) -{ - QVariantMap result = resultData.toMap(); - QVariantList categories = result["categories"].toList(); - CategoryFactory *catFactory = CategoryFactory::instance(); - QListWidget* list = ui->nexusCategoryList; - list->clear(); - for (auto category : categories) { - auto catMap = category.toMap(); - QScopedPointer nexusItem(new QListWidgetItem()); - nexusItem->setData(Qt::DisplayRole, catMap["name"].toString()); - nexusItem->setData(Qt::UserRole, catMap["category_id"].toInt()); - list->addItem(nexusItem.take()); - } -} - - -void CategoriesDialog::nxmRequestFailed(QString, int, int, QVariant, int, int errorCode, const QString& errorMessage) -{ - MessageDialog::showMessage(tr("Error %1: Request to Nexus failed: %2").arg(errorCode).arg(errorMessage), this); -} - - -void CategoriesDialog::on_categoriesTable_customContextMenuRequested(const QPoint &pos) -{ - m_ContextRow = ui->categoriesTable->rowAt(pos.y()); - QMenu menu; - menu.addAction(tr("Add"), this, SLOT(addCategory_clicked())); - menu.addAction(tr("Remove"), this, SLOT(removeCategory_clicked())); - - menu.exec(ui->categoriesTable->mapToGlobal(pos)); -} +/* +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 "categoriesdialog.h" +#include "categories.h" +#include "settings.h" +#include "ui_categoriesdialog.h" +#include "utility.h" +#include "nexusinterface.h" +#include "messagedialog.h" +#include +#include +#include +#include + +class NewIDValidator : public QIntValidator +{ +public: + NewIDValidator(const std::set& ids) : m_UsedIDs(ids) {} + virtual State validate(QString& input, int& pos) const + { + State intRes = QIntValidator::validate(input, pos); + if (intRes == Acceptable) { + bool ok = false; + int id = input.toInt(&ok); + if (m_UsedIDs.find(id) != m_UsedIDs.end()) { + return QValidator::Intermediate; + } + } + return intRes; + } + +private: + const std::set& m_UsedIDs; +}; + +class ExistingIDValidator : public QIntValidator +{ +public: + ExistingIDValidator(const std::set& ids) : m_UsedIDs(ids) {} + virtual State validate(QString& input, int& pos) const + { + State intRes = QIntValidator::validate(input, pos); + if (intRes == Acceptable) { + bool ok = false; + int id = input.toInt(&ok); + if ((id == 0) || (m_UsedIDs.find(id) != m_UsedIDs.end())) { + return QValidator::Acceptable; + } else { + return QValidator::Intermediate; + } + } else { + return intRes; + } + } + +private: + const std::set& m_UsedIDs; +}; + +class ValidatingDelegate : public QItemDelegate +{ + +public: + ValidatingDelegate(QObject* parent, QValidator* validator) + : QItemDelegate(parent), m_Validator(validator) + {} + + QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem&, + const QModelIndex&) const + { + QLineEdit* edit = new QLineEdit(parent); + edit->setValidator(m_Validator); + return edit; + } + virtual void setModelData(QWidget* editor, QAbstractItemModel* model, + const QModelIndex& index) const + { + QLineEdit* edit = qobject_cast(editor); + int pos = 0; + QString editText = edit->text(); + if (m_Validator->validate(editText, pos) == QValidator::Acceptable) { + QItemDelegate::setModelData(editor, model, index); + } + } + +private: + QValidator* m_Validator; +}; + + +CategoriesDialog::CategoriesDialog(PluginContainer* pluginContainer, QWidget* parent) + : TutorableDialog("Categories", parent), ui(new Ui::CategoriesDialog), m_PluginContainer(pluginContainer) +{ + ui->setupUi(this); + fillTable(); + connect(ui->categoriesTable, SIGNAL(cellChanged(int,int)), this, + SLOT(cellChanged(int,int))); + connect(ui->nexusRefresh, SIGNAL(clicked()), this, SLOT(nexusRefresh_clicked())); + connect(ui->nexusImportButton, SIGNAL(clicked()), this, SLOT(nexusImport_clicked())); +} + +CategoriesDialog::~CategoriesDialog() +{ + delete ui; +} + +int CategoriesDialog::exec() +{ + GeometrySaver gs(Settings::instance(), this); + return QDialog::exec(); +} + +void CategoriesDialog::cellChanged(int row, int) +{ + int currentID = ui->categoriesTable->item(row, 0)->text().toInt(); + if (currentID > m_HighestID) { + m_HighestID = currentID; + } +} + +void CategoriesDialog::commitChanges() +{ + CategoryFactory* categories = CategoryFactory::instance(); + categories->reset(); + + for (int i = 0; i < ui->categoriesTable->rowCount(); ++i) { + int index = ui->categoriesTable->verticalHeader()->logicalIndex(i); + QVariantList nexusData = ui->categoriesTable->item(index, 3)->data(Qt::UserRole).toList(); + std::vector nexusCats; + for (auto nexusCat : nexusData) { + nexusCats.push_back(CategoryFactory::NexusCategory(nexusCat.toList()[0].toString(), nexusCat.toList()[1].toInt())); + } + + categories->addCategory( + ui->categoriesTable->item(index, 0)->text().toInt(), + ui->categoriesTable->item(index, 1)->text(), + nexusCats, + ui->categoriesTable->item(index, 2)->text().toInt()); + } + + categories->setParents(); + + std::vector nexusCats; + for (int i = 0; i < ui->nexusCategoryList->count(); ++i) { + nexusCats.push_back( + CategoryFactory::NexusCategory( + ui->nexusCategoryList->item(i)->data(Qt::DisplayRole).toString(), + ui->nexusCategoryList->item(i)->data(Qt::UserRole).toInt() + ) + ); + } + + categories->setNexusCategories(nexusCats); + + categories->saveCategories(); +} + +void CategoriesDialog::refreshIDs() +{ + m_HighestID = 0; + for (int i = 0; i < ui->categoriesTable->rowCount(); ++i) { + int id = ui->categoriesTable->item(i, 0)->text().toInt(); + if (id > m_HighestID) { + m_HighestID = id; + } + m_IDs.insert(id); + } +} + +void CategoriesDialog::fillTable() +{ + CategoryFactory* categories = CategoryFactory::instance(); + QTableWidget* table = ui->categoriesTable; + QListWidget* list = ui->nexusCategoryList; + + table->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Fixed); + table->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch); + table->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Fixed); + table->horizontalHeader()->setSectionResizeMode(3, QHeaderView::Stretch); + table->verticalHeader()->setSectionsMovable(true); + table->verticalHeader()->setSectionResizeMode(QHeaderView::Fixed); + table->setItemDelegateForColumn(0, new ValidatingDelegate(this, new NewIDValidator(m_IDs))); + table->setItemDelegateForColumn(2, new ValidatingDelegate(this, new ExistingIDValidator(m_IDs))); + table->setItemDelegateForColumn(3, new ValidatingDelegate(this, new QRegularExpressionValidator(QRegularExpression("([0-9]+)?(,[0-9]+)*"), this))); + + int row = 0; + for (std::vector::const_iterator iter = categories->m_Categories.begin(); + iter != categories->m_Categories.end(); ++iter, ++row) { + const CategoryFactory::Category& category = *iter; + if (category.m_ID == 0) { + --row; + continue; + } + table->insertRow(row); + // table->setVerticalHeaderItem(row, new QTableWidgetItem(" ")); + + QScopedPointer idItem(new QTableWidgetItem()); + idItem->setData(Qt::DisplayRole, category.m_ID); + + QScopedPointer nameItem(new QTableWidgetItem(category.m_Name)); + QScopedPointer parentIDItem(new QTableWidgetItem()); + parentIDItem->setData(Qt::DisplayRole, category.m_ParentID); + QScopedPointer nexusCatItem(new QTableWidgetItem()); + + table->setItem(row, 0, idItem.take()); + table->setItem(row, 1, nameItem.take()); + table->setItem(row, 2, parentIDItem.take()); + table->setItem(row, 3, nexusCatItem.take()); + } + + for (auto nexusCat : categories->m_NexusMap) + { + QScopedPointer nexusItem(new QListWidgetItem()); + nexusItem->setData(Qt::DisplayRole, nexusCat.second.m_Name); + nexusItem->setData(Qt::UserRole, nexusCat.second.m_ID); + list->addItem(nexusItem.take()); + auto item = table->item(categories->resolveNexusID(nexusCat.first)-1, 3); + if (item != nullptr) { + auto itemData = item->data(Qt::UserRole).toList(); + QVariantList newData; + newData.append(nexusCat.second.m_Name); + newData.append(nexusCat.second.m_ID); + itemData.insert(itemData.length(), newData); + QStringList names; + for (auto cat : itemData) { + names.append(cat.toList()[0].toString()); + } + item->setData(Qt::UserRole, itemData); + item->setData(Qt::DisplayRole, names.join(", ")); + } + } + + refreshIDs(); +} + +void CategoriesDialog::addCategory_clicked() +{ + int row = m_ContextRow >= 0 ? m_ContextRow : 0; + ui->categoriesTable->insertRow(row); + ui->categoriesTable->setVerticalHeaderItem(row, new QTableWidgetItem(" ")); + ui->categoriesTable->setItem(row, 0, + new QTableWidgetItem(QString::number(++m_HighestID))); + ui->categoriesTable->setItem(row, 1, new QTableWidgetItem("new")); + ui->categoriesTable->setItem(row, 2, new QTableWidgetItem("0")); + ui->categoriesTable->setItem(row, 3, new QTableWidgetItem("")); +} + +void CategoriesDialog::removeCategory_clicked() +{ + ui->categoriesTable->removeRow(m_ContextRow); +} + + +void CategoriesDialog::nexusRefresh_clicked() +{ + NexusInterface &nexus = NexusInterface::instance(); + nexus.setPluginContainer(m_PluginContainer); + nexus.requestGameInfo(Settings::instance().game().plugin()->gameShortName(), this, QVariant(), QString()); +} + + +void CategoriesDialog::nexusImport_clicked() +{ + if (QMessageBox::question(nullptr, tr("Import Nexus Categories?"), + tr("This will overwrite your existing categories with the loaded Nexus categories."), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + QTableWidget* table = ui->categoriesTable; + QListWidget* list = ui->nexusCategoryList; + + table->setRowCount(0); + refreshIDs(); + int row = 0; + for (int i = 0; i < list->count(); ++i) { + row = table->rowCount(); + table->insertRow(table->rowCount()); + // table->setVerticalHeaderItem(row, new QTableWidgetItem(" ")); + + QScopedPointer idItem(new QTableWidgetItem()); + idItem->setData(Qt::DisplayRole, ++m_HighestID); + + QScopedPointer nameItem(new QTableWidgetItem(list->item(i)->data(Qt::DisplayRole).toString())); + QStringList nexusLabel; + QVariantList nexusData; + nexusLabel.append(list->item(i)->data(Qt::DisplayRole).toString()); + QVariantList data; + data.append(QVariant(list->item(i)->data(Qt::DisplayRole).toString())); + data.append(QVariant(list->item(i)->data(Qt::UserRole).toInt())); + nexusData.insert(nexusData.size(), data); + QScopedPointer nexusCatItem(new QTableWidgetItem(nexusLabel.join(", "))); + nexusCatItem->setData(Qt::UserRole, nexusData); + QScopedPointer parentIDItem(new QTableWidgetItem()); + parentIDItem->setData(Qt::DisplayRole, 0); + + table->setItem(row, 0, idItem.take()); + table->setItem(row, 1, nameItem.take()); + table->setItem(row, 2, parentIDItem.take()); + table->setItem(row, 3, nexusCatItem.take()); + } + refreshIDs(); + } +} + + +void CategoriesDialog::nxmGameInfoAvailable(QString gameName, QVariant, QVariant resultData, int) +{ + QVariantMap result = resultData.toMap(); + QVariantList categories = result["categories"].toList(); + CategoryFactory *catFactory = CategoryFactory::instance(); + QListWidget* list = ui->nexusCategoryList; + list->clear(); + for (auto category : categories) { + auto catMap = category.toMap(); + QScopedPointer nexusItem(new QListWidgetItem()); + nexusItem->setData(Qt::DisplayRole, catMap["name"].toString()); + nexusItem->setData(Qt::UserRole, catMap["category_id"].toInt()); + list->addItem(nexusItem.take()); + } +} + + +void CategoriesDialog::nxmRequestFailed(QString, int, int, QVariant, int, int errorCode, const QString& errorMessage) +{ + MessageDialog::showMessage(tr("Error %1: Request to Nexus failed: %2").arg(errorCode).arg(errorMessage), this); +} + + +void CategoriesDialog::on_categoriesTable_customContextMenuRequested(const QPoint& pos) +{ + m_ContextRow = ui->categoriesTable->rowAt(pos.y()); + QMenu menu; + menu.addAction(tr("Add"), this, SLOT(addCategory_clicked())); + menu.addAction(tr("Remove"), this, SLOT(removeCategory_clicked())); + + menu.exec(ui->categoriesTable->mapToGlobal(pos)); +} diff --git a/src/categoriesdialog.h b/src/categoriesdialog.h index 6662bf446..a2bd7240a 100644 --- a/src/categoriesdialog.h +++ b/src/categoriesdialog.h @@ -1,89 +1,86 @@ -/* -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 CATEGORIESDIALOG_H -#define CATEGORIESDIALOG_H - -#include "tutorabledialog.h" -#include "categories.h" -#include "plugincontainer.h" -#include - -namespace Ui { -class CategoriesDialog; -} - -/** - * @brief Dialog that allows users to configure mod categories - **/ -class CategoriesDialog : public MOBase::TutorableDialog -{ - Q_OBJECT - -public: - - explicit CategoriesDialog(PluginContainer *pluginContainer, QWidget *parent = 0); - ~CategoriesDialog(); - - // also saves and restores geometry - // - int exec() override; - - /** - * @brief store changes here to the global categories store (categories.h) - * - **/ - void commitChanges(); - -public slots: - - void nxmGameInfoAvailable(QString gameName, QVariant, QVariant resultData, int); - void nxmRequestFailed(QString, int, int, QVariant, int, int errorCode, const QString& errorMessage); - -signals: - void refreshNexusCategories(); - -private slots: - - void on_categoriesTable_customContextMenuRequested(const QPoint &pos); - void addCategory_clicked(); - void removeCategory_clicked(); - void nexusRefresh_clicked(); - void nexusImport_clicked(); - void cellChanged(int row, int column); - -private: - - void refreshIDs(); - void fillTable(); - -private: - - Ui::CategoriesDialog *ui; - PluginContainer *m_PluginContainer; - int m_ContextRow; - - int m_HighestID; - std::set m_IDs; - std::vector m_NexusCategories; - -}; - -#endif // CATEGORIESDIALOG_H - +/* +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 CATEGORIESDIALOG_H +#define CATEGORIESDIALOG_H + +#include "tutorabledialog.h" +#include "categories.h" +#include "plugincontainer.h" +#include + +namespace Ui +{ +class CategoriesDialog; +} + +/** + * @brief Dialog that allows users to configure mod categories + **/ +class CategoriesDialog : public MOBase::TutorableDialog +{ + Q_OBJECT + +public: + explicit CategoriesDialog(PluginContainer* pluginContainer, QWidget* parent = 0); + ~CategoriesDialog(); + + // also saves and restores geometry + // + int exec() override; + + /** + * @brief store changes here to the global categories store (categories.h) + * + **/ + void commitChanges(); + +public slots: + + void nxmGameInfoAvailable(QString gameName, QVariant, QVariant resultData, int); + void nxmRequestFailed(QString, int, int, QVariant, int, int errorCode, const QString& errorMessage); + +signals: + void refreshNexusCategories(); + +private slots: + + void on_categoriesTable_customContextMenuRequested(const QPoint& pos); + void addCategory_clicked(); + void removeCategory_clicked(); + void nexusRefresh_clicked(); + void nexusImport_clicked(); + void cellChanged(int row, int column); + +private: + void refreshIDs(); + void fillTable(); + +private: + Ui::CategoriesDialog* ui; + PluginContainer* m_PluginContainer; + int m_ContextRow; + + int m_HighestID; + std::set m_IDs; + std::vector m_NexusCategories; + +}; + +#endif // CATEGORIESDIALOG_H diff --git a/src/colortable.cpp b/src/colortable.cpp index b1e4ef6cb..c4e21d3db 100644 --- a/src/colortable.cpp +++ b/src/colortable.cpp @@ -1,29 +1,23 @@ #include "colortable.h" -#include "modflagicondelegate.h" #include "modconflicticondelegate.h" +#include "modflagicondelegate.h" #include "settings.h" class ColorItem; ColorItem* colorItemForRow(QTableWidget* table, int row); -void paintBackground( - QTableWidget* table, QPainter* p, const QStyleOptionViewItem& option, - const QModelIndex& index); - +void paintBackground(QTableWidget* table, QPainter* p, + const QStyleOptionViewItem& option, const QModelIndex& index); // delegate for the sample text column; paints the background color // class ColoredBackgroundDelegate : public QStyledItemDelegate { public: - ColoredBackgroundDelegate(QTableWidget* table) - : m_table(table) - { - } + ColoredBackgroundDelegate(QTableWidget* table) : m_table(table) {} - void paint( - QPainter* p, const QStyleOptionViewItem& option, - const QModelIndex& index) const override + void paint(QPainter* p, const QStyleOptionViewItem& option, + const QModelIndex& index) const override { paintBackground(m_table, p, option, index); @@ -41,20 +35,15 @@ class ColoredBackgroundDelegate : public QStyledItemDelegate QTableWidget* m_table; }; - // delegate for the icons column; paints the background and icons // class FakeModFlagIconDelegate : public ModFlagIconDelegate { public: - explicit FakeModFlagIconDelegate(QTableWidget* table) - : m_table(table) - { - } + explicit FakeModFlagIconDelegate(QTableWidget* table) : m_table(table) {} - void paint( - QPainter* painter, const QStyleOptionViewItem& option, - const QModelIndex& index) const override + void paint(QPainter* painter, const QStyleOptionViewItem& option, + const QModelIndex& index) const override { paintBackground(m_table, painter, option, index); ModFlagIconDelegate::paintIcons(painter, option, index, getIcons(index)); @@ -63,12 +52,8 @@ class FakeModFlagIconDelegate : public ModFlagIconDelegate protected: QList getIcons(const QModelIndex& index) const override { - const auto flags = { - ModInfo::FLAG_BACKUP, - ModInfo::FLAG_NOTENDORSED, - ModInfo::FLAG_NOTES, - ModInfo::FLAG_ALTERNATE_GAME - }; + const auto flags = {ModInfo::FLAG_BACKUP, ModInfo::FLAG_NOTENDORSED, + ModInfo::FLAG_NOTES, ModInfo::FLAG_ALTERNATE_GAME}; return getIconsForFlags(flags, false); } @@ -82,20 +67,15 @@ class FakeModFlagIconDelegate : public ModFlagIconDelegate QTableWidget* m_table; }; - // delegate for the icons column; paints the background and icons // class FakeModConflictIconDelegate : public ModConflictIconDelegate { public: - explicit FakeModConflictIconDelegate(QTableWidget* table) - : m_table(table) - { - } + explicit FakeModConflictIconDelegate(QTableWidget* table) : m_table(table) {} - void paint( - QPainter* painter, const QStyleOptionViewItem& option, - const QModelIndex& index) const override + void paint(QPainter* painter, const QStyleOptionViewItem& option, + const QModelIndex& index) const override { paintBackground(m_table, painter, option, index); ModFlagIconDelegate::paintIcons(painter, option, index, getIcons(index)); @@ -104,12 +84,10 @@ class FakeModConflictIconDelegate : public ModConflictIconDelegate protected: QList getIcons(const QModelIndex& index) const override { - const auto flags = { - ModInfo::FLAG_CONFLICT_MIXED, - ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITE, - ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITTEN, - ModInfo::FLAG_ARCHIVE_CONFLICT_MIXED - }; + const auto flags = {ModInfo::FLAG_CONFLICT_MIXED, + ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITE, + ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITTEN, + ModInfo::FLAG_ARCHIVE_CONFLICT_MIXED}; return getIconsForFlags(flags, false); } @@ -123,18 +101,15 @@ class FakeModConflictIconDelegate : public ModConflictIconDelegate QTableWidget* m_table; }; - // item used in the first column of the table // class ColorItem : public QTableWidgetItem { public: - ColorItem( - QString caption, QColor defaultColor, - std::function get, - std::function commit) : - m_caption(std::move(caption)), m_default(defaultColor), - m_get(get), m_commit(commit) + ColorItem(QString caption, QColor defaultColor, std::function get, + std::function commit) + : m_caption(std::move(caption)), m_default(defaultColor), m_get(get), + m_commit(commit) { setText(m_caption); set(get()); @@ -142,17 +117,11 @@ class ColorItem : public QTableWidgetItem // color caption // - const QString& caption() const - { - return m_caption; - } + const QString& caption() const { return m_caption; } // the current color // - QColor get() const - { - return m_temp; - } + QColor get() const { return m_temp; } // sets the current color, commit() must be called to save it // @@ -168,27 +137,20 @@ class ColorItem : public QTableWidgetItem // resets the current color, commit() must be called to save it // - bool reset() - { - return set(m_default); - } + bool reset() { return set(m_default); } // saves the current color // - void commit() - { - m_commit(m_temp); - } + void commit() { m_commit(m_temp); } private: const QString m_caption; const QColor m_default; - std::function m_get; - std::function m_commit; + std::function m_get; + std::function m_commit; QColor m_temp; }; - ColorItem* colorItemForRow(QTableWidget* table, int row) { return dynamic_cast(table->item(row, 0)); @@ -199,27 +161,24 @@ void forEachColorItem(QTableWidget* table, F&& f) { const auto rowCount = table->rowCount(); - for (int i=0; isave(); p->fillRect(option.rect, ci->get()); p->restore(); } } - -ColorTable::ColorTable(QWidget* parent) - : QTableWidget(parent), m_settings(nullptr) +ColorTable::ColorTable(QWidget* parent) : QTableWidget(parent), m_settings(nullptr) { setColumnCount(4); setHorizontalHeaderLabels({"", "", "", ""}); @@ -228,9 +187,9 @@ ColorTable::ColorTable(QWidget* parent) setItemDelegateForColumn(2, new FakeModConflictIconDelegate(this)); setItemDelegateForColumn(3, new FakeModFlagIconDelegate(this)); - connect( - this, &QTableWidget::cellActivated, - [&]{ onColorActivated(); }); + connect(this, &QTableWidget::cellActivated, [&] { + onColorActivated(); + }); } void ColorTable::load(Settings& s) @@ -238,40 +197,58 @@ void ColorTable::load(Settings& s) m_settings = &s; addColor( - QObject::tr("Is overwritten (loose files)"), - QColor(0, 255, 0, 64), - [this]{ return m_settings->colors().modlistOverwrittenLoose(); }, - [this](auto&& v){ m_settings->colors().setModlistOverwrittenLoose(v); }); + QObject::tr("Is overwritten (loose files)"), QColor(0, 255, 0, 64), + [this] { + return m_settings->colors().modlistOverwrittenLoose(); + }, + [this](auto&& v) { + m_settings->colors().setModlistOverwrittenLoose(v); + }); addColor( - QObject::tr("Is overwriting (loose files)"), - QColor(255, 0, 0, 64), - [this]{ return m_settings->colors().modlistOverwritingLoose(); }, - [this](auto&& v){ m_settings->colors().setModlistOverwritingLoose(v); }); + QObject::tr("Is overwriting (loose files)"), QColor(255, 0, 0, 64), + [this] { + return m_settings->colors().modlistOverwritingLoose(); + }, + [this](auto&& v) { + m_settings->colors().setModlistOverwritingLoose(v); + }); addColor( - QObject::tr("Is overwritten (archives)"), - QColor(0, 255, 255, 64), - [this]{ return m_settings->colors().modlistOverwrittenArchive(); }, - [this](auto&& v){ m_settings->colors().setModlistOverwrittenArchive(v); }); + QObject::tr("Is overwritten (archives)"), QColor(0, 255, 255, 64), + [this] { + return m_settings->colors().modlistOverwrittenArchive(); + }, + [this](auto&& v) { + m_settings->colors().setModlistOverwrittenArchive(v); + }); addColor( - QObject::tr("Is overwriting (archives)"), - QColor(255, 0, 255, 64), - [this]{ return m_settings->colors().modlistOverwritingArchive(); }, - [this](auto&& v){ m_settings->colors().setModlistOverwritingArchive(v); }); + QObject::tr("Is overwriting (archives)"), QColor(255, 0, 255, 64), + [this] { + return m_settings->colors().modlistOverwritingArchive(); + }, + [this](auto&& v) { + m_settings->colors().setModlistOverwritingArchive(v); + }); addColor( - QObject::tr("Mod contains selected plugin"), - QColor(0, 0, 255, 64), - [this]{ return m_settings->colors().modlistContainsPlugin(); }, - [this](auto&& v){ m_settings->colors().setModlistContainsPlugin(v); }); + QObject::tr("Mod contains selected plugin"), QColor(0, 0, 255, 64), + [this] { + return m_settings->colors().modlistContainsPlugin(); + }, + [this](auto&& v) { + m_settings->colors().setModlistContainsPlugin(v); + }); addColor( - QObject::tr("Plugin is contained in selected mod"), - QColor(0, 0, 255, 64), - [this]{ return m_settings->colors().pluginListContained(); }, - [this](auto&& v){ m_settings->colors().setPluginListContained(v); }); + QObject::tr("Plugin is contained in selected mod"), QColor(0, 0, 255, 64), + [this] { + return m_settings->colors().pluginListContained(); + }, + [this](auto&& v) { + m_settings->colors().setPluginListContained(v); + }); } void ColorTable::resetColors() @@ -296,10 +273,9 @@ void ColorTable::commitColors() }); } -void ColorTable::addColor( - const QString& text, const QColor& defaultColor, - std::function get, - std::function commit) +void ColorTable::addColor(const QString& text, const QColor& defaultColor, + std::function get, + std::function commit) { const auto r = rowCount(); setRowCount(r + 1); @@ -319,13 +295,13 @@ void ColorTable::onColorActivated() } const auto row = rows[0].row(); - auto* ci = colorItemForRow(this, row); + auto* ci = colorItemForRow(this, row); if (!ci) { return; } const QColor result = QColorDialog::getColor( - ci->get(), topLevelWidget(), ci->caption(), QColorDialog::ShowAlphaChannel); + ci->get(), topLevelWidget(), ci->caption(), QColorDialog::ShowAlphaChannel); if (result.isValid()) { ci->set(result); diff --git a/src/colortable.h b/src/colortable.h index 039b60241..075171c3d 100644 --- a/src/colortable.h +++ b/src/colortable.h @@ -10,7 +10,7 @@ class Settings; class ColorTable : public QTableWidget { public: - ColorTable(QWidget* parent=nullptr); + ColorTable(QWidget* parent = nullptr); // adds colors to the table from the settings // @@ -28,12 +28,10 @@ class ColorTable : public QTableWidget private: Settings* m_settings; - void addColor( - const QString& text, const QColor& defaultColor, - std::function get, - std::function commit); + void addColor(const QString& text, const QColor& defaultColor, + std::function get, std::function commit); void onColorActivated(); }; -#endif // COLORTABLE_H +#endif // COLORTABLE_H diff --git a/src/commandline.cpp b/src/commandline.cpp index f64ff7cb5..9082d710a 100644 --- a/src/commandline.cpp +++ b/src/commandline.cpp @@ -1,11 +1,11 @@ #include "commandline.h" #include "env.h" -#include "organizercore.h" #include "instancemanager.h" -#include "multiprocess.h" #include "loglist.h" -#include "shared/util.h" +#include "multiprocess.h" +#include "organizercore.h" #include "shared/appconfig.h" +#include "shared/util.h" #include #include @@ -14,19 +14,18 @@ namespace cl using namespace MOBase; -std::string pad_right(std::string s, std::size_t n, char c=' ') +std::string pad_right(std::string s, std::size_t n, char c = ' ') { if (s.size() < n) - s.append(n - s.size() , c); + s.append(n - s.size(), c); return s; } // formats the list of pairs in two columns // -std::string table( - const std::vector>& v, - std::size_t indent, std::size_t spacing) +std::string table(const std::vector>& v, + std::size_t indent, std::size_t spacing) { std::size_t longest = 0; @@ -35,40 +34,28 @@ std::string table( std::string s; - for (auto&& p : v) - { + for (auto&& p : v) { if (!s.empty()) s += "\n"; - s += - std::string(indent, ' ') + - pad_right(p.first, longest) + " " + - std::string(spacing, ' ') + - p.second; + s += std::string(indent, ' ') + pad_right(p.first, longest) + " " + + std::string(spacing, ' ') + p.second; } return s; - } - -CommandLine::CommandLine() - : m_command(nullptr) +CommandLine::CommandLine() : m_command(nullptr) { createOptions(); - add< - RunCommand, - ReloadPluginCommand, - RefreshCommand, - CrashDumpCommand, - LaunchCommand>(); + add(); } std::optional CommandLine::process(const std::wstring& line) { - try - { + try { auto args = po::split_winmain(line); if (!args.empty()) { // remove program name @@ -79,18 +66,16 @@ std::optional CommandLine::process(const std::wstring& line) // command name, but not the rest, which will be collected below auto parsed = po::wcommand_line_parser(args) - .options(m_allOptions) - .positional(m_positional) - .allow_unregistered() - .run(); + .options(m_allOptions) + .positional(m_positional) + .allow_unregistered() + .run(); po::store(parsed, m_vm); po::notify(m_vm); // collect options past the command name - auto opts = po::collect_unrecognized( - parsed.options, po::include_positional); - + auto opts = po::collect_unrecognized(parsed.options, po::include_positional); if (m_vm.count("command")) { // there's a word as the first argument; this may be a command name or @@ -106,8 +91,7 @@ std::optional CommandLine::process(const std::wstring& line) // remove the command name itself opts.erase(opts.begin()); - try - { + try { // legacy commands handle their own parsing, such as 'launch'; don't // attempt to parse anything here if (!c->legacy()) { @@ -140,14 +124,10 @@ std::optional CommandLine::process(const std::wstring& line) m_command = c.get(); return runEarly(); - } - catch(po::error& e) - { + } catch (po::error& e) { env::Console console; - std::cerr - << e.what() << "\n" - << usage(c.get()) << "\n"; + std::cerr << e.what() << "\n" << usage(c.get()) << "\n"; return 1; } @@ -155,7 +135,6 @@ std::optional CommandLine::process(const std::wstring& line) } } - // the first word on the command line is not a valid command, try the other // stuff; this is used in setupCore() below when called from // MOApplication::doOneRun() @@ -167,7 +146,6 @@ std::optional CommandLine::process(const std::wstring& line) return 0; } - if (!opts.empty()) { const auto qs = QString::fromStdWString(opts[0]); @@ -202,14 +180,10 @@ std::optional CommandLine::process(const std::wstring& line) } return {}; - } - catch(po::error& e) - { + } catch (po::error& e) { env::Console console; - std::cerr - << e.what() << "\n" - << usage() << "\n"; + std::cerr << e.what() << "\n" << usage() << "\n"; return 1; } @@ -250,7 +224,7 @@ std::optional CommandLine::runPostApplication(MOApplication& a) if (m_vm.count("instance") && m_vm["instance"].as() == "") { env::Console c; - if (auto i=InstanceManager::singleton().currentInstance()) { + if (auto i = InstanceManager::singleton().currentInstance()) { std::cout << i->displayName().toStdString() << "\n"; } else { std::cout << "no instance configured\n"; @@ -283,13 +257,12 @@ std::optional CommandLine::runPostOrganizer(OrganizerCore& core) // make sure MO doesn't exit even if locking is disabled, ForceWait and // PreventExit will do that core.processRunner() - .setFromShortcut(m_shortcut) - .setWaitForCompletion(ProcessRunner::ForCommandLine, UILocker::PreventExit) - .run(); + .setFromShortcut(m_shortcut) + .setWaitForCompletion(ProcessRunner::ForCommandLine, UILocker::PreventExit) + .run(); return 0; - } - catch (std::exception&) { + } catch (std::exception&) { // user was already warned return 1; } @@ -301,23 +274,19 @@ std::optional CommandLine::runPostOrganizer(OrganizerCore& core) const QString exeName = *m_executable; log::debug("starting {} from command line", exeName); - try - { + try { // pass the remaining parameters to the binary // // make sure MO doesn't exit even if locking is disabled, ForceWait and // PreventExit will do that core.processRunner() - .setFromFileOrExecutable(exeName, m_untouched) - .setWaitForCompletion(ProcessRunner::ForCommandLine, UILocker::PreventExit) - .run(); + .setFromFileOrExecutable(exeName, m_untouched) + .setWaitForCompletion(ProcessRunner::ForCommandLine, UILocker::PreventExit) + .run(); return 0; - } - catch (const std::exception &e) - { - reportError( - QObject::tr("failed to start application: %1").arg(e.what())); + } catch (const std::exception& e) { + reportError(QObject::tr("failed to start application: %1").arg(e.what())); return 1; } } else if (m_command) { @@ -331,42 +300,31 @@ void CommandLine::clear() { m_vm.clear(); m_shortcut = {}; - m_nxmLink = {}; + m_nxmLink = {}; } void CommandLine::createOptions() { - m_visibleOptions.add_options() - ("help", - "show this message") - - ("multiple", - "allow multiple MO processes to run; see below") + m_visibleOptions.add_options()("help", "show this message") - ("pick", - "show the select instance dialog on startup") + ("multiple", "allow multiple MO processes to run; see below") - ("logs", - "duplicates the logs to stdout") + ("pick", "show the select instance dialog on startup") - ("instance,i", - po::value()->implicit_value(""), - "use the given instance (defaults to last used)") + ("logs", "duplicates the logs to stdout") - ("profile,p", - po::value(), - "use the given profile (defaults to last used)"); + ("instance,i", po::value()->implicit_value(""), + "use the given instance (defaults to last used)") + ("profile,p", po::value(), + "use the given profile (defaults to last used)"); po::options_description options; - options.add_options() - ("command", po::value(), "command") - ("subargs", po::value >(), "args"); + options.add_options()("command", po::value(), "command")( + "subargs", po::value>(), "args"); // one command name, followed by any arguments for that command - m_positional - .add("command", 1) - .add("subargs", -1); + m_positional.add("command", 1).add("subargs", -1); m_allOptions.add(m_visibleOptions); m_allOptions.add(options); @@ -376,27 +334,22 @@ std::string CommandLine::usage(const Command* c) const { std::ostringstream oss; - oss - << "\n" - << "Usage:\n"; + oss << "\n" + << "Usage:\n"; if (c) { - oss - << " ModOrganizer.exe [global-options] " << c->usageLine() << "\n\n"; + oss << " ModOrganizer.exe [global-options] " << c->usageLine() << "\n\n"; const std::string more = c->moreInfo(); if (!more.empty()) { oss << more << "\n\n"; } - oss - << "Command options:\n" - << c->visibleOptions() << "\n"; + oss << "Command options:\n" << c->visibleOptions() << "\n"; } else { - oss - << " ModOrganizer.exe [options] [[command] [command-options]]\n" - << "\n" - << "Commands:\n"; + oss << " ModOrganizer.exe [options] [[command] [command-options]]\n" + << "\n" + << "Commands:\n"; // name and description for all commands std::vector> v; @@ -409,14 +362,11 @@ std::string CommandLine::usage(const Command* c) const v.push_back({c->name(), c->description()}); } - oss - << table(v, 2, 4) << "\n" - << "\n"; + oss << table(v, 2, 4) << "\n" + << "\n"; } - oss - << "Global options:\n" - << m_visibleOptions << "\n"; + oss << "Global options:\n" << m_visibleOptions << "\n"; // show the more text unless this is usage for a specific command if (!c) { @@ -480,32 +430,29 @@ const QStringList& CommandLine::untouched() const std::string CommandLine::more() const { - return - "Multiple processes\n" - " A note on terminology: 'instance' can either mean an MO process\n" - " that's running on the system, or a set of mods and profiles managed\n" - " by MO. To avoid confusion, the term 'process' is used below for the\n" - " former.\n" - " \n" - " --multiple can be used to allow multiple MO processes to run\n" - " simultaneously. This is unsupported and can create all sorts of weird\n" - " problems. To minimize these:\n" - " \n" - " 1) Never have multiple MO processes running that manage the same\n" - " game instance.\n" - " 2) If an executable is launched from an MO process, only this\n" - " process may launch executables until all processes are \n" - " terminated.\n" - " \n" - " It is recommended to close _all_ MO processes as soon as multiple\n" - " processes become unnecessary."; + return "Multiple processes\n" + " A note on terminology: 'instance' can either mean an MO process\n" + " that's running on the system, or a set of mods and profiles managed\n" + " by MO. To avoid confusion, the term 'process' is used below for the\n" + " former.\n" + " \n" + " --multiple can be used to allow multiple MO processes to run\n" + " simultaneously. This is unsupported and can create all sorts of weird\n" + " problems. To minimize these:\n" + " \n" + " 1) Never have multiple MO processes running that manage the same\n" + " game instance.\n" + " 2) If an executable is launched from an MO process, only this\n" + " process may launch executables until all processes are \n" + " terminated.\n" + " \n" + " It is recommended to close _all_ MO processes as soon as multiple\n" + " processes become unnecessary."; } - Command::Meta::Meta(std::string n, std::string d, std::string u, std::string m) - : name(n), description(d), usage(u), more(m) -{ -} + : name(n), description(d), usage(u), more(m) +{} std::string Command::name() const { @@ -541,8 +488,7 @@ po::options_description Command::visibleOptions() const { po::options_description d(getVisibleOptions()); - d.add_options() - ("help", "shows this message"); + d.add_options()("help", "shows this message"); return d; } @@ -575,13 +521,11 @@ po::positional_options_description Command::getPositional() const return {}; } -void Command::set( - const std::wstring& originalLine, - po::variables_map vm, - std::vector untouched) +void Command::set(const std::wstring& originalLine, po::variables_map vm, + std::vector untouched) { - m_original = originalLine; - m_vm = vm; + m_original = originalLine; + m_vm = vm; m_untouched = untouched; } @@ -625,25 +569,20 @@ const std::vector& Command::untouched() const return m_untouched; } - po::options_description CrashDumpCommand::getVisibleOptions() const { po::options_description d; - d.add_options() - ("type", po::value()->default_value("mini"), "mini|data|full"); + d.add_options()("type", po::value()->default_value("mini"), + "mini|data|full"); return d; } Command::Meta CrashDumpCommand::meta() const { - return { - "crashdump", - "writes a crashdump for a running process of MO", - "[options]", - "" - }; + return {"crashdump", "writes a crashdump for a running process of MO", "[options]", + ""}; } std::optional CrashDumpCommand::runEarly() @@ -651,7 +590,7 @@ std::optional CrashDumpCommand::runEarly() env::Console console; const auto typeString = vm()["type"].as(); - const auto type = env::coreDumpTypeFromString(typeString); + const auto type = env::coreDumpTypeFromString(typeString); // dump const auto b = env::coredumpOther(type); @@ -665,7 +604,6 @@ std::optional CrashDumpCommand::runEarly() return (b ? 0 : 1); } - Command::Meta LaunchCommand::meta() const { return {"launch", "(internal, do not use)", "", ""}; @@ -691,14 +629,15 @@ std::optional LaunchCommand::runEarly() int LaunchCommand::SpawnWaitProcess(LPCWSTR workingDirectory, LPCWSTR commandLine) { - PROCESS_INFORMATION pi{ 0 }; - STARTUPINFO si{ 0 }; - si.cb = sizeof(si); + PROCESS_INFORMATION pi{0}; + STARTUPINFO si{0}; + si.cb = sizeof(si); std::wstring commandLineCopy = commandLine; - if (!CreateProcessW(NULL, &commandLineCopy[0], NULL, NULL, FALSE, 0, NULL, workingDirectory, &si, &pi)) { - // A bit of a problem where to log the error message here, at least this way you can get the message - // using a either DebugView or a live debugger: + if (!CreateProcessW(NULL, &commandLineCopy[0], NULL, NULL, FALSE, 0, NULL, + workingDirectory, &si, &pi)) { + // A bit of a problem where to log the error message here, at least this way you can + // get the message using a either DebugView or a live debugger: std::wostringstream ost; ost << L"CreateProcess failed: " << commandLine << ", " << GetLastError(); OutputDebugStringW(ost.str().c_str()); @@ -714,15 +653,15 @@ int LaunchCommand::SpawnWaitProcess(LPCWSTR workingDirectory, LPCWSTR commandLin return static_cast(exitCode); } -// Parses the first parseArgCount arguments of the current process command line and returns -// them in parsedArgs, the rest of the command line is returned untouched. -LPCWSTR LaunchCommand::UntouchedCommandLineArguments( - int parseArgCount, std::vector& parsedArgs) +// Parses the first parseArgCount arguments of the current process command line and +// returns them in parsedArgs, the rest of the command line is returned untouched. +LPCWSTR +LaunchCommand::UntouchedCommandLineArguments(int parseArgCount, + std::vector& parsedArgs) { LPCWSTR cmd = GetCommandLineW(); - LPCWSTR arg = nullptr; // to skip executable name - for (; parseArgCount >= 0 && *cmd; ++cmd) - { + LPCWSTR arg = nullptr; // to skip executable name + for (; parseArgCount >= 0 && *cmd; ++cmd) { if (*cmd == '"') { int escaped = 0; for (++cmd; *cmd && (*cmd != '"' || escaped % 2 != 0); ++cmd) @@ -730,8 +669,8 @@ LPCWSTR LaunchCommand::UntouchedCommandLineArguments( } if (*cmd == ' ') { if (arg) - if (cmd-1 > arg && *arg == '"' && *(cmd-1) == '"') - parsedArgs.push_back(std::wstring(arg+1, cmd-1)); + if (cmd - 1 > arg && *arg == '"' && *(cmd - 1) == '"') + parsedArgs.push_back(std::wstring(arg + 1, cmd - 1)); else parsedArgs.push_back(std::wstring(arg, cmd)); arg = cmd + 1; @@ -741,29 +680,25 @@ LPCWSTR LaunchCommand::UntouchedCommandLineArguments( return cmd; } - Command::Meta RunCommand::meta() const { - return { - "run", - "runs a program, file or a configured executable", - "[options] NAME", + return {"run", "runs a program, file or a configured executable", "[options] NAME", - "Runs a program or a file with the virtual filesystem. If NAME is a path\n" - "to a non-executable file, the program that is associated with the file\n" - "extension is run instead. With -e, NAME must refer to the name of an\n" - "executable in the instance (for example, \"SKSE\")." - }; + "Runs a program or a file with the virtual filesystem. If NAME is a path\n" + "to a non-executable file, the program that is associated with the file\n" + "extension is run instead. With -e, NAME must refer to the name of an\n" + "executable in the instance (for example, \"SKSE\")."}; } po::options_description RunCommand::getVisibleOptions() const { po::options_description d; - d.add_options() - ("executable,e", po::value()->default_value(false)->zero_tokens(), "the name is a configured executable name") - ("arguments,a", po::value(), "override arguments") - ("cwd,c", po::value(), "override working directory"); + d.add_options()("executable,e", + po::value()->default_value(false)->zero_tokens(), + "the name is a configured executable name")( + "arguments,a", po::value(), "override arguments")( + "cwd,c", po::value(), "override working directory"); return d; } @@ -772,8 +707,8 @@ po::options_description RunCommand::getInternalOptions() const { po::options_description d; - d.add_options() - ("NAME", po::value()->required(), "program or executable name"); + d.add_options()("NAME", po::value()->required(), + "program or executable name"); return d; } @@ -796,8 +731,7 @@ std::optional RunCommand::runPostOrganizer(OrganizerCore& core) { const auto program = QString::fromStdString(vm()["NAME"].as()); - try - { + try { // make sure MO doesn't exit even if locking is disabled, ForceWait and // PreventExit will do that auto p = core.processRunner(); @@ -814,9 +748,9 @@ std::optional RunCommand::runPostOrganizer(OrganizerCore& core) if (itor == exes.end()) { // not found reportError( - QObject::tr("Executable '%1' not found in instance '%2'.") - .arg(program) - .arg(InstanceManager::singleton().currentInstance()->displayName())); + QObject::tr("Executable '%1' not found in instance '%2'.") + .arg(program) + .arg(InstanceManager::singleton().currentInstance()->displayName())); return 1; } @@ -840,40 +774,33 @@ std::optional RunCommand::runPostOrganizer(OrganizerCore& core) const auto r = p.run(); if (r == ProcessRunner::Error) { reportError( - QObject::tr("Failed to run '%1'. The logs might have more information.").arg(program)); + QObject::tr("Failed to run '%1'. The logs might have more information.") + .arg(program)); return 1; } return 0; - } - catch (const std::exception &e) { + } catch (const std::exception& e) { reportError( - QObject::tr("Failed to run '%1'. The logs might have more information. %2") - .arg(program).arg(e.what())); + QObject::tr("Failed to run '%1'. The logs might have more information. %2") + .arg(program) + .arg(e.what())); return 1; } } - - Command::Meta ReloadPluginCommand::meta() const { - return { - "reload-plugin", - "reloads the given plugin", - "PLUGIN", - "" - }; + return {"reload-plugin", "reloads the given plugin", "PLUGIN", ""}; } po::options_description ReloadPluginCommand::getInternalOptions() const { po::options_description d; - d.add_options() - ("PLUGIN", po::value()->required(), "plugin name"); + d.add_options()("PLUGIN", po::value()->required(), "plugin name"); return d; } @@ -896,10 +823,9 @@ std::optional ReloadPluginCommand::runPostOrganizer(OrganizerCore& core) { const QString name = QString::fromStdString(vm()["PLUGIN"].as()); - QString filepath = QDir( - qApp->applicationDirPath() + "/" + - ToQString(AppConfig::pluginPath())) - .absoluteFilePath(name); + QString filepath = + QDir(qApp->applicationDirPath() + "/" + ToQString(AppConfig::pluginPath())) + .absoluteFilePath(name); log::debug("reloading plugin from {}", filepath); core.pluginContainer().reloadPlugin(filepath); @@ -907,14 +833,9 @@ std::optional ReloadPluginCommand::runPostOrganizer(OrganizerCore& core) return {}; } - Command::Meta RefreshCommand::meta() const { - return { - "refresh", - "refreshes MO (same as F5)", - "", "" - }; + return {"refresh", "refreshes MO (same as F5)", "", ""}; } bool RefreshCommand::canForwardToPrimary() const @@ -928,4 +849,4 @@ std::optional RefreshCommand::runPostOrganizer(OrganizerCore& core) return {}; } -} // namespace +} // namespace cl diff --git a/src/commandline.h b/src/commandline.h index a0b70fb2e..c93d77f5e 100644 --- a/src/commandline.h +++ b/src/commandline.h @@ -1,8 +1,8 @@ #ifndef MODORGANIZER_COMMANDLINE_INCLUDED #define MODORGANIZER_COMMANDLINE_INCLUDED #include "moshortcut.h" -#include #include +#include class OrganizerCore; class MOApplication; @@ -37,7 +37,6 @@ class Command // std::string moreInfo() const; - // returns all options for this command, including hidden ones; used to parse // the command line // @@ -58,11 +57,8 @@ class Command // remembers the given values // - void set( - const std::wstring& originalLine, - po::variables_map vm, - std::vector untouched); - + void set(const std::wstring& originalLine, po::variables_map vm, + std::vector untouched); // called as soon as the command line has been parsed; return something to // exit immediately @@ -105,17 +101,14 @@ class Command { std::string name, description, usage, more; - Meta( - std::string name, std::string description, - std::string usage, std::string more); + Meta(std::string name, std::string description, std::string usage, + std::string more); }; - // meta // virtual Meta meta() const = 0; - // returns visible options specific to this command // virtual po::options_description getVisibleOptions() const; @@ -128,7 +121,6 @@ class Command // virtual po::positional_options_description getPositional() const; - // returns the original command line // const std::wstring& originalCmd() const; @@ -147,7 +139,6 @@ class Command std::vector m_untouched; }; - // generates a crash dump for another MO process // class CrashDumpCommand : public Command @@ -158,7 +149,6 @@ class CrashDumpCommand : public Command std::optional runEarly() override; }; - // this is the `launch` command used when starting a process from within the // virtualized directory, see processrunner.cpp // @@ -182,11 +172,10 @@ class LaunchCommand : public Command int SpawnWaitProcess(LPCWSTR workingDirectory, LPCWSTR commandLine); - LPCWSTR UntouchedCommandLineArguments( - int parseArgCount, std::vector& parsedArgs); + LPCWSTR UntouchedCommandLineArguments(int parseArgCount, + std::vector& parsedArgs); }; - // runs a program or an executable // class RunCommand : public Command @@ -202,7 +191,6 @@ class RunCommand : public Command std::optional runPostOrganizer(OrganizerCore& core) override; }; - // reloads the given plugin // class ReloadPluginCommand : public Command @@ -217,7 +205,6 @@ class ReloadPluginCommand : public Command std::optional runPostOrganizer(OrganizerCore& core) override; }; - // refreshes mo // class RefreshCommand : public Command @@ -228,8 +215,6 @@ class RefreshCommand : public Command std::optional runPostOrganizer(OrganizerCore& core) override; }; - - // parses the command line and runs any given command // // the command line used to support a few commands but with no real conventions; @@ -308,7 +293,6 @@ class CommandLine // bool forwardToPrimary(MOMultiProcess& multiProcess); - // clears parsed options, used when MO is "restarted" so the options aren't // processed again // @@ -316,7 +300,7 @@ class CommandLine // global usage string plus usage for the given command, if any // - std::string usage(const Command* c=nullptr) const; + std::string usage(const Command* c = nullptr) const; // whether --pick was given // @@ -374,6 +358,6 @@ class CommandLine std::optional runEarly(); }; -} // namespace +} // namespace cl #endif // MODORGANIZER_COMMANDLINE_INCLUDED diff --git a/src/copyeventfilter.cpp b/src/copyeventfilter.cpp index bd61e04d0..cee574c33 100644 --- a/src/copyeventfilter.cpp +++ b/src/copyeventfilter.cpp @@ -4,18 +4,16 @@ #include #include -CopyEventFilter::CopyEventFilter(QAbstractItemView* view, int column, int role) : - CopyEventFilter(view, [=](auto& index) { return index.sibling(index.row(), column).data(role).toString(); }) -{ - -} +CopyEventFilter::CopyEventFilter(QAbstractItemView* view, int column, int role) + : CopyEventFilter(view, [=](auto& index) { + return index.sibling(index.row(), column).data(role).toString(); + }) +{} -CopyEventFilter::CopyEventFilter( - QAbstractItemView* view, std::function format) : - QObject(view), m_view(view), m_format(format) -{ - -} +CopyEventFilter::CopyEventFilter(QAbstractItemView* view, + std::function format) + : QObject(view), m_view(view), m_format(format) +{} void CopyEventFilter::copySelection() const { @@ -25,9 +23,10 @@ void CopyEventFilter::copySelection() const // sort to reflect the visual order QModelIndexList selectedRows = m_view->selectionModel()->selectedRows(); - std::sort(selectedRows.begin(), selectedRows.end(), [=](const auto& lidx, const auto& ridx) { - return m_view->visualRect(lidx).top() < m_view->visualRect(ridx).top(); - }); + std::sort(selectedRows.begin(), selectedRows.end(), + [=](const auto& lidx, const auto& ridx) { + return m_view->visualRect(lidx).top() < m_view->visualRect(ridx).top(); + }); QStringList rows; for (auto& idx : selectedRows) { @@ -41,8 +40,7 @@ bool CopyEventFilter::eventFilter(QObject* sender, QEvent* event) { if (sender == m_view && event->type() == QEvent::KeyPress) { QKeyEvent* keyEvent = static_cast(event); - if (keyEvent->modifiers() == Qt::ControlModifier - && keyEvent->key() == Qt::Key_C) { + if (keyEvent->modifiers() == Qt::ControlModifier && keyEvent->key() == Qt::Key_C) { copySelection(); return true; } diff --git a/src/copyeventfilter.h b/src/copyeventfilter.h index 141ddae13..0ad515844 100644 --- a/src/copyeventfilter.h +++ b/src/copyeventfilter.h @@ -22,9 +22,9 @@ class CopyEventFilter : public QObject Q_OBJECT public: - CopyEventFilter(QAbstractItemView* view, int column = 0, int role = Qt::DisplayRole); - CopyEventFilter(QAbstractItemView* view, std::function format); + CopyEventFilter(QAbstractItemView* view, + std::function format); // copy the selection of the view associated with this // event filter into the clipboard @@ -34,10 +34,8 @@ class CopyEventFilter : public QObject bool eventFilter(QObject* sender, QEvent* event) override; private: - QAbstractItemView* m_view; std::function m_format; - }; #endif diff --git a/src/createinstancedialog.cpp b/src/createinstancedialog.cpp index e728aec1f..c5f51e0ec 100644 --- a/src/createinstancedialog.cpp +++ b/src/createinstancedialog.cpp @@ -1,17 +1,17 @@ #include "createinstancedialog.h" -#include "ui_createinstancedialog.h" #include "createinstancedialogpages.h" #include "instancemanager.h" #include "settings.h" -#include "shared/util.h" #include "shared/appconfig.h" +#include "shared/util.h" +#include "ui_createinstancedialog.h" #include #include using namespace MOBase; - -class Failed {}; +class Failed +{}; // create() will create all the directories in `target`; if any path component // fails to create, it will throw Failed @@ -22,31 +22,24 @@ class Failed {}; class DirectoryCreator { public: - DirectoryCreator(const DirectoryCreator&) = delete; + DirectoryCreator(const DirectoryCreator&) = delete; DirectoryCreator& operator=(const DirectoryCreator&) = delete; - static std::unique_ptr create( - const QDir& target, std::function log) + static std::unique_ptr create(const QDir& target, + std::function log) { return std::unique_ptr(new DirectoryCreator(target, log)); } - ~DirectoryCreator() - { - rollback(); - } + ~DirectoryCreator() { rollback(); } - void commit() - { - m_created.clear(); - } + void commit() { m_created.clear(); } void rollback() noexcept { - try - { + try { // delete each directory starting from the end - for (auto itor=m_created.rbegin(); itor!=m_created.rend(); ++itor) { + for (auto itor = m_created.rbegin(); itor != m_created.rend(); ++itor) { const auto r = shell::DeleteDirectoryRecursive(*itor); if (!r) { m_logger(r.toString()); @@ -54,23 +47,19 @@ class DirectoryCreator } m_created.clear(); - } - catch(...) - { + } catch (...) { // eat it } } private: - std::function m_logger; + std::function m_logger; - DirectoryCreator(const QDir& target, std::function log) - : m_logger(log) + DirectoryCreator(const QDir& target, std::function log) : m_logger(log) { - try - { + try { // split on separators - const QString s = QDir::toNativeSeparators(target.absolutePath()); + const QString s = QDir::toNativeSeparators(target.absolutePath()); const QStringList cs = s.split("\\"); if (cs.empty()) { @@ -81,7 +70,7 @@ class DirectoryCreator QDir d(cs[0]); // for each directory after the root - for (int i=1; i m_created; }; - -CreateInstanceDialog::CreateInstanceDialog( - const PluginContainer& pc, Settings* s, QWidget *parent) : - QDialog(parent), ui(new Ui::CreateInstanceDialog), m_pc(pc), m_settings(s), - m_switching(false), m_singlePage(false) +CreateInstanceDialog::CreateInstanceDialog(const PluginContainer& pc, Settings* s, + QWidget* parent) + : QDialog(parent), ui(new Ui::CreateInstanceDialog), m_pc(pc), m_settings(s), + m_switching(false), m_singlePage(false) { using namespace cid; @@ -131,8 +117,7 @@ CreateInstanceDialog::CreateInstanceDialog( ui->pages->setCurrentIndex(0); ui->launch->setChecked(true); - if (!InstanceManager::singleton().hasAnyInstances()) - { + if (!InstanceManager::singleton().hasAnyInstances()) { // first run of MO, there are no instances yet, force launch ui->launch->setEnabled(false); } @@ -147,13 +132,25 @@ CreateInstanceDialog::CreateInstanceDialog( addShortcutAction(QKeySequence::Find, Actions::Find); - addShortcut(Qt::ALT+Qt::Key_Left, [&]{ back(); }); - addShortcut(Qt::ALT+Qt::Key_Right, [&]{ next(false); }); - addShortcut(Qt::CTRL+Qt::Key_Return, [&]{ next(); }); + addShortcut(Qt::ALT + Qt::Key_Left, [&] { + back(); + }); + addShortcut(Qt::ALT + Qt::Key_Right, [&] { + next(false); + }); + addShortcut(Qt::CTRL + Qt::Key_Return, [&] { + next(); + }); - connect(ui->next, &QPushButton::clicked, [&]{ next(); }); - connect(ui->back, &QPushButton::clicked, [&]{ back(); }); - connect(ui->cancel, &QPushButton::clicked, [&]{ reject(); }); + connect(ui->next, &QPushButton::clicked, [&] { + next(); + }); + connect(ui->back, &QPushButton::clicked, [&] { + back(); + }); + connect(ui->cancel, &QPushButton::clicked, [&] { + reject(); + }); } CreateInstanceDialog::~CreateInstanceDialog() = default; @@ -175,7 +172,7 @@ Settings* CreateInstanceDialog::settings() bool CreateInstanceDialog::isOnLastPage() const { - for (int i=ui->pages->currentIndex() + 1; i < ui->pages->count(); ++i) { + for (int i = ui->pages->currentIndex() + 1; i < ui->pages->count(); ++i) { if (!m_pages[i]->skip()) { return false; } @@ -190,7 +187,7 @@ void CreateInstanceDialog::next(bool allowFinish) return; } - const auto i = ui->pages->currentIndex(); + const auto i = ui->pages->currentIndex(); const auto last = isOnLastPage(); if (last) { @@ -216,8 +213,7 @@ void CreateInstanceDialog::back() changePage(-1); } -void CreateInstanceDialog::addShortcut( - QKeySequence seq, std::function f) +void CreateInstanceDialog::addShortcut(QKeySequence seq, std::function f) { auto* sc = new QShortcut(seq, this); @@ -229,7 +225,9 @@ void CreateInstanceDialog::addShortcut( void CreateInstanceDialog::addShortcutAction(QKeySequence seq, Actions a) { - addShortcut(seq, [this, a]{ doAction(a); }); + addShortcut(seq, [this, a] { + doAction(a); + }); } void CreateInstanceDialog::doAction(Actions a) @@ -313,13 +311,10 @@ void CreateInstanceDialog::finish() return DirectoryCreator::create(path, logger); }; - // don't restart if this is the first instance, it'll be selected and opened const bool mustRestart = InstanceManager::singleton().hasAnyInstances(); - - try - { + try { std::vector> dirs; // creating all these directories; if any of them fail, this throws and @@ -331,7 +326,6 @@ void CreateInstanceDialog::finish() dirs.push_back(createDir(PathSettings::resolve(ci.paths.profiles, ci.paths.base))); dirs.push_back(createDir(PathSettings::resolve(ci.paths.overwrite, ci.paths.base))); - // creating ini Settings s(ci.iniPath); s.game().setName(ci.game->gameName()); @@ -367,25 +361,23 @@ void CreateInstanceDialog::finish() const auto r = s.sync(); if (r != QSettings::NoError) { - switch (r) - { - case QSettings::AccessError: - logCreation(formatSystemMessage(ERROR_ACCESS_DENIED)); - break; - - case QSettings::FormatError: - logCreation(tr("Format error.")); - break; - - default: - logCreation(tr("Error %1.").arg(static_cast(r))); - break; + switch (r) { + case QSettings::AccessError: + logCreation(formatSystemMessage(ERROR_ACCESS_DENIED)); + break; + + case QSettings::FormatError: + logCreation(tr("Format error.")); + break; + + default: + logCreation(tr("Error %1.").arg(static_cast(r))); + break; } throw Failed(); } - // committing all the directories so they don't get deleted for (auto& d : dirs) { d->commit(); @@ -405,9 +397,7 @@ void CreateInstanceDialog::finish() // close the dialog accept(); - } - catch(Failed&) - { + } catch (Failed&) { // if Failed was thrown, all the directories have been deleted } } @@ -436,7 +426,7 @@ void CreateInstanceDialog::selectPage(std::size_t i) void CreateInstanceDialog::updateNavigation() { - const auto i = ui->pages->currentIndex(); + const auto i = ui->pages->currentIndex(); const auto last = isOnLastPage(); ui->next->setEnabled(canNext()); @@ -459,8 +449,7 @@ bool CreateInstanceDialog::canBack() const { auto i = ui->pages->currentIndex(); - for (;;) - { + for (;;) { if (i == 0) { break; } @@ -480,8 +469,7 @@ bool CreateInstanceDialog::switching() const return m_switching; } -CreateInstanceDialog::CreationInfo -CreateInstanceDialog::rawCreationInfo() const +CreateInstanceDialog::CreationInfo CreateInstanceDialog::rawCreationInfo() const { const auto iniFilename = QString::fromStdWString(AppConfig::iniFileName()); @@ -500,17 +488,15 @@ CreateInstanceDialog::rawCreationInfo() const ci.dataPath = InstanceManager::singleton().instancePath(ci.instanceName); } - ci.dataPath = QDir::toNativeSeparators(ci.dataPath); - ci.iniPath = ci.dataPath + "/" + iniFilename; + ci.dataPath = QDir::toNativeSeparators(ci.dataPath); + ci.iniPath = ci.dataPath + "/" + iniFilename; return ci; } -CreateInstanceDialog::CreationInfo -CreateInstanceDialog::creationInfo() const +CreateInstanceDialog::CreationInfo CreateInstanceDialog::creationInfo() const { - auto fixVarDir = [](QString& path, const std::wstring& defaultDir) - { + auto fixVarDir = [](QString& path, const std::wstring& defaultDir) { // if the path is empty, it wasn't filled by the user, probably because // the "Advanced" checkbox wasn't checked, so use the base dir variable // with the default dir @@ -524,17 +510,14 @@ CreateInstanceDialog::creationInfo() const path = QDir::toNativeSeparators(path); }; - auto fixDirPath = [](QString& path) - { + auto fixDirPath = [](QString& path) { path = QDir::toNativeSeparators(QDir(path).absolutePath()); }; - auto fixFilePath = [](QString& path) - { + auto fixFilePath = [](QString& path) { path = QDir::toNativeSeparators(QFileInfo(path).absolutePath()); }; - auto ci = rawCreationInfo(); fixDirPath(ci.paths.base); diff --git a/src/createinstancedialog.h b/src/createinstancedialog.h index 8e8fe5176..fe3c5646f 100644 --- a/src/createinstancedialog.h +++ b/src/createinstancedialog.h @@ -3,9 +3,18 @@ #include -namespace MOBase { class IPluginGame; } -namespace Ui { class CreateInstanceDialog; }; -namespace cid { class Page; } +namespace MOBase +{ +class IPluginGame; +} +namespace Ui +{ +class CreateInstanceDialog; +}; +namespace cid +{ +class Page; +} class PluginContainer; class Settings; @@ -71,9 +80,8 @@ class CreateInstanceDialog : public QDialog Paths paths; }; - - CreateInstanceDialog( - const PluginContainer& pc, Settings* s, QWidget *parent = nullptr); + CreateInstanceDialog(const PluginContainer& pc, Settings* s, + QWidget* parent = nullptr); ~CreateInstanceDialog(); @@ -87,7 +95,7 @@ class CreateInstanceDialog : public QDialog void setSinglePage(const QString& instanceName) { for (auto&& p : m_pages) { - if (auto* tp=dynamic_cast(p.get())) { + if (auto* tp = dynamic_cast(p.get())) { tp->setSkip(false); } else { p->setSkip(true); @@ -103,7 +111,7 @@ class CreateInstanceDialog : public QDialog Page* getPage() { for (auto&& p : m_pages) { - if (auto* tp=dynamic_cast(p.get())) { + if (auto* tp = dynamic_cast(p.get())) { return tp; } } @@ -111,11 +119,10 @@ class CreateInstanceDialog : public QDialog return nullptr; } - // moves to the next page; if `allowFinish` is true, calls finish() if // currently on the last page // - void next(bool allowFinish=true); + void next(bool allowFinish = true); // moves to the previous page, if any // @@ -144,7 +151,6 @@ class CreateInstanceDialog : public QDialog // void finish(); - // updates the navigation buttons based on the current page // void updateNavigation(); @@ -176,10 +182,9 @@ class CreateInstanceDialog : public QDialog bool m_switching; bool m_singlePage; - // creates a shortcut for the given sequence // - void addShortcut(QKeySequence seq, std::function f); + void addShortcut(QKeySequence seq, std::function f); // creates a shortcut for the given sequence and executes the action when // activated @@ -219,4 +224,4 @@ class CreateInstanceDialog : public QDialog } }; -#endif // MODORGANIZER_CREATEINSTANCEDIALOG_INCLUDED +#endif // MODORGANIZER_CREATEINSTANCEDIALOG_INCLUDED diff --git a/src/createinstancedialogpages.cpp b/src/createinstancedialogpages.cpp index 481f032d5..c8cec45f2 100644 --- a/src/createinstancedialogpages.cpp +++ b/src/createinstancedialogpages.cpp @@ -1,14 +1,14 @@ #include "createinstancedialogpages.h" -#include "ui_createinstancedialog.h" +#include "filesystemutilities.h" #include "instancemanager.h" -#include "settings.h" #include "plugincontainer.h" +#include "settings.h" #include "settingsdialognexus.h" #include "shared/appconfig.h" +#include "ui_createinstancedialog.h" #include #include #include -#include "filesystemutilities.h" namespace cid { @@ -20,31 +20,27 @@ using MOBase::TaskDialog; // QString makeDefaultPath(const std::wstring& dir) { - return QDir::toNativeSeparators(PathSettings::makeDefaultPath( - QString::fromStdWString(dir))); + return QDir::toNativeSeparators( + PathSettings::makeDefaultPath(QString::fromStdWString(dir))); } QString toLocalizedString(CreateInstanceDialog::Types t) { - switch (t) - { - case CreateInstanceDialog::Global: - return QObject::tr("Global"); + switch (t) { + case CreateInstanceDialog::Global: + return QObject::tr("Global"); - case CreateInstanceDialog::Portable: - return QObject::tr("Portable"); + case CreateInstanceDialog::Portable: + return QObject::tr("Portable"); - default: - return QObject::tr("Instance type: %1").arg(QObject::tr("?")); + default: + return QObject::tr("Instance type: %1").arg(QObject::tr("?")); } } - - PlaceholderLabel::PlaceholderLabel(QLabel* label) - : m_label(label), m_original(label->text()) -{ -} + : m_label(label), m_original(label->text()) +{} void PlaceholderLabel::setText(const QString& arg) { @@ -58,13 +54,10 @@ void PlaceholderLabel::setVisible(bool b) m_label->setVisible(b); } - - -Page::Page(CreateInstanceDialog& dlg) : - ui(dlg.getUI()), m_dlg(dlg), m_pc(dlg.pluginContainer()), - m_skip(false), m_firstActivation(true) -{ -} +Page::Page(CreateInstanceDialog& dlg) + : ui(dlg.getUI()), m_dlg(dlg), m_pc(dlg.pluginContainer()), m_skip(false), + m_firstActivation(true) +{} bool Page::ready() const { @@ -114,7 +107,6 @@ bool Page::action(CreateInstanceDialog::Actions a) return false; } - CreateInstanceDialog::Types Page::selectedInstanceType() const { // no-op @@ -151,9 +143,8 @@ CreateInstanceDialog::Paths Page::selectedPaths() const return {}; } - IntroPage::IntroPage(CreateInstanceDialog& dlg) - : Page(dlg), m_skip(GlobalSettings::hideCreateInstanceIntro()) + : Page(dlg), m_skip(GlobalSettings::hideCreateInstanceIntro()) { QObject::connect(ui->hideIntro, &QCheckBox::toggled, [&] { GlobalSettings::setHideCreateInstanceIntro(ui->hideIntro->isChecked()); @@ -165,18 +156,15 @@ bool IntroPage::doSkip() const return m_skip; } - TypePage::TypePage(CreateInstanceDialog& dlg) - : Page(dlg), m_type(CreateInstanceDialog::NoType) + : Page(dlg), m_type(CreateInstanceDialog::NoType) { // replace placeholders with actual paths - ui->createGlobal->setDescription( - ui->createGlobal->description() - .arg(InstanceManager::singleton().globalInstancesRootPath())); + ui->createGlobal->setDescription(ui->createGlobal->description().arg( + InstanceManager::singleton().globalInstancesRootPath())); - ui->createPortable->setDescription( - ui->createPortable->description() - .arg(InstanceManager::singleton().portablePath())); + ui->createPortable->setDescription(ui->createPortable->description().arg( + InstanceManager::singleton().portablePath())); // disable portable button if it already exists if (InstanceManager::singleton().portableInstanceExists()) { @@ -187,11 +175,13 @@ TypePage::TypePage(CreateInstanceDialog& dlg) ui->portableExistsLabel->setVisible(false); } - QObject::connect( - ui->createGlobal, &QAbstractButton::clicked, [&]{ global(); }); + QObject::connect(ui->createGlobal, &QAbstractButton::clicked, [&] { + global(); + }); - QObject::connect( - ui->createPortable, &QAbstractButton::clicked, [&]{ portable(); }); + QObject::connect(ui->createPortable, &QAbstractButton::clicked, [&] { + portable(); + }); } bool TypePage::ready() const @@ -231,26 +221,26 @@ void TypePage::doActivated(bool firstTime) } } - -GamePage::Game::Game(IPluginGame* g) - : game(g), installed(g->isInstalled()) +GamePage::Game::Game(IPluginGame* g) : game(g), installed(g->isInstalled()) { if (installed) { dir = game->gameDirectory().path(); } } - -GamePage::GamePage(CreateInstanceDialog& dlg) - : Page(dlg), m_selection(nullptr) +GamePage::GamePage(CreateInstanceDialog& dlg) : Page(dlg), m_selection(nullptr) { createGames(); fillList(); m_filter.setEdit(ui->gamesFilter); - QObject::connect(&m_filter, &FilterWidget::changed, [&]{ fillList(); }); - QObject::connect(ui->showAllGames, &QCheckBox::clicked, [&]{ fillList(); }); + QObject::connect(&m_filter, &FilterWidget::changed, [&] { + fillList(); + }); + QObject::connect(ui->showAllGames, &QCheckBox::clicked, [&] { + fillList(); + }); } bool GamePage::ready() const @@ -293,14 +283,14 @@ void GamePage::select(IPluginGame* game, const QString& dir) Game* checked = findGame(game); if (checked) { - if (!checked->installed || (detectMicrosoftStore(checked->dir) && !confirmMicrosoftStore(checked->dir, checked->game))) { + if (!checked->installed || (detectMicrosoftStore(checked->dir) && + !confirmMicrosoftStore(checked->dir, checked->game))) { if (dir.isEmpty()) { // the selected game has no installation directory and none was given, // ask the user const auto path = QFileDialog::getExistingDirectory( - &m_dlg, QObject::tr("Find game installation for %1") - .arg(game->gameName())); + &m_dlg, QObject::tr("Find game installation for %1").arg(game->gameName())); if (path.isEmpty()) { // cancelled @@ -315,20 +305,19 @@ void GamePage::select(IPluginGame* game, const QString& dir) if (checked) { // plugin was found, remember this path - checked->dir = path; + checked->dir = path; checked->installed = true; } } } else { // the selected game didn't detect anything, but a directory was given, // so use that - checked->dir = dir; - checked->installed = true; + checked->dir = dir; + checked->installed = true; } } } - // select this plugin, if any m_selection = checked; @@ -348,8 +337,8 @@ void GamePage::select(IPluginGame* game, const QString& dir) void GamePage::selectCustom() { - const auto path = QFileDialog::getExistingDirectory( - &m_dlg, QObject::tr("Find game installation")); + const auto path = + QFileDialog::getExistingDirectory(&m_dlg, QObject::tr("Find game installation")); if (path.isEmpty()) { // reselect the previous button @@ -368,7 +357,7 @@ void GamePage::selectCustom() for (auto& g : m_games) { if (g->game->looksValid(path)) { // found one - g->dir = path; + g->dir = path; g->installed = true; // select it @@ -399,11 +388,11 @@ void GamePage::warnUnrecognized(const QString& path) QMessageBox dlg(&m_dlg); dlg.setWindowTitle(QObject::tr("Unrecognized game")); - dlg.setText(QObject::tr( - "The folder %1 does not seem to contain a game Mod Organizer can " - "manage.").arg(path)); - dlg.setInformativeText( - QObject::tr("See details for the list of supported games.")); + dlg.setText( + QObject::tr("The folder %1 does not seem to contain a game Mod Organizer can " + "manage.") + .arg(path)); + dlg.setInformativeText(QObject::tr("See details for the list of supported games.")); dlg.setDetailedText(supportedGames); dlg.setIcon(QMessageBox::Warning); dlg.setStandardButtons(QMessageBox::Ok); @@ -533,7 +522,7 @@ void GamePage::clearButtons() // stretch widgets added with addStretch() are not in the parent widget, // they have to be deleted from the layout itself - while (auto* child=ly->takeAt(0)) + while (auto* child = ly->takeAt(0)) delete child; // add a stretch, buttons will be added before @@ -552,8 +541,7 @@ QCommandLinkButton* GamePage::createCustomButton() auto* b = new QCommandLinkButton; b->setText(QObject::tr("Browse...")); - b->setDescription( - QObject::tr("The folder must contain a valid game installation")); + b->setDescription(QObject::tr("The folder must contain a valid game installation")); QObject::connect(b, &QAbstractButton::clicked, [&] { selectCustom(); @@ -647,7 +635,7 @@ GamePage::Game* GamePage::checkInstallation(const QString& path, Game* g) } // remember this path - g->dir = path; + g->dir = path; g->installed = true; updateButton(g); @@ -657,102 +645,91 @@ GamePage::Game* GamePage::checkInstallation(const QString& path, Game* g) bool GamePage::detectMicrosoftStore(const QString& path) { - return path.contains("/ModifiableWindowsApps/") || - path.contains("/WindowsApps/"); + return path.contains("/ModifiableWindowsApps/") || path.contains("/WindowsApps/"); } bool GamePage::confirmMicrosoftStore(const QString& path, IPluginGame* game) { - const auto r = TaskDialog(&m_dlg) - .title(QObject::tr("Microsoft Store game")) - .main(QObject::tr("Microsoft Store game")) - .content(QObject::tr( - "The folder %1 seems to be a Microsoft Store game install. Games" - " installed through the Microsoft Store are not supported by Mod Organizer" - " and will not work properly.") - .arg(path)) - .button({ - game ? QObject::tr("Use this folder for %1").arg(game->gameName()) - : QObject::tr("Use this folder"), - QObject::tr("I know what I'm doing"), - QMessageBox::Ignore}) - .button({ - QObject::tr("Cancel"), - QMessageBox::Cancel}) - .exec(); + const auto r = + TaskDialog(&m_dlg) + .title(QObject::tr("Microsoft Store game")) + .main(QObject::tr("Microsoft Store game")) + .content( + QObject::tr( + "The folder %1 seems to be a Microsoft Store game install. Games" + " installed through the Microsoft Store are not supported by Mod " + "Organizer" + " and will not work properly.") + .arg(path)) + .button({game ? QObject::tr("Use this folder for %1").arg(game->gameName()) + : QObject::tr("Use this folder"), + QObject::tr("I know what I'm doing"), QMessageBox::Ignore}) + .button({QObject::tr("Cancel"), QMessageBox::Cancel}) + .exec(); return (r == QMessageBox::Ignore); } bool GamePage::confirmUnknown(const QString& path, IPluginGame* game) { - const auto r = TaskDialog(&m_dlg) - .title(QObject::tr("Unrecognized game")) - .main(QObject::tr("Unrecognized game")) - .content(QObject::tr( - "The folder %1 does not seem to contain an installation for " - "%2 or " - "for any other game Mod Organizer can manage.") - .arg(path) - .arg(game->gameName())) - .button({ - QObject::tr("Use this folder for %1").arg(game->gameName()), - QObject::tr("I know what I'm doing"), - QMessageBox::Ignore}) - .button({ - QObject::tr("Cancel"), - QMessageBox::Cancel}) - .exec(); + const auto r = + TaskDialog(&m_dlg) + .title(QObject::tr("Unrecognized game")) + .main(QObject::tr("Unrecognized game")) + .content( + QObject::tr("The folder %1 does not seem to contain an installation for " + "%2 or " + "for any other game Mod Organizer can manage.") + .arg(path) + .arg(game->gameName())) + .button({QObject::tr("Use this folder for %1").arg(game->gameName()), + QObject::tr("I know what I'm doing"), QMessageBox::Ignore}) + .button({QObject::tr("Cancel"), QMessageBox::Cancel}) + .exec(); return (r == QMessageBox::Ignore); } -IPluginGame* GamePage::confirmOtherGame( - const QString& path, - IPluginGame* selectedGame, IPluginGame* guessedGame) -{ - const auto r = TaskDialog(&m_dlg) - .title(QObject::tr("Incorrect game")) - .main(QObject::tr("Incorrect game")) - .content(QObject::tr( - "The folder %1 seems to contain an installation for " - "%2, " - "not " - "%3.") - .arg(path) - .arg(guessedGame->gameName()) - .arg(selectedGame->gameName())) - .button({ - QObject::tr("Manage %1 instead").arg(guessedGame->gameName()), - QMessageBox::Ok}) - .button({ - QObject::tr("Use this folder for %1").arg(selectedGame->gameName()), - QObject::tr("I know what I'm doing"), - QMessageBox::Ignore}) - .button({ - QObject::tr("Cancel"), - QMessageBox::Cancel}) - .exec(); - - switch (r) - { - case QMessageBox::Ok: - return guessedGame; - - case QMessageBox::Ignore: - return selectedGame; - - case QMessageBox::Cancel: - default: - return nullptr; +IPluginGame* GamePage::confirmOtherGame(const QString& path, IPluginGame* selectedGame, + IPluginGame* guessedGame) +{ + const auto r = + TaskDialog(&m_dlg) + .title(QObject::tr("Incorrect game")) + .main(QObject::tr("Incorrect game")) + .content( + QObject::tr( + "The folder %1 seems to contain an installation for " + "%2, " + "not " + "%3.") + .arg(path) + .arg(guessedGame->gameName()) + .arg(selectedGame->gameName())) + .button({QObject::tr("Manage %1 instead").arg(guessedGame->gameName()), + QMessageBox::Ok}) + .button({QObject::tr("Use this folder for %1").arg(selectedGame->gameName()), + QObject::tr("I know what I'm doing"), QMessageBox::Ignore}) + .button({QObject::tr("Cancel"), QMessageBox::Cancel}) + .exec(); + + switch (r) { + case QMessageBox::Ok: + return guessedGame; + + case QMessageBox::Ignore: + return selectedGame; + + case QMessageBox::Cancel: + default: + return nullptr; } } - VariantsPage::VariantsPage(CreateInstanceDialog& dlg) - : Page(dlg), m_previousGame(nullptr) -{ -} + : Page(dlg), m_previousGame(nullptr) +{} bool VariantsPage::ready() const { @@ -780,7 +757,7 @@ void VariantsPage::doActivated(bool) if (m_previousGame != g) { // recreate the list, the game has changed m_previousGame = g; - m_selection = ""; + m_selection = ""; fillList(); } } @@ -848,17 +825,17 @@ void VariantsPage::fillList() } } - -NamePage::NamePage(CreateInstanceDialog& dlg) : - Page(dlg), m_modified(false), m_okay(false), - m_label(ui->instanceNameLabel), m_exists(ui->instanceNameExists), - m_invalid(ui->instanceNameInvalid) +NamePage::NamePage(CreateInstanceDialog& dlg) + : Page(dlg), m_modified(false), m_okay(false), m_label(ui->instanceNameLabel), + m_exists(ui->instanceNameExists), m_invalid(ui->instanceNameInvalid) { - QObject::connect( - ui->instanceName, &QLineEdit::textEdited, [&]{ onChanged(); }); + QObject::connect(ui->instanceName, &QLineEdit::textEdited, [&] { + onChanged(); + }); - QObject::connect( - ui->instanceName, &QLineEdit::returnPressed, [&]{ next(); }); + QObject::connect(ui->instanceName, &QLineEdit::returnPressed, [&] { + next(); + }); } bool NamePage::ready() const @@ -913,15 +890,15 @@ void NamePage::onChanged() void NamePage::verify() { const auto root = InstanceManager::singleton().globalInstancesRootPath(); - m_okay = checkName(root, ui->instanceName->text()); + m_okay = checkName(root, ui->instanceName->text()); updateNavigation(); } bool NamePage::checkName(QString parentDir, QString name) { - bool exists = false; + bool exists = false; bool invalid = false; - bool empty = false; + bool empty = false; name = name.trimmed(); @@ -954,22 +931,25 @@ bool NamePage::checkName(QString parentDir, QString name) return okay; } - -PathsPage::PathsPage(CreateInstanceDialog& dlg) : - Page(dlg), m_lastType(CreateInstanceDialog::NoType), - m_label(ui->pathsLabel), - m_simpleExists(ui->locationExists), m_simpleInvalid(ui->locationInvalid), - m_advancedExists(ui->advancedDirExists), - m_advancedInvalid(ui->advancedDirInvalid), - m_okay(false) +PathsPage::PathsPage(CreateInstanceDialog& dlg) + : Page(dlg), m_lastType(CreateInstanceDialog::NoType), m_label(ui->pathsLabel), + m_simpleExists(ui->locationExists), m_simpleInvalid(ui->locationInvalid), + m_advancedExists(ui->advancedDirExists), + m_advancedInvalid(ui->advancedDirInvalid), m_okay(false) { auto setEdit = [&](QLineEdit* e) { - QObject::connect(e, &QLineEdit::textEdited, [&]{ onChanged(); }); - QObject::connect(e, &QLineEdit::returnPressed, [&]{ next(); }); + QObject::connect(e, &QLineEdit::textEdited, [&] { + onChanged(); + }); + QObject::connect(e, &QLineEdit::returnPressed, [&] { + next(); + }); }; auto setBrowse = [&](QAbstractButton* b, QLineEdit* e) { - QObject::connect(b, &QAbstractButton::clicked, [this, e]{ browse(e); }); + QObject::connect(b, &QAbstractButton::clicked, [this, e] { + browse(e); + }); }; setEdit(ui->location); @@ -986,8 +966,9 @@ PathsPage::PathsPage(CreateInstanceDialog& dlg) : setBrowse(ui->browseProfiles, ui->profiles); setBrowse(ui->browseOverwrite, ui->overwrite); - QObject::connect( - ui->advancedPathOptions, &QCheckBox::clicked, [&]{ onAdvanced(); }); + QObject::connect(ui->advancedPathOptions, &QCheckBox::clicked, [&] { + onAdvanced(); + }); ui->pathPages->setCurrentIndex(0); } @@ -1008,7 +989,6 @@ void PathsPage::doActivated(bool firstTime) // regenerated const bool changed = (m_lastInstanceName != name) || (m_lastType != type); - // generating and paths setPaths(name, changed); checkPaths(); @@ -1017,7 +997,7 @@ void PathsPage::doActivated(bool firstTime) m_label.setText(m_dlg.rawCreationInfo().game->gameName()); m_lastInstanceName = name; - m_lastType = type; + m_lastType = type; if (firstTime) { ui->location->setFocus(); @@ -1029,10 +1009,10 @@ CreateInstanceDialog::Paths PathsPage::selectedPaths() const CreateInstanceDialog::Paths p; if (ui->advancedPathOptions->isChecked()) { - p.base = ui->base->text(); + p.base = ui->base->text(); p.downloads = ui->downloads->text(); - p.mods = ui->mods->text(); - p.profiles = ui->profiles->text(); + p.mods = ui->mods->text(); + p.profiles = ui->profiles->text(); p.overwrite = ui->overwrite->text(); } else { p.base = ui->location->text(); @@ -1061,16 +1041,14 @@ void PathsPage::checkPaths() { if (ui->advancedPathOptions->isChecked()) { // checking advanced paths - m_okay = - checkAdvancedPath(ui->base->text()) && - checkAdvancedPath(resolve(ui->downloads->text())) && - checkAdvancedPath(resolve(ui->mods->text())) && - checkAdvancedPath(resolve(ui->profiles->text())) && - checkAdvancedPath(resolve(ui->overwrite->text())); + m_okay = checkAdvancedPath(ui->base->text()) && + checkAdvancedPath(resolve(ui->downloads->text())) && + checkAdvancedPath(resolve(ui->mods->text())) && + checkAdvancedPath(resolve(ui->profiles->text())) && + checkAdvancedPath(resolve(ui->overwrite->text())); } else { // checking simple path - m_okay = - checkSimplePath(ui->location->text()); + m_okay = checkSimplePath(ui->location->text()); } } @@ -1113,7 +1091,7 @@ void PathsPage::setPaths(const QString& name, bool force) basePath = InstanceManager::singleton().portablePath(); } else { const auto root = InstanceManager::singleton().globalInstancesRootPath(); - basePath = root + "/" + name; + basePath = root + "/" + name; } basePath = QDir::toNativeSeparators(QDir::cleanPath(basePath)); @@ -1136,15 +1114,14 @@ void PathsPage::setIfEmpty(QLineEdit* e, const QString& path, bool force) } } -bool PathsPage::checkPath( - QString path, - PlaceholderLabel& existsLabel, PlaceholderLabel& invalidLabel) +bool PathsPage::checkPath(QString path, PlaceholderLabel& existsLabel, + PlaceholderLabel& invalidLabel) { auto& m = InstanceManager::singleton(); - bool exists = false; + bool exists = false; bool invalid = false; - bool empty = false; + bool empty = false; path = QDir::toNativeSeparators(path.trimmed()); @@ -1193,17 +1170,10 @@ bool PathsPage::checkPath( return okay; } - -NexusPage::NexusPage(CreateInstanceDialog& dlg) - : Page(dlg), m_skip(false) +NexusPage::NexusPage(CreateInstanceDialog& dlg) : Page(dlg), m_skip(false) { - m_connectionUI.reset(new NexusConnectionUI( - &m_dlg, - dlg.settings(), - ui->nexusConnect, - nullptr, - ui->nexusManual, - ui->nexusLog)); + m_connectionUI.reset(new NexusConnectionUI(&m_dlg, dlg.settings(), ui->nexusConnect, + nullptr, ui->nexusManual, ui->nexusLog)); // just check it once, or connecting and then going back and forth would skip // the page, which would be unexpected @@ -1223,11 +1193,7 @@ bool NexusPage::doSkip() const return m_skip; } - -ConfirmationPage::ConfirmationPage(CreateInstanceDialog& dlg) - : Page(dlg) -{ -} +ConfirmationPage::ConfirmationPage(CreateInstanceDialog& dlg) : Page(dlg) {} void ConfirmationPage::doActivated(bool) { @@ -1241,11 +1207,9 @@ QString ConfirmationPage::makeReview() const const auto ci = m_dlg.rawCreationInfo(); - lines.push_back(QObject::tr("Instance type: %1") - .arg(toLocalizedString(ci.type))); + lines.push_back(QObject::tr("Instance type: %1").arg(toLocalizedString(ci.type))); - lines.push_back(QObject::tr("Instance location: %1") - .arg(ci.dataPath)); + lines.push_back(QObject::tr("Instance location: %1").arg(ci.dataPath)); if (ci.type != CreateInstanceDialog::Portable) { lines.push_back(QObject::tr("Instance name: %1").arg(ci.instanceName)); @@ -1282,4 +1246,4 @@ QString ConfirmationPage::dirLine(const QString& caption, const QString& path) c return QString(" - %1: %2").arg(caption).arg(path); } -} // namespace +} // namespace cid diff --git a/src/createinstancedialogpages.h b/src/createinstancedialogpages.h index 7237bfb2a..d9751a526 100644 --- a/src/createinstancedialogpages.h +++ b/src/createinstancedialogpages.h @@ -4,14 +4,16 @@ #include "createinstancedialog.h" #include +#include #include #include -#include -namespace MOBase { class IPluginGame; } +namespace MOBase +{ +class IPluginGame; +} class NexusConnectionUI; - namespace cid { @@ -19,7 +21,6 @@ namespace cid // QString makeDefaultPath(const std::wstring& dir); - // remembers the original text of the given label and, if it contains a %1, // sets it in setText() // @@ -42,7 +43,6 @@ class PlaceholderLabel QString m_original; }; - // one page in the wizard // // each page can implement one or more selected*() below; those are called @@ -81,13 +81,11 @@ class Page // void next(); - // called from the dialog when an action is requested on the current page; // returns true when handled // virtual bool action(CreateInstanceDialog::Actions a); - // returns the instance type // virtual CreateInstanceDialog::Types selectedInstanceType() const; @@ -119,7 +117,6 @@ class Page bool m_skip; bool m_firstActivation; - // called every time a page is shown in the screen; `firstTime` is true for // first activation // @@ -130,7 +127,6 @@ class Page virtual bool doSkip() const; }; - // introduction page, can be disabled by a global setting // class IntroPage : public Page @@ -148,7 +144,6 @@ class IntroPage : public Page bool m_skip; }; - // instance type page // class TypePage : public Page @@ -181,7 +176,6 @@ class TypePage : public Page CreateInstanceDialog::Types m_type; }; - // game plugin page, displays a list of command buttons for each game, along // with a "browse" button for custom directories and filtering stuff // @@ -221,14 +215,13 @@ class GamePage : public Page // returns the selected game directory QString selectedGameLocation() const override; - // selects the given game and toggles its associated button; the game // directory can be overridden // // pops up a directory selection dialog if `dir` is empty and the plugin // hasn't detected the game // - void select(MOBase::IPluginGame* game, const QString& dir={}); + void select(MOBase::IPluginGame* game, const QString& dir = {}); // pops up a directory selection dialog and looks for a plugin to manage // it @@ -260,9 +253,8 @@ class GamePage : public Page // or by the user bool installed = false; - Game(MOBase::IPluginGame* g); - Game(const Game&) = delete; + Game(const Game&) = delete; Game& operator=(const Game&) = delete; }; @@ -276,7 +268,6 @@ class GamePage : public Page // filter MOBase::FilterWidget m_filter; - // returns a list of all the game plugins sorted with natsort // std::vector sortedGamePlugins() const; @@ -319,7 +310,6 @@ class GamePage : public Page // QCommandLinkButton* createCustomButton(); - // clears the button list and adds all the buttons to it, depending on // filtering and stuff // @@ -351,9 +341,9 @@ class GamePage : public Page // 2) use the selection anyway, selectedGame is returned; or // 3) cancel, null is returned // - MOBase::IPluginGame* confirmOtherGame( - const QString& path, - MOBase::IPluginGame* selectedGame, MOBase::IPluginGame* guessedGame); + MOBase::IPluginGame* confirmOtherGame(const QString& path, + MOBase::IPluginGame* selectedGame, + MOBase::IPluginGame* guessedGame); // detects if the given path likely contains a Microsoft Store game // @@ -365,7 +355,6 @@ class GamePage : public Page bool confirmMicrosoftStore(const QString& path, MOBase::IPluginGame* game); }; - // game variants page; displays a list of command buttons for game variants, as // reported by the game plugin // @@ -408,12 +397,10 @@ class VariantsPage : public Page // selected variant QString m_selection; - // fills the list with buttons void fillList(); }; - // instance name page; displays a textbox where the user can enter a name and // does basic checks to make sure the name is valid and not a duplicate // @@ -461,7 +448,6 @@ class NamePage : public Page // whether the instance name is valid bool m_okay; - // called when the user modifies the textbox, remember that it has changed and // calls verify() // @@ -477,7 +463,6 @@ class NamePage : public Page bool checkName(QString parentDir, QString name); }; - // instance paths page; shows a single textbox for the base directory, or a // series of textboxes for all the configurable paths if the advanced checkbox // is checked @@ -523,7 +508,6 @@ class PathsPage : public Page // whether the paths are valid bool m_okay; - // called when the user changes any textbox, checks the path and updates nav // void onChanged(); @@ -547,9 +531,8 @@ class PathsPage : public Page // returns false if the path is invalid or already exists, sets the given // labels accordingly // - bool checkPath( - QString path, - PlaceholderLabel& existsLabel, PlaceholderLabel& invalidLabel); + bool checkPath(QString path, PlaceholderLabel& existsLabel, + PlaceholderLabel& invalidLabel); // replaces %base_dir% in the given path by whatever's in the base path // textbox @@ -574,7 +557,6 @@ class PathsPage : public Page void setIfEmpty(QLineEdit* e, const QString& path, bool force); }; - // nexus connection page; this reuses the ui found in the settings dialog and // is skipped if there's already an api key in the credentials manager // @@ -603,7 +585,6 @@ class NexusPage : public Page bool m_skip; }; - // shows a text log of all the creation parameters // class ConfirmationPage : public Page @@ -626,6 +607,6 @@ class ConfirmationPage : public Page QString dirLine(const QString& caption, const QString& path) const; }; -} // namespace +} // namespace cid -#endif // MODORGANIZER_CREATEINSTANCEDIALOGPAGES_INCLUDED +#endif // MODORGANIZER_CREATEINSTANCEDIALOGPAGES_INCLUDED diff --git a/src/credentialsdialog.cpp b/src/credentialsdialog.cpp index 73e753879..57037f91c 100644 --- a/src/credentialsdialog.cpp +++ b/src/credentialsdialog.cpp @@ -1,43 +1,42 @@ -/* -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 "credentialsdialog.h" -#include "ui_credentialsdialog.h" - -CredentialsDialog::CredentialsDialog(QWidget *parent) : - QDialog(parent), - ui(new Ui::CredentialsDialog) -{ - ui->setupUi(this); -} - -CredentialsDialog::~CredentialsDialog() -{ - delete ui; -} - -bool CredentialsDialog::store() const -{ - return ui->rememberCheck->isChecked(); -} - -bool CredentialsDialog::neverAsk() const -{ - return ui->dontaskBox->isChecked(); -} +/* +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 "credentialsdialog.h" +#include "ui_credentialsdialog.h" + +CredentialsDialog::CredentialsDialog(QWidget* parent) + : QDialog(parent), ui(new Ui::CredentialsDialog) +{ + ui->setupUi(this); +} + +CredentialsDialog::~CredentialsDialog() +{ + delete ui; +} + +bool CredentialsDialog::store() const +{ + return ui->rememberCheck->isChecked(); +} + +bool CredentialsDialog::neverAsk() const +{ + return ui->dontaskBox->isChecked(); +} diff --git a/src/credentialsdialog.h b/src/credentialsdialog.h index 2f3bcbb74..f2b0c8208 100644 --- a/src/credentialsdialog.h +++ b/src/credentialsdialog.h @@ -1,44 +1,45 @@ -/* -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 CREDENTIALSDIALOG_H -#define CREDENTIALSDIALOG_H - -#include - -namespace Ui { -class CredentialsDialog; -} - -class CredentialsDialog : public QDialog -{ - Q_OBJECT - -public: - explicit CredentialsDialog(QWidget *parent = 0); - ~CredentialsDialog(); - - bool store() const; - bool neverAsk() const; - -private: - Ui::CredentialsDialog *ui; -}; - -#endif // CREDENTIALSDIALOG_H +/* +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 CREDENTIALSDIALOG_H +#define CREDENTIALSDIALOG_H + +#include + +namespace Ui +{ +class CredentialsDialog; +} + +class CredentialsDialog : public QDialog +{ + Q_OBJECT + +public: + explicit CredentialsDialog(QWidget* parent = 0); + ~CredentialsDialog(); + + bool store() const; + bool neverAsk() const; + +private: + Ui::CredentialsDialog* ui; +}; + +#endif // CREDENTIALSDIALOG_H diff --git a/src/csvbuilder.cpp b/src/csvbuilder.cpp index 788c9438a..6a33afdae 100644 --- a/src/csvbuilder.cpp +++ b/src/csvbuilder.cpp @@ -1,272 +1,254 @@ -#include "csvbuilder.h" - - -CSVBuilder::CSVBuilder(QIODevice *target) - : m_Out(target), m_Separator(','), m_LineBreak(BREAK_CRLF) -{ - m_Out.setEncoding(QStringConverter::Encoding::Utf8); - - m_QuoteMode[TYPE_INTEGER] = QUOTE_NEVER; - m_QuoteMode[TYPE_FLOAT] = QUOTE_NEVER; - m_QuoteMode[TYPE_STRING] = QUOTE_ONDEMAND; -} - - -CSVBuilder::~CSVBuilder() -{ - -} - - -void CSVBuilder::setFieldSeparator(char sep) -{ - char oldSeparator = m_Separator; - m_Separator = sep; - try { - checkFields(m_Fields); - } catch (const CSVException&) { - m_Separator = oldSeparator; - throw; - } -} - - -void CSVBuilder::setLineBreak(CSVBuilder::ELineBreak lineBreak) -{ - m_LineBreak = lineBreak; -} - - -void CSVBuilder::setEscapeMode(CSVBuilder::EFieldType type, CSVBuilder::EQuoteMode mode) -{ - m_QuoteMode[type] = mode; -} - - -void CSVBuilder::setFields(const std::vector > &fields) -{ - std::vector fieldNames; - std::map fieldTypes; - - for (auto iter = fields.begin(); iter != fields.end(); ++iter) { - fieldNames.push_back(iter->first); - fieldTypes[iter->first] = iter->second; - } - - checkFields(fieldNames); - - m_Fields = fieldNames; - m_FieldTypes = fieldTypes; - m_Defaults.clear(); - m_RowBuffer.clear(); - -} - - -void CSVBuilder::checkValue(const QString &field, const QVariant &value) -{ - auto typeIter = m_FieldTypes.find(field); - if (typeIter == m_FieldTypes.end()) { - throw CSVException(QObject::tr("invalid field name \"%1\"").arg(field)); - } - - switch (typeIter->second) { - case TYPE_INTEGER: { - if (!value.canConvert()) { - throw CSVException(QObject::tr("invalid type for \"%1\" (should be integer)").arg(field)); - } - } break; - case TYPE_STRING: { - if (!value.canConvert()) { - throw CSVException(QObject::tr("invalid type for \"%1\" (should be string)").arg(field)); - } - } break; - case TYPE_FLOAT: { - if (!value.canConvert()) { - throw CSVException(QObject::tr("invalid type for \"%1\" (should be float)").arg(field)); - } - } break; - } -} - - -void CSVBuilder::setDefault(const QString &field, const QVariant &value) -{ - checkValue(field, value); - m_Defaults[field] = value; -} - - -void CSVBuilder::writeHeader() -{ - if (m_Fields.size() == 0) { - throw CSVException(QObject::tr("no fields set up yet!")); - } - - for (auto iter = m_Fields.begin(); iter != m_Fields.end(); ++iter) { - if (iter != m_Fields.begin()) { - m_Out << m_Separator; - } - m_Out << *iter; - } - m_Out << lineBreak(); - m_Out.flush(); -} - - -void CSVBuilder::setRowField(const QString &field, const QVariant &value) -{ - checkValue(field, value); - m_RowBuffer[field] = value; -} - - -void CSVBuilder::writeData(const std::map &data, bool check) -{ - QString line; - QTextStream temp(&line); - - for (auto iter = m_Fields.begin(); iter != m_Fields.end(); ++iter) { - if (iter != m_Fields.begin()) { - temp << separator(); - } - - QVariant val; - - auto valIter = data.find(*iter); - if (valIter == data.end()) { - auto defaultIter = m_Defaults.find(*iter); - if (defaultIter == m_Defaults.end()) { - throw CSVException(QObject::tr("field not set \"%1\"").arg(*iter)); - } else { - val = defaultIter->second; - } - } else { - val = valIter->second; - } - - if (check) { - checkValue(*iter, val); - } - - switch (m_FieldTypes[*iter]) { - case TYPE_INTEGER: { - quoteInsert(temp, val.toInt()); - } break; - case TYPE_FLOAT: { - quoteInsert(temp, val.toFloat()); - } break; - case TYPE_STRING: { - quoteInsert(temp, val.toString()); - } break; - } - } - m_Out << line << lineBreak(); - m_Out.flush(); -} - - -void CSVBuilder::quoteInsert(QTextStream &stream, int value) -{ - switch (m_QuoteMode[TYPE_INTEGER]) { - case QUOTE_NEVER: - case QUOTE_ONDEMAND: { - stream << value; - } break; - case QUOTE_ALWAYS: { - stream << "\"" << value << "\""; - } break; - } -} - - -void CSVBuilder::quoteInsert(QTextStream &stream, float value) -{ - switch (m_QuoteMode[TYPE_FLOAT]) { - case QUOTE_NEVER: - case QUOTE_ONDEMAND: { - stream << value; - } break; - case QUOTE_ALWAYS: { - stream << "\"" << value << "\""; - } break; - } -} - - -void CSVBuilder::quoteInsert(QTextStream &stream, const QString &value) -{ - switch (m_QuoteMode[TYPE_STRING]) { - case QUOTE_NEVER: { - stream << value; - } break; - case QUOTE_ONDEMAND: { - if (value.contains("[,\r\n]")) { - stream << "\"" << value.mid(0).replace("\"", "\"\"") << "\""; - } else { - stream << value; - } - } break; - case QUOTE_ALWAYS: { - stream << "\"" << value.mid(0).replace("\"", "\"\"") << "\""; - } break; - } -} - - -void CSVBuilder::writeRow() -{ - writeData(m_RowBuffer, false); // data was tested on input - m_RowBuffer.clear(); -} - - -void CSVBuilder::addRow(const std::map &data) -{ - writeData(data, true); -} - - -void CSVBuilder::checkFields(const std::vector &fields) -{ - for (auto iter = fields.begin(); iter != fields.end(); ++iter) { - if (iter->contains(m_Separator) || - iter->contains('\r') || - iter->contains('\n') || - iter->contains('"')) { - throw CSVException(QObject::tr("invalid character in field \"%1\"").arg(*iter)); - } - if (iter->length() == 0) { - throw CSVException(QObject::tr("empty field name")); - } - } -} - - -/* - -if(cell.contains(KDefaultEscapeChar) || cell.contains(KDefaultNewLine) - || cell.contains(KDefaultDelimiter)) { - m_currentLine->append(cell.replace(KDefaultNewLine, - QString(KDefaultEscapeChar) + KDefaultEscapeChar) - .prepend(KDefaultEscapeChar) - .append(KDefaultEscapeChar)); -} else { -m_currentLine->append(cell); -}*/ - - -const char *CSVBuilder::lineBreak() -{ - switch (m_LineBreak) { - case BREAK_CR: return "\r"; - case BREAK_CRLF: return "\r\n"; - case BREAK_LF: return "\n"; - default: return "\n"; // default shouldn't be necessary - } -} - -const char CSVBuilder::separator() -{ - return m_Separator; -} +#include "csvbuilder.h" + +CSVBuilder::CSVBuilder(QIODevice* target) + : m_Out(target), m_Separator(','), m_LineBreak(BREAK_CRLF) +{ + m_Out.setEncoding(QStringConverter::Encoding::Utf8); + + m_QuoteMode[TYPE_INTEGER] = QUOTE_NEVER; + m_QuoteMode[TYPE_FLOAT] = QUOTE_NEVER; + m_QuoteMode[TYPE_STRING] = QUOTE_ONDEMAND; +} + +CSVBuilder::~CSVBuilder() {} + +void CSVBuilder::setFieldSeparator(char sep) +{ + char oldSeparator = m_Separator; + m_Separator = sep; + try { + checkFields(m_Fields); + } catch (const CSVException&) { + m_Separator = oldSeparator; + throw; + } +} + +void CSVBuilder::setLineBreak(CSVBuilder::ELineBreak lineBreak) +{ + m_LineBreak = lineBreak; +} + +void CSVBuilder::setEscapeMode(CSVBuilder::EFieldType type, CSVBuilder::EQuoteMode mode) +{ + m_QuoteMode[type] = mode; +} + +void CSVBuilder::setFields(const std::vector>& fields) +{ + std::vector fieldNames; + std::map fieldTypes; + + for (auto iter = fields.begin(); iter != fields.end(); ++iter) { + fieldNames.push_back(iter->first); + fieldTypes[iter->first] = iter->second; + } + + checkFields(fieldNames); + + m_Fields = fieldNames; + m_FieldTypes = fieldTypes; + m_Defaults.clear(); + m_RowBuffer.clear(); +} + +void CSVBuilder::checkValue(const QString& field, const QVariant& value) +{ + auto typeIter = m_FieldTypes.find(field); + if (typeIter == m_FieldTypes.end()) { + throw CSVException(QObject::tr("invalid field name \"%1\"").arg(field)); + } + + switch (typeIter->second) { + case TYPE_INTEGER: { + if (!value.canConvert()) { + throw CSVException( + QObject::tr("invalid type for \"%1\" (should be integer)").arg(field)); + } + } break; + case TYPE_STRING: { + if (!value.canConvert()) { + throw CSVException( + QObject::tr("invalid type for \"%1\" (should be string)").arg(field)); + } + } break; + case TYPE_FLOAT: { + if (!value.canConvert()) { + throw CSVException( + QObject::tr("invalid type for \"%1\" (should be float)").arg(field)); + } + } break; + } +} + +void CSVBuilder::setDefault(const QString& field, const QVariant& value) +{ + checkValue(field, value); + m_Defaults[field] = value; +} + +void CSVBuilder::writeHeader() +{ + if (m_Fields.size() == 0) { + throw CSVException(QObject::tr("no fields set up yet!")); + } + + for (auto iter = m_Fields.begin(); iter != m_Fields.end(); ++iter) { + if (iter != m_Fields.begin()) { + m_Out << m_Separator; + } + m_Out << *iter; + } + m_Out << lineBreak(); + m_Out.flush(); +} + +void CSVBuilder::setRowField(const QString& field, const QVariant& value) +{ + checkValue(field, value); + m_RowBuffer[field] = value; +} + +void CSVBuilder::writeData(const std::map& data, bool check) +{ + QString line; + QTextStream temp(&line); + + for (auto iter = m_Fields.begin(); iter != m_Fields.end(); ++iter) { + if (iter != m_Fields.begin()) { + temp << separator(); + } + + QVariant val; + + auto valIter = data.find(*iter); + if (valIter == data.end()) { + auto defaultIter = m_Defaults.find(*iter); + if (defaultIter == m_Defaults.end()) { + throw CSVException(QObject::tr("field not set \"%1\"").arg(*iter)); + } else { + val = defaultIter->second; + } + } else { + val = valIter->second; + } + + if (check) { + checkValue(*iter, val); + } + + switch (m_FieldTypes[*iter]) { + case TYPE_INTEGER: { + quoteInsert(temp, val.toInt()); + } break; + case TYPE_FLOAT: { + quoteInsert(temp, val.toFloat()); + } break; + case TYPE_STRING: { + quoteInsert(temp, val.toString()); + } break; + } + } + m_Out << line << lineBreak(); + m_Out.flush(); +} + +void CSVBuilder::quoteInsert(QTextStream& stream, int value) +{ + switch (m_QuoteMode[TYPE_INTEGER]) { + case QUOTE_NEVER: + case QUOTE_ONDEMAND: { + stream << value; + } break; + case QUOTE_ALWAYS: { + stream << "\"" << value << "\""; + } break; + } +} + +void CSVBuilder::quoteInsert(QTextStream& stream, float value) +{ + switch (m_QuoteMode[TYPE_FLOAT]) { + case QUOTE_NEVER: + case QUOTE_ONDEMAND: { + stream << value; + } break; + case QUOTE_ALWAYS: { + stream << "\"" << value << "\""; + } break; + } +} + +void CSVBuilder::quoteInsert(QTextStream& stream, const QString& value) +{ + switch (m_QuoteMode[TYPE_STRING]) { + case QUOTE_NEVER: { + stream << value; + } break; + case QUOTE_ONDEMAND: { + if (value.contains("[,\r\n]")) { + stream << "\"" << value.mid(0).replace("\"", "\"\"") << "\""; + } else { + stream << value; + } + } break; + case QUOTE_ALWAYS: { + stream << "\"" << value.mid(0).replace("\"", "\"\"") << "\""; + } break; + } +} + +void CSVBuilder::writeRow() +{ + writeData(m_RowBuffer, false); // data was tested on input + m_RowBuffer.clear(); +} + +void CSVBuilder::addRow(const std::map& data) +{ + writeData(data, true); +} + +void CSVBuilder::checkFields(const std::vector& fields) +{ + for (auto iter = fields.begin(); iter != fields.end(); ++iter) { + if (iter->contains(m_Separator) || iter->contains('\r') || iter->contains('\n') || + iter->contains('"')) { + throw CSVException(QObject::tr("invalid character in field \"%1\"").arg(*iter)); + } + if (iter->length() == 0) { + throw CSVException(QObject::tr("empty field name")); + } + } +} + +/* + +if(cell.contains(KDefaultEscapeChar) || cell.contains(KDefaultNewLine) + || cell.contains(KDefaultDelimiter)) { + m_currentLine->append(cell.replace(KDefaultNewLine, + QString(KDefaultEscapeChar) + KDefaultEscapeChar) + .prepend(KDefaultEscapeChar) + .append(KDefaultEscapeChar)); +} else { +m_currentLine->append(cell); +}*/ + +const char* CSVBuilder::lineBreak() +{ + switch (m_LineBreak) { + case BREAK_CR: + return "\r"; + case BREAK_CRLF: + return "\r\n"; + case BREAK_LF: + return "\n"; + default: + return "\n"; // default shouldn't be necessary + } +} + +const char CSVBuilder::separator() +{ + return m_Separator; +} diff --git a/src/csvbuilder.h b/src/csvbuilder.h index f2e0e9879..3a4e05518 100644 --- a/src/csvbuilder.h +++ b/src/csvbuilder.h @@ -1,93 +1,87 @@ -#ifndef CSVBUILDER_H -#define CSVBUILDER_H - - -#include -#include -#include -#include - - -class CSVException : public std::exception { - -public: - CSVException(const QString &text) - : std::exception(), m_Message(text.toLocal8Bit()) {} - - virtual const char* what() const throw() - { return m_Message.constData(); } -private: - QByteArray m_Message; - -}; - - -class CSVBuilder -{ - -public: - - enum EFieldType { - TYPE_INTEGER, - TYPE_STRING, - TYPE_FLOAT - }; - - enum EQuoteMode { - QUOTE_NEVER, - QUOTE_ONDEMAND, - QUOTE_ALWAYS - }; - - enum ELineBreak { - BREAK_LF, - BREAK_CRLF, - BREAK_CR - }; - -public: - - CSVBuilder(QIODevice *target); - ~CSVBuilder(); - - void setFieldSeparator(char sep); - void setLineBreak(ELineBreak lineBreak); - void setEscapeMode(EFieldType type, EQuoteMode mode); - void setFields(const std::vector > &fields); - void setDefault(const QString &field, const QVariant &value); - - void writeHeader(); - - void setRowField(const QString &field, const QVariant &value); - void writeRow(); - - void addRow(const std::map &data); - -private: - - const char *lineBreak(); - const char separator(); - - void fieldValid(); - void checkFields(const std::vector &fields); - void checkValue(const QString &field, const QVariant &value); - void writeData(const std::map &data, bool check); - - void quoteInsert(QTextStream &stream, int value); - void quoteInsert(QTextStream &stream, float value); - void quoteInsert(QTextStream &stream, const QString &value); - -private: - - QTextStream m_Out; - char m_Separator; - ELineBreak m_LineBreak; - std::map m_QuoteMode; - std::vector m_Fields; - std::map m_FieldTypes; - std::map m_Defaults; - std::map m_RowBuffer; - -}; - -#endif // CSVBUILDER_H +#ifndef CSVBUILDER_H +#define CSVBUILDER_H + +#include +#include +#include +#include + +class CSVException : public std::exception +{ + +public: + CSVException(const QString& text) : std::exception(), m_Message(text.toLocal8Bit()) {} + + virtual const char* what() const throw() { return m_Message.constData(); } + +private: + QByteArray m_Message; +}; + +class CSVBuilder +{ + +public: + enum EFieldType + { + TYPE_INTEGER, + TYPE_STRING, + TYPE_FLOAT + }; + + enum EQuoteMode + { + QUOTE_NEVER, + QUOTE_ONDEMAND, + QUOTE_ALWAYS + }; + + enum ELineBreak + { + BREAK_LF, + BREAK_CRLF, + BREAK_CR + }; + +public: + CSVBuilder(QIODevice* target); + ~CSVBuilder(); + + void setFieldSeparator(char sep); + void setLineBreak(ELineBreak lineBreak); + void setEscapeMode(EFieldType type, EQuoteMode mode); + void setFields(const std::vector>& fields); + void setDefault(const QString& field, const QVariant& value); + + void writeHeader(); + + void setRowField(const QString& field, const QVariant& value); + void writeRow(); + + void addRow(const std::map& data); + +private: + const char* lineBreak(); + const char separator(); + + void fieldValid(); + void checkFields(const std::vector& fields); + void checkValue(const QString& field, const QVariant& value); + void writeData(const std::map& data, bool check); + + void quoteInsert(QTextStream& stream, int value); + void quoteInsert(QTextStream& stream, float value); + void quoteInsert(QTextStream& stream, const QString& value); + +private: + QTextStream m_Out; + char m_Separator; + ELineBreak m_LineBreak; + std::map m_QuoteMode; + std::vector m_Fields; + std::map m_FieldTypes; + std::map m_Defaults; + std::map m_RowBuffer; +}; + +#endif // CSVBUILDER_H diff --git a/src/datatab.cpp b/src/datatab.cpp index 0aee247ef..567fa99d7 100644 --- a/src/datatab.cpp +++ b/src/datatab.cpp @@ -1,10 +1,10 @@ #include "datatab.h" -#include "ui_mainwindow.h" -#include "settings.h" -#include "organizercore.h" -#include "messagedialog.h" #include "filetree.h" #include "filetreemodel.h" +#include "messagedialog.h" +#include "organizercore.h" +#include "settings.h" +#include "ui_mainwindow.h" #include #include @@ -14,15 +14,16 @@ using namespace MOBase; // in mainwindow.cpp QString UnmanagedModName(); - -DataTab::DataTab( - OrganizerCore& core, PluginContainer& pc, - QWidget* parent, Ui::MainWindow* mwui) : - m_core(core), m_pluginContainer(pc), m_parent(parent), - ui{ - mwui->tabWidget, mwui->dataTab, mwui->dataTabRefresh, mwui->dataTree, - mwui->dataTabShowOnlyConflicts, mwui->dataTabShowFromArchives}, - m_needUpdate(true) +DataTab::DataTab(OrganizerCore& core, PluginContainer& pc, QWidget* parent, + Ui::MainWindow* mwui) + : m_core(core), m_pluginContainer(pc), + m_parent(parent), ui{mwui->tabWidget, + mwui->dataTab, + mwui->dataTabRefresh, + mwui->dataTree, + mwui->dataTabShowOnlyConflicts, + mwui->dataTabShowFromArchives}, + m_needUpdate(true) { m_filetree.reset(new FileTree(core, m_pluginContainer, ui.tree)); m_filter.setUseSourceSort(true); @@ -31,37 +32,33 @@ DataTab::DataTab( m_filter.setList(mwui->dataTree); m_filter.setUpdateDelay(true); - if (auto* m=m_filter.proxyModel()) { + if (auto* m = m_filter.proxyModel()) { m->setDynamicSortFilter(false); } - connect( - &m_filter, &FilterWidget::aboutToChange, - [&]{ ensureFullyLoaded(); }); + connect(&m_filter, &FilterWidget::aboutToChange, [&] { + ensureFullyLoaded(); + }); - connect( - ui.refresh, &QPushButton::clicked, - [&]{ onRefresh(); }); + connect(ui.refresh, &QPushButton::clicked, [&] { + onRefresh(); + }); - connect( - ui.conflicts, &QCheckBox::toggled, - [&]{ onConflicts(); }); + connect(ui.conflicts, &QCheckBox::toggled, [&] { + onConflicts(); + }); - connect( - ui.archives, &QCheckBox::toggled, - [&]{ onArchives(); }); + connect(ui.archives, &QCheckBox::toggled, [&] { + onArchives(); + }); - connect( - m_filetree.get(), &FileTree::executablesChanged, - this, &DataTab::executablesChanged); + connect(m_filetree.get(), &FileTree::executablesChanged, this, + &DataTab::executablesChanged); - connect( - m_filetree.get(), &FileTree::originModified, - this, &DataTab::originModified); + connect(m_filetree.get(), &FileTree::originModified, this, &DataTab::originModified); - connect( - m_filetree.get(), &FileTree::displayModInformation, - this, &DataTab::displayModInformation); + connect(m_filetree.get(), &FileTree::displayModInformation, this, + &DataTab::displayModInformation); } void DataTab::saveState(Settings& s) const @@ -109,8 +106,7 @@ void DataTab::updateTree() { if (isActive()) { doUpdateTree(); - } - else { + } else { m_needUpdate = true; } } @@ -123,7 +119,7 @@ void DataTab::doUpdateTree() if (!m_filter.empty()) { ensureFullyLoaded(); - if (auto* m=m_filter.proxyModel()) { + if (auto* m = m_filter.proxyModel()) { m->invalidate(); } } diff --git a/src/datatab.h b/src/datatab.h index ad172ab04..3eb916811 100644 --- a/src/datatab.h +++ b/src/datatab.h @@ -1,29 +1,34 @@ #ifndef MODORGANIZER_DATATAB_INCLUDED #define MODORGANIZER_DATATAB_INCLUDED -#include "modinfodialogfwd.h" #include "modinfo.h" -#include +#include "modinfodialogfwd.h" +#include #include #include -#include +#include -namespace Ui { class MainWindow; } +namespace Ui +{ +class MainWindow; +} class OrganizerCore; class Settings; class PluginContainer; class FileTree; -namespace MOShared { class DirectoryEntry; } +namespace MOShared +{ +class DirectoryEntry; +} class DataTab : public QObject { Q_OBJECT; public: - DataTab( - OrganizerCore& core, PluginContainer& pc, - QWidget* parent, Ui::MainWindow* ui); + DataTab(OrganizerCore& core, PluginContainer& pc, QWidget* parent, + Ui::MainWindow* ui); void saveState(Settings& s) const; void restoreState(const Settings& s); @@ -70,4 +75,4 @@ class DataTab : public QObject void doUpdateTree(); }; -#endif // MODORGANIZER_DATATAB_INCLUDED +#endif // MODORGANIZER_DATATAB_INCLUDED diff --git a/src/directoryrefresher.cpp b/src/directoryrefresher.cpp index e6d5524f9..63ac28d78 100644 --- a/src/directoryrefresher.cpp +++ b/src/directoryrefresher.cpp @@ -1,515 +1,499 @@ -/* -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 "directoryrefresher.h" -#include "shared/fileentry.h" -#include "shared/filesorigin.h" - -#include "iplugingame.h" -#include "utility.h" -#include "report.h" -#include "modinfo.h" -#include "settings.h" -#include "envfs.h" -#include "modinfodialogfwd.h" -#include "shared/util.h" - -#include - -#include -#include -#include - -#include - - -using namespace MOBase; -using namespace MOShared; - - -DirectoryStats::DirectoryStats() -{ - std::memset(this, 0, sizeof(DirectoryStats)); -} - -DirectoryStats& DirectoryStats::operator+=(const DirectoryStats& o) -{ - dirTimes += o.dirTimes; - fileTimes += o.fileTimes; - sortTimes += o.sortTimes; - - subdirLookupTimes += o.subdirLookupTimes; - addDirectoryTimes += o.addDirectoryTimes; - - filesLookupTimes += o.filesLookupTimes; - addFileTimes += o.addFileTimes; - addOriginToFileTimes += o.addOriginToFileTimes; - addFileToOriginTimes += o.addFileToOriginTimes; - addFileToRegisterTimes += o.addFileToRegisterTimes; - - originExists += o.originExists; - originCreate += o.originCreate; - originsNeededEnabled += o.originsNeededEnabled; - - subdirExists += o.subdirExists; - subdirCreate += o.subdirCreate; - - fileExists += o.fileExists; - fileCreate += o.fileCreate; - filesInsertedInRegister += o.filesInsertedInRegister; - filesAssignedInRegister += o.filesAssignedInRegister; - - return *this; -} - -std::string DirectoryStats::csvHeader() -{ - QStringList sl = { - "dirTimes", - "fileTimes", - "sortTimes", - "subdirLookupTimes", - "addDirectoryTimes", - "filesLookupTimes", - "addFileTimes", - "addOriginToFileTimes", - "addFileToOriginTimes", - "addFileToRegisterTimes", - "originExists", - "originCreate", - "originsNeededEnabled", - "subdirExists", - "subdirCreate", - "fileExists", - "fileCreate", - "filesInsertedInRegister", - "filesAssignedInRegister"}; - - return sl.join(",").toStdString(); -} - -std::string DirectoryStats::toCsv() const -{ - QStringList oss; - - auto s = [](auto ns) { - return ns.count() / 1000.0 / 1000.0 / 1000.0; - }; - - oss - << QString::number(s(dirTimes)) - << QString::number(s(fileTimes)) - << QString::number(s(sortTimes)) - - << QString::number(s(subdirLookupTimes)) - << QString::number(s(addDirectoryTimes)) - - << QString::number(s(filesLookupTimes)) - << QString::number(s(addFileTimes)) - << QString::number(s(addOriginToFileTimes)) - << QString::number(s(addFileToOriginTimes)) - << QString::number(s(addFileToRegisterTimes)) - - << QString::number(originExists) - << QString::number(originCreate) - << QString::number(originsNeededEnabled) - - << QString::number(subdirExists) - << QString::number(subdirCreate) - - << QString::number(fileExists) - << QString::number(fileCreate) - << QString::number(filesInsertedInRegister) - << QString::number(filesAssignedInRegister); - - return oss.join(",").toStdString(); -} - -void dumpStats(std::vector& stats) -{ - static int run = 0; - static const std::string file("c:\\tmp\\data.csv"); - - if (run == 0) { - std::ofstream out(file, std::ios::out|std::ios::trunc); - out << fmt::format("what,run,{}", DirectoryStats::csvHeader()) << "\n"; - } - - std::sort(stats.begin(), stats.end(), [](auto&& a, auto&& b){ - return (naturalCompare(QString::fromStdString(a.mod), QString::fromStdString(b.mod)) < 0); - }); - - std::ofstream out(file, std::ios::app); - - DirectoryStats total; - for (const auto& s : stats) { - out << fmt::format("{},{},{}", s.mod, run, s.toCsv()) << "\n"; - total += s; - } - - out << fmt::format("total,{},{}", run, total.toCsv()) << "\n"; - - ++run; -} - - -DirectoryRefresher::DirectoryRefresher(std::size_t threadCount) - : m_threadCount(threadCount), m_lastFileCount(0) -{ -} - -DirectoryEntry *DirectoryRefresher::stealDirectoryStructure() -{ - QMutexLocker locker(&m_RefreshLock); - return m_Root.release(); -} - -void DirectoryRefresher::setMods(const std::vector > &mods - , const std::set &managedArchives) -{ - QMutexLocker locker(&m_RefreshLock); - - m_Mods.clear(); - for (auto mod = mods.begin(); mod != mods.end(); ++mod) { - QString name = std::get<0>(*mod); - ModInfo::Ptr info = ModInfo::getByIndex(ModInfo::getIndex(name)); - m_Mods.push_back(EntryInfo(name, std::get<1>(*mod), info->stealFiles(), info->archives(), std::get<2>(*mod))); - } - - m_EnabledArchives = managedArchives; -} - -void DirectoryRefresher::cleanStructure(DirectoryEntry *structure) -{ - static const wchar_t *files[] = { L"meta.ini", L"readme.txt" }; - for (int i = 0; i < sizeof(files) / sizeof(wchar_t*); ++i) { - structure->removeFile(files[i]); - } - - static const wchar_t *dirs[] = { L"fomod" }; - for (int i = 0; i < sizeof(dirs) / sizeof(wchar_t*); ++i) { - structure->removeDir(std::wstring(dirs[i])); - } -} - -void DirectoryRefresher::addModBSAToStructure( - DirectoryEntry* root, const QString& modName, - int priority, const QString& directory, const QStringList& archives) -{ - const IPluginGame *game = qApp->property("managed_game").value(); - - QStringList loadOrder; - - GamePlugins* gamePlugins = game->feature(); - if (gamePlugins) { - loadOrder = gamePlugins->getLoadOrder(); - } - - std::vector lo; - for (auto&& s : loadOrder) { - lo.push_back(s.toStdWString()); - } - - std::vector archivesW; - for (auto&& a : archives) { - archivesW.push_back(a.toStdWString()); - } - - std::set enabledArchives; - for (auto&& a : m_EnabledArchives) { - enabledArchives.insert(a.toStdWString()); - } - - DirectoryStats dummy; - - root->addFromAllBSAs( - modName.toStdWString(), - QDir::toNativeSeparators(directory).toStdWString(), - priority, - archivesW, - enabledArchives, - lo, - dummy); -} - -void DirectoryRefresher::stealModFilesIntoStructure( - DirectoryEntry *directoryStructure, const QString &modName, - int priority, const QString &directory, const QStringList &stealFiles) -{ - std::wstring directoryW = ToWString(QDir::toNativeSeparators(directory)); - - // instead of adding all the files of the target directory, we just change the root of the specified - // files to this mod - DirectoryStats dummy; - FilesOrigin &origin = directoryStructure->createOrigin( - ToWString(modName), directoryW, priority, dummy); - - for (const QString &filename : stealFiles) { - if (filename.isEmpty()) { - log::warn("Trying to find file with no name"); - log::warn(" . modName: {}", modName); - log::warn(" . directory: {}", directory); - log::warn(" . priority: {}", priority); - for (int i = 0; i < stealFiles.length(); ++i) - log::warn(" . stealFiles[{}]: {}", i, stealFiles[i]); - continue; - } - QFileInfo fileInfo(filename); - FileEntryPtr file = directoryStructure->findFile(ToWString(fileInfo.fileName())); - if (file.get() != nullptr) { - if (file->getOrigin() == 0) { - // replace data as the origin on this bsa - file->removeOrigin(0); - } - origin.addFile(file->getIndex()); - file->addOrigin(origin.getID(), file->getFileTime(), L"", -1); - } else { - QString warnStr = fileInfo.absolutePath(); - if (warnStr.isEmpty()) - warnStr = filename; - log::warn("file not found: {}", warnStr); - } - } -} - -void DirectoryRefresher::addModFilesToStructure( - DirectoryEntry *directoryStructure, const QString &modName, - int priority, const QString &directory, const QStringList &stealFiles) -{ - TimeThis tt("DirectoryRefresher::addModFilesToStructure()"); - - std::wstring directoryW = ToWString(QDir::toNativeSeparators(directory)); - DirectoryStats dummy; - - if (stealFiles.length() > 0) { - stealModFilesIntoStructure( - directoryStructure, modName, priority, directory, stealFiles); - } else { - directoryStructure->addFromOrigin( - ToWString(modName), directoryW, priority, dummy); - } -} - -void DirectoryRefresher::addModToStructure(DirectoryEntry *directoryStructure - , const QString &modName - , int priority - , const QString &directory - , const QStringList &stealFiles - , const QStringList &archives) -{ - TimeThis tt("DirectoryRefresher::addModToStructure()"); - - DirectoryStats dummy; - - if (stealFiles.length() > 0) { - stealModFilesIntoStructure( - directoryStructure, modName, priority, directory, stealFiles); - } else { - std::wstring directoryW = ToWString(QDir::toNativeSeparators(directory)); - directoryStructure->addFromOrigin( - ToWString(modName), directoryW, priority, dummy); - } - - if (Settings::instance().archiveParsing()) { - addModBSAToStructure( - directoryStructure, modName, priority, directory, archives); - } -} - - -struct ModThread -{ - DirectoryRefreshProgress* progress = nullptr; - DirectoryEntry* ds = nullptr; - std::wstring modName; - std::wstring path; - int prio = -1; - std::vector archives; - std::set enabledArchives; - DirectoryStats* stats = nullptr; - env::DirectoryWalker walker; - - std::condition_variable cv; - std::mutex mutex; - bool ready = false; - - void wakeup() - { - { - std::scoped_lock lock(mutex); - ready = true; - } - - cv.notify_one(); - } - - void run() - { - std::unique_lock lock(mutex); - cv.wait(lock, [&]{ return ready; }); - - SetThisThreadName(QString::fromStdWString(modName + L" refresher")); - ds->addFromOrigin(walker, modName, path, prio, *stats); - - if (Settings::instance().archiveParsing()) { - const IPluginGame *game = qApp->property("managed_game").value(); - - QStringList loadOrder; - GamePlugins* gamePlugins = game->feature(); - if (gamePlugins) { - loadOrder = gamePlugins->getLoadOrder(); - } - - std::vector lo; - for (auto&& s : loadOrder) { - lo.push_back(s.toStdWString()); - } - - ds->addFromAllBSAs( - modName, path, prio, archives, enabledArchives, lo, *stats); - } - - if (progress) { - progress->addDone(); - } - - SetThisThreadName(QString::fromStdWString(L"idle refresher")); - ready = false; - } -}; - -env::ThreadPool g_threads; - - -void DirectoryRefresher::updateProgress(const DirectoryRefreshProgress* p) -{ - // careful: called from multiple threads - emit progress(p); -} - -void DirectoryRefresher::addMultipleModsFilesToStructure( - MOShared::DirectoryEntry *directoryStructure, - const std::vector& entries, DirectoryRefreshProgress* progress) -{ - std::vector stats(entries.size()); - - if (progress) { - progress->start(entries.size()); - } - - log::debug("refresher: using {} threads", m_threadCount); - g_threads.setMax(m_threadCount); - - for (std::size_t i=0; i 0) { - stealModFilesIntoStructure( - directoryStructure, e.modName, prio, e.absolutePath, e.stealFiles); - - if (progress) { - progress->addDone(); - } - } else { - auto& mt = g_threads.request(); - - mt.progress = progress; - mt.ds = directoryStructure; - mt.modName = e.modName.toStdWString(); - mt.path = QDir::toNativeSeparators(e.absolutePath).toStdWString(); - mt.prio = prio; - - mt.archives.clear(); - for (auto&& a : e.archives) { - mt.archives.push_back(a.toStdWString()); - } - - mt.enabledArchives.clear(); - for (auto&& a : m_EnabledArchives) { - mt.enabledArchives.insert(a.toStdWString()); - } - - mt.stats = &stats[i]; - - mt.wakeup(); - } - } catch (const std::exception& ex) { - emit error(tr("failed to read mod (%1): %2").arg(e.modName, ex.what())); - } - } - - g_threads.waitForAll(); - - if constexpr (DirectoryStats::EnableInstrumentation) { - dumpStats(stats); - } -} - -void DirectoryRefresher::refresh() -{ - SetThisThreadName("DirectoryRefresher"); - TimeThis tt("DirectoryRefresher::refresh()"); - auto* p = new DirectoryRefreshProgress(this); - - { - QMutexLocker locker(&m_RefreshLock); - - m_Root.reset(new DirectoryEntry(L"data", nullptr, 0)); - - IPluginGame *game = qApp->property("managed_game").value(); - - std::wstring dataDirectory = - QDir::toNativeSeparators(game->dataDirectory().absolutePath()).toStdWString(); - - { - DirectoryStats dummy; - m_Root->addFromOrigin(L"data", dataDirectory, 0, dummy); - } - - for (auto directory : game->secondaryDataDirectories().toStdMap()) { - DirectoryStats dummy; - m_Root->addFromOrigin(directory.first.toStdWString(), QDir::toNativeSeparators(directory.second.absolutePath()).toStdWString(), 0, dummy); - } - - std::sort(m_Mods.begin(), m_Mods.end(), [](auto lhs, auto rhs) { - return lhs.priority < rhs.priority; - }); - - addMultipleModsFilesToStructure(m_Root.get(), m_Mods, p); - - m_Root->getFileRegister()->sortOrigins(); - - cleanStructure(m_Root.get()); - - m_lastFileCount = m_Root->getFileRegister()->highestCount(); - log::debug("refresher saw {} files", m_lastFileCount); - } - - p->finish(); - - emit progress(p); - emit refreshed(); -} +/* +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 "directoryrefresher.h" +#include "shared/fileentry.h" +#include "shared/filesorigin.h" + +#include "envfs.h" +#include "iplugingame.h" +#include "modinfo.h" +#include "modinfodialogfwd.h" +#include "report.h" +#include "settings.h" +#include "shared/util.h" +#include "utility.h" + +#include + +#include +#include +#include + +#include + +using namespace MOBase; +using namespace MOShared; + +DirectoryStats::DirectoryStats() +{ + std::memset(this, 0, sizeof(DirectoryStats)); +} + +DirectoryStats& DirectoryStats::operator+=(const DirectoryStats& o) +{ + dirTimes += o.dirTimes; + fileTimes += o.fileTimes; + sortTimes += o.sortTimes; + + subdirLookupTimes += o.subdirLookupTimes; + addDirectoryTimes += o.addDirectoryTimes; + + filesLookupTimes += o.filesLookupTimes; + addFileTimes += o.addFileTimes; + addOriginToFileTimes += o.addOriginToFileTimes; + addFileToOriginTimes += o.addFileToOriginTimes; + addFileToRegisterTimes += o.addFileToRegisterTimes; + + originExists += o.originExists; + originCreate += o.originCreate; + originsNeededEnabled += o.originsNeededEnabled; + + subdirExists += o.subdirExists; + subdirCreate += o.subdirCreate; + + fileExists += o.fileExists; + fileCreate += o.fileCreate; + filesInsertedInRegister += o.filesInsertedInRegister; + filesAssignedInRegister += o.filesAssignedInRegister; + + return *this; +} + +std::string DirectoryStats::csvHeader() +{ + QStringList sl = {"dirTimes", + "fileTimes", + "sortTimes", + "subdirLookupTimes", + "addDirectoryTimes", + "filesLookupTimes", + "addFileTimes", + "addOriginToFileTimes", + "addFileToOriginTimes", + "addFileToRegisterTimes", + "originExists", + "originCreate", + "originsNeededEnabled", + "subdirExists", + "subdirCreate", + "fileExists", + "fileCreate", + "filesInsertedInRegister", + "filesAssignedInRegister"}; + + return sl.join(",").toStdString(); +} + +std::string DirectoryStats::toCsv() const +{ + QStringList oss; + + auto s = [](auto ns) { + return ns.count() / 1000.0 / 1000.0 / 1000.0; + }; + + oss << QString::number(s(dirTimes)) << QString::number(s(fileTimes)) + << QString::number(s(sortTimes)) + + << QString::number(s(subdirLookupTimes)) << QString::number(s(addDirectoryTimes)) + + << QString::number(s(filesLookupTimes)) << QString::number(s(addFileTimes)) + << QString::number(s(addOriginToFileTimes)) + << QString::number(s(addFileToOriginTimes)) + << QString::number(s(addFileToRegisterTimes)) + + << QString::number(originExists) << QString::number(originCreate) + << QString::number(originsNeededEnabled) + + << QString::number(subdirExists) << QString::number(subdirCreate) + + << QString::number(fileExists) << QString::number(fileCreate) + << QString::number(filesInsertedInRegister) + << QString::number(filesAssignedInRegister); + + return oss.join(",").toStdString(); +} + +void dumpStats(std::vector& stats) +{ + static int run = 0; + static const std::string file("c:\\tmp\\data.csv"); + + if (run == 0) { + std::ofstream out(file, std::ios::out | std::ios::trunc); + out << fmt::format("what,run,{}", DirectoryStats::csvHeader()) << "\n"; + } + + std::sort(stats.begin(), stats.end(), [](auto&& a, auto&& b) { + return (naturalCompare(QString::fromStdString(a.mod), + QString::fromStdString(b.mod)) < 0); + }); + + std::ofstream out(file, std::ios::app); + + DirectoryStats total; + for (const auto& s : stats) { + out << fmt::format("{},{},{}", s.mod, run, s.toCsv()) << "\n"; + total += s; + } + + out << fmt::format("total,{},{}", run, total.toCsv()) << "\n"; + + ++run; +} + +DirectoryRefresher::DirectoryRefresher(std::size_t threadCount) + : m_threadCount(threadCount), m_lastFileCount(0) +{} + +DirectoryEntry* DirectoryRefresher::stealDirectoryStructure() +{ + QMutexLocker locker(&m_RefreshLock); + return m_Root.release(); +} + +void DirectoryRefresher::setMods( + const std::vector>& mods, + const std::set& managedArchives) +{ + QMutexLocker locker(&m_RefreshLock); + + m_Mods.clear(); + for (auto mod = mods.begin(); mod != mods.end(); ++mod) { + QString name = std::get<0>(*mod); + ModInfo::Ptr info = ModInfo::getByIndex(ModInfo::getIndex(name)); + m_Mods.push_back(EntryInfo(name, std::get<1>(*mod), info->stealFiles(), + info->archives(), std::get<2>(*mod))); + } + + m_EnabledArchives = managedArchives; +} + +void DirectoryRefresher::cleanStructure(DirectoryEntry* structure) +{ + static const wchar_t* files[] = {L"meta.ini", L"readme.txt"}; + for (int i = 0; i < sizeof(files) / sizeof(wchar_t*); ++i) { + structure->removeFile(files[i]); + } + + static const wchar_t* dirs[] = {L"fomod"}; + for (int i = 0; i < sizeof(dirs) / sizeof(wchar_t*); ++i) { + structure->removeDir(std::wstring(dirs[i])); + } +} + +void DirectoryRefresher::addModBSAToStructure(DirectoryEntry* root, + const QString& modName, int priority, + const QString& directory, + const QStringList& archives) +{ + const IPluginGame* game = qApp->property("managed_game").value(); + + QStringList loadOrder; + + GamePlugins* gamePlugins = game->feature(); + if (gamePlugins) { + loadOrder = gamePlugins->getLoadOrder(); + } + + std::vector lo; + for (auto&& s : loadOrder) { + lo.push_back(s.toStdWString()); + } + + std::vector archivesW; + for (auto&& a : archives) { + archivesW.push_back(a.toStdWString()); + } + + std::set enabledArchives; + for (auto&& a : m_EnabledArchives) { + enabledArchives.insert(a.toStdWString()); + } + + DirectoryStats dummy; + + root->addFromAllBSAs(modName.toStdWString(), + QDir::toNativeSeparators(directory).toStdWString(), priority, + archivesW, enabledArchives, lo, dummy); +} + +void DirectoryRefresher::stealModFilesIntoStructure(DirectoryEntry* directoryStructure, + const QString& modName, + int priority, + const QString& directory, + const QStringList& stealFiles) +{ + std::wstring directoryW = ToWString(QDir::toNativeSeparators(directory)); + + // instead of adding all the files of the target directory, we just change the root of + // the specified files to this mod + DirectoryStats dummy; + FilesOrigin& origin = + directoryStructure->createOrigin(ToWString(modName), directoryW, priority, dummy); + + for (const QString& filename : stealFiles) { + if (filename.isEmpty()) { + log::warn("Trying to find file with no name"); + log::warn(" . modName: {}", modName); + log::warn(" . directory: {}", directory); + log::warn(" . priority: {}", priority); + for (int i = 0; i < stealFiles.length(); ++i) + log::warn(" . stealFiles[{}]: {}", i, stealFiles[i]); + continue; + } + QFileInfo fileInfo(filename); + FileEntryPtr file = directoryStructure->findFile(ToWString(fileInfo.fileName())); + if (file.get() != nullptr) { + if (file->getOrigin() == 0) { + // replace data as the origin on this bsa + file->removeOrigin(0); + } + origin.addFile(file->getIndex()); + file->addOrigin(origin.getID(), file->getFileTime(), L"", -1); + } else { + QString warnStr = fileInfo.absolutePath(); + if (warnStr.isEmpty()) + warnStr = filename; + log::warn("file not found: {}", warnStr); + } + } +} + +void DirectoryRefresher::addModFilesToStructure(DirectoryEntry* directoryStructure, + const QString& modName, int priority, + const QString& directory, + const QStringList& stealFiles) +{ + TimeThis tt("DirectoryRefresher::addModFilesToStructure()"); + + std::wstring directoryW = ToWString(QDir::toNativeSeparators(directory)); + DirectoryStats dummy; + + if (stealFiles.length() > 0) { + stealModFilesIntoStructure(directoryStructure, modName, priority, directory, + stealFiles); + } else { + directoryStructure->addFromOrigin(ToWString(modName), directoryW, priority, dummy); + } +} + +void DirectoryRefresher::addModToStructure(DirectoryEntry* directoryStructure, + const QString& modName, int priority, + const QString& directory, + const QStringList& stealFiles, + const QStringList& archives) +{ + TimeThis tt("DirectoryRefresher::addModToStructure()"); + + DirectoryStats dummy; + + if (stealFiles.length() > 0) { + stealModFilesIntoStructure(directoryStructure, modName, priority, directory, + stealFiles); + } else { + std::wstring directoryW = ToWString(QDir::toNativeSeparators(directory)); + directoryStructure->addFromOrigin(ToWString(modName), directoryW, priority, dummy); + } + + if (Settings::instance().archiveParsing()) { + addModBSAToStructure(directoryStructure, modName, priority, directory, archives); + } +} + +struct ModThread +{ + DirectoryRefreshProgress* progress = nullptr; + DirectoryEntry* ds = nullptr; + std::wstring modName; + std::wstring path; + int prio = -1; + std::vector archives; + std::set enabledArchives; + DirectoryStats* stats = nullptr; + env::DirectoryWalker walker; + + std::condition_variable cv; + std::mutex mutex; + bool ready = false; + + void wakeup() + { + { + std::scoped_lock lock(mutex); + ready = true; + } + + cv.notify_one(); + } + + void run() + { + std::unique_lock lock(mutex); + cv.wait(lock, [&] { + return ready; + }); + + SetThisThreadName(QString::fromStdWString(modName + L" refresher")); + ds->addFromOrigin(walker, modName, path, prio, *stats); + + if (Settings::instance().archiveParsing()) { + const IPluginGame* game = qApp->property("managed_game").value(); + + QStringList loadOrder; + GamePlugins* gamePlugins = game->feature(); + if (gamePlugins) { + loadOrder = gamePlugins->getLoadOrder(); + } + + std::vector lo; + for (auto&& s : loadOrder) { + lo.push_back(s.toStdWString()); + } + + ds->addFromAllBSAs(modName, path, prio, archives, enabledArchives, lo, *stats); + } + + if (progress) { + progress->addDone(); + } + + SetThisThreadName(QString::fromStdWString(L"idle refresher")); + ready = false; + } +}; + +env::ThreadPool g_threads; + +void DirectoryRefresher::updateProgress(const DirectoryRefreshProgress* p) +{ + // careful: called from multiple threads + emit progress(p); +} + +void DirectoryRefresher::addMultipleModsFilesToStructure( + MOShared::DirectoryEntry* directoryStructure, const std::vector& entries, + DirectoryRefreshProgress* progress) +{ + std::vector stats(entries.size()); + + if (progress) { + progress->start(entries.size()); + } + + log::debug("refresher: using {} threads", m_threadCount); + g_threads.setMax(m_threadCount); + + for (std::size_t i = 0; i < entries.size(); ++i) { + const auto& e = entries[i]; + const int prio = e.priority + 1; + + if constexpr (DirectoryStats::EnableInstrumentation) { + stats[i].mod = entries[i].modName.toStdString(); + } + + try { + if (e.stealFiles.length() > 0) { + stealModFilesIntoStructure(directoryStructure, e.modName, prio, e.absolutePath, + e.stealFiles); + + if (progress) { + progress->addDone(); + } + } else { + auto& mt = g_threads.request(); + + mt.progress = progress; + mt.ds = directoryStructure; + mt.modName = e.modName.toStdWString(); + mt.path = QDir::toNativeSeparators(e.absolutePath).toStdWString(); + mt.prio = prio; + + mt.archives.clear(); + for (auto&& a : e.archives) { + mt.archives.push_back(a.toStdWString()); + } + + mt.enabledArchives.clear(); + for (auto&& a : m_EnabledArchives) { + mt.enabledArchives.insert(a.toStdWString()); + } + + mt.stats = &stats[i]; + + mt.wakeup(); + } + } catch (const std::exception& ex) { + emit error(tr("failed to read mod (%1): %2").arg(e.modName, ex.what())); + } + } + + g_threads.waitForAll(); + + if constexpr (DirectoryStats::EnableInstrumentation) { + dumpStats(stats); + } +} + +void DirectoryRefresher::refresh() +{ + SetThisThreadName("DirectoryRefresher"); + TimeThis tt("DirectoryRefresher::refresh()"); + auto* p = new DirectoryRefreshProgress(this); + + { + QMutexLocker locker(&m_RefreshLock); + + m_Root.reset(new DirectoryEntry(L"data", nullptr, 0)); + + IPluginGame* game = qApp->property("managed_game").value(); + + std::wstring dataDirectory = + QDir::toNativeSeparators(game->dataDirectory().absolutePath()).toStdWString(); + + { + DirectoryStats dummy; + m_Root->addFromOrigin(L"data", dataDirectory, 0, dummy); + } + + for (auto directory : game->secondaryDataDirectories().toStdMap()) { + DirectoryStats dummy; + m_Root->addFromOrigin(directory.first.toStdWString(), QDir::toNativeSeparators(directory.second.absolutePath()).toStdWString(), 0, dummy); + } + + std::sort(m_Mods.begin(), m_Mods.end(), [](auto lhs, auto rhs) { + return lhs.priority < rhs.priority; + }); + + addMultipleModsFilesToStructure(m_Root.get(), m_Mods, p); + + m_Root->getFileRegister()->sortOrigins(); + + cleanStructure(m_Root.get()); + + m_lastFileCount = m_Root->getFileRegister()->highestCount(); + log::debug("refresher saw {} files", m_lastFileCount); + } + + p->finish(); + + emit progress(p); + emit refreshed(); +} diff --git a/src/directoryrefresher.h b/src/directoryrefresher.h index 56394d94e..2e6de1a06 100644 --- a/src/directoryrefresher.h +++ b/src/directoryrefresher.h @@ -1,211 +1,207 @@ -/* -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 DIRECTORYREFRESHER_H -#define DIRECTORYREFRESHER_H - -#include "shared/directoryentry.h" -#include "shared/fileregisterfwd.h" -#include "profile.h" -#include -#include -#include -#include -#include -#include - -/** - * @brief used to asynchronously generate the virtual view of the combined data directory - **/ -class DirectoryRefresher : public QObject -{ - Q_OBJECT; - -public: - struct EntryInfo - { - EntryInfo(const QString &modName, const QString &absolutePath, - const QStringList &stealFiles, const QStringList &archives, int priority) - : modName(modName), absolutePath(absolutePath), stealFiles(stealFiles) - , archives(archives), priority(priority) - { - } - - QString modName; - QString absolutePath; - QStringList stealFiles; - QStringList archives; - int priority; - }; - - - DirectoryRefresher(std::size_t threadCount); - - /** - * @brief retrieve the updated directory structure - * - * returns a pointer to the updated directory structure. DirectoryRefresher - * deletes its own pointer and the caller takes custody of the pointer - * - * @return updated directory structure - **/ - MOShared::DirectoryEntry* stealDirectoryStructure(); - - /** - * @brief sets up the mods to be included in the directory structure - * - * @param mods list of the mods to include - **/ - void setMods(const std::vector > &mods, const std::set &managedArchives); - - /** - * @brief sets up the directory where mods are stored - * @param modDirectory the mod directory - * @note this function could be obsoleted easily by storing absolute paths in the parameter to setMods. This is legacy - */ - //void setModDirectory(const QString &modDirectory); - - /** - * @brief remove files from the directory structure that are known to be irrelevant to the game - * @param the structure to clean - */ - static void cleanStructure(MOShared::DirectoryEntry *structure); - - /** - * @brief add files for a mod to the directory structure, including bsas - * @param directoryStructure - * @param modName - * @param priority - * @param directory - * @param stealFiles - * @param archives - */ - void addModToStructure(MOShared::DirectoryEntry *directoryStructure, const QString &modName, int priority, const QString &directory, const QStringList &stealFiles, const QStringList &archives); - - /** - * @brief add only the bsas of a mod to the directory structure - * @param directoryStructure - * @param modName - * @param priority - * @param directory - * @param archives - */ - void addModBSAToStructure(MOShared::DirectoryEntry *directoryStructure, const QString &modName, int priority, const QString &directory, const QStringList &archives); - - /** - * @brief add only regular files ofr a mod to the directory structure - * @param directoryStructure - * @param modName - * @param priority - * @param directory - * @param stealFiles - */ - void addModFilesToStructure( - MOShared::DirectoryEntry *directoryStructure, const QString &modName, - int priority, const QString &directory, const QStringList &stealFiles); - - void addMultipleModsFilesToStructure( - MOShared::DirectoryEntry *directoryStructure, - const std::vector& entries, - DirectoryRefreshProgress* progress=nullptr); - - void updateProgress(const DirectoryRefreshProgress* p); - -public slots: - - /** - * @brief generate a directory structure from the mods set earlier - **/ - void refresh(); - -signals: - - void progress(const DirectoryRefreshProgress* p); - void error(const QString &error); - void refreshed(); - -private: - std::vector m_Mods; - std::set m_EnabledArchives; - std::unique_ptr m_Root; - QMutex m_RefreshLock; - std::size_t m_threadCount; - std::size_t m_lastFileCount; - - void stealModFilesIntoStructure( - MOShared::DirectoryEntry *directoryStructure, const QString &modName, - int priority, const QString &directory, const QStringList &stealFiles); -}; - - -class DirectoryRefreshProgress : public QObject -{ - Q_OBJECT - -public: - DirectoryRefreshProgress(DirectoryRefresher* r) : - QObject(r), m_refresher(r), m_modCount(0), m_modDone(0), m_finished(false) - { - } - - void start(std::size_t modCount) - { - m_modCount = modCount; - m_modDone = 0; - m_finished = false; - } - - - bool finished() const - { - return m_finished; - } - - int percentDone() const - { - int percent = 100; - - if (m_modCount > 0) { - const double d = static_cast(m_modDone) / m_modCount; - percent = static_cast(d * 100); - } - - return percent; - } - - - void finish() - { - m_finished = true; - } - - void addDone() - { - ++m_modDone; - m_refresher->updateProgress(this); - } - -private: - DirectoryRefresher* m_refresher; - std::size_t m_modCount; - std::atomic m_modDone; - bool m_finished; -}; - -#endif // DIRECTORYREFRESHER_H +/* +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 DIRECTORYREFRESHER_H +#define DIRECTORYREFRESHER_H + +#include "profile.h" +#include "shared/directoryentry.h" +#include "shared/fileregisterfwd.h" +#include +#include +#include +#include +#include +#include + +/** + * @brief used to asynchronously generate the virtual view of the combined data + *directory + **/ +class DirectoryRefresher : public QObject +{ + Q_OBJECT; + +public: + struct EntryInfo + { + EntryInfo(const QString& modName, const QString& absolutePath, + const QStringList& stealFiles, const QStringList& archives, int priority) + : modName(modName), absolutePath(absolutePath), stealFiles(stealFiles), + archives(archives), priority(priority) + {} + + QString modName; + QString absolutePath; + QStringList stealFiles; + QStringList archives; + int priority; + }; + + DirectoryRefresher(std::size_t threadCount); + + /** + * @brief retrieve the updated directory structure + * + * returns a pointer to the updated directory structure. DirectoryRefresher + * deletes its own pointer and the caller takes custody of the pointer + * + * @return updated directory structure + **/ + MOShared::DirectoryEntry* stealDirectoryStructure(); + + /** + * @brief sets up the mods to be included in the directory structure + * + * @param mods list of the mods to include + **/ + void setMods(const std::vector>& mods, + const std::set& managedArchives); + + /** + * @brief sets up the directory where mods are stored + * @param modDirectory the mod directory + * @note this function could be obsoleted easily by storing absolute paths in the + * parameter to setMods. This is legacy + */ + // void setModDirectory(const QString &modDirectory); + + /** + * @brief remove files from the directory structure that are known to be irrelevant to + * the game + * @param the structure to clean + */ + static void cleanStructure(MOShared::DirectoryEntry* structure); + + /** + * @brief add files for a mod to the directory structure, including bsas + * @param directoryStructure + * @param modName + * @param priority + * @param directory + * @param stealFiles + * @param archives + */ + void addModToStructure(MOShared::DirectoryEntry* directoryStructure, + const QString& modName, int priority, const QString& directory, + const QStringList& stealFiles, const QStringList& archives); + + /** + * @brief add only the bsas of a mod to the directory structure + * @param directoryStructure + * @param modName + * @param priority + * @param directory + * @param archives + */ + void addModBSAToStructure(MOShared::DirectoryEntry* directoryStructure, + const QString& modName, int priority, + const QString& directory, const QStringList& archives); + + /** + * @brief add only regular files ofr a mod to the directory structure + * @param directoryStructure + * @param modName + * @param priority + * @param directory + * @param stealFiles + */ + void addModFilesToStructure(MOShared::DirectoryEntry* directoryStructure, + const QString& modName, int priority, + const QString& directory, const QStringList& stealFiles); + + void addMultipleModsFilesToStructure(MOShared::DirectoryEntry* directoryStructure, + const std::vector& entries, + DirectoryRefreshProgress* progress = nullptr); + + void updateProgress(const DirectoryRefreshProgress* p); + +public slots: + + /** + * @brief generate a directory structure from the mods set earlier + **/ + void refresh(); + +signals: + + void progress(const DirectoryRefreshProgress* p); + void error(const QString& error); + void refreshed(); + +private: + std::vector m_Mods; + std::set m_EnabledArchives; + std::unique_ptr m_Root; + QMutex m_RefreshLock; + std::size_t m_threadCount; + std::size_t m_lastFileCount; + + void stealModFilesIntoStructure(MOShared::DirectoryEntry* directoryStructure, + const QString& modName, int priority, + const QString& directory, + const QStringList& stealFiles); +}; + +class DirectoryRefreshProgress : public QObject +{ + Q_OBJECT + +public: + DirectoryRefreshProgress(DirectoryRefresher* r) + : QObject(r), m_refresher(r), m_modCount(0), m_modDone(0), m_finished(false) + {} + + void start(std::size_t modCount) + { + m_modCount = modCount; + m_modDone = 0; + m_finished = false; + } + + bool finished() const { return m_finished; } + + int percentDone() const + { + int percent = 100; + + if (m_modCount > 0) { + const double d = static_cast(m_modDone) / m_modCount; + percent = static_cast(d * 100); + } + + return percent; + } + + void finish() { m_finished = true; } + + void addDone() + { + ++m_modDone; + m_refresher->updateProgress(this); + } + +private: + DirectoryRefresher* m_refresher; + std::size_t m_modCount; + std::atomic m_modDone; + bool m_finished; +}; + +#endif // DIRECTORYREFRESHER_H diff --git a/src/disableproxyplugindialog.cpp b/src/disableproxyplugindialog.cpp index c05e30e38..a99b0a073 100644 --- a/src/disableproxyplugindialog.cpp +++ b/src/disableproxyplugindialog.cpp @@ -5,15 +5,17 @@ using namespace MOBase; DisableProxyPluginDialog::DisableProxyPluginDialog( - MOBase::IPlugin* proxyPlugin, std::vector const& required, QWidget* parent) - : QDialog(parent), ui(new Ui::DisableProxyPluginDialog) + MOBase::IPlugin* proxyPlugin, std::vector const& required, + QWidget* parent) + : QDialog(parent), ui(new Ui::DisableProxyPluginDialog) { ui->setupUi(this); - ui->topLabel->setText(QObject::tr( - "Disabling the '%1' plugin will prevent the following %2 plugin(s) from working:", "", required.size()) - .arg(proxyPlugin->localizedName()) - .arg(required.size())); + ui->topLabel->setText(QObject::tr("Disabling the '%1' plugin will prevent the " + "following %2 plugin(s) from working:", + "", required.size()) + .arg(proxyPlugin->localizedName()) + .arg(required.size())); connect(ui->noBtn, &QPushButton::clicked, this, &QDialog::reject); connect(ui->yesBtn, &QPushButton::clicked, this, &QDialog::accept); @@ -21,8 +23,10 @@ DisableProxyPluginDialog::DisableProxyPluginDialog( ui->requiredPlugins->setSelectionMode(QAbstractItemView::NoSelection); ui->requiredPlugins->setRowCount(required.size()); for (int i = 0; i < required.size(); ++i) { - ui->requiredPlugins->setItem(i, 0, new QTableWidgetItem(required[i]->localizedName())); - ui->requiredPlugins->setItem(i, 1, new QTableWidgetItem(required[i]->description())); + ui->requiredPlugins->setItem(i, 0, + new QTableWidgetItem(required[i]->localizedName())); + ui->requiredPlugins->setItem(i, 1, + new QTableWidgetItem(required[i]->description())); ui->requiredPlugins->setRowHeight(i, 9); } ui->requiredPlugins->verticalHeader()->setVisible(false); diff --git a/src/disableproxyplugindialog.h b/src/disableproxyplugindialog.h index b55a3d435..421697b67 100644 --- a/src/disableproxyplugindialog.h +++ b/src/disableproxyplugindialog.h @@ -24,20 +24,21 @@ along with Mod Organizer. If not, see . #include "ipluginproxy.h" -namespace Ui { class DisableProxyPluginDialog; } +namespace Ui +{ +class DisableProxyPluginDialog; +} -class DisableProxyPluginDialog : public QDialog { +class DisableProxyPluginDialog : public QDialog +{ public: - - DisableProxyPluginDialog( - MOBase::IPlugin* proxyPlugin, - std::vector const& required, - QWidget* parent = nullptr); + DisableProxyPluginDialog(MOBase::IPlugin* proxyPlugin, + std::vector const& required, + QWidget* parent = nullptr); private slots: Ui::DisableProxyPluginDialog* ui; - }; #endif diff --git a/src/downloadlist.cpp b/src/downloadlist.cpp index 387fcad1f..e38d13fa0 100644 --- a/src/downloadlist.cpp +++ b/src/downloadlist.cpp @@ -1,291 +1,332 @@ -/* -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 "organizercore.h" -#include "downloadlist.h" -#include "downloadmanager.h" -#include "modlistdropinfo.h" -#include "settings.h" -#include -#include -#include -#include -#include -#include - -using namespace MOBase; - -DownloadList::DownloadList(OrganizerCore& core, QObject *parent) - : QAbstractTableModel(parent), m_manager(*core.downloadManager()), m_settings(core.settings()) -{ - connect(&m_manager, SIGNAL(update(int)), this, SLOT(update(int))); - connect(&m_manager, SIGNAL(aboutToUpdate()), this, SLOT(aboutToUpdate())); -} - - -int DownloadList::rowCount(const QModelIndex& parent) const -{ - if (!parent.isValid()) { - // root item - return m_manager.numTotalDownloads() + m_manager.numPendingDownloads(); - } else { - return 0; - } -} - - -int DownloadList::columnCount(const QModelIndex&) const -{ - return COL_COUNT; -} - - -QModelIndex DownloadList::index(int row, int column, const QModelIndex&) const -{ - return createIndex(row, column, row); -} - - -QModelIndex DownloadList::parent(const QModelIndex&) const -{ - return QModelIndex(); -} - - -QVariant DownloadList::headerData(int section, Qt::Orientation orientation, int role) const -{ - if ((role == Qt::DisplayRole) && - (orientation == Qt::Horizontal)) { - switch (section) { - case COL_NAME: return tr("Name"); - case COL_MODNAME: return tr("Mod name"); - case COL_VERSION: return tr("Version"); - case COL_ID: return tr("Nexus ID"); - case COL_SIZE: return tr("Size"); - case COL_STATUS: return tr("Status"); - case COL_FILETIME: return tr("Filetime"); - case COL_SOURCEGAME: return tr("Source Game"); - default: return QVariant(); - } - } else { - return QAbstractItemModel::headerData(section, orientation, role); - } -} - -Qt::ItemFlags DownloadList::flags(const QModelIndex& idx) const -{ - return QAbstractTableModel::flags(idx) | Qt::ItemIsDragEnabled; -} - -QMimeData* DownloadList::mimeData(const QModelIndexList& indexes) const -{ - QMimeData* result = QAbstractItemModel::mimeData(indexes); - result->setData("text/plain", ModListDropInfo::DownloadText); - return result; -} - -QVariant DownloadList::data(const QModelIndex &index, int role) const -{ - bool pendingDownload = index.row() >= m_manager.numTotalDownloads(); - if (role == Qt::DisplayRole) { - if (pendingDownload) { - std::tuple nexusids = m_manager.getPendingDownload(index.row() - m_manager.numTotalDownloads()); - switch (index.column()) { - case COL_NAME: return tr("< game %1 mod %2 file %3 >").arg(std::get<0>(nexusids)).arg(std::get<1>(nexusids)).arg(std::get<2>(nexusids)); - case COL_SIZE: return tr("Unknown"); - case COL_STATUS: return tr("Pending"); - } - } else { - switch (index.column()) { - case COL_NAME: return m_settings.interface().metaDownloads() ? m_manager.getDisplayName(index.row()) : m_manager.getFileName(index.row()); - case COL_MODNAME: { - if (m_manager.isInfoIncomplete(index.row())) { - return {}; - } else { - const MOBase::ModRepositoryFileInfo *info = m_manager.getFileInfo(index.row()); - return info->modName; - } - } - case COL_VERSION: { - if (m_manager.isInfoIncomplete(index.row())) { - return {}; - } else { - const MOBase::ModRepositoryFileInfo *info = m_manager.getFileInfo(index.row()); - return info->version.canonicalString(); - } - } - case COL_ID: { - if (m_manager.isInfoIncomplete(index.row())) { - return {}; - } else { - return QString("%1").arg(m_manager.getModID(index.row())); - } - } - case COL_SOURCEGAME: { - if (m_manager.isInfoIncomplete(index.row())) { - return {}; - } else { - return QString("%1").arg(m_manager.getDisplayGameName(index.row())); - } - } - case COL_SIZE: return MOBase::localizedByteSize(m_manager.getFileSize(index.row())); - case COL_FILETIME: return m_manager.getFileTime(index.row()); - case COL_STATUS: - switch (m_manager.getState(index.row())) { - // STATE_DOWNLOADING handled by DownloadProgressDelegate - case DownloadManager::STATE_STARTED: return tr("Started"); - case DownloadManager::STATE_CANCELING: return tr("Canceling"); - case DownloadManager::STATE_PAUSING: return tr("Pausing"); - case DownloadManager::STATE_CANCELED: return tr("Canceled"); - case DownloadManager::STATE_PAUSED: return tr("Paused"); - case DownloadManager::STATE_ERROR: return tr("Error"); - case DownloadManager::STATE_FETCHINGMODINFO: return tr("Fetching Info"); - case DownloadManager::STATE_FETCHINGFILEINFO: return tr("Fetching Info"); - case DownloadManager::STATE_FETCHINGMODINFO_MD5: return tr("Fetching Info"); - case DownloadManager::STATE_READY: return tr("Downloaded"); - case DownloadManager::STATE_INSTALLED: return tr("Installed"); - case DownloadManager::STATE_UNINSTALLED: return tr("Uninstalled"); - } - } - } - } else if (role == Qt::ForegroundRole && index.column() == COL_STATUS) { - if (pendingDownload) { - return QColor(Qt::darkBlue); - } else { - DownloadManager::DownloadState state = m_manager.getState(index.row()); - if (state == DownloadManager::STATE_READY) - return QColor(Qt::darkGreen); - else if (state == DownloadManager::STATE_UNINSTALLED) - return QColor(Qt::darkYellow); - else if (state == DownloadManager::STATE_PAUSED) - return QColor(Qt::darkRed); - } - } else if (role == Qt::ToolTipRole) { - if (pendingDownload) { - return tr("Pending download"); - } else { - QString text = m_manager.getFileName(index.row()) + "\n"; - if (m_manager.isInfoIncomplete(index.row())) { - text += tr("Information missing, please select \"Query Info\" from the context menu to re-retrieve."); - } else { - const MOBase::ModRepositoryFileInfo *info = m_manager.getFileInfo(index.row()); - return QString("%1 (ID %2) %3
    %4").arg(info->modName).arg(m_manager.getModID(index.row())).arg(info->version.canonicalString()).arg(info->description.chopped(4096)); - } - return text; - } - } else if (role == Qt::DecorationRole && index.column() == COL_NAME) { - if (!pendingDownload && m_manager.getState(index.row()) >= DownloadManager::STATE_READY - && m_manager.isInfoIncomplete(index.row())) - return QIcon(":/MO/gui/warning_16"); - } else if (role == Qt::TextAlignmentRole) { - if (index.column() == COL_SIZE) - return QVariant(Qt::AlignVCenter | Qt::AlignRight); - else - return QVariant(Qt::AlignVCenter | Qt::AlignLeft); - } - return QVariant(); -} - - -void DownloadList::aboutToUpdate() -{ - emit beginResetModel(); -} - - -void DownloadList::update(int row) -{ - if (row < 0) - emit endResetModel(); - else if (row < this->rowCount()) - emit dataChanged(this->index(row, 0, QModelIndex()), this->index(row, this->columnCount(QModelIndex())-1, QModelIndex())); - else - log::error("invalid row {} in download list, update failed", row); -} - -bool DownloadList::lessThanPredicate(const QModelIndex &left, const QModelIndex &right) -{ - int leftIndex = left.row(); - int rightIndex = right.row(); - if ((leftIndex < m_manager.numTotalDownloads()) - && (rightIndex < m_manager.numTotalDownloads())) { - if (left.column() == DownloadList::COL_NAME) { - return left.data(Qt::DisplayRole).toString().compare(right.data(Qt::DisplayRole).toString(), Qt::CaseInsensitive) < 0; - } else if (left.column() == DownloadList::COL_MODNAME) { - QString leftName, rightName; - - if (!m_manager.isInfoIncomplete(left.row())) { - const MOBase::ModRepositoryFileInfo *info = m_manager.getFileInfo(left.row()); - leftName = info->modName; - } - - if (!m_manager.isInfoIncomplete(right.row())) { - const MOBase::ModRepositoryFileInfo *info = m_manager.getFileInfo(right.row()); - rightName = info->modName; - } - - return leftName.compare(rightName, Qt::CaseInsensitive) < 0; - } else if (left.column() == DownloadList::COL_VERSION) { - MOBase::VersionInfo versionLeft, versionRight; - - if (!m_manager.isInfoIncomplete(left.row())) { - const MOBase::ModRepositoryFileInfo *info = m_manager.getFileInfo(left.row()); - versionLeft = info->version; - } - - if (!m_manager.isInfoIncomplete(right.row())) { - const MOBase::ModRepositoryFileInfo *info = m_manager.getFileInfo(right.row()); - versionRight = info->version; - } - - return versionLeft < versionRight; - } else if (left.column() == DownloadList::COL_ID) { - int leftID=0, rightID=0; - - if (!m_manager.isInfoIncomplete(left.row())) { - const MOBase::ModRepositoryFileInfo *info = m_manager.getFileInfo(left.row()); - leftID = info->modID; - } - - if (!m_manager.isInfoIncomplete(right.row())) { - const MOBase::ModRepositoryFileInfo *info = m_manager.getFileInfo(right.row()); - rightID = info->modID; - } - - return leftID < rightID; - } else if (left.column() == DownloadList::COL_STATUS) { - DownloadManager::DownloadState leftState = m_manager.getState(left.row()); - DownloadManager::DownloadState rightState = m_manager.getState(right.row()); - if (leftState == rightState) - return m_manager.getFileTime(left.row()) > m_manager.getFileTime(right.row()); - else - return leftState < rightState; - } else if (left.column() == DownloadList::COL_SIZE) { - return m_manager.getFileSize(left.row()) < m_manager.getFileSize(right.row()); - } else if (left.column() == DownloadList::COL_FILETIME) { - return m_manager.getFileTime(left.row()) < m_manager.getFileTime(right.row()); - } else if (left.column() == DownloadList::COL_SOURCEGAME) { - return m_manager.getDisplayGameName(left.row()) < m_manager.getDisplayGameName(right.row()); - } else { - return leftIndex < rightIndex; - } - } else { - return leftIndex < rightIndex; - } -} +/* +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 "downloadlist.h" +#include "downloadmanager.h" +#include "modlistdropinfo.h" +#include "organizercore.h" +#include "settings.h" +#include +#include +#include +#include +#include +#include + +using namespace MOBase; + +DownloadList::DownloadList(OrganizerCore& core, QObject* parent) + : QAbstractTableModel(parent), m_manager(*core.downloadManager()), + m_settings(core.settings()) +{ + connect(&m_manager, SIGNAL(update(int)), this, SLOT(update(int))); + connect(&m_manager, SIGNAL(aboutToUpdate()), this, SLOT(aboutToUpdate())); +} + +int DownloadList::rowCount(const QModelIndex& parent) const +{ + if (!parent.isValid()) { + // root item + return m_manager.numTotalDownloads() + m_manager.numPendingDownloads(); + } else { + return 0; + } +} + +int DownloadList::columnCount(const QModelIndex&) const +{ + return COL_COUNT; +} + +QModelIndex DownloadList::index(int row, int column, const QModelIndex&) const +{ + return createIndex(row, column, row); +} + +QModelIndex DownloadList::parent(const QModelIndex&) const +{ + return QModelIndex(); +} + +QVariant DownloadList::headerData(int section, Qt::Orientation orientation, + int role) const +{ + if ((role == Qt::DisplayRole) && (orientation == Qt::Horizontal)) { + switch (section) { + case COL_NAME: + return tr("Name"); + case COL_MODNAME: + return tr("Mod name"); + case COL_VERSION: + return tr("Version"); + case COL_ID: + return tr("Nexus ID"); + case COL_SIZE: + return tr("Size"); + case COL_STATUS: + return tr("Status"); + case COL_FILETIME: + return tr("Filetime"); + case COL_SOURCEGAME: + return tr("Source Game"); + default: + return QVariant(); + } + } else { + return QAbstractItemModel::headerData(section, orientation, role); + } +} + +Qt::ItemFlags DownloadList::flags(const QModelIndex& idx) const +{ + return QAbstractTableModel::flags(idx) | Qt::ItemIsDragEnabled; +} + +QMimeData* DownloadList::mimeData(const QModelIndexList& indexes) const +{ + QMimeData* result = QAbstractItemModel::mimeData(indexes); + result->setData("text/plain", ModListDropInfo::DownloadText); + return result; +} + +QVariant DownloadList::data(const QModelIndex& index, int role) const +{ + bool pendingDownload = index.row() >= m_manager.numTotalDownloads(); + if (role == Qt::DisplayRole) { + if (pendingDownload) { + std::tuple nexusids = + m_manager.getPendingDownload(index.row() - m_manager.numTotalDownloads()); + switch (index.column()) { + case COL_NAME: + return tr("< game %1 mod %2 file %3 >") + .arg(std::get<0>(nexusids)) + .arg(std::get<1>(nexusids)) + .arg(std::get<2>(nexusids)); + case COL_SIZE: + return tr("Unknown"); + case COL_STATUS: + return tr("Pending"); + } + } else { + switch (index.column()) { + case COL_NAME: + return m_settings.interface().metaDownloads() + ? m_manager.getDisplayName(index.row()) + : m_manager.getFileName(index.row()); + case COL_MODNAME: { + if (m_manager.isInfoIncomplete(index.row())) { + return {}; + } else { + const MOBase::ModRepositoryFileInfo* info = + m_manager.getFileInfo(index.row()); + return info->modName; + } + } + case COL_VERSION: { + if (m_manager.isInfoIncomplete(index.row())) { + return {}; + } else { + const MOBase::ModRepositoryFileInfo* info = + m_manager.getFileInfo(index.row()); + return info->version.canonicalString(); + } + } + case COL_ID: { + if (m_manager.isInfoIncomplete(index.row())) { + return {}; + } else { + return QString("%1").arg(m_manager.getModID(index.row())); + } + } + case COL_SOURCEGAME: { + if (m_manager.isInfoIncomplete(index.row())) { + return {}; + } else { + return QString("%1").arg(m_manager.getDisplayGameName(index.row())); + } + } + case COL_SIZE: + return MOBase::localizedByteSize(m_manager.getFileSize(index.row())); + case COL_FILETIME: + return m_manager.getFileTime(index.row()); + case COL_STATUS: + switch (m_manager.getState(index.row())) { + // STATE_DOWNLOADING handled by DownloadProgressDelegate + case DownloadManager::STATE_STARTED: + return tr("Started"); + case DownloadManager::STATE_CANCELING: + return tr("Canceling"); + case DownloadManager::STATE_PAUSING: + return tr("Pausing"); + case DownloadManager::STATE_CANCELED: + return tr("Canceled"); + case DownloadManager::STATE_PAUSED: + return tr("Paused"); + case DownloadManager::STATE_ERROR: + return tr("Error"); + case DownloadManager::STATE_FETCHINGMODINFO: + return tr("Fetching Info"); + case DownloadManager::STATE_FETCHINGFILEINFO: + return tr("Fetching Info"); + case DownloadManager::STATE_FETCHINGMODINFO_MD5: + return tr("Fetching Info"); + case DownloadManager::STATE_READY: + return tr("Downloaded"); + case DownloadManager::STATE_INSTALLED: + return tr("Installed"); + case DownloadManager::STATE_UNINSTALLED: + return tr("Uninstalled"); + } + } + } + } else if (role == Qt::ForegroundRole && index.column() == COL_STATUS) { + if (pendingDownload) { + return QColor(Qt::darkBlue); + } else { + DownloadManager::DownloadState state = m_manager.getState(index.row()); + if (state == DownloadManager::STATE_READY) + return QColor(Qt::darkGreen); + else if (state == DownloadManager::STATE_UNINSTALLED) + return QColor(Qt::darkYellow); + else if (state == DownloadManager::STATE_PAUSED) + return QColor(Qt::darkRed); + } + } else if (role == Qt::ToolTipRole) { + if (pendingDownload) { + return tr("Pending download"); + } else { + QString text = m_manager.getFileName(index.row()) + "\n"; + if (m_manager.isInfoIncomplete(index.row())) { + text += tr("Information missing, please select \"Query Info\" from the context " + "menu to re-retrieve."); + } else { + const MOBase::ModRepositoryFileInfo* info = m_manager.getFileInfo(index.row()); + return QString("%1 (ID %2) %3
    %4") + .arg(info->modName) + .arg(m_manager.getModID(index.row())) + .arg(info->version.canonicalString()) + .arg(info->description.chopped(4096)); + } + return text; + } + } else if (role == Qt::DecorationRole && index.column() == COL_NAME) { + if (!pendingDownload && + m_manager.getState(index.row()) >= DownloadManager::STATE_READY && + m_manager.isInfoIncomplete(index.row())) + return QIcon(":/MO/gui/warning_16"); + } else if (role == Qt::TextAlignmentRole) { + if (index.column() == COL_SIZE) + return QVariant(Qt::AlignVCenter | Qt::AlignRight); + else + return QVariant(Qt::AlignVCenter | Qt::AlignLeft); + } + return QVariant(); +} + +void DownloadList::aboutToUpdate() +{ + emit beginResetModel(); +} + +void DownloadList::update(int row) +{ + if (row < 0) + emit endResetModel(); + else if (row < this->rowCount()) + emit dataChanged( + this->index(row, 0, QModelIndex()), + this->index(row, this->columnCount(QModelIndex()) - 1, QModelIndex())); + else + log::error("invalid row {} in download list, update failed", row); +} + +bool DownloadList::lessThanPredicate(const QModelIndex& left, const QModelIndex& right) +{ + int leftIndex = left.row(); + int rightIndex = right.row(); + if ((leftIndex < m_manager.numTotalDownloads()) && + (rightIndex < m_manager.numTotalDownloads())) { + if (left.column() == DownloadList::COL_NAME) { + return left.data(Qt::DisplayRole) + .toString() + .compare(right.data(Qt::DisplayRole).toString(), Qt::CaseInsensitive) < + 0; + } else if (left.column() == DownloadList::COL_MODNAME) { + QString leftName, rightName; + + if (!m_manager.isInfoIncomplete(left.row())) { + const MOBase::ModRepositoryFileInfo* info = m_manager.getFileInfo(left.row()); + leftName = info->modName; + } + + if (!m_manager.isInfoIncomplete(right.row())) { + const MOBase::ModRepositoryFileInfo* info = m_manager.getFileInfo(right.row()); + rightName = info->modName; + } + + return leftName.compare(rightName, Qt::CaseInsensitive) < 0; + } else if (left.column() == DownloadList::COL_VERSION) { + MOBase::VersionInfo versionLeft, versionRight; + + if (!m_manager.isInfoIncomplete(left.row())) { + const MOBase::ModRepositoryFileInfo* info = m_manager.getFileInfo(left.row()); + versionLeft = info->version; + } + + if (!m_manager.isInfoIncomplete(right.row())) { + const MOBase::ModRepositoryFileInfo* info = m_manager.getFileInfo(right.row()); + versionRight = info->version; + } + + return versionLeft < versionRight; + } else if (left.column() == DownloadList::COL_ID) { + int leftID = 0, rightID = 0; + + if (!m_manager.isInfoIncomplete(left.row())) { + const MOBase::ModRepositoryFileInfo* info = m_manager.getFileInfo(left.row()); + leftID = info->modID; + } + + if (!m_manager.isInfoIncomplete(right.row())) { + const MOBase::ModRepositoryFileInfo* info = m_manager.getFileInfo(right.row()); + rightID = info->modID; + } + + return leftID < rightID; + } else if (left.column() == DownloadList::COL_STATUS) { + DownloadManager::DownloadState leftState = m_manager.getState(left.row()); + DownloadManager::DownloadState rightState = m_manager.getState(right.row()); + if (leftState == rightState) + return m_manager.getFileTime(left.row()) > m_manager.getFileTime(right.row()); + else + return leftState < rightState; + } else if (left.column() == DownloadList::COL_SIZE) { + return m_manager.getFileSize(left.row()) < m_manager.getFileSize(right.row()); + } else if (left.column() == DownloadList::COL_FILETIME) { + return m_manager.getFileTime(left.row()) < m_manager.getFileTime(right.row()); + } else if (left.column() == DownloadList::COL_SOURCEGAME) { + return m_manager.getDisplayGameName(left.row()) < + m_manager.getDisplayGameName(right.row()); + } else { + return leftIndex < rightIndex; + } + } else { + return leftIndex < rightIndex; + } +} diff --git a/src/downloadlist.h b/src/downloadlist.h index cec8b6b07..c9adaa7f2 100644 --- a/src/downloadlist.h +++ b/src/downloadlist.h @@ -1,104 +1,102 @@ -/* -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 DOWNLOADLIST_H -#define DOWNLOADLIST_H - -#include - -class OrganizerCore; -class DownloadManager; -class Settings; - - -/** - * @brief model of the list of active and completed downloads - **/ -class DownloadList : public QAbstractTableModel -{ - - Q_OBJECT - -public: - - enum EColumn { - COL_NAME = 0, - COL_STATUS, - COL_SIZE, - COL_FILETIME, - COL_MODNAME, - COL_VERSION, - COL_ID, - COL_SOURCEGAME, - - // number of columns - COL_COUNT - }; - -public: - - explicit DownloadList(OrganizerCore& core, QObject *parent = 0); - - /** - * @brief retrieve the number of rows to display. Invoked by Qt - * - * @param parent not relevant for this implementation - * @return number of rows to display - **/ - virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; - virtual int columnCount(const QModelIndex &parent) const; - - QModelIndex index(int row, int column, const QModelIndex &parent) const; - QModelIndex parent(const QModelIndex &child) const; - Qt::ItemFlags flags(const QModelIndex& idx) const override; - QMimeData* mimeData(const QModelIndexList& indexes) const override; - - virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const; - - /** - * @brief retrieve the data to display in a specific row. Invoked by Qt - * - * @param index location to look up - * @param role ... Defaults to Qt::DisplayRole. - * @return this implementation only returns the row, the QItemDelegate implementation is expected to fetch its information from the DownloadManager - **/ - virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; - - // used in DownloadsTab as the sorting predicate for the filter widget - // - bool lessThanPredicate(const QModelIndex &left, const QModelIndex &right); - -public slots: - - /** - * @brief used to inform the model that data has changed - * - * @param row the row that changed. This can be negative to update the whole view - **/ - void update(int row); - - void aboutToUpdate(); - -private: - - DownloadManager& m_manager; - Settings& m_settings; -}; - -#endif // DOWNLOADLIST_H +/* +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 DOWNLOADLIST_H +#define DOWNLOADLIST_H + +#include + +class OrganizerCore; +class DownloadManager; +class Settings; + +/** + * @brief model of the list of active and completed downloads + **/ +class DownloadList : public QAbstractTableModel +{ + + Q_OBJECT + +public: + enum EColumn + { + COL_NAME = 0, + COL_STATUS, + COL_SIZE, + COL_FILETIME, + COL_MODNAME, + COL_VERSION, + COL_ID, + COL_SOURCEGAME, + + // number of columns + COL_COUNT + }; + +public: + explicit DownloadList(OrganizerCore& core, QObject* parent = 0); + + /** + * @brief retrieve the number of rows to display. Invoked by Qt + * + * @param parent not relevant for this implementation + * @return number of rows to display + **/ + virtual int rowCount(const QModelIndex& parent = QModelIndex()) const; + virtual int columnCount(const QModelIndex& parent) const; + + QModelIndex index(int row, int column, const QModelIndex& parent) const; + QModelIndex parent(const QModelIndex& child) const; + Qt::ItemFlags flags(const QModelIndex& idx) const override; + QMimeData* mimeData(const QModelIndexList& indexes) const override; + + virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const; + + /** + * @brief retrieve the data to display in a specific row. Invoked by Qt + * + * @param index location to look up + * @param role ... Defaults to Qt::DisplayRole. + * @return this implementation only returns the row, the QItemDelegate implementation + *is expected to fetch its information from the DownloadManager + **/ + virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; + + // used in DownloadsTab as the sorting predicate for the filter widget + // + bool lessThanPredicate(const QModelIndex& left, const QModelIndex& right); + +public slots: + + /** + * @brief used to inform the model that data has changed + * + * @param row the row that changed. This can be negative to update the whole view + **/ + void update(int row); + + void aboutToUpdate(); + +private: + DownloadManager& m_manager; + Settings& m_settings; +}; + +#endif // DOWNLOADLIST_H diff --git a/src/downloadlistview.cpp b/src/downloadlistview.cpp index 239933bd3..a895d2e21 100644 --- a/src/downloadlistview.cpp +++ b/src/downloadlistview.cpp @@ -1,447 +1,502 @@ -/* -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 "downloadlist.h" -#include "downloadlistview.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace MOBase; - -DownloadProgressDelegate::DownloadProgressDelegate( - DownloadManager* manager, DownloadListView* list) - : QStyledItemDelegate(list), m_Manager(manager), m_List(list) -{ -} - -void DownloadProgressDelegate::paint( - QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const -{ - QModelIndex sourceIndex; - - if (auto* proxy=dynamic_cast(m_List->model())) { - sourceIndex = proxy->mapToSource(index); - } else { - sourceIndex = index; - } - - bool pendingDownload = (sourceIndex.row() >= m_Manager->numTotalDownloads()); - if (sourceIndex.column() == DownloadList::COL_STATUS && !pendingDownload - && m_Manager->getState(sourceIndex.row()) == DownloadManager::STATE_DOWNLOADING) { - QProgressBar progressBar; - progressBar.setProperty("downloadView", option.widget->property("downloadView")); - progressBar.setProperty("downloadProgress", true); - progressBar.resize(option.rect.width(), option.rect.height()); - progressBar.setTextVisible(true); - progressBar.setAlignment(Qt::AlignCenter); - progressBar.setMinimum(0); - progressBar.setMaximum(100); - progressBar.setValue(m_Manager->getProgress(sourceIndex.row()).first); - progressBar.setFormat(m_Manager->getProgress(sourceIndex.row()).second); - progressBar.setStyle(QApplication::style()); - - // paint the background with default delegate first to preserve table cell styling - QStyledItemDelegate::paint(painter, option, index); - - painter->save(); - painter->translate(option.rect.topLeft()); - progressBar.render(painter); - painter->restore(); - } else { - QStyledItemDelegate::paint(painter, option, index); - } -} - -void DownloadListHeader::customResizeSections() -{ - // find the rightmost column that is not hidden - int rightVisible = count() - 1; - while (isSectionHidden(rightVisible) && rightVisible > 0) - rightVisible--; - - // if that column is already squashed, squash others to the right side -- - // otherwise to the left - if (sectionSize(rightVisible) == minimumSectionSize()) { - for (int idx = rightVisible; idx >= 0; idx--) { - if (!isSectionHidden(idx)) { - if (length() != width()) - resizeSection(idx, std::max(sectionSize(idx) + width() - length(), minimumSectionSize())); - else - break; - } - } - } else { - for (int idx = 0; idx <= rightVisible; idx++) { - if (!isSectionHidden(idx)) { - if (length() != width()) - resizeSection(idx, std::max(sectionSize(idx) + width() - length(), minimumSectionSize())); - else - break; - } - } - } -} - -void DownloadListHeader::mouseReleaseEvent(QMouseEvent *event) -{ - QHeaderView::mouseReleaseEvent(event); - customResizeSections(); -} - -DownloadListView::DownloadListView(QWidget *parent) - : QTreeView(parent) -{ - setHeader(new DownloadListHeader(Qt::Horizontal, this)); - - header()->setDefaultAlignment(Qt::AlignLeft | Qt::AlignVCenter); - header()->setSectionsMovable(true); - header()->setContextMenuPolicy(Qt::CustomContextMenu); - header()->setCascadingSectionResizes(true); - header()->setStretchLastSection(false); - header()->setSectionResizeMode(QHeaderView::Interactive); - header()->setDefaultSectionSize(100); - - setUniformRowHeights(true); - setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - sortByColumn(1, Qt::DescendingOrder); - - connect(header(), SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(onHeaderCustomContextMenu(QPoint))); - connect(this, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(onDoubleClick(QModelIndex))); - connect(this, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(onCustomContextMenu(QPoint))); -} - -DownloadListView::~DownloadListView() -{ -} - -void DownloadListView::setManager(DownloadManager *manager) -{ - m_Manager = manager; - - // hide these columns by default - // - // note that this is overridden by the ini if MO has been started at least - // once before, which is handled in MainWindow::processUpdates() for older - // versions - header()->hideSection(DownloadList::COL_MODNAME); - header()->hideSection(DownloadList::COL_VERSION); - header()->hideSection(DownloadList::COL_ID); - header()->hideSection(DownloadList::COL_SOURCEGAME); -} - -void DownloadListView::setSourceModel(DownloadList *sourceModel) -{ - m_SourceModel = sourceModel; -} - -void DownloadListView::onDoubleClick(const QModelIndex &index) -{ - QModelIndex sourceIndex = qobject_cast(model())->mapToSource(index); - if (m_Manager->getState(sourceIndex.row()) >= DownloadManager::STATE_READY) - emit installDownload(sourceIndex.row()); - else if ((m_Manager->getState(sourceIndex.row()) == DownloadManager::STATE_PAUSED) - || (m_Manager->getState(sourceIndex.row()) == DownloadManager::STATE_PAUSING)) - emit resumeDownload(sourceIndex.row()); -} - -void DownloadListView::onHeaderCustomContextMenu(const QPoint &point) -{ - QMenu menu; - - // display a list of all headers as checkboxes - QAbstractItemModel *model = header()->model(); - for (int i = 1; i < model->columnCount(); ++i) { - QString columnName = model->headerData(i, Qt::Horizontal).toString(); - QCheckBox *checkBox = new QCheckBox(&menu); - checkBox->setText(columnName); - checkBox->setChecked(!header()->isSectionHidden(i)); - QWidgetAction *checkableAction = new QWidgetAction(&menu); - checkableAction->setDefaultWidget(checkBox); - menu.addAction(checkableAction); - } - - menu.exec(header()->viewport()->mapToGlobal(point)); - - // view/hide columns depending on check-state - int i = 1; - for (const QAction *action : menu.actions()) { - const QWidgetAction *widgetAction = qobject_cast(action); - if (widgetAction != nullptr) { - const QCheckBox *checkBox = qobject_cast(widgetAction->defaultWidget()); - if (checkBox != nullptr) { - header()->setSectionHidden(i, !checkBox->isChecked()); - } - } - ++i; - } - - qobject_cast(header())->customResizeSections(); -} - -void DownloadListView::resizeEvent(QResizeEvent *event) -{ - QTreeView::resizeEvent(event); - qobject_cast(header())->customResizeSections(); -} - -void DownloadListView::onCustomContextMenu(const QPoint &point) -{ - QMenu menu(this); - QModelIndex index = indexAt(point); - bool hidden = false; - - try { - if (index.row() >= 0) { - const int row = qobject_cast(model())->mapToSource(index).row(); - DownloadManager::DownloadState state = m_Manager->getState(row); - - hidden = m_Manager->isHidden(row); - - if (state >= DownloadManager::STATE_READY) { - menu.addAction(tr("Install"), [=] { issueInstall(row); }); - if (m_Manager->isInfoIncomplete(row)) - menu.addAction(tr("Query Info"), [=] { issueQueryInfoMd5(row); }); - else - menu.addAction(tr("Visit on Nexus"), [=] { issueVisitOnNexus(row); }); - menu.addAction(tr("Open File"), [=] { issueOpenFile(row); }); - menu.addAction(tr("Open Meta File"), [=] { issueOpenMetaFile(row); }); - menu.addAction(tr("Reveal in Explorer"), [=] { issueOpenInDownloadsFolder(row); }); - - menu.addSeparator(); - - menu.addAction(tr("Delete..."), [=] { issueDelete(row); }); - if (hidden) - menu.addAction(tr("Un-Hide"), [=] { issueRestoreToView(row); }); - else - menu.addAction(tr("Hide"), [=] { issueRemoveFromView(row); }); - } else if (state == DownloadManager::STATE_DOWNLOADING) { - menu.addAction(tr("Cancel"), [=] { issueCancel(row); }); - menu.addAction(tr("Pause"), [=] { issuePause(row); }); - menu.addAction(tr("Reveal in Explorer"), [=] { issueOpenInDownloadsFolder(row); }); - } - else if ((state == DownloadManager::STATE_PAUSED) || (state == DownloadManager::STATE_ERROR) - || (state == DownloadManager::STATE_PAUSING)) { - menu.addAction(tr("Delete..."), [=] { issueDelete(row); }); - menu.addAction(tr("Resume"), [=] { issueResume(row); }); - menu.addAction(tr("Reveal in Explorer"), [=] { issueOpenInDownloadsFolder(row); }); - } - - menu.addSeparator(); - } - } - catch(std::exception&) { - // this happens when the download index is not found, ignore it and don't - // display download-specific actions - } - - menu.addAction(tr("Delete Installed Downloads..."), [=] { issueDeleteCompleted(); }); - menu.addAction(tr("Delete Uninstalled Downloads..."), [=] { issueDeleteUninstalled(); }); - menu.addAction(tr("Delete All Downloads..."), [=] { issueDeleteAll(); }); - - menu.addSeparator(); - if (!hidden) { - menu.addAction(tr("Hide Installed..."), [=] { issueRemoveFromViewCompleted(); }); - menu.addAction(tr("Hide Uninstalled..."), [=] { issueRemoveFromViewUninstalled(); }); - menu.addAction(tr("Hide All..."), [=] { issueRemoveFromViewAll(); }); - } else { - menu.addAction(tr("Un-Hide All..."), [=] { issueRestoreToViewAll(); } ); - } - - menu.exec(viewport()->mapToGlobal(point)); -} - -void DownloadListView::keyPressEvent(QKeyEvent* event) -{ - if (selectionModel()->hasSelection()) { - const int row = qobject_cast(model())->mapToSource(currentIndex()).row(); - auto state = m_Manager->getState(row); - if (state >= DownloadManager::STATE_READY) { - if (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return) { - issueInstall(row); - } - else if (event->key() == Qt::Key_Delete) { - issueDelete(row); - } - } - else if (state == DownloadManager::STATE_DOWNLOADING) { - if (event->key() == Qt::Key_Delete) { - issueCancel(row); - } - else if (event->key() == Qt::Key_Space) { - issuePause(event->key()); - } - } - else if (state == DownloadManager::STATE_PAUSED - || state == DownloadManager::STATE_ERROR - || state == DownloadManager::STATE_PAUSING) { - if (event->key() == Qt::Key_Delete) { - issueDelete(row); - } - else if (event->key() == Qt::Key_Space) { - issueResume(row); - } - } - } - QTreeView::keyPressEvent(event); -} - -void DownloadListView::issueInstall(int index) -{ - emit installDownload(index); -} - -void DownloadListView::issueQueryInfo(int index) -{ - emit queryInfo(index); -} - -void DownloadListView::issueQueryInfoMd5(int index) -{ - emit queryInfoMd5(index); -} - -void DownloadListView::issueDelete(int index) -{ - const auto r = MOBase::TaskDialog(this, tr("Delete download")) - .main("Are you sure you want to delete this download?") - .content(m_Manager->getFilePath(index)) - .icon(QMessageBox::Question) - .button({tr("Move to the Recycle Bin"), QMessageBox::Yes}) - .button({tr("Cancel"), QMessageBox::Cancel}) - .exec(); - - if (r != QMessageBox::Yes) { - return; - } - - emit removeDownload(index, true); -} - -void DownloadListView::issueRemoveFromView(int index) -{ - log::debug("removing from view: {}", index); - emit removeDownload(index, false); -} - -void DownloadListView::issueRestoreToView(int index) -{ - emit restoreDownload(index); -} - -void DownloadListView::issueRestoreToViewAll() -{ - emit restoreDownload(-1); -} - -void DownloadListView::issueVisitOnNexus(int index) -{ - emit visitOnNexus(index); -} - -void DownloadListView::issueOpenFile(int index) -{ - emit openFile(index); -} - -void DownloadListView::issueOpenMetaFile(int index) { - emit openMetaFile(index); -} - -void DownloadListView::issueOpenInDownloadsFolder(int index) -{ - emit openInDownloadsFolder(index); -} - -void DownloadListView::issueCancel(int index) -{ - emit cancelDownload(index); -} - -void DownloadListView::issuePause(int index) -{ - emit pauseDownload(index); -} - -void DownloadListView::issueResume(int index) -{ - emit resumeDownload(index); -} - -void DownloadListView::issueDeleteAll() -{ - if (QMessageBox::warning(nullptr, tr("Delete Files?"), - tr("This will remove all finished downloads from this list and from disk.\n\nAre you absolutely sure you want to proceed?"), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - emit removeDownload(-1, true); - } -} - -void DownloadListView::issueDeleteCompleted() -{ - if (QMessageBox::warning(nullptr, tr("Delete Files?"), - tr("This will remove all installed downloads from this list and from disk.\n\nAre you absolutely sure you want to proceed?"), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - emit removeDownload(-2, true); - } -} - -void DownloadListView::issueDeleteUninstalled() -{ - if (QMessageBox::warning(nullptr, tr("Delete Files?"), - tr("This will remove all uninstalled downloads from this list and from disk.\n\nAre you absolutely sure you want to proceed?"), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - emit removeDownload(-3, true); - } -} - -void DownloadListView::issueRemoveFromViewAll() -{ - if (QMessageBox::question(nullptr, tr("Hide Files?"), - tr("This will remove all finished downloads from this list (but NOT from disk)."), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - emit removeDownload(-1, false); - } -} - -void DownloadListView::issueRemoveFromViewCompleted() -{ - if (QMessageBox::question(nullptr, tr("Hide Files?"), - tr("This will remove all installed downloads from this list (but NOT from disk)."), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - emit removeDownload(-2, false); - } -} - -void DownloadListView::issueRemoveFromViewUninstalled() -{ - if (QMessageBox::question(nullptr, tr("Hide Files?"), - tr("This will remove all uninstalled downloads from this list (but NOT from disk)."), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - emit removeDownload(-3, false); - } -} +/* +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 "downloadlistview.h" +#include "downloadlist.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace MOBase; + +DownloadProgressDelegate::DownloadProgressDelegate(DownloadManager* manager, + DownloadListView* list) + : QStyledItemDelegate(list), m_Manager(manager), m_List(list) +{} + +void DownloadProgressDelegate::paint(QPainter* painter, + const QStyleOptionViewItem& option, + const QModelIndex& index) const +{ + QModelIndex sourceIndex; + + if (auto* proxy = dynamic_cast(m_List->model())) { + sourceIndex = proxy->mapToSource(index); + } else { + sourceIndex = index; + } + + bool pendingDownload = (sourceIndex.row() >= m_Manager->numTotalDownloads()); + if (sourceIndex.column() == DownloadList::COL_STATUS && !pendingDownload && + m_Manager->getState(sourceIndex.row()) == DownloadManager::STATE_DOWNLOADING) { + QProgressBar progressBar; + progressBar.setProperty("downloadView", option.widget->property("downloadView")); + progressBar.setProperty("downloadProgress", true); + progressBar.resize(option.rect.width(), option.rect.height()); + progressBar.setTextVisible(true); + progressBar.setAlignment(Qt::AlignCenter); + progressBar.setMinimum(0); + progressBar.setMaximum(100); + progressBar.setValue(m_Manager->getProgress(sourceIndex.row()).first); + progressBar.setFormat(m_Manager->getProgress(sourceIndex.row()).second); + progressBar.setStyle(QApplication::style()); + + // paint the background with default delegate first to preserve table cell styling + QStyledItemDelegate::paint(painter, option, index); + + painter->save(); + painter->translate(option.rect.topLeft()); + progressBar.render(painter); + painter->restore(); + } else { + QStyledItemDelegate::paint(painter, option, index); + } +} + +void DownloadListHeader::customResizeSections() +{ + // find the rightmost column that is not hidden + int rightVisible = count() - 1; + while (isSectionHidden(rightVisible) && rightVisible > 0) + rightVisible--; + + // if that column is already squashed, squash others to the right side -- + // otherwise to the left + if (sectionSize(rightVisible) == minimumSectionSize()) { + for (int idx = rightVisible; idx >= 0; idx--) { + if (!isSectionHidden(idx)) { + if (length() != width()) + resizeSection(idx, std::max(sectionSize(idx) + width() - length(), + minimumSectionSize())); + else + break; + } + } + } else { + for (int idx = 0; idx <= rightVisible; idx++) { + if (!isSectionHidden(idx)) { + if (length() != width()) + resizeSection(idx, std::max(sectionSize(idx) + width() - length(), + minimumSectionSize())); + else + break; + } + } + } +} + +void DownloadListHeader::mouseReleaseEvent(QMouseEvent* event) +{ + QHeaderView::mouseReleaseEvent(event); + customResizeSections(); +} + +DownloadListView::DownloadListView(QWidget* parent) : QTreeView(parent) +{ + setHeader(new DownloadListHeader(Qt::Horizontal, this)); + + header()->setDefaultAlignment(Qt::AlignLeft | Qt::AlignVCenter); + header()->setSectionsMovable(true); + header()->setContextMenuPolicy(Qt::CustomContextMenu); + header()->setCascadingSectionResizes(true); + header()->setStretchLastSection(false); + header()->setSectionResizeMode(QHeaderView::Interactive); + header()->setDefaultSectionSize(100); + + setUniformRowHeights(true); + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + sortByColumn(1, Qt::DescendingOrder); + + connect(header(), SIGNAL(customContextMenuRequested(QPoint)), this, + SLOT(onHeaderCustomContextMenu(QPoint))); + connect(this, SIGNAL(doubleClicked(QModelIndex)), this, + SLOT(onDoubleClick(QModelIndex))); + connect(this, SIGNAL(customContextMenuRequested(QPoint)), this, + SLOT(onCustomContextMenu(QPoint))); +} + +DownloadListView::~DownloadListView() {} + +void DownloadListView::setManager(DownloadManager* manager) +{ + m_Manager = manager; + + // hide these columns by default + // + // note that this is overridden by the ini if MO has been started at least + // once before, which is handled in MainWindow::processUpdates() for older + // versions + header()->hideSection(DownloadList::COL_MODNAME); + header()->hideSection(DownloadList::COL_VERSION); + header()->hideSection(DownloadList::COL_ID); + header()->hideSection(DownloadList::COL_SOURCEGAME); +} + +void DownloadListView::setSourceModel(DownloadList* sourceModel) +{ + m_SourceModel = sourceModel; +} + +void DownloadListView::onDoubleClick(const QModelIndex& index) +{ + QModelIndex sourceIndex = + qobject_cast(model())->mapToSource(index); + if (m_Manager->getState(sourceIndex.row()) >= DownloadManager::STATE_READY) + emit installDownload(sourceIndex.row()); + else if ((m_Manager->getState(sourceIndex.row()) == DownloadManager::STATE_PAUSED) || + (m_Manager->getState(sourceIndex.row()) == DownloadManager::STATE_PAUSING)) + emit resumeDownload(sourceIndex.row()); +} + +void DownloadListView::onHeaderCustomContextMenu(const QPoint& point) +{ + QMenu menu; + + // display a list of all headers as checkboxes + QAbstractItemModel* model = header()->model(); + for (int i = 1; i < model->columnCount(); ++i) { + QString columnName = model->headerData(i, Qt::Horizontal).toString(); + QCheckBox* checkBox = new QCheckBox(&menu); + checkBox->setText(columnName); + checkBox->setChecked(!header()->isSectionHidden(i)); + QWidgetAction* checkableAction = new QWidgetAction(&menu); + checkableAction->setDefaultWidget(checkBox); + menu.addAction(checkableAction); + } + + menu.exec(header()->viewport()->mapToGlobal(point)); + + // view/hide columns depending on check-state + int i = 1; + for (const QAction* action : menu.actions()) { + const QWidgetAction* widgetAction = qobject_cast(action); + if (widgetAction != nullptr) { + const QCheckBox* checkBox = + qobject_cast(widgetAction->defaultWidget()); + if (checkBox != nullptr) { + header()->setSectionHidden(i, !checkBox->isChecked()); + } + } + ++i; + } + + qobject_cast(header())->customResizeSections(); +} + +void DownloadListView::resizeEvent(QResizeEvent* event) +{ + QTreeView::resizeEvent(event); + qobject_cast(header())->customResizeSections(); +} + +void DownloadListView::onCustomContextMenu(const QPoint& point) +{ + QMenu menu(this); + QModelIndex index = indexAt(point); + bool hidden = false; + + try { + if (index.row() >= 0) { + const int row = + qobject_cast(model())->mapToSource(index).row(); + DownloadManager::DownloadState state = m_Manager->getState(row); + + hidden = m_Manager->isHidden(row); + + if (state >= DownloadManager::STATE_READY) { + menu.addAction(tr("Install"), [=] { + issueInstall(row); + }); + if (m_Manager->isInfoIncomplete(row)) + menu.addAction(tr("Query Info"), [=] { + issueQueryInfoMd5(row); + }); + else + menu.addAction(tr("Visit on Nexus"), [=] { + issueVisitOnNexus(row); + }); + menu.addAction(tr("Open File"), [=] { + issueOpenFile(row); + }); + menu.addAction(tr("Open Meta File"), [=] { + issueOpenMetaFile(row); + }); + menu.addAction(tr("Reveal in Explorer"), [=] { + issueOpenInDownloadsFolder(row); + }); + + menu.addSeparator(); + + menu.addAction(tr("Delete..."), [=] { + issueDelete(row); + }); + if (hidden) + menu.addAction(tr("Un-Hide"), [=] { + issueRestoreToView(row); + }); + else + menu.addAction(tr("Hide"), [=] { + issueRemoveFromView(row); + }); + } else if (state == DownloadManager::STATE_DOWNLOADING) { + menu.addAction(tr("Cancel"), [=] { + issueCancel(row); + }); + menu.addAction(tr("Pause"), [=] { + issuePause(row); + }); + menu.addAction(tr("Reveal in Explorer"), [=] { + issueOpenInDownloadsFolder(row); + }); + } else if ((state == DownloadManager::STATE_PAUSED) || + (state == DownloadManager::STATE_ERROR) || + (state == DownloadManager::STATE_PAUSING)) { + menu.addAction(tr("Delete..."), [=] { + issueDelete(row); + }); + menu.addAction(tr("Resume"), [=] { + issueResume(row); + }); + menu.addAction(tr("Reveal in Explorer"), [=] { + issueOpenInDownloadsFolder(row); + }); + } + + menu.addSeparator(); + } + } catch (std::exception&) { + // this happens when the download index is not found, ignore it and don't + // display download-specific actions + } + + menu.addAction(tr("Delete Installed Downloads..."), [=] { + issueDeleteCompleted(); + }); + menu.addAction(tr("Delete Uninstalled Downloads..."), [=] { + issueDeleteUninstalled(); + }); + menu.addAction(tr("Delete All Downloads..."), [=] { + issueDeleteAll(); + }); + + menu.addSeparator(); + if (!hidden) { + menu.addAction(tr("Hide Installed..."), [=] { + issueRemoveFromViewCompleted(); + }); + menu.addAction(tr("Hide Uninstalled..."), [=] { + issueRemoveFromViewUninstalled(); + }); + menu.addAction(tr("Hide All..."), [=] { + issueRemoveFromViewAll(); + }); + } else { + menu.addAction(tr("Un-Hide All..."), [=] { + issueRestoreToViewAll(); + }); + } + + menu.exec(viewport()->mapToGlobal(point)); +} + +void DownloadListView::keyPressEvent(QKeyEvent* event) +{ + if (selectionModel()->hasSelection()) { + const int row = qobject_cast(model()) + ->mapToSource(currentIndex()) + .row(); + auto state = m_Manager->getState(row); + if (state >= DownloadManager::STATE_READY) { + if (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return) { + issueInstall(row); + } else if (event->key() == Qt::Key_Delete) { + issueDelete(row); + } + } else if (state == DownloadManager::STATE_DOWNLOADING) { + if (event->key() == Qt::Key_Delete) { + issueCancel(row); + } else if (event->key() == Qt::Key_Space) { + issuePause(event->key()); + } + } else if (state == DownloadManager::STATE_PAUSED || + state == DownloadManager::STATE_ERROR || + state == DownloadManager::STATE_PAUSING) { + if (event->key() == Qt::Key_Delete) { + issueDelete(row); + } else if (event->key() == Qt::Key_Space) { + issueResume(row); + } + } + } + QTreeView::keyPressEvent(event); +} + +void DownloadListView::issueInstall(int index) +{ + emit installDownload(index); +} + +void DownloadListView::issueQueryInfo(int index) +{ + emit queryInfo(index); +} + +void DownloadListView::issueQueryInfoMd5(int index) +{ + emit queryInfoMd5(index); +} + +void DownloadListView::issueDelete(int index) +{ + const auto r = MOBase::TaskDialog(this, tr("Delete download")) + .main("Are you sure you want to delete this download?") + .content(m_Manager->getFilePath(index)) + .icon(QMessageBox::Question) + .button({tr("Move to the Recycle Bin"), QMessageBox::Yes}) + .button({tr("Cancel"), QMessageBox::Cancel}) + .exec(); + + if (r != QMessageBox::Yes) { + return; + } + + emit removeDownload(index, true); +} + +void DownloadListView::issueRemoveFromView(int index) +{ + log::debug("removing from view: {}", index); + emit removeDownload(index, false); +} + +void DownloadListView::issueRestoreToView(int index) +{ + emit restoreDownload(index); +} + +void DownloadListView::issueRestoreToViewAll() +{ + emit restoreDownload(-1); +} + +void DownloadListView::issueVisitOnNexus(int index) +{ + emit visitOnNexus(index); +} + +void DownloadListView::issueOpenFile(int index) +{ + emit openFile(index); +} + +void DownloadListView::issueOpenMetaFile(int index) +{ + emit openMetaFile(index); +} + +void DownloadListView::issueOpenInDownloadsFolder(int index) +{ + emit openInDownloadsFolder(index); +} + +void DownloadListView::issueCancel(int index) +{ + emit cancelDownload(index); +} + +void DownloadListView::issuePause(int index) +{ + emit pauseDownload(index); +} + +void DownloadListView::issueResume(int index) +{ + emit resumeDownload(index); +} + +void DownloadListView::issueDeleteAll() +{ + if (QMessageBox::warning( + nullptr, tr("Delete Files?"), + tr("This will remove all finished downloads from this list and from " + "disk.\n\nAre you absolutely sure you want to proceed?"), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + emit removeDownload(-1, true); + } +} + +void DownloadListView::issueDeleteCompleted() +{ + if (QMessageBox::warning( + nullptr, tr("Delete Files?"), + tr("This will remove all installed downloads from this list and from " + "disk.\n\nAre you absolutely sure you want to proceed?"), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + emit removeDownload(-2, true); + } +} + +void DownloadListView::issueDeleteUninstalled() +{ + if (QMessageBox::warning( + nullptr, tr("Delete Files?"), + tr("This will remove all uninstalled downloads from this list and from " + "disk.\n\nAre you absolutely sure you want to proceed?"), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + emit removeDownload(-3, true); + } +} + +void DownloadListView::issueRemoveFromViewAll() +{ + if (QMessageBox::question(nullptr, tr("Hide Files?"), + tr("This will remove all finished downloads from this list " + "(but NOT from disk)."), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + emit removeDownload(-1, false); + } +} + +void DownloadListView::issueRemoveFromViewCompleted() +{ + if (QMessageBox::question(nullptr, tr("Hide Files?"), + tr("This will remove all installed downloads from this " + "list (but NOT from disk)."), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + emit removeDownload(-2, false); + } +} + +void DownloadListView::issueRemoveFromViewUninstalled() +{ + if (QMessageBox::question(nullptr, tr("Hide Files?"), + tr("This will remove all uninstalled downloads from this " + "list (but NOT from disk)."), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + emit removeDownload(-3, false); + } +} diff --git a/src/downloadlistview.h b/src/downloadlistview.h index 247a71cc9..65d988494 100644 --- a/src/downloadlistview.h +++ b/src/downloadlistview.h @@ -1,128 +1,130 @@ -/* -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 DOWNLOADLISTWIDGET_H -#define DOWNLOADLISTWIDGET_H - -#include "downloadmanager.h" -#include "downloadlist.h" -#include -#include -#include -#include -#include -#include -#include - - -namespace Ui { - class DownloadListView; -} - -class DownloadListView; - -class DownloadProgressDelegate : public QStyledItemDelegate -{ - Q_OBJECT - -public: - DownloadProgressDelegate(DownloadManager* manager, DownloadListView* list); - - void paint(QPainter *painter, const QStyleOptionViewItem &option, - const QModelIndex &index) const override; - -private: - DownloadManager* m_Manager; - DownloadListView* m_List; -}; - -class DownloadListHeader : public QHeaderView -{ - Q_OBJECT - -public: - explicit DownloadListHeader(Qt::Orientation orientation, QWidget *parent = nullptr) : QHeaderView(orientation, parent) {} - void customResizeSections(); - -private: - void mouseReleaseEvent(QMouseEvent *event) override; -}; - -class DownloadListView : public QTreeView -{ - Q_OBJECT - -public: - explicit DownloadListView(QWidget *parent = 0); - ~DownloadListView(); - - void setManager(DownloadManager *manager); - void setSourceModel(DownloadList *sourceModel); - -signals: - void installDownload(int index); - void queryInfo(int index); - void queryInfoMd5(int index); - void removeDownload(int index, bool deleteFile); - void restoreDownload(int index); - void cancelDownload(int index); - void pauseDownload(int index); - void resumeDownload(int index); - void visitOnNexus(int index); - void openFile(int index); - void openMetaFile(int index); - void openInDownloadsFolder(int index); - -protected: - void keyPressEvent(QKeyEvent* event) override; - -private slots: - void onDoubleClick(const QModelIndex& index); - void onCustomContextMenu(const QPoint& point); - void onHeaderCustomContextMenu(const QPoint& point); - - void issueInstall(int index); - void issueDelete(int index); - void issueRemoveFromView(int index); - void issueRestoreToView(int index); - void issueRestoreToViewAll(); - void issueVisitOnNexus(int index); - void issueOpenFile(int index); - void issueOpenMetaFile(int index); - void issueOpenInDownloadsFolder(int index); - void issueCancel(int index); - void issuePause(int index); - void issueResume(int index); - void issueDeleteAll(); - void issueDeleteCompleted(); - void issueDeleteUninstalled(); - void issueRemoveFromViewAll(); - void issueRemoveFromViewCompleted(); - void issueRemoveFromViewUninstalled(); - void issueQueryInfo(int index); - void issueQueryInfoMd5(int index); - -private: - DownloadManager *m_Manager; - DownloadList *m_SourceModel = 0; - - void resizeEvent(QResizeEvent *event); -}; - -#endif // DOWNLOADLISTWIDGET_H +/* +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 DOWNLOADLISTWIDGET_H +#define DOWNLOADLISTWIDGET_H + +#include "downloadlist.h" +#include "downloadmanager.h" +#include +#include +#include +#include +#include +#include +#include + +namespace Ui +{ +class DownloadListView; +} + +class DownloadListView; + +class DownloadProgressDelegate : public QStyledItemDelegate +{ + Q_OBJECT + +public: + DownloadProgressDelegate(DownloadManager* manager, DownloadListView* list); + + void paint(QPainter* painter, const QStyleOptionViewItem& option, + const QModelIndex& index) const override; + +private: + DownloadManager* m_Manager; + DownloadListView* m_List; +}; + +class DownloadListHeader : public QHeaderView +{ + Q_OBJECT + +public: + explicit DownloadListHeader(Qt::Orientation orientation, QWidget* parent = nullptr) + : QHeaderView(orientation, parent) + {} + void customResizeSections(); + +private: + void mouseReleaseEvent(QMouseEvent* event) override; +}; + +class DownloadListView : public QTreeView +{ + Q_OBJECT + +public: + explicit DownloadListView(QWidget* parent = 0); + ~DownloadListView(); + + void setManager(DownloadManager* manager); + void setSourceModel(DownloadList* sourceModel); + +signals: + void installDownload(int index); + void queryInfo(int index); + void queryInfoMd5(int index); + void removeDownload(int index, bool deleteFile); + void restoreDownload(int index); + void cancelDownload(int index); + void pauseDownload(int index); + void resumeDownload(int index); + void visitOnNexus(int index); + void openFile(int index); + void openMetaFile(int index); + void openInDownloadsFolder(int index); + +protected: + void keyPressEvent(QKeyEvent* event) override; + +private slots: + void onDoubleClick(const QModelIndex& index); + void onCustomContextMenu(const QPoint& point); + void onHeaderCustomContextMenu(const QPoint& point); + + void issueInstall(int index); + void issueDelete(int index); + void issueRemoveFromView(int index); + void issueRestoreToView(int index); + void issueRestoreToViewAll(); + void issueVisitOnNexus(int index); + void issueOpenFile(int index); + void issueOpenMetaFile(int index); + void issueOpenInDownloadsFolder(int index); + void issueCancel(int index); + void issuePause(int index); + void issueResume(int index); + void issueDeleteAll(); + void issueDeleteCompleted(); + void issueDeleteUninstalled(); + void issueRemoveFromViewAll(); + void issueRemoveFromViewCompleted(); + void issueRemoveFromViewUninstalled(); + void issueQueryInfo(int index); + void issueQueryInfoMd5(int index); + +private: + DownloadManager* m_Manager; + DownloadList* m_SourceModel = 0; + + void resizeEvent(QResizeEvent* event); +}; + +#endif // DOWNLOADLISTWIDGET_H diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index 11bfa31c2..e00ddf6fc 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -19,76 +19,79 @@ along with Mod Organizer. If not, see . #include "downloadmanager.h" -#include "organizercore.h" -#include "nxmurl.h" +#include "bbcode.h" +#include "envfs.h" +#include "filesystemutilities.h" +#include "iplugingame.h" #include "nexusinterface.h" #include "nxmaccessmanager.h" -#include "iplugingame.h" -#include "envfs.h" -#include -#include -#include "utility.h" +#include "nxmurl.h" +#include "organizercore.h" #include "selectiondialog.h" -#include "bbcode.h" #include "shared/util.h" -#include +#include "utility.h" +#include #include -#include "filesystemutilities.h" +#include +#include -#include -#include -#include +#include #include +#include +#include +#include #include #include -#include #include -#include +#include #include #include - using namespace MOBase; - -// TODO limit number of downloads, also display download during nxm requests, store modid/fileid with downloads - +// TODO limit number of downloads, also display download during nxm requests, store +// modid/fileid with downloads static const char UNFINISHED[] = ".unfinished"; unsigned int DownloadManager::DownloadInfo::s_NextDownloadID = 1U; -int DownloadManager::m_DirWatcherDisabler = 0; +int DownloadManager::m_DirWatcherDisabler = 0; - -DownloadManager::DownloadInfo *DownloadManager::DownloadInfo::createNew(const ModRepositoryFileInfo *fileInfo, const QStringList &URLs) +DownloadManager::DownloadInfo* +DownloadManager::DownloadInfo::createNew(const ModRepositoryFileInfo* fileInfo, + const QStringList& URLs) { - DownloadInfo *info = new DownloadInfo; + DownloadInfo* info = new DownloadInfo; info->m_DownloadID = s_NextDownloadID++; info->m_StartTime.start(); - info->m_PreResumeSize = 0LL; - info->m_Progress = std::make_pair(0, "0.0 B/s "); - info->m_ResumePos = 0; - info->m_FileInfo = new ModRepositoryFileInfo(*fileInfo); - info->m_Urls = URLs; - info->m_CurrentUrl = 0; - info->m_Tries = AUTOMATIC_RETRIES; - info->m_State = STATE_STARTED; + info->m_PreResumeSize = 0LL; + info->m_Progress = std::make_pair(0, "0.0 B/s "); + info->m_ResumePos = 0; + info->m_FileInfo = new ModRepositoryFileInfo(*fileInfo); + info->m_Urls = URLs; + info->m_CurrentUrl = 0; + info->m_Tries = AUTOMATIC_RETRIES; + info->m_State = STATE_STARTED; info->m_TaskProgressId = TaskProgressManager::instance().getId(); - info->m_Reply = nullptr; + info->m_Reply = nullptr; return info; } -DownloadManager::DownloadInfo *DownloadManager::DownloadInfo::createFromMeta( - const QString &filePath, bool showHidden, const QString outputDirectory, - std::optional fileSize) +DownloadManager::DownloadInfo* +DownloadManager::DownloadInfo::createFromMeta(const QString& filePath, bool showHidden, + const QString outputDirectory, + std::optional fileSize) { - DownloadInfo *info = new DownloadInfo; + DownloadInfo* info = new DownloadInfo; QString metaFileName = filePath + ".meta"; QFileInfo metaFileInfo(metaFileName); - if (QDir::fromNativeSeparators(metaFileInfo.path()).compare(QDir::fromNativeSeparators(outputDirectory), Qt::CaseInsensitive) != 0) return nullptr; + if (QDir::fromNativeSeparators(metaFileInfo.path()) + .compare(QDir::fromNativeSeparators(outputDirectory), Qt::CaseInsensitive) != + 0) + return nullptr; QSettings metaFile(metaFileName, QSettings::IniFormat); if (!showHidden && metaFile.value("removed", false).toBool()) { return nullptr; @@ -99,8 +102,8 @@ DownloadManager::DownloadInfo *DownloadManager::DownloadInfo::createFromMeta( QString fileName = QFileInfo(filePath).fileName(); if (fileName.endsWith(UNFINISHED)) { - info->m_FileName = fileName.mid( - 0, fileName.length() - static_cast(strlen(UNFINISHED))); + info->m_FileName = + fileName.mid(0, fileName.length() - static_cast(strlen(UNFINISHED))); info->m_State = STATE_PAUSED; } else { info->m_FileName = fileName; @@ -118,33 +121,34 @@ DownloadManager::DownloadInfo *DownloadManager::DownloadInfo::createFromMeta( info->m_DownloadID = s_NextDownloadID++; info->m_Output.setFileName(filePath); - info->m_TotalSize = fileSize ? *fileSize : QFileInfo(filePath).size(); - info->m_PreResumeSize = info->m_TotalSize; - info->m_CurrentUrl = 0; - info->m_Urls = metaFile.value("url", "").toString().split(";"); - info->m_Tries = 0; + info->m_TotalSize = fileSize ? *fileSize : QFileInfo(filePath).size(); + info->m_PreResumeSize = info->m_TotalSize; + info->m_CurrentUrl = 0; + info->m_Urls = metaFile.value("url", "").toString().split(";"); + info->m_Tries = 0; info->m_TaskProgressId = TaskProgressManager::instance().getId(); - QString gameName = metaFile.value("gameName", "").toString(); - int modID = metaFile.value("modID", 0).toInt(); - int fileID = metaFile.value("fileID", 0).toInt(); - info->m_FileInfo = new ModRepositoryFileInfo(gameName, modID, fileID); - info->m_FileInfo->name = metaFile.value("name", "").toString(); + QString gameName = metaFile.value("gameName", "").toString(); + int modID = metaFile.value("modID", 0).toInt(); + int fileID = metaFile.value("fileID", 0).toInt(); + info->m_FileInfo = new ModRepositoryFileInfo(gameName, modID, fileID); + info->m_FileInfo->name = metaFile.value("name", "").toString(); if (info->m_FileInfo->name == "0") { // bug in earlier version info->m_FileInfo->name = ""; } - info->m_FileInfo->modName = metaFile.value("modName", "").toString(); - info->m_FileInfo->gameName = gameName; - info->m_FileInfo->modID = modID; - info->m_FileInfo->fileID = fileID; + info->m_FileInfo->modName = metaFile.value("modName", "").toString(); + info->m_FileInfo->gameName = gameName; + info->m_FileInfo->modID = modID; + info->m_FileInfo->fileID = fileID; info->m_FileInfo->description = metaFile.value("description").toString(); info->m_FileInfo->version.parse(metaFile.value("version", "0").toString()); - info->m_FileInfo->newestVersion.parse(metaFile.value("newestVersion", "0").toString()); - info->m_FileInfo->categoryID = metaFile.value("category", 0).toInt(); + info->m_FileInfo->newestVersion.parse( + metaFile.value("newestVersion", "0").toString()); + info->m_FileInfo->categoryID = metaFile.value("category", 0).toInt(); info->m_FileInfo->fileCategory = metaFile.value("fileCategory", 0).toInt(); - info->m_FileInfo->repository = metaFile.value("repository", "Nexus").toString(); - info->m_FileInfo->userData = metaFile.value("userData").toMap(); - info->m_Reply = nullptr; + info->m_FileInfo->repository = metaFile.value("repository", "Nexus").toString(); + info->m_FileInfo->userData = metaFile.value("userData").toMap(); + info->m_Reply = nullptr; return info; } @@ -154,16 +158,13 @@ void DownloadManager::startDisableDirWatcher() DownloadManager::m_DirWatcherDisabler++; } - void DownloadManager::endDisableDirWatcher() { - if (DownloadManager::m_DirWatcherDisabler > 0) - { + if (DownloadManager::m_DirWatcherDisabler > 0) { if (DownloadManager::m_DirWatcherDisabler == 1) QCoreApplication::processEvents(); DownloadManager::m_DirWatcherDisabler--; - } - else { + } else { DownloadManager::m_DirWatcherDisabler = 0; } } @@ -171,7 +172,7 @@ void DownloadManager::endDisableDirWatcher() void DownloadManager::DownloadInfo::setName(QString newName, bool renameFile) { QString oldMetaFileName = QString("%1.meta").arg(m_FileName); - m_FileName = QFileInfo(newName).fileName(); + m_FileName = QFileInfo(newName).fileName(); if ((m_State == DownloadManager::STATE_STARTED) || (m_State == DownloadManager::STATE_DOWNLOADING) || (m_State == DownloadManager::STATE_PAUSED)) { @@ -180,7 +181,9 @@ void DownloadManager::DownloadInfo::setName(QString newName, bool renameFile) } if (renameFile) { if ((newName != m_Output.fileName()) && !m_Output.rename(newName)) { - reportError(tr("failed to rename \"%1\" to \"%2\"").arg(m_Output.fileName()).arg(newName)); + reportError(tr("failed to rename \"%1\" to \"%2\"") + .arg(m_Output.fileName()) + .arg(newName)); return; } @@ -204,22 +207,22 @@ QString DownloadManager::DownloadInfo::currentURL() return m_Urls[m_CurrentUrl]; } - -DownloadManager::DownloadManager(NexusInterface *nexusInterface, QObject *parent) : - m_NexusInterface(nexusInterface), m_DirWatcher(), m_ShowHidden(false), - m_ParentWidget(nullptr) +DownloadManager::DownloadManager(NexusInterface* nexusInterface, QObject* parent) + : m_NexusInterface(nexusInterface), m_DirWatcher(), m_ShowHidden(false), + m_ParentWidget(nullptr) { m_OrganizerCore = dynamic_cast(parent); - connect(&m_DirWatcher, SIGNAL(directoryChanged(QString)), this, SLOT(directoryChanged(QString))); + connect(&m_DirWatcher, SIGNAL(directoryChanged(QString)), this, + SLOT(directoryChanged(QString))); m_TimeoutTimer.setSingleShot(false); - //connect(&m_TimeoutTimer, SIGNAL(timeout()), this, SLOT(checkDownloadTimeout())); + // connect(&m_TimeoutTimer, SIGNAL(timeout()), this, SLOT(checkDownloadTimeout())); m_TimeoutTimer.start(5 * 1000); } - DownloadManager::~DownloadManager() { - for (QVector::iterator iter = m_ActiveDownloads.begin(); iter != m_ActiveDownloads.end(); ++iter) { + for (QVector::iterator iter = m_ActiveDownloads.begin(); + iter != m_ActiveDownloads.end(); ++iter) { delete *iter; } m_ActiveDownloads.clear(); @@ -232,7 +235,8 @@ void DownloadManager::setParentWidget(QWidget* w) bool DownloadManager::downloadsInProgress() { - for (QVector::iterator iter = m_ActiveDownloads.begin(); iter != m_ActiveDownloads.end(); ++iter) { + for (QVector::iterator iter = m_ActiveDownloads.begin(); + iter != m_ActiveDownloads.end(); ++iter) { if ((*iter)->m_State < STATE_READY) { return true; } @@ -242,7 +246,8 @@ bool DownloadManager::downloadsInProgress() bool DownloadManager::downloadsInProgressNoPause() { - for (QVector::iterator iter = m_ActiveDownloads.begin(); iter != m_ActiveDownloads.end(); ++iter) { + for (QVector::iterator iter = m_ActiveDownloads.begin(); + iter != m_ActiveDownloads.end(); ++iter) { if ((*iter)->m_State < STATE_READY && (*iter)->m_State != STATE_PAUSED) { return true; } @@ -250,7 +255,6 @@ bool DownloadManager::downloadsInProgressNoPause() return false; } - void DownloadManager::pauseAll() { @@ -263,13 +267,13 @@ void DownloadManager::pauseAll() ::Sleep(100); - bool done = false; + bool done = false; QTime startTime = QTime::currentTime(); // further loops: busy waiting for all downloads to complete. This could be neater... while (!done && (startTime.secsTo(QTime::currentTime()) < 5)) { QCoreApplication::processEvents(); done = true; - foreach (DownloadInfo *info, m_ActiveDownloads) { + foreach (DownloadInfo* info, m_ActiveDownloads) { if ((info->m_State < STATE_CANCELED) || (info->m_State == STATE_FETCHINGFILEINFO) || (info->m_State == STATE_FETCHINGMODINFO) || @@ -282,11 +286,10 @@ void DownloadManager::pauseAll() ::Sleep(100); } } - } - -void DownloadManager::setOutputDirectory(const QString &outputDirectory, const bool refresh) +void DownloadManager::setOutputDirectory(const QString& outputDirectory, + const bool refresh) { QStringList directories = m_DirWatcher.directories(); if (directories.length() != 0) { @@ -305,7 +308,7 @@ void DownloadManager::setShowHidden(bool showHidden) refreshList(); } -void DownloadManager::setPluginContainer(PluginContainer *pluginContainer) +void DownloadManager::setPluginContainer(PluginContainer* pluginContainer) { m_NexusInterface->setPluginContainer(pluginContainer); } @@ -315,14 +318,16 @@ void DownloadManager::refreshList() TimeThis tt("DownloadManager::refreshList()"); try { - //avoid triggering other refreshes + // avoid triggering other refreshes startDisableDirWatcher(); int downloadsBefore = m_ActiveDownloads.size(); // remove finished downloads - for (QVector::iterator iter = m_ActiveDownloads.begin(); iter != m_ActiveDownloads.end();) { - if (((*iter)->m_State == STATE_READY) || ((*iter)->m_State == STATE_INSTALLED) || ((*iter)->m_State == STATE_UNINSTALLED)) { + for (QVector::iterator iter = m_ActiveDownloads.begin(); + iter != m_ActiveDownloads.end();) { + if (((*iter)->m_State == STATE_READY) || ((*iter)->m_State == STATE_INSTALLED) || + ((*iter)->m_State == STATE_UNINSTALLED)) { delete *iter; iter = m_ActiveDownloads.erase(iter); } else { @@ -330,7 +335,8 @@ void DownloadManager::refreshList() } } - const QStringList supportedExtensions = m_OrganizerCore->installationManager()->getSupportedExtensions(); + const QStringList supportedExtensions = + m_OrganizerCore->installationManager()->getSupportedExtensions(); std::vector nameFilters; for (const auto& extension : supportedExtensions) { nameFilters.push_back(L"." + extension.toLower().toStdWString()); @@ -338,13 +344,13 @@ void DownloadManager::refreshList() nameFilters.push_back(QString(UNFINISHED).toLower().toStdWString()); - QDir dir(QDir::fromNativeSeparators(m_OutputDirectory)); - // find orphaned meta files and delete them (sounds cruel but it's better for everyone) + // find orphaned meta files and delete them (sounds cruel but it's better for + // everyone) QStringList orphans; QStringList metaFiles = dir.entryList(QStringList() << "*.meta"); - foreach (const QString &metaFile, metaFiles) { + foreach (const QString& metaFile, metaFiles) { QString baseFile = metaFile.left(metaFile.length() - 5); if (!QFile::exists(dir.absoluteFilePath(baseFile))) { orphans.append(dir.absoluteFilePath(metaFile)); @@ -355,7 +361,6 @@ void DownloadManager::refreshList() shellDelete(orphans, true); } - std::set seen; struct Context @@ -369,54 +374,54 @@ void DownloadManager::refreshList() for (auto&& d : m_ActiveDownloads) { cx.seen.insert(d->m_FileName.toLower().toStdWString()); - cx.seen.insert(QFileInfo(d->m_Output.fileName()).fileName().toLower().toStdWString()); + cx.seen.insert( + QFileInfo(d->m_Output.fileName()).fileName().toLower().toStdWString()); } - env::forEachEntry( - QDir::toNativeSeparators(m_OutputDirectory).toStdWString(), &cx, nullptr, nullptr, - [](void* data, std::wstring_view f, FILETIME, uint64_t size) { - auto& cx = *static_cast(data); - - std::wstring lc = MOShared::ToLowerCopy(f); - - bool interestingExt = false; - for (auto&& ext : cx.extensions) { - if (lc.ends_with(ext)) { - interestingExt = true; - break; + QDir::toNativeSeparators(m_OutputDirectory).toStdWString(), &cx, nullptr, + nullptr, [](void* data, std::wstring_view f, FILETIME, uint64_t size) { + auto& cx = *static_cast(data); + + std::wstring lc = MOShared::ToLowerCopy(f); + + bool interestingExt = false; + for (auto&& ext : cx.extensions) { + if (lc.ends_with(ext)) { + interestingExt = true; + break; + } } - } - if (!interestingExt) { - return; - } + if (!interestingExt) { + return; + } - if (cx.seen.contains(lc)) { - return; - } + if (cx.seen.contains(lc)) { + return; + } - QString fileName = - QDir::fromNativeSeparators(cx.self.m_OutputDirectory) + "/" + - QString::fromWCharArray(f.data(), f.size()); + QString fileName = QDir::fromNativeSeparators(cx.self.m_OutputDirectory) + + "/" + QString::fromWCharArray(f.data(), f.size()); - DownloadInfo *info = DownloadInfo::createFromMeta( - fileName, cx.self.m_ShowHidden, cx.self.m_OutputDirectory, size); + DownloadInfo* info = DownloadInfo::createFromMeta( + fileName, cx.self.m_ShowHidden, cx.self.m_OutputDirectory, size); - if (info == nullptr) { - return; - } + if (info == nullptr) { + return; + } - cx.self.m_ActiveDownloads.push_front(info); - cx.seen.insert(std::move(lc)); - cx.seen.insert(QFileInfo(info->m_Output.fileName()).fileName().toLower().toStdWString()); - }); + cx.self.m_ActiveDownloads.push_front(info); + cx.seen.insert(std::move(lc)); + cx.seen.insert( + QFileInfo(info->m_Output.fileName()).fileName().toLower().toStdWString()); + }); log::debug("saw {} downloads", m_ActiveDownloads.size()); emit update(-1); - //let watcher trigger refreshes again + // let watcher trigger refreshes again endDisableDirWatcher(); } catch (const std::bad_alloc&) { @@ -424,9 +429,8 @@ void DownloadManager::refreshList() } } - -bool DownloadManager::addDownload(const QStringList &URLs, QString gameName, - int modID, int fileID, const ModRepositoryFileInfo *fileInfo) +bool DownloadManager::addDownload(const QStringList& URLs, QString gameName, int modID, + int fileID, const ModRepositoryFileInfo* fileInfo) { QString fileName = QFileInfo(URLs.first()).fileName(); if (fileName.isEmpty()) { @@ -436,31 +440,36 @@ bool DownloadManager::addDownload(const QStringList &URLs, QString gameName, QUrl preferredUrl = QUrl::fromEncoded(URLs.first().toLocal8Bit()); log::debug("selected download url: {}", preferredUrl.toString()); QHttp2Configuration h2Conf; - h2Conf.setSessionReceiveWindowSize(16777215); // 16 MiB, based on Chrome and Firefox values + h2Conf.setSessionReceiveWindowSize( + 16777215); // 16 MiB, based on Chrome and Firefox values h2Conf.setStreamReceiveWindowSize(16777215); QNetworkRequest request(preferredUrl); - request.setHeader(QNetworkRequest::UserAgentHeader, m_NexusInterface->getAccessManager()->userAgent()); + request.setHeader(QNetworkRequest::UserAgentHeader, + m_NexusInterface->getAccessManager()->userAgent()); request.setHttp2Configuration(h2Conf); - return addDownload(m_NexusInterface->getAccessManager()->get(request), URLs, fileName, gameName, modID, fileID, fileInfo); + return addDownload(m_NexusInterface->getAccessManager()->get(request), URLs, fileName, + gameName, modID, fileID, fileInfo); } - -bool DownloadManager::addDownload(QNetworkReply *reply, const ModRepositoryFileInfo *fileInfo) +bool DownloadManager::addDownload(QNetworkReply* reply, + const ModRepositoryFileInfo* fileInfo) { QString fileName = getFileNameFromNetworkReply(reply); if (fileName.isEmpty()) { fileName = "unknown"; } - return addDownload(reply, QStringList(reply->url().toString()), fileName, fileInfo->gameName, fileInfo->modID, fileInfo->fileID, fileInfo); + return addDownload(reply, QStringList(reply->url().toString()), fileName, + fileInfo->gameName, fileInfo->modID, fileInfo->fileID, fileInfo); } - -bool DownloadManager::addDownload(QNetworkReply *reply, const QStringList &URLs, const QString &fileName, - QString gameName, int modID, int fileID, const ModRepositoryFileInfo *fileInfo) +bool DownloadManager::addDownload(QNetworkReply* reply, const QStringList& URLs, + const QString& fileName, QString gameName, int modID, + int fileID, const ModRepositoryFileInfo* fileInfo) { - // download invoked from an already open network reply (i.e. download link in the browser) - DownloadInfo *newDownload = DownloadInfo::createNew(fileInfo, URLs); + // download invoked from an already open network reply (i.e. download link in the + // browser) + DownloadInfo* newDownload = DownloadInfo::createNew(fileInfo, URLs); QString baseName = fileName; if (!fileInfo->fileName.isEmpty()) { @@ -475,8 +484,7 @@ bool DownloadManager::addDownload(QNetworkReply *reply, const QStringList &URLs, // baseName could be a URL at this point so strip out the URL query int queryIndex = baseName.indexOf("?"); - if (queryIndex >= 0) - { + if (queryIndex >= 0) { baseName.truncate(queryIndex); } @@ -485,26 +493,27 @@ bool DownloadManager::addDownload(QNetworkReply *reply, const QStringList &URLs, endDisableDirWatcher(); startDownload(reply, newDownload, false); -// emit update(-1); + // emit update(-1); return true; } - void DownloadManager::removePending(QString gameName, int modID, int fileID) { QString gameShortName = gameName; QStringList games(m_ManagedGame->validShortNames()); games += m_ManagedGame->gameShortName(); for (auto game : games) { - MOBase::IPluginGame *gamePlugin = m_OrganizerCore->getGame(game); - if (gamePlugin != nullptr && gamePlugin->gameNexusName().compare(gameName, Qt::CaseInsensitive) == 0) { + MOBase::IPluginGame* gamePlugin = m_OrganizerCore->getGame(game); + if (gamePlugin != nullptr && + gamePlugin->gameNexusName().compare(gameName, Qt::CaseInsensitive) == 0) { gameShortName = gamePlugin->gameShortName(); break; } } emit aboutToUpdate(); for (auto iter : m_PendingDownloads) { - if (gameShortName.compare(std::get<0>(iter), Qt::CaseInsensitive) == 0 && (std::get<1>(iter) == modID) && (std::get<2>(iter) == fileID)) { + if (gameShortName.compare(std::get<0>(iter), Qt::CaseInsensitive) == 0 && + (std::get<1>(iter) == modID) && (std::get<2>(iter) == fileID)) { m_PendingDownloads.removeAt(m_PendingDownloads.indexOf(iter)); break; } @@ -512,10 +521,11 @@ void DownloadManager::removePending(QString gameName, int modID, int fileID) emit update(-1); } - -void DownloadManager::startDownload(QNetworkReply *reply, DownloadInfo *newDownload, bool resume) +void DownloadManager::startDownload(QNetworkReply* reply, DownloadInfo* newDownload, + bool resume) { - reply->setReadBufferSize(1024 * 1024); // don't read more than 1MB at once to avoid memory troubles + reply->setReadBufferSize( + 1024 * 1024); // don't read more than 1MB at once to avoid memory troubles newDownload->m_Reply = reply; setState(newDownload, STATE_DOWNLOADING); if (newDownload->m_Urls.count() == 0) { @@ -532,18 +542,23 @@ void DownloadManager::startDownload(QNetworkReply *reply, DownloadInfo *newDownl if (!newDownload->m_Output.open(mode)) { reportError(tr("failed to download %1: could not open output file: %2") - .arg(reply->url().toString()).arg(newDownload->m_Output.fileName())); + .arg(reply->url().toString()) + .arg(newDownload->m_Output.fileName())); return; } - connect(newDownload->m_Reply, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(downloadProgress(qint64, qint64))); - connect(newDownload->m_Reply, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError))); + connect(newDownload->m_Reply, SIGNAL(downloadProgress(qint64, qint64)), this, + SLOT(downloadProgress(qint64, qint64))); + connect(newDownload->m_Reply, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), + this, SLOT(downloadError(QNetworkReply::NetworkError))); connect(newDownload->m_Reply, SIGNAL(readyRead()), this, SLOT(downloadReadyRead())); - connect(newDownload->m_Reply, SIGNAL(metaDataChanged()), this, SLOT(metaDataChanged())); + connect(newDownload->m_Reply, SIGNAL(metaDataChanged()), this, + SLOT(metaDataChanged())); if (!resume) { newDownload->m_PreResumeSize = newDownload->m_Output.size(); - removePending(newDownload->m_FileInfo->gameName, newDownload->m_FileInfo->modID, newDownload->m_FileInfo->fileID); + removePending(newDownload->m_FileInfo->gameName, newDownload->m_FileInfo->modID, + newDownload->m_FileInfo->fileID); emit aboutToUpdate(); m_ActiveDownloads.append(newDownload); @@ -554,9 +569,13 @@ void DownloadManager::startDownload(QNetworkReply *reply, DownloadInfo *newDownl if (QFile::exists(m_OutputDirectory + "/" + newDownload->m_FileName)) { setState(newDownload, STATE_PAUSING); QCoreApplication::processEvents(); - if (QMessageBox::question(m_ParentWidget, tr("Download again?"), tr("A file with the same name \"%1\" has already been downloaded. " - "Do you want to download it again? The new file will receive a different name.").arg(newDownload->m_FileName), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) { + if (QMessageBox::question( + m_ParentWidget, tr("Download again?"), + tr("A file with the same name \"%1\" has already been downloaded. " + "Do you want to download it again? The new file will receive a " + "different name.") + .arg(newDownload->m_FileName), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) { if (reply->isFinished()) setState(newDownload, STATE_CANCELED); else @@ -573,13 +592,11 @@ void DownloadManager::startDownload(QNetworkReply *reply, DownloadInfo *newDownl } else connect(newDownload->m_Reply, SIGNAL(finished()), this, SLOT(downloadFinished())); - QCoreApplication::processEvents(); if (newDownload->m_State != STATE_DOWNLOADING && - newDownload->m_State != STATE_READY && - newDownload->m_State != STATE_FETCHINGMODINFO && - reply->isFinished()) { + newDownload->m_State != STATE_READY && + newDownload->m_State != STATE_FETCHINGMODINFO && reply->isFinished()) { downloadFinished(indexByInfo(newDownload)); return; } @@ -587,8 +604,7 @@ void DownloadManager::startDownload(QNetworkReply *reply, DownloadInfo *newDownl connect(newDownload->m_Reply, SIGNAL(finished()), this, SLOT(downloadFinished())); } - -void DownloadManager::addNXMDownload(const QString &url) +void DownloadManager::addNXMDownload(const QString& url) { NXMUrl nxmInfo(url); @@ -608,77 +624,90 @@ void DownloadManager::addNXMDownload(const QString &url) continue; } - if ( - nxmInfo.game().compare(gamePlugin->gameShortName(), Qt::CaseInsensitive) == 0 || - nxmInfo.game().compare(gamePlugin->gameNexusName(), Qt::CaseInsensitive) == 0 - ) { + if (nxmInfo.game().compare(gamePlugin->gameShortName(), Qt::CaseInsensitive) == 0 || + nxmInfo.game().compare(gamePlugin->gameNexusName(), Qt::CaseInsensitive) == 0) { foundGame = gamePlugin; break; } } log::debug("add nxm download: {}", url); if (foundGame == nullptr) { - log::debug("download requested for wrong game (game: {}, url: {})", m_ManagedGame->gameShortName(), nxmInfo.game()); - QMessageBox::information(m_ParentWidget, tr("Wrong Game"), tr("The download link is for a mod for \"%1\" but this instance of MO " - "has been set up for \"%2\".").arg(nxmInfo.game()).arg(m_ManagedGame->gameShortName()), QMessageBox::Ok); + log::debug("download requested for wrong game (game: {}, url: {})", + m_ManagedGame->gameShortName(), nxmInfo.game()); + QMessageBox::information( + m_ParentWidget, tr("Wrong Game"), + tr("The download link is for a mod for \"%1\" but this instance of MO " + "has been set up for \"%2\".") + .arg(nxmInfo.game()) + .arg(m_ManagedGame->gameShortName()), + QMessageBox::Ok); return; } for (auto tuple : m_PendingDownloads) { - if (std::get<0>(tuple).compare(foundGame->gameShortName(), Qt::CaseInsensitive) == 0, std::get<1>(tuple) == nxmInfo.modId() && std::get<2>(tuple) == nxmInfo.fileId()) { + if (std::get<0>(tuple).compare(foundGame->gameShortName(), Qt::CaseInsensitive) == + 0, + std::get<1>(tuple) == nxmInfo.modId() && + std::get<2>(tuple) == nxmInfo.fileId()) { const auto infoStr = - tr("There is already a download queued for this file.\n\nMod %1\nFile %2") - .arg(nxmInfo.modId()).arg(nxmInfo.fileId()); + tr("There is already a download queued for this file.\n\nMod %1\nFile %2") + .arg(nxmInfo.modId()) + .arg(nxmInfo.fileId()); - log::debug( - "download requested is already queued (mod: {}, file: {})", - nxmInfo.modId(), nxmInfo.fileId()); + log::debug("download requested is already queued (mod: {}, file: {})", + nxmInfo.modId(), nxmInfo.fileId()); - QMessageBox::information(m_ParentWidget, tr("Already Queued"), infoStr, QMessageBox::Ok); + QMessageBox::information(m_ParentWidget, tr("Already Queued"), infoStr, + QMessageBox::Ok); return; } } - for (DownloadInfo *download : m_ActiveDownloads) { - if (download->m_FileInfo->modID == nxmInfo.modId() && download->m_FileInfo->fileID == nxmInfo.fileId()) { - if (download->m_State == STATE_DOWNLOADING || download->m_State == STATE_PAUSED || download->m_State == STATE_STARTED) { - QString debugStr("download requested is already started (mod %1: %2, file %3: %4)"); - QString infoStr(tr("There is already a download started for this file.\n\nMod %1:\t%2\nFile %3:\t%4")); + for (DownloadInfo* download : m_ActiveDownloads) { + if (download->m_FileInfo->modID == nxmInfo.modId() && + download->m_FileInfo->fileID == nxmInfo.fileId()) { + if (download->m_State == STATE_DOWNLOADING || download->m_State == STATE_PAUSED || + download->m_State == STATE_STARTED) { + QString debugStr( + "download requested is already started (mod %1: %2, file %3: %4)"); + QString infoStr(tr("There is already a download started for this file.\n\nMod " + "%1:\t%2\nFile %3:\t%4")); // %1 debugStr = debugStr.arg(download->m_FileInfo->modID); - infoStr = infoStr.arg(download->m_FileInfo->modID); + infoStr = infoStr.arg(download->m_FileInfo->modID); // %2 if (!download->m_FileInfo->name.isEmpty()) { debugStr = debugStr.arg(download->m_FileInfo->name); - infoStr = infoStr.arg(download->m_FileInfo->name); + infoStr = infoStr.arg(download->m_FileInfo->name); } else if (!download->m_FileInfo->modName.isEmpty()) { debugStr = debugStr.arg(download->m_FileInfo->modName); - infoStr = infoStr.arg(download->m_FileInfo->modName); + infoStr = infoStr.arg(download->m_FileInfo->modName); } else { debugStr = debugStr.arg(QStringLiteral("")); - infoStr = infoStr.arg(QStringLiteral("")); + infoStr = infoStr.arg(QStringLiteral("")); } // %3 debugStr = debugStr.arg(download->m_FileInfo->fileID); - infoStr = infoStr.arg(download->m_FileInfo->fileID); + infoStr = infoStr.arg(download->m_FileInfo->fileID); // %4 if (!download->m_FileInfo->fileName.isEmpty()) { debugStr = debugStr.arg(download->m_FileInfo->fileName); - infoStr = infoStr.arg(download->m_FileInfo->fileName); + infoStr = infoStr.arg(download->m_FileInfo->fileName); } else if (!download->m_FileName.isEmpty()) { debugStr = debugStr.arg(download->m_FileName); - infoStr = infoStr.arg(download->m_FileName); + infoStr = infoStr.arg(download->m_FileName); } else { debugStr = debugStr.arg(QStringLiteral("")); - infoStr = infoStr.arg(QStringLiteral("")); + infoStr = infoStr.arg(QStringLiteral("")); } log::debug("{}", debugStr); - QMessageBox::information(m_ParentWidget, tr("Already Started"), infoStr, QMessageBox::Ok); + QMessageBox::information(m_ParentWidget, tr("Already Started"), infoStr, + QMessageBox::Ok); return; } } @@ -686,32 +715,34 @@ void DownloadManager::addNXMDownload(const QString &url) emit aboutToUpdate(); - m_PendingDownloads.append(std::make_tuple(foundGame->gameShortName(), nxmInfo.modId(), nxmInfo.fileId())); + m_PendingDownloads.append( + std::make_tuple(foundGame->gameShortName(), nxmInfo.modId(), nxmInfo.fileId())); emit update(-1); emit downloadAdded(); - ModRepositoryFileInfo *info = new ModRepositoryFileInfo(); + ModRepositoryFileInfo* info = new ModRepositoryFileInfo(); - info->nexusKey = nxmInfo.key(); - info->nexusExpires = nxmInfo.expires(); + info->nexusKey = nxmInfo.key(); + info->nexusExpires = nxmInfo.expires(); info->nexusDownloadUser = nxmInfo.userId(); - QObject *test = info; - m_RequestIDs.insert(m_NexusInterface->requestFileInfo(foundGame->gameShortName(), nxmInfo.modId(), nxmInfo.fileId(), this, QVariant::fromValue(test), "")); + QObject* test = info; + m_RequestIDs.insert(m_NexusInterface->requestFileInfo( + foundGame->gameShortName(), nxmInfo.modId(), nxmInfo.fileId(), this, + QVariant::fromValue(test), "")); } - void DownloadManager::removeFile(int index, bool deleteFile) { - //Avoid triggering refreshes from DirWatcher + // Avoid triggering refreshes from DirWatcher startDisableDirWatcher(); if (index >= m_ActiveDownloads.size()) { throw MyException(tr("remove: invalid download index %1").arg(index)); } - DownloadInfo *download = m_ActiveDownloads.at(index); - QString filePath = m_OutputDirectory + "/" + download->m_FileName; + DownloadInfo* download = m_ActiveDownloads.at(index); + QString filePath = m_OutputDirectory + "/" + download->m_FileName; if ((download->m_State == STATE_STARTED) || (download->m_State == STATE_DOWNLOADING)) { // shouldn't have been possible @@ -737,7 +768,7 @@ void DownloadManager::removeFile(int index, bool deleteFile) } } else { QSettings metaSettings(filePath.append(".meta"), QSettings::IniFormat); - if(!download->m_Hidden) + if (!download->m_Hidden) metaSettings.setValue("removed", true); } m_DownloadRemoved(index); @@ -748,64 +779,64 @@ void DownloadManager::removeFile(int index, bool deleteFile) class LessThanWrapper { public: - LessThanWrapper(DownloadManager *manager) : m_Manager(manager) {} - bool operator()(int LHS, int RHS) { - return m_Manager->getFileName(LHS).compare(m_Manager->getFileName(RHS), Qt::CaseInsensitive) < 0; - + LessThanWrapper(DownloadManager* manager) : m_Manager(manager) {} + bool operator()(int LHS, int RHS) + { + return m_Manager->getFileName(LHS).compare(m_Manager->getFileName(RHS), + Qt::CaseInsensitive) < 0; } private: - DownloadManager *m_Manager; + DownloadManager* m_Manager; }; - bool DownloadManager::ByName(int LHS, int RHS) { return m_ActiveDownloads[LHS]->m_FileName < m_ActiveDownloads[RHS]->m_FileName; } - void DownloadManager::refreshAlphabeticalTranslation() { m_AlphabeticalTranslation.clear(); int pos = 0; - for (QVector::iterator iter = m_ActiveDownloads.begin(); iter != m_ActiveDownloads.end(); ++iter, ++pos) { + for (QVector::iterator iter = m_ActiveDownloads.begin(); + iter != m_ActiveDownloads.end(); ++iter, ++pos) { m_AlphabeticalTranslation.push_back(pos); } - std::sort(m_AlphabeticalTranslation.begin(), m_AlphabeticalTranslation.end(), LessThanWrapper(this)); + std::sort(m_AlphabeticalTranslation.begin(), m_AlphabeticalTranslation.end(), + LessThanWrapper(this)); } - void DownloadManager::restoreDownload(int index) { if (index < 0) { - DownloadState minState = STATE_READY ; - index = 0; + DownloadState minState = STATE_READY; + index = 0; - for (QVector::const_iterator iter = m_ActiveDownloads.begin(); iter != m_ActiveDownloads.end(); ++iter ) { + for (QVector::const_iterator iter = m_ActiveDownloads.begin(); + iter != m_ActiveDownloads.end(); ++iter) { if ((*iter)->m_State >= minState) { restoreDownload(index); } index++; } - } - else { + } else { if (index >= m_ActiveDownloads.size()) { throw MyException(tr("restore: invalid download index: %1").arg(index)); } - DownloadInfo *download = m_ActiveDownloads.at(index); + DownloadInfo* download = m_ActiveDownloads.at(index); if (download->m_Hidden) { download->m_Hidden = false; QString filePath = m_OutputDirectory + "/" + download->m_FileName; - //avoid dirWatcher triggering refreshes + // avoid dirWatcher triggering refreshes startDisableDirWatcher(); - QSettings metaSettings(filePath.append(".meta"), QSettings::IniFormat); + QSettings metaSettings(filePath.append(".meta"), QSettings::IniFormat); metaSettings.setValue("removed", false); endDisableDirWatcher(); @@ -813,21 +844,21 @@ void DownloadManager::restoreDownload(int index) } } - void DownloadManager::removeDownload(int index, bool deleteFile) { try { - //avoid dirWatcher triggering refreshes + // avoid dirWatcher triggering refreshes startDisableDirWatcher(); emit aboutToUpdate(); if (index < 0) { - bool removeAll = (index == -1); + bool removeAll = (index == -1); DownloadState removeState = (index == -2 ? STATE_INSTALLED : STATE_UNINSTALLED); index = 0; - for (QVector::iterator iter = m_ActiveDownloads.begin(); iter != m_ActiveDownloads.end();) { + for (QVector::iterator iter = m_ActiveDownloads.begin(); + iter != m_ActiveDownloads.end();) { DownloadState downloadState = (*iter)->m_State; if ((removeAll && (downloadState >= STATE_READY)) || (removeState == downloadState)) { @@ -842,7 +873,7 @@ void DownloadManager::removeDownload(int index, bool deleteFile) } else { if (index >= m_ActiveDownloads.size()) { reportError(tr("remove: invalid download index %1").arg(index)); - //emit update(-1); + // emit update(-1); endDisableDirWatcher(); return; } @@ -853,13 +884,12 @@ void DownloadManager::removeDownload(int index, bool deleteFile) } emit update(-1); endDisableDirWatcher(); - } catch (const std::exception &e) { + } catch (const std::exception& e) { log::error("failed to remove download: {}", e.what()); } refreshList(); } - void DownloadManager::cancelDownload(int index) { if ((index < 0) || (index >= m_ActiveDownloads.size())) { @@ -872,7 +902,6 @@ void DownloadManager::cancelDownload(int index) } } - void DownloadManager::pauseDownload(int index) { if ((index < 0) || (index >= m_ActiveDownloads.size())) { @@ -880,7 +909,7 @@ void DownloadManager::pauseDownload(int index) return; } - DownloadInfo *info = m_ActiveDownloads.at(index); + DownloadInfo* info = m_ActiveDownloads.at(index); if (info->m_State == STATE_DOWNLOADING) { if ((info->m_Reply != nullptr) && (info->m_Reply->isRunning())) { @@ -901,8 +930,8 @@ void DownloadManager::resumeDownload(int index) reportError(tr("resume: invalid download index %1").arg(index)); return; } - DownloadInfo *info = m_ActiveDownloads[index]; - info->m_Tries = AUTOMATIC_RETRIES; + DownloadInfo* info = m_ActiveDownloads[index]; + info->m_Tries = AUTOMATIC_RETRIES; resumeDownloadInt(index); } @@ -912,11 +941,12 @@ void DownloadManager::resumeDownloadInt(int index) reportError(tr("resume (int): invalid download index %1").arg(index)); return; } - DownloadInfo *info = m_ActiveDownloads[index]; + DownloadInfo* info = m_ActiveDownloads[index]; // Check for finished download; - if (info->m_TotalSize <= info->m_Output.size() && info->m_Reply != nullptr - && info->m_Reply->isOpen() && info->m_Reply->isFinished() && info->m_State != STATE_ERROR) { + if (info->m_TotalSize <= info->m_Output.size() && info->m_Reply != nullptr && + info->m_Reply->isOpen() && info->m_Reply->isFinished() && + info->m_State != STATE_ERROR) { setState(info, STATE_DOWNLOADING); downloadFinished(index); return; @@ -931,9 +961,10 @@ void DownloadManager::resumeDownloadInt(int index) } } } - if ((info->m_Urls.size() == 0) - || ((info->m_Urls.size() == 1) && (info->m_Urls[0].size() == 0))) { - emit showMessage(tr("No known download urls. Sorry, this download can't be resumed.")); + if ((info->m_Urls.size() == 0) || + ((info->m_Urls.size() == 1) && (info->m_Urls[0].size() == 0))) { + emit showMessage( + tr("No known download urls. Sorry, this download can't be resumed.")); return; } if (info->m_State == STATE_ERROR) { @@ -941,27 +972,31 @@ void DownloadManager::resumeDownloadInt(int index) } log::debug("request resume from url {}", info->currentURL()); QNetworkRequest request(QUrl::fromEncoded(info->currentURL().toLocal8Bit())); - request.setHeader(QNetworkRequest::UserAgentHeader, m_NexusInterface->getAccessManager()->userAgent()); + request.setHeader(QNetworkRequest::UserAgentHeader, + m_NexusInterface->getAccessManager()->userAgent()); if (info->m_State != STATE_ERROR) { - info->m_ResumePos = info->m_Output.size(); + info->m_ResumePos = info->m_Output.size(); QByteArray rangeHeader = "bytes=" + QByteArray::number(info->m_ResumePos) + "-"; request.setRawHeader("Range", rangeHeader); } - info->m_DownloadLast = 0; + info->m_DownloadLast = 0; info->m_DownloadTimeLast = 0; - info->m_DownloadAcc = accumulator_set>(tag::rolling_window::window_size = 200); - info->m_DownloadTimeAcc = accumulator_set>(tag::rolling_window::window_size = 200); + info->m_DownloadAcc = accumulator_set>( + tag::rolling_window::window_size = 200); + info->m_DownloadTimeAcc = accumulator_set>( + tag::rolling_window::window_size = 200); log::debug("resume at {} bytes", info->m_ResumePos); startDownload(m_NexusInterface->getAccessManager()->get(request), info, true); } emit update(index); } - -DownloadManager::DownloadInfo *DownloadManager::downloadInfoByID(unsigned int id) +DownloadManager::DownloadInfo* DownloadManager::downloadInfoByID(unsigned int id) { auto iter = std::find_if(m_ActiveDownloads.begin(), m_ActiveDownloads.end(), - [id](DownloadInfo *info) { return info->m_DownloadID == id; }); + [id](DownloadInfo* info) { + return info->m_DownloadID == id; + }); if (iter != m_ActiveDownloads.end()) { return *iter; } else { @@ -969,14 +1004,13 @@ DownloadManager::DownloadInfo *DownloadManager::downloadInfoByID(unsigned int id } } - void DownloadManager::queryInfo(int index) { if ((index < 0) || (index >= m_ActiveDownloads.size())) { reportError(tr("query: invalid download index %1").arg(index)); return; } - DownloadInfo *info = m_ActiveDownloads[index]; + DownloadInfo* info = m_ActiveDownloads[index]; if (info->m_FileInfo->repository != "Nexus") { log::warn("re-querying file info is currently only possible with Nexus"); @@ -991,12 +1025,13 @@ void DownloadManager::queryInfo(int index) if (info->m_FileInfo->modID <= 0) { QString fileName = getFileName(index); QString ignore; - NexusInterface::interpretNexusFileName(fileName, ignore, info->m_FileInfo->modID, true); + NexusInterface::interpretNexusFileName(fileName, ignore, info->m_FileInfo->modID, + true); if (info->m_FileInfo->modID < 0) { - bool ok = false; - int modId = QInputDialog::getInt( - nullptr, tr("Please enter the nexus mod id"), tr("Mod ID:"), 1, 1, - std::numeric_limits::max(), 1, &ok); + bool ok = false; + int modId = QInputDialog::getInt(nullptr, tr("Please enter the nexus mod id"), + tr("Mod ID:"), 1, 1, + std::numeric_limits::max(), 1, &ok); // careful now: while the dialog was displayed, events were processed. // the download list might have changed and our info-ptr invalidated. if (ok) @@ -1006,21 +1041,20 @@ void DownloadManager::queryInfo(int index) } if (info->m_FileInfo->gameName.size() == 0) { - SelectionDialog selection(tr("Please select the source game code for %1").arg(getFileName(index))); + SelectionDialog selection( + tr("Please select the source game code for %1").arg(getFileName(index))); - std::vector> choices = m_NexusInterface->getGameChoices(m_ManagedGame); - if (choices.size() == 1) - { + std::vector> choices = + m_NexusInterface->getGameChoices(m_ManagedGame); + if (choices.size() == 1) { info->m_FileInfo->gameName = choices[0].first; - } - else { + } else { for (auto choice : choices) { selection.addChoice(choice.first, choice.second, choice.first); } if (selection.exec() == QDialog::Accepted) { info->m_FileInfo->gameName = selection.getChoiceData().toString(); - } - else { + } else { info->m_FileInfo->gameName = m_ManagedGame->gameShortName(); } } @@ -1035,7 +1069,7 @@ void DownloadManager::queryInfoMd5(int index) reportError(tr("query: invalid download index %1").arg(index)); return; } - DownloadInfo *info = m_ActiveDownloads[index]; + DownloadInfo* info = m_ActiveDownloads[index]; if (info->m_FileInfo->repository != "Nexus") { log::warn("re-querying file info is currently only possible with Nexus"); @@ -1052,7 +1086,8 @@ void DownloadManager::queryInfoMd5(int index) QFile downloadFile(info->m_FileName); if (!downloadFile.exists()) { - downloadFile.setFileName(m_OrganizerCore->downloadsPath() + "\\" + info->m_FileName); + downloadFile.setFileName(m_OrganizerCore->downloadsPath() + "\\" + + info->m_FileName); } if (!downloadFile.exists()) { log::error("Can't find download file '{}'", info->m_FileName); @@ -1066,14 +1101,12 @@ void DownloadManager::queryInfoMd5(int index) QCryptographicHash hash(QCryptographicHash::Md5); const qint64 progressStep = 10 * 1024 * 1024; QProgressDialog progress(tr("Hashing download file '%1'").arg(info->m_FileName), - tr("Cancel"), - 0, - downloadFile.size() / progressStep); + tr("Cancel"), 0, downloadFile.size() / progressStep); progress.setWindowModality(Qt::WindowModal); progress.setMinimumDuration(1000); for (qint64 i = 0; i < downloadFile.size(); i += progressStep) { - progress.setValue(progress.value()+1); + progress.setValue(progress.value() + 1); if (progress.wasCanceled()) { break; } @@ -1087,7 +1120,7 @@ void DownloadManager::queryInfoMd5(int index) progress.close(); downloadFile.close(); - info->m_Hash = hash.result(); + info->m_Hash = hash.result(); info->m_ReQueried = true; setState(info, STATE_FETCHINGMODINFO_MD5); } @@ -1098,7 +1131,7 @@ void DownloadManager::visitOnNexus(int index) reportError(tr("VisitNexus: invalid download index %1").arg(index)); return; } - DownloadInfo *info = m_ActiveDownloads[index]; + DownloadInfo* info = m_ActiveDownloads[index]; if (info->m_FileInfo->repository != "Nexus") { log::warn("Visiting mod page is currently only possible with Nexus"); @@ -1114,8 +1147,7 @@ void DownloadManager::visitOnNexus(int index) QString gameName = info->m_FileInfo->gameName; if (modID > 0) { shell::Open(QUrl(m_NexusInterface->getModURL(modID, gameName))); - } - else { + } else { emit showMessage(tr("Nexus ID for this Mod is unknown")); } } @@ -1137,21 +1169,21 @@ void DownloadManager::openFile(int index) return; } -void DownloadManager::openMetaFile(int index) { +void DownloadManager::openMetaFile(int index) +{ if (index < 0 || index >= m_ActiveDownloads.size()) { log::error("OpenMetaFile: invalid download index {}", index); return; } - const auto path = QDir(m_OutputDirectory); + const auto path = QDir(m_OutputDirectory); const auto filePath = getFilePath(index); const auto metaPath = filePath + ".meta"; if (path.exists(metaPath)) { shell::Open(metaPath); return; - } - else { + } else { QSettings metaFile(metaPath, QSettings::IniFormat); metaFile.setValue("removed", false); } @@ -1176,8 +1208,7 @@ void DownloadManager::openInDownloadsFolder(int index) if (QFile::exists(path)) { shell::Explore(path); return; - } - else { + } else { const auto unfinished = path + ".unfinished"; if (QFile::exists(unfinished)) { shell::Explore(unfinished); @@ -1188,7 +1219,6 @@ void DownloadManager::openInDownloadsFolder(int index) shell::Explore(m_OutputDirectory); } - int DownloadManager::numTotalDownloads() const { return m_ActiveDownloads.size(); @@ -1220,14 +1250,22 @@ QString DownloadManager::getFilePath(int index) const QString DownloadManager::getFileTypeString(int fileType) { switch (fileType) { - case 1: return tr("Main"); - case 2: return tr("Update"); - case 3: return tr("Optional"); - case 4: return tr("Old"); - case 5: return tr("Miscellaneous"); - case 6: return tr("Deleted"); - case 7: return tr("Archived"); - default: return tr("Unknown"); + case 1: + return tr("Main"); + case 2: + return tr("Update"); + case 3: + return tr("Optional"); + case 4: + return tr("Old"); + case 5: + return tr("Miscellaneous"); + case 6: + return tr("Deleted"); + case 7: + return tr("Archived"); + default: + return tr("Unknown"); } } @@ -1237,14 +1275,15 @@ QString DownloadManager::getDisplayName(int index) const throw MyException(tr("display name: invalid download index %1").arg(index)); } - DownloadInfo *info = m_ActiveDownloads.at(index); + DownloadInfo* info = m_ActiveDownloads.at(index); QTextDocument doc; if (!info->m_FileInfo->name.isEmpty()) { doc.setHtml(info->m_FileInfo->name); - return QString("%1 (%2, v%3)").arg(doc.toPlainText()) - .arg(getFileTypeString(info->m_FileInfo->fileCategory)) - .arg(info->m_FileInfo->version.displayString()); + return QString("%1 (%2, v%3)") + .arg(doc.toPlainText()) + .arg(getFileTypeString(info->m_FileInfo->fileCategory)) + .arg(info->m_FileInfo->version.displayString()); } else { doc.setHtml(info->m_FileName); return doc.toPlainText(); @@ -1279,7 +1318,7 @@ QDateTime DownloadManager::getFileTime(int index) const throw MyException(tr("file time: invalid download index %1").arg(index)); } - DownloadInfo *info = m_ActiveDownloads.at(index); + DownloadInfo* info = m_ActiveDownloads.at(index); if (!info->m_Created.isValid()) { QFileInfo fileInfo(info->m_Output); info->m_Created = fileInfo.birthTime(); @@ -1301,7 +1340,6 @@ qint64 DownloadManager::getFileSize(int index) const return m_ActiveDownloads.at(index)->m_TotalSize; } - std::pair DownloadManager::getProgress(int index) const { if ((index < 0) || (index >= m_ActiveDownloads.size())) { @@ -1311,7 +1349,6 @@ std::pair DownloadManager::getProgress(int index) const return m_ActiveDownloads.at(index)->m_Progress; } - DownloadManager::DownloadState DownloadManager::getState(int index) const { if ((index < 0) || (index >= m_ActiveDownloads.size())) { @@ -1321,14 +1358,13 @@ DownloadManager::DownloadState DownloadManager::getState(int index) const return m_ActiveDownloads.at(index)->m_State; } - bool DownloadManager::isInfoIncomplete(int index) const { if ((index < 0) || (index >= m_ActiveDownloads.size())) { throw MyException(tr("infocomplete: invalid download index %1").arg(index)); } - DownloadInfo *info = m_ActiveDownloads.at(index); + DownloadInfo* info = m_ActiveDownloads.at(index); if (info->m_FileInfo->repository != "Nexus") { // other repositories currently don't support re-querying info anyway return false; @@ -1336,7 +1372,6 @@ bool DownloadManager::isInfoIncomplete(int index) const return (info->m_FileInfo->fileID == 0) || (info->m_FileInfo->modID == 0); } - int DownloadManager::getModID(int index) const { if ((index < 0) || (index >= m_ActiveDownloads.size())) { @@ -1358,7 +1393,7 @@ QString DownloadManager::getDisplayGameName(int index) const if ((index < 0) || (index >= m_ActiveDownloads.size())) { throw MyException(tr("mod id: invalid download index %1").arg(index)); } - QString gameName = m_ActiveDownloads.at(index)->m_FileInfo->gameName; + QString gameName = m_ActiveDownloads.at(index)->m_FileInfo->gameName; IPluginGame* gamePlugin = m_OrganizerCore->getGame(gameName); if (gamePlugin) { gameName = gamePlugin->gameName(); @@ -1382,8 +1417,7 @@ bool DownloadManager::isHidden(int index) const return m_ActiveDownloads.at(index)->m_Hidden; } - -const ModRepositoryFileInfo *DownloadManager::getFileInfo(int index) const +const ModRepositoryFileInfo* DownloadManager::getFileInfo(int index) const { if ((index < 0) || (index >= m_ActiveDownloads.size())) { throw MyException(tr("file info: invalid download index %1").arg(index)); @@ -1392,17 +1426,16 @@ const ModRepositoryFileInfo *DownloadManager::getFileInfo(int index) const return m_ActiveDownloads.at(index)->m_FileInfo; } - void DownloadManager::markInstalled(int index) { if ((index < 0) || (index >= m_ActiveDownloads.size())) { throw MyException(tr("mark installed: invalid download index %1").arg(index)); } - //Avoid triggering refreshes from DirWatcher + // Avoid triggering refreshes from DirWatcher startDisableDirWatcher(); - DownloadInfo *info = m_ActiveDownloads.at(index); + DownloadInfo* info = m_ActiveDownloads.at(index); QSettings metaFile(info->m_Output.fileName() + ".meta", QSettings::IniFormat); metaFile.setValue("installed", true); metaFile.setValue("uninstalled", false); @@ -1418,9 +1451,9 @@ void DownloadManager::markInstalled(QString fileName) if (index >= 0) { markInstalled(index); } else { - DownloadInfo *info = getDownloadInfo(fileName); + DownloadInfo* info = getDownloadInfo(fileName); if (info != nullptr) { - //Avoid triggering refreshes from DirWatcher + // Avoid triggering refreshes from DirWatcher startDisableDirWatcher(); QSettings metaFile(info->m_Output.fileName() + ".meta", QSettings::IniFormat); @@ -1444,10 +1477,10 @@ void DownloadManager::markUninstalled(int index) throw MyException(tr("mark uninstalled: invalid download index %1").arg(index)); } - //Avoid triggering refreshes from DirWatcher + // Avoid triggering refreshes from DirWatcher startDisableDirWatcher(); - DownloadInfo *info = m_ActiveDownloads.at(index); + DownloadInfo* info = m_ActiveDownloads.at(index); QSettings metaFile(info->m_Output.fileName() + ".meta", QSettings::IniFormat); metaFile.setValue("uninstalled", true); @@ -1456,18 +1489,17 @@ void DownloadManager::markUninstalled(int index) setState(m_ActiveDownloads.at(index), STATE_UNINSTALLED); } - void DownloadManager::markUninstalled(QString fileName) { int index = indexByName(fileName); if (index >= 0) { markUninstalled(index); } else { - QString filePath = QDir::fromNativeSeparators(m_OutputDirectory) + "/" + fileName; - DownloadInfo *info = getDownloadInfo(filePath); + QString filePath = QDir::fromNativeSeparators(m_OutputDirectory) + "/" + fileName; + DownloadInfo* info = getDownloadInfo(filePath); if (info != nullptr) { - //Avoid triggering refreshes from DirWatcher + // Avoid triggering refreshes from DirWatcher startDisableDirWatcher(); QSettings metaFile(info->m_Output.fileName() + ".meta", QSettings::IniFormat); @@ -1479,13 +1511,13 @@ void DownloadManager::markUninstalled(QString fileName) } } - -QString DownloadManager::getDownloadFileName(const QString &baseName, bool rename) const +QString DownloadManager::getDownloadFileName(const QString& baseName, bool rename) const { QString fullPath = m_OutputDirectory + "/" + MOBase::sanitizeFileName(baseName); if (QFile::exists(fullPath) && rename) { int i = 1; - while (QFile::exists(QString("%1/%2_%3").arg(m_OutputDirectory).arg(i).arg(baseName))) { + while (QFile::exists( + QString("%1/%2_%3").arg(m_OutputDirectory).arg(i).arg(baseName))) { ++i; } @@ -1494,14 +1526,14 @@ QString DownloadManager::getDownloadFileName(const QString &baseName, bool renam return fullPath; } - -QString DownloadManager::getFileNameFromNetworkReply(QNetworkReply *reply) +QString DownloadManager::getFileNameFromNetworkReply(QNetworkReply* reply) { if (reply->hasRawHeader("Content-Disposition")) { std::regex exp("filename=\"(.*)\""); std::cmatch result; - if (std::regex_search(reply->rawHeader("Content-Disposition").constData(), result, exp)) { + if (std::regex_search(reply->rawHeader("Content-Disposition").constData(), result, + exp)) { return MOBase::sanitizeFileName(QString::fromUtf8(result.str(1).c_str())); } } @@ -1509,8 +1541,8 @@ QString DownloadManager::getFileNameFromNetworkReply(QNetworkReply *reply) return QString(); } - -void DownloadManager::setState(DownloadManager::DownloadInfo *info, DownloadManager::DownloadState state) +void DownloadManager::setState(DownloadManager::DownloadInfo* info, + DownloadManager::DownloadState state) { int row = 0; for (int i = 0; i < m_ActiveDownloads.size(); ++i) { @@ -1521,41 +1553,48 @@ void DownloadManager::setState(DownloadManager::DownloadInfo *info, DownloadMana } info->m_State = state; switch (state) { - case STATE_PAUSED: { - info->m_Reply->abort(); - info->m_Output.close(); - m_DownloadPaused(row); - } break; - case STATE_ERROR: { - info->m_Reply->abort(); - info->m_Output.close(); - m_DownloadFailed(row); - } break; - case STATE_CANCELED: { - info->m_Reply->abort(); - m_DownloadFailed(row); - } break; - case STATE_FETCHINGMODINFO: { - m_RequestIDs.insert(m_NexusInterface->requestDescription(info->m_FileInfo->gameName, info->m_FileInfo->modID, this, info->m_DownloadID, QString())); - } break; - case STATE_FETCHINGFILEINFO: { - m_RequestIDs.insert(m_NexusInterface->requestFiles(info->m_FileInfo->gameName, info->m_FileInfo->modID, this, info->m_DownloadID, QString())); - } break; - case STATE_FETCHINGMODINFO_MD5: { - log::debug("Searching {} for MD5 of {}", info->m_GamesToQuery[0], QString(info->m_Hash.toHex())); - m_RequestIDs.insert(m_NexusInterface->requestInfoFromMd5(info->m_GamesToQuery[0], info->m_Hash, this, info->m_DownloadID, QString())); - } break; - case STATE_READY: { - createMetaFile(info); - m_DownloadComplete(row); - } break; - default: /* NOP */ break; + case STATE_PAUSED: { + info->m_Reply->abort(); + info->m_Output.close(); + m_DownloadPaused(row); + } break; + case STATE_ERROR: { + info->m_Reply->abort(); + info->m_Output.close(); + m_DownloadFailed(row); + } break; + case STATE_CANCELED: { + info->m_Reply->abort(); + m_DownloadFailed(row); + } break; + case STATE_FETCHINGMODINFO: { + m_RequestIDs.insert(m_NexusInterface->requestDescription( + info->m_FileInfo->gameName, info->m_FileInfo->modID, this, info->m_DownloadID, + QString())); + } break; + case STATE_FETCHINGFILEINFO: { + m_RequestIDs.insert(m_NexusInterface->requestFiles(info->m_FileInfo->gameName, + info->m_FileInfo->modID, this, + info->m_DownloadID, QString())); + } break; + case STATE_FETCHINGMODINFO_MD5: { + log::debug("Searching {} for MD5 of {}", info->m_GamesToQuery[0], + QString(info->m_Hash.toHex())); + m_RequestIDs.insert(m_NexusInterface->requestInfoFromMd5( + info->m_GamesToQuery[0], info->m_Hash, this, info->m_DownloadID, QString())); + } break; + case STATE_READY: { + createMetaFile(info); + m_DownloadComplete(row); + } break; + default: /* NOP */ + break; } emit stateChanged(row, state); } - -DownloadManager::DownloadInfo *DownloadManager::findDownload(QObject *reply, int *index) const +DownloadManager::DownloadInfo* DownloadManager::findDownload(QObject* reply, + int* index) const { // reverse search as newer, thus more relevant, downloads are at the end for (int i = m_ActiveDownloads.size() - 1; i >= 0; --i) { @@ -1569,7 +1608,6 @@ DownloadManager::DownloadInfo *DownloadManager::findDownload(QObject *reply, int return nullptr; } - void DownloadManager::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) { if (bytesTotal == 0) { @@ -1577,20 +1615,20 @@ void DownloadManager::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) } int index = 0; try { - DownloadInfo *info = findDownload(this->sender(), &index); + DownloadInfo* info = findDownload(this->sender(), &index); if (info != nullptr) { info->m_HasData = true; if (info->m_State == STATE_CANCELING) { setState(info, STATE_CANCELED); } else if (info->m_State == STATE_PAUSING) { setState(info, STATE_PAUSED); - } - else { + } else { if (bytesTotal > info->m_TotalSize) { info->m_TotalSize = bytesTotal; } - int oldProgress = info->m_Progress.first; - info->m_Progress.first = ((info->m_ResumePos + bytesReceived) * 100) / (info->m_ResumePos + bytesTotal); + int oldProgress = info->m_Progress.first; + info->m_Progress.first = ((info->m_ResumePos + bytesReceived) * 100) / + (info->m_ResumePos + bytesTotal); qint64 elapsed = info->m_StartTime.elapsed(); info->m_DownloadAcc(bytesReceived - info->m_DownloadLast); @@ -1599,16 +1637,19 @@ void DownloadManager::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) info->m_DownloadTimeLast = elapsed; // calculate the download speed - const double speed = rolling_mean(info->m_DownloadAcc) / (rolling_mean(info->m_DownloadTimeAcc) / 1000.0);; + const double speed = rolling_mean(info->m_DownloadAcc) / + (rolling_mean(info->m_DownloadTimeAcc) / 1000.0); + ; const qint64 remaining = (bytesTotal - bytesReceived) / speed * 1000; info->m_Progress.second = tr("%1% - %2 - ~%3") - .arg(info->m_Progress.first) - .arg(MOBase::localizedByteSpeed(speed)) - .arg(MOBase::localizedTimeRemaining(remaining)); + .arg(info->m_Progress.first) + .arg(MOBase::localizedByteSpeed(speed)) + .arg(MOBase::localizedTimeRemaining(remaining)); - TaskProgressManager::instance().updateProgress(info->m_TaskProgressId, bytesReceived, bytesTotal); + TaskProgressManager::instance().updateProgress(info->m_TaskProgressId, + bytesReceived, bytesTotal); emit update(index); } } @@ -1617,7 +1658,6 @@ void DownloadManager::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) } } - void DownloadManager::downloadReadyRead() { try { @@ -1627,13 +1667,13 @@ void DownloadManager::downloadReadyRead() } } - -void DownloadManager::createMetaFile(DownloadInfo *info) +void DownloadManager::createMetaFile(DownloadInfo* info) { - //Avoid triggering refreshes from DirWatcher + // Avoid triggering refreshes from DirWatcher startDisableDirWatcher(); - QSettings metaFile(QString("%1.meta").arg(info->m_Output.fileName()), QSettings::IniFormat); + QSettings metaFile(QString("%1.meta").arg(info->m_Output.fileName()), + QSettings::IniFormat); metaFile.setValue("gameName", info->m_FileInfo->gameName); metaFile.setValue("modID", info->m_FileInfo->modID); metaFile.setValue("fileID", info->m_FileInfo->fileID); @@ -1651,7 +1691,7 @@ void DownloadManager::createMetaFile(DownloadInfo *info) metaFile.setValue("installed", info->m_State == DownloadManager::STATE_INSTALLED); metaFile.setValue("uninstalled", info->m_State == DownloadManager::STATE_UNINSTALLED); metaFile.setValue("paused", (info->m_State == DownloadManager::STATE_PAUSED) || - (info->m_State == DownloadManager::STATE_ERROR)); + (info->m_State == DownloadManager::STATE_ERROR)); metaFile.setValue("removed", info->m_Hidden); endDisableDirWatcher(); @@ -1663,8 +1703,8 @@ void DownloadManager::createMetaFile(DownloadInfo *info) } } - -void DownloadManager::nxmDescriptionAvailable(QString, int, QVariant userData, QVariant resultData, int requestID) +void DownloadManager::nxmDescriptionAvailable(QString, int, QVariant userData, + QVariant resultData, int requestID) { std::set::iterator idIter = m_RequestIDs.find(requestID); if (idIter == m_RequestIDs.end()) { @@ -1675,8 +1715,9 @@ void DownloadManager::nxmDescriptionAvailable(QString, int, QVariant userData, Q QVariantMap result = resultData.toMap(); - DownloadInfo *info = downloadInfoByID(userData.toInt()); - if (info == nullptr) return; + DownloadInfo* info = downloadInfoByID(userData.toInt()); + if (info == nullptr) + return; info->m_FileInfo->categoryID = result["category_id"].toInt(); QTextDocument doc; doc.setHtml(result["name"].toString().trimmed()); @@ -1688,7 +1729,8 @@ void DownloadManager::nxmDescriptionAvailable(QString, int, QVariant userData, Q } } -void DownloadManager::nxmFilesAvailable(QString, int, QVariant userData, QVariant resultData, int requestID) +void DownloadManager::nxmFilesAvailable(QString, int, QVariant userData, + QVariant resultData, int requestID) { std::set::iterator idIter = m_RequestIDs.find(requestID); if (idIter == m_RequestIDs.end()) { @@ -1697,13 +1739,13 @@ void DownloadManager::nxmFilesAvailable(QString, int, QVariant userData, QVarian m_RequestIDs.erase(idIter); } - DownloadInfo *info = downloadInfoByID(userData.toInt()); - if (info == nullptr) return; + DownloadInfo* info = downloadInfoByID(userData.toInt()); + if (info == nullptr) + return; QVariantMap result = resultData.toMap(); QVariantList files = result["files"].toList(); - // MO sometimes prepends _ to the filename in case of duplicate downloads. // this may muck up the file name comparison QString alternativeLocalName = info->m_FileName; @@ -1717,22 +1759,25 @@ void DownloadManager::nxmFilesAvailable(QString, int, QVariant userData, QVarian bool found = false; for (QVariant file : files) { - QVariantMap fileInfo = file.toMap(); - QString fileName = fileInfo["file_name"].toString(); + QVariantMap fileInfo = file.toMap(); + QString fileName = fileInfo["file_name"].toString(); QString fileNameVariant = fileName.mid(0).replace(' ', '_'); - if ((fileName == info->m_RemoteFileName) || (fileNameVariant == info->m_RemoteFileName) || - (fileName == info->m_FileName) || (fileNameVariant == info->m_FileName) || - (fileName == alternativeLocalName) || (fileNameVariant == alternativeLocalName)) { + if ((fileName == info->m_RemoteFileName) || + (fileNameVariant == info->m_RemoteFileName) || (fileName == info->m_FileName) || + (fileNameVariant == info->m_FileName) || (fileName == alternativeLocalName) || + (fileNameVariant == alternativeLocalName)) { info->m_FileInfo->name = fileInfo["name"].toString(); info->m_FileInfo->version.parse(fileInfo["version"].toString()); if (!info->m_FileInfo->version.isValid()) { info->m_FileInfo->version = info->m_FileInfo->newestVersion; } info->m_FileInfo->fileCategory = fileInfo["category_id"].toInt(); - info->m_FileInfo->fileTime = QDateTime::fromMSecsSinceEpoch(fileInfo["uploaded_timestamp"].toLongLong()); - info->m_FileInfo->fileID = fileInfo["file_id"].toInt(); + info->m_FileInfo->fileTime = + QDateTime::fromMSecsSinceEpoch(fileInfo["uploaded_timestamp"].toLongLong()); + info->m_FileInfo->fileID = fileInfo["file_id"].toInt(); info->m_FileInfo->fileName = fileInfo["file_name"].toString(); - info->m_FileInfo->description = BBCode::convertToHTML(fileInfo["description"].toString()); + info->m_FileInfo->description = + BBCode::convertToHTML(fileInfo["description"].toString()); found = true; break; } @@ -1742,40 +1787,46 @@ void DownloadManager::nxmFilesAvailable(QString, int, QVariant userData, QVarian if (found) { emit showMessage(tr("Information updated")); } else if (result.count() == 0) { - emit showMessage(tr("No matching file found on Nexus! Maybe this file is no longer available or it was renamed?")); + emit showMessage(tr("No matching file found on Nexus! Maybe this file is no " + "longer available or it was renamed?")); } else { - SelectionDialog selection(tr("No file on Nexus matches the selected file by name. Please manually choose the correct one.")); - std::sort(files.begin(), files.end(), [](const QVariant& lhs, const QVariant& rhs) - {return lhs.toMap()["uploaded_timestamp"].toInt() > rhs.toMap()["uploaded_timestamp"].toInt();}); + SelectionDialog selection(tr("No file on Nexus matches the selected file by " + "name. Please manually choose the correct one.")); + std::sort(files.begin(), files.end(), + [](const QVariant& lhs, const QVariant& rhs) { + return lhs.toMap()["uploaded_timestamp"].toInt() > + rhs.toMap()["uploaded_timestamp"].toInt(); + }); for (QVariant file : files) { QVariantMap fileInfo = file.toMap(); if (fileInfo["category_id"].toInt() != NexusInterface::FileStatus::REMOVED && - fileInfo["category_id"].toInt() != NexusInterface::FileStatus::ARCHIVED) + fileInfo["category_id"].toInt() != NexusInterface::FileStatus::ARCHIVED) selection.addChoice(fileInfo["file_name"].toString(), "", file); } if (selection.exec() == QDialog::Accepted) { - QVariantMap fileInfo = selection.getChoiceData().toMap(); + QVariantMap fileInfo = selection.getChoiceData().toMap(); info->m_FileInfo->name = fileInfo["name"].toString(); info->m_FileInfo->version.parse(fileInfo["version"].toString()); info->m_FileInfo->fileCategory = fileInfo["category_id"].toInt(); - info->m_FileInfo->fileID = fileInfo["file_id"].toInt(); + info->m_FileInfo->fileID = fileInfo["file_id"].toInt(); } else { - emit showMessage(tr("No matching file found on Nexus! Maybe this file is no longer available or it was renamed?")); + emit showMessage(tr("No matching file found on Nexus! Maybe this file is no " + "longer available or it was renamed?")); } } } else { if (info->m_FileInfo->fileID == 0) { - log::warn( - "could not determine file id for {} (state {})", - info->m_FileName, info->m_State); + log::warn("could not determine file id for {} (state {})", info->m_FileName, + info->m_State); } } setState(info, STATE_READY); } - -void DownloadManager::nxmFileInfoAvailable(QString gameName, int modID, int fileID, QVariant userData, QVariant resultData, int requestID) +void DownloadManager::nxmFileInfoAvailable(QString gameName, int modID, int fileID, + QVariant userData, QVariant resultData, + int requestID) { std::set::iterator idIter = m_RequestIDs.find(requestID); if (idIter == m_RequestIDs.end()) { @@ -1784,17 +1835,19 @@ void DownloadManager::nxmFileInfoAvailable(QString gameName, int modID, int file m_RequestIDs.erase(idIter); } - ModRepositoryFileInfo *info = qobject_cast(qvariant_cast(userData)); + ModRepositoryFileInfo* info = + qobject_cast(qvariant_cast(userData)); QVariantMap result = resultData.toMap(); - info->name = result["name"].toString(); + info->name = result["name"].toString(); info->version.parse(result["version"].toString()); if (!info->version.isValid()) { info->version = info->newestVersion; } - info->fileName = result["file_name"].toString(); + info->fileName = result["file_name"].toString(); info->fileCategory = result["category_id"].toInt(); - info->fileTime = QDateTime::fromMSecsSinceEpoch(result["uploaded_timestamp"].toLongLong()); + info->fileTime = + QDateTime::fromMSecsSinceEpoch(result["uploaded_timestamp"].toLongLong()); info->description = BBCode::convertToHTML(result["description"].toString()); info->repository = "Nexus"; @@ -1802,31 +1855,33 @@ void DownloadManager::nxmFileInfoAvailable(QString gameName, int modID, int file QStringList games(m_ManagedGame->validShortNames()); games += m_ManagedGame->gameShortName(); for (auto game : games) { - MOBase::IPluginGame *gamePlugin = m_OrganizerCore->getGame(game); - if (gamePlugin != nullptr && gamePlugin->gameNexusName().compare(gameName, Qt::CaseInsensitive) == 0) { + MOBase::IPluginGame* gamePlugin = m_OrganizerCore->getGame(game); + if (gamePlugin != nullptr && + gamePlugin->gameNexusName().compare(gameName, Qt::CaseInsensitive) == 0) { info->gameName = gamePlugin->gameShortName(); } } - info->modID = modID; + info->modID = modID; info->fileID = fileID; - QObject *test = info; - m_RequestIDs.insert(m_NexusInterface->requestDownloadURL(info->gameName, info->modID, info->fileID, this, QVariant::fromValue(test), QString())); + QObject* test = info; + m_RequestIDs.insert( + m_NexusInterface->requestDownloadURL(info->gameName, info->modID, info->fileID, + this, QVariant::fromValue(test), QString())); } -static int evaluateFileInfoMap( - const QVariantMap &map, - const ServerList::container& preferredServers) +static int evaluateFileInfoMap(const QVariantMap& map, + const ServerList::container& preferredServers) { - int preference = 0; - bool found = false; + int preference = 0; + bool found = false; const auto name = map["short_name"].toString(); for (const auto& server : preferredServers) { if (server.name() == name) { preference = server.preferred(); - found = true; + found = true; break; } } @@ -1835,21 +1890,20 @@ static int evaluateFileInfoMap( return 0; } - return 100 + preference * 20; + return 100 + preference * 20; } // sort function to sort by best download server // -bool ServerByPreference( - const ServerList::container& preferredServers, - const QVariant &LHS, const QVariant &RHS) +bool ServerByPreference(const ServerList::container& preferredServers, + const QVariant& LHS, const QVariant& RHS) { const auto a = evaluateFileInfoMap(LHS.toMap(), preferredServers); const auto b = evaluateFileInfoMap(RHS.toMap(), preferredServers); return (a > b); } -int DownloadManager::startDownloadURLs(const QStringList &urls) +int DownloadManager::startDownloadURLs(const QStringList& urls) { ModRepositoryFileInfo info; addDownload(urls, "", -1, -1, &info); @@ -1859,7 +1913,10 @@ int DownloadManager::startDownloadURLs(const QStringList &urls) int DownloadManager::startDownloadNexusFile(int modID, int fileID) { int newID = m_ActiveDownloads.size(); - addNXMDownload(QString("nxm://%1/mods/%2/files/%3").arg(m_ManagedGame->gameShortName()).arg(modID).arg(fileID)); + addNXMDownload(QString("nxm://%1/mods/%2/files/%3") + .arg(m_ManagedGame->gameShortName()) + .arg(modID) + .arg(fileID)); return newID; } @@ -1868,27 +1925,31 @@ QString DownloadManager::downloadPath(int id) return getFilePath(id); } -boost::signals2::connection DownloadManager::onDownloadComplete(const std::function& callback) +boost::signals2::connection +DownloadManager::onDownloadComplete(const std::function& callback) { return m_DownloadComplete.connect(callback); } -boost::signals2::connection DownloadManager::onDownloadPaused(const std::function& callback) +boost::signals2::connection +DownloadManager::onDownloadPaused(const std::function& callback) { return m_DownloadPaused.connect(callback); } -boost::signals2::connection DownloadManager::onDownloadFailed(const std::function& callback) +boost::signals2::connection +DownloadManager::onDownloadFailed(const std::function& callback) { return m_DownloadFailed.connect(callback); } -boost::signals2::connection DownloadManager::onDownloadRemoved(const std::function& callback) +boost::signals2::connection +DownloadManager::onDownloadRemoved(const std::function& callback) { return m_DownloadRemoved.connect(callback); } -int DownloadManager::indexByName(const QString &fileName) const +int DownloadManager::indexByName(const QString& fileName) const { for (int i = 0; i < m_ActiveDownloads.size(); ++i) { if (m_ActiveDownloads[i]->m_FileName == fileName) { @@ -1908,7 +1969,9 @@ int DownloadManager::indexByInfo(const DownloadInfo* info) const return -1; } -void DownloadManager::nxmDownloadURLsAvailable(QString gameName, int modID, int fileID, QVariant userData, QVariant resultData, int requestID) +void DownloadManager::nxmDownloadURLsAvailable(QString gameName, int modID, int fileID, + QVariant userData, QVariant resultData, + int requestID) { using namespace boost::placeholders; @@ -1919,7 +1982,8 @@ void DownloadManager::nxmDownloadURLsAvailable(QString gameName, int modID, int m_RequestIDs.erase(idIter); } - ModRepositoryFileInfo *info = qobject_cast(qvariant_cast(userData)); + ModRepositoryFileInfo* info = + qobject_cast(qvariant_cast(userData)); QVariantList resultList = resultData.toList(); if (resultList.length() == 0) { removePending(gameName, modID, fileID); @@ -1929,23 +1993,21 @@ void DownloadManager::nxmDownloadURLsAvailable(QString gameName, int modID, int const auto servers = m_OrganizerCore->settings().network().servers(); - std::sort( - resultList.begin(), - resultList.end(), - boost::bind(&ServerByPreference, servers.getPreferred(), _1, _2)); + std::sort(resultList.begin(), resultList.end(), + boost::bind(&ServerByPreference, servers.getPreferred(), _1, _2)); info->userData["downloadMap"] = resultList; QStringList URLs; - foreach (const QVariant &server, resultList) { + foreach (const QVariant& server, resultList) { URLs.append(server.toMap()["URI"].toString()); } addDownload(URLs, gameName, modID, fileID, info); } - -void DownloadManager::nxmFileInfoFromMd5Available(QString gameName, QVariant userData, QVariant resultData, int requestID) +void DownloadManager::nxmFileInfoFromMd5Available(QString gameName, QVariant userData, + QVariant resultData, int requestID) { std::set::iterator idIter = m_RequestIDs.find(requestID); if (idIter == m_RequestIDs.end()) { @@ -1954,23 +2016,24 @@ void DownloadManager::nxmFileInfoFromMd5Available(QString gameName, QVariant use m_RequestIDs.erase(idIter); } - DownloadInfo *info = downloadInfoByID(userData.toInt()); + DownloadInfo* info = downloadInfoByID(userData.toInt()); - //This can come back with multiple results with the same file was uploaded multiple times (for whatever reason) + // This can come back with multiple results with the same file was uploaded multiple + // times (for whatever reason) auto resultlist = resultData.toList(); - int chosenIdx = resultlist.count() > 1 ? -1 : 0; + int chosenIdx = resultlist.count() > 1 ? -1 : 0; - //Look for the exact file name + // Look for the exact file name if (chosenIdx < 0) { for (int i = 0; i < resultlist.count(); i++) { - auto results = resultlist[i].toMap(); + auto results = resultlist[i].toMap(); auto fileDetails = results["file_details"].toMap(); - if (fileDetails["file_name"].toString().compare(info->m_FileName, Qt::CaseInsensitive) == 0) { + if (fileDetails["file_name"].toString().compare(info->m_FileName, + Qt::CaseInsensitive) == 0) { if (chosenIdx < 0) { - chosenIdx = i; //intentional to not break in order to check other results - } - else { + chosenIdx = i; // intentional to not break in order to check other results + } else { log::debug("Multiple files with same name found during MD5 search."); chosenIdx = -1; break; @@ -1979,16 +2042,16 @@ void DownloadManager::nxmFileInfoFromMd5Available(QString gameName, QVariant use } } - //Look for the only active one + // Look for the only active one if (chosenIdx < 0) { for (int i = 0; i < resultlist.count(); i++) { - auto results = resultlist[i].toMap(); + auto results = resultlist[i].toMap(); auto fileDetails = results["file_details"].toMap(); if (fileDetails["category_id"].toInt() != NexusInterface::FileStatus::REMOVED && fileDetails["category_id"].toInt() != NexusInterface::FileStatus::ARCHIVED) { if (chosenIdx < 0) { - chosenIdx = i; //intentional to not break in order to check other results + chosenIdx = i; // intentional to not break in order to check other results } else { log::debug("Multiple active files found during MD5 search."); chosenIdx = -1; @@ -1998,36 +2061,38 @@ void DownloadManager::nxmFileInfoFromMd5Available(QString gameName, QVariant use } } - //Unable to determine the correct mod / file. Revert to the old method + // Unable to determine the correct mod / file. Revert to the old method if (chosenIdx < 0) { - //don't use the normal state set function as we don't want to create a meta file + // don't use the normal state set function as we don't want to create a meta file info->m_State = DownloadManager::STATE_READY; queryInfo(m_ActiveDownloads.indexOf(info)); return; } - auto results = resultlist[chosenIdx].toMap(); + auto results = resultlist[chosenIdx].toMap(); auto fileDetails = results["file_details"].toMap(); - auto modDetails = results["mod"].toMap(); + auto modDetails = results["mod"].toMap(); - info->m_FileInfo->name = fileDetails["name"].toString(); + info->m_FileInfo->name = fileDetails["name"].toString(); info->m_FileInfo->fileID = fileDetails["file_id"].toInt(); - info->m_FileInfo->description = BBCode::convertToHTML(fileDetails["description"].toString()); + info->m_FileInfo->description = + BBCode::convertToHTML(fileDetails["description"].toString()); info->m_FileInfo->version.parse(fileDetails["version"].toString()); if (!info->m_FileInfo->version.isValid()) info->m_FileInfo->version.parse(fileDetails["mod_version"].toString()); info->m_FileInfo->fileCategory = fileDetails["category_id"].toInt(); - info->m_FileInfo->modID = modDetails["mod_id"].toInt(); - info->m_FileInfo->modName = modDetails["name"].toString(); + info->m_FileInfo->modID = modDetails["mod_id"].toInt(); + info->m_FileInfo->modName = modDetails["name"].toString(); info->m_FileInfo->categoryID = modDetails["category_id"].toInt(); QString gameShortName = gameName; QStringList games(m_ManagedGame->validShortNames()); games += m_ManagedGame->gameShortName(); for (auto game : games) { - MOBase::IPluginGame *gamePlugin = m_OrganizerCore->getGame(game); - if (gamePlugin != nullptr && gamePlugin->gameNexusName().compare(gameName, Qt::CaseInsensitive) == 0) { + MOBase::IPluginGame* gamePlugin = m_OrganizerCore->getGame(game); + if (gamePlugin != nullptr && + gamePlugin->gameNexusName().compare(gameName, Qt::CaseInsensitive) == 0) { gameShortName = gamePlugin->gameShortName(); break; } @@ -2035,7 +2100,7 @@ void DownloadManager::nxmFileInfoFromMd5Available(QString gameName, QVariant use info->m_FileInfo->gameName = gameShortName; - //If the file description is not present, send another query to get it + // If the file description is not present, send another query to get it if (!fileDetails["description"].isValid()) { info->m_RemoteFileName = fileDetails["file_name"].toString(); setState(info, STATE_FETCHINGFILEINFO); @@ -2044,8 +2109,9 @@ void DownloadManager::nxmFileInfoFromMd5Available(QString gameName, QVariant use } } - -void DownloadManager::nxmRequestFailed(QString gameName, int modID, int fileID, QVariant userData, int requestID, int errorCode, const QString &errorString) +void DownloadManager::nxmRequestFailed(QString gameName, int modID, int fileID, + QVariant userData, int requestID, int errorCode, + const QString& errorString) { std::set::iterator idIter = m_RequestIDs.find(requestID); if (idIter == m_RequestIDs.end()) { @@ -2054,12 +2120,13 @@ void DownloadManager::nxmRequestFailed(QString gameName, int modID, int fileID, m_RequestIDs.erase(idIter); } - DownloadInfo *userDataInfo = downloadInfoByID(userData.toInt()); + DownloadInfo* userDataInfo = downloadInfoByID(userData.toInt()); int index = 0; - for (QVector::iterator iter = m_ActiveDownloads.begin(); iter != m_ActiveDownloads.end(); ++iter, ++index) { - DownloadInfo *info = *iter; + for (QVector::iterator iter = m_ActiveDownloads.begin(); + iter != m_ActiveDownloads.end(); ++iter, ++index) { + DownloadInfo* info = *iter; if (info != userDataInfo) continue; @@ -2093,17 +2160,16 @@ void DownloadManager::nxmRequestFailed(QString gameName, int modID, int fileID, emit showMessage(tr("Failed to request file info from nexus: %1").arg(errorString)); } - void DownloadManager::downloadFinished(int index) { - DownloadInfo *info; + DownloadInfo* info; if (index) info = m_ActiveDownloads[index]; else info = findDownload(this->sender(), &index); if (info != nullptr) { - QNetworkReply *reply = info->m_Reply; + QNetworkReply* reply = info->m_Reply; QByteArray data; if (reply->isOpen() && info->m_HasData) { data = reply->readAll(); @@ -2113,18 +2179,26 @@ void DownloadManager::downloadFinished(int index) TaskProgressManager::instance().forgetMe(info->m_TaskProgressId); bool error = false; - if ((info->m_State != STATE_CANCELING) && - (info->m_State != STATE_PAUSING)) { - bool textData = reply->header(QNetworkRequest::ContentTypeHeader).toString().startsWith("text", Qt::CaseInsensitive); + if ((info->m_State != STATE_CANCELING) && (info->m_State != STATE_PAUSING)) { + bool textData = reply->header(QNetworkRequest::ContentTypeHeader) + .toString() + .startsWith("text", Qt::CaseInsensitive); if (textData) - emit showMessage(tr("Warning: Content type is: %1").arg(reply->header(QNetworkRequest::ContentTypeHeader).toString())); + emit showMessage( + tr("Warning: Content type is: %1") + .arg(reply->header(QNetworkRequest::ContentTypeHeader).toString())); if ((info->m_Output.size() == 0) || - ((reply->error() != QNetworkReply::NoError) - && (reply->error() != QNetworkReply::OperationCanceledError))) { + ((reply->error() != QNetworkReply::NoError) && + (reply->error() != QNetworkReply::OperationCanceledError))) { if (reply->error() == QNetworkReply::UnknownContentError) - emit showMessage(tr("Download header content length: %1 downloaded file size: %2").arg(reply->header(QNetworkRequest::ContentLengthHeader).toLongLong()).arg(info->m_Output.size())); + emit showMessage( + tr("Download header content length: %1 downloaded file size: %2") + .arg(reply->header(QNetworkRequest::ContentLengthHeader).toLongLong()) + .arg(info->m_Output.size())); if (info->m_Tries == 0) { - emit showMessage(tr("Download failed: %1 (%2)").arg(reply->errorString()).arg(reply->error())); + emit showMessage(tr("Download failed: %1 (%2)") + .arg(reply->errorString()) + .arg(reply->error())); } error = true; setState(info, STATE_ERROR); @@ -2146,7 +2220,9 @@ void DownloadManager::downloadFinished(int index) delete info; m_ActiveDownloads.erase(m_ActiveDownloads.begin() + index); if (error) - emit showMessage(tr("We were unable to download the file due to errors after four retries. There may be an issue with the Nexus servers.")); + emit showMessage( + tr("We were unable to download the file due to errors after four retries. " + "There may be an issue with the Nexus servers.")); emit update(-1); } else if (info->isPausedState() || info->m_State == STATE_PAUSING) { info->m_Output.close(); @@ -2155,20 +2231,25 @@ void DownloadManager::downloadFinished(int index) } else { QString url = info->m_Urls[info->m_CurrentUrl]; if (info->m_FileInfo->userData.contains("downloadMap")) { - foreach (const QVariant &server, info->m_FileInfo->userData["downloadMap"].toList()) { + foreach (const QVariant& server, + info->m_FileInfo->userData["downloadMap"].toList()) { QVariantMap serverMap = server.toMap(); if (serverMap["URI"].toString() == url) { int deltaTime = info->m_StartTime.elapsed() / 1000; if (deltaTime > 5) { - emit downloadSpeed(serverMap["short_name"].toString(), (info->m_TotalSize - info->m_PreResumeSize) / deltaTime); - } // no division by zero please! Also, if the download is shorter than a few seconds, the result is way to inprecise + emit downloadSpeed(serverMap["short_name"].toString(), + (info->m_TotalSize - info->m_PreResumeSize) / + deltaTime); + } // no division by zero please! Also, if the download is shorter than a + // few seconds, the result is way to inprecise break; } } } bool isNexus = info->m_FileInfo->repository == "Nexus"; - // need to change state before changing the file name, otherwise .unfinished is appended + // need to change state before changing the file name, otherwise .unfinished is + // appended if (isNexus) { setState(info, STATE_FETCHINGMODINFO); } else { @@ -2182,7 +2263,8 @@ void DownloadManager::downloadFinished(int index) if (!newName.isEmpty() && (oldName.isEmpty())) { info->setName(getDownloadFileName(newName), true); } else { - info->setName(m_OutputDirectory + "/" + info->m_FileName, true); // don't rename but remove the ".unfinished" extension + info->setName(m_OutputDirectory + "/" + info->m_FileName, + true); // don't rename but remove the ".unfinished" extension } endDisableDirWatcher(); @@ -2204,23 +2286,21 @@ void DownloadManager::downloadFinished(int index) } } - void DownloadManager::downloadError(QNetworkReply::NetworkError error) { if (error != QNetworkReply::OperationCanceledError) { - QNetworkReply *reply = qobject_cast(sender()); + QNetworkReply* reply = qobject_cast(sender()); log::warn("{} ({})", - reply != nullptr ? reply->errorString() : "Download error occured", - error); + reply != nullptr ? reply->errorString() : "Download error occured", + error); } } - void DownloadManager::metaDataChanged() { int index = 0; - DownloadInfo *info = findDownload(this->sender(), &index); + DownloadInfo* info = findDownload(this->sender(), &index); if (info != nullptr) { QString newName = getFileNameFromNetworkReply(info->m_Reply); if (!newName.isEmpty() && (info->m_FileName.isEmpty())) { @@ -2228,7 +2308,8 @@ void DownloadManager::metaDataChanged() info->setName(getDownloadFileName(newName), true); endDisableDirWatcher(); refreshAlphabeticalTranslation(); - if (!info->m_Output.isOpen() && !info->m_Output.open(QIODevice::WriteOnly | QIODevice::Append)) { + if (!info->m_Output.isOpen() && + !info->m_Output.open(QIODevice::WriteOnly | QIODevice::Append)) { reportError(tr("failed to re-open %1").arg(info->m_FileName)); setState(info, STATE_CANCELING); } @@ -2240,11 +2321,11 @@ void DownloadManager::metaDataChanged() void DownloadManager::directoryChanged(const QString&) { - if(DownloadManager::m_DirWatcherDisabler==0) + if (DownloadManager::m_DirWatcherDisabler == 0) refreshList(); } -void DownloadManager::managedGameChanged(MOBase::IPluginGame const *managedGame) +void DownloadManager::managedGameChanged(MOBase::IPluginGame const* managedGame) { m_ManagedGame = managedGame; } @@ -2252,8 +2333,11 @@ void DownloadManager::managedGameChanged(MOBase::IPluginGame const *managedGame) void DownloadManager::checkDownloadTimeout() { for (int i = 0; i < m_ActiveDownloads.size(); ++i) { - if (m_ActiveDownloads[i]->m_StartTime.elapsed() - m_ActiveDownloads[i]->m_DownloadTimeLast > 5 * 1000 && - m_ActiveDownloads[i]->m_State == STATE_DOWNLOADING && m_ActiveDownloads[i]->m_Reply != nullptr && + if (m_ActiveDownloads[i]->m_StartTime.elapsed() - + m_ActiveDownloads[i]->m_DownloadTimeLast > + 5 * 1000 && + m_ActiveDownloads[i]->m_State == STATE_DOWNLOADING && + m_ActiveDownloads[i]->m_Reply != nullptr && m_ActiveDownloads[i]->m_Reply->isOpen()) { pauseDownload(i); downloadFinished(i); @@ -2262,21 +2346,23 @@ void DownloadManager::checkDownloadTimeout() } } -void DownloadManager::writeData(DownloadInfo *info) +void DownloadManager::writeData(DownloadInfo* info) { if (info != nullptr) { qint64 ret = info->m_Output.write(info->m_Reply->readAll()); if (ret < info->m_Reply->size()) { - QString fileName = info->m_FileName; // m_FileName may be destroyed after setState + QString fileName = + info->m_FileName; // m_FileName may be destroyed after setState setState(info, DownloadState::STATE_CANCELED); - log::error( - "Unable to write download \"{}\" to drive (return {})", - info->m_FileName, ret); + log::error("Unable to write download \"{}\" to drive (return {})", + info->m_FileName, ret); reportError(tr("Unable to write download to drive (return %1).\n" "Check the drive's available storage.\n\n" - "Canceling download \"%2\"...").arg(ret).arg(fileName)); + "Canceling download \"%2\"...") + .arg(ret) + .arg(fileName)); } } } diff --git a/src/downloadmanager.h b/src/downloadmanager.h index f4c796d50..359455d95 100644 --- a/src/downloadmanager.h +++ b/src/downloadmanager.h @@ -21,44 +21,48 @@ along with Mod Organizer. If not, see . #define DOWNLOADMANAGER_H #include "serverinfo.h" -#include -#include -#include -#include -#include -#include +#include #include +#include +#include #include +#include +#include +#include +#include #include #include -#include +#include #include -#include -#include -#include -#include -#include #include #include #include +#include +#include +#include +#include using namespace boost::accumulators; -namespace MOBase { class IPluginGame; } +namespace MOBase +{ +class IPluginGame; +} class NexusInterface; class PluginContainer; class OrganizerCore; /*! - * \brief manages downloading of files and provides progress information for gui elements + * \brief manages downloading of files and provides progress information for gui + *elements **/ class DownloadManager : public QObject { Q_OBJECT public: - - enum DownloadState { + enum DownloadState + { STATE_STARTED = 0, STATE_DOWNLOADING, STATE_CANCELING, @@ -76,8 +80,8 @@ class DownloadManager : public QObject }; private: - - struct DownloadInfo { + struct DownloadInfo + { ~DownloadInfo() { delete m_FileInfo; } accumulator_set> m_DownloadAcc; accumulator_set> m_DownloadTimeAcc; @@ -86,7 +90,7 @@ class DownloadManager : public QObject unsigned int m_DownloadID; QString m_FileName; QFile m_Output; - QNetworkReply *m_Reply; + QNetworkReply* m_Reply; QElapsedTimer m_StartTime; qint64 m_PreResumeSize; std::pair m_Progress; @@ -96,7 +100,8 @@ class DownloadManager : public QObject QStringList m_Urls; qint64 m_ResumePos; qint64 m_TotalSize; - QDateTime m_Created; // used as a cache in DownloadManager::getFileTime, may not be valid elsewhere + QDateTime m_Created; // used as a cache in DownloadManager::getFileTime, may not be + // valid elsewhere QByteArray m_Hash; QStringList m_GamesToQuery; QString m_RemoteFileName; @@ -106,22 +111,23 @@ class DownloadManager : public QObject quint32 m_TaskProgressId; - MOBase::ModRepositoryFileInfo *m_FileInfo { nullptr }; + MOBase::ModRepositoryFileInfo* m_FileInfo{nullptr}; bool m_Hidden; - static DownloadInfo *createNew(const MOBase::ModRepositoryFileInfo *fileInfo, const QStringList &URLs); - static DownloadInfo *createFromMeta( - const QString &filePath, bool showHidden, const QString outputDirectory, - std::optional fileSize={}); + static DownloadInfo* createNew(const MOBase::ModRepositoryFileInfo* fileInfo, + const QStringList& URLs); + static DownloadInfo* createFromMeta(const QString& filePath, bool showHidden, + const QString outputDirectory, + std::optional fileSize = {}); /** * @brief rename the file * this will change the file name as well as the display name. It will automatically * append .unfinished to the name if this file is still being downloaded * @param newName the new name to setName - * @param renameFile if true, the file is assumed to exist and renamed. If the file does not - * yet exist, set this to false + * @param renameFile if true, the file is assumed to exist and renamed. If the file + *does not yet exist, set this to false **/ void setName(QString newName, bool renameFile); @@ -130,13 +136,17 @@ class DownloadManager : public QObject bool isPausedState(); QString currentURL(); + private: static unsigned int s_NextDownloadID; + private: - DownloadInfo() : m_TotalSize(0), m_ReQueried(false), m_Hidden(false), m_HasData(false), - m_DownloadTimeLast(0), m_DownloadLast(0), - m_DownloadAcc(tag::rolling_window::window_size = 200), - m_DownloadTimeAcc(tag::rolling_window::window_size = 200) {} + DownloadInfo() + : m_TotalSize(0), m_ReQueried(false), m_Hidden(false), m_HasData(false), + m_DownloadTimeLast(0), m_DownloadLast(0), + m_DownloadAcc(tag::rolling_window::window_size = 200), + m_DownloadTimeAcc(tag::rolling_window::window_size = 200) + {} }; friend class DownloadManagerProxy; @@ -144,14 +154,14 @@ class DownloadManager : public QObject using SignalDownloadCallback = boost::signals2::signal; public: - /** * @brief constructor * - * @param nexusInterface interface to use to retrieve information from the relevant nexus page + * @param nexusInterface interface to use to retrieve information from the relevant + *nexus page * @param parent parent object **/ - explicit DownloadManager(NexusInterface *nexusInterface, QObject *parent); + explicit DownloadManager(NexusInterface* nexusInterface, QObject* parent); ~DownloadManager(); @@ -165,10 +175,12 @@ class DownloadManager : public QObject bool downloadsInProgress(); /** - * @brief determine if a download is currently in progress, does not count paused ones. - * - * @return true if there is currently a download in progress (that is not paused already). - **/ + * @brief determine if a download is currently in progress, does not count paused + *ones. + * + * @return true if there is currently a download in progress (that is not paused + *already). + **/ bool downloadsInProgressNoPause(); /** @@ -176,17 +188,19 @@ class DownloadManager : public QObject * * @param outputDirectory the new output directory **/ - void setOutputDirectory(const QString &outputDirectory, const bool refresh = true); + void setOutputDirectory(const QString& outputDirectory, const bool refresh = true); /** - * @brief disables feedback from the downlods fileSystemWhatcher untill disableDownloadsWatcherEnd() is called - * - **/ + * @brief disables feedback from the downlods fileSystemWhatcher untill + *disableDownloadsWatcherEnd() is called + * + **/ static void startDisableDirWatcher(); /** - * @brief re-enables feedback from the downlods fileSystemWhatcher after disableDownloadsWatcherStart() was called - **/ + * @brief re-enables feedback from the downlods fileSystemWhatcher after + *disableDownloadsWatcherStart() was called + **/ static void endDisableDirWatcher(); /** @@ -199,26 +213,32 @@ class DownloadManager : public QObject */ void setShowHidden(bool showHidden); - void setPluginContainer(PluginContainer *pluginContainer); + void setPluginContainer(PluginContainer* pluginContainer); /** * @brief download from an already open network connection * * @param reply the network reply to download from * @param fileInfo information about the file, like mod id, file id, version, ... - * @return true if the download was started, false if it wasn't. The latter currently only happens if there is a duplicate and the user decides not to download again + * @return true if the download was started, false if it wasn't. The latter currently + *only happens if there is a duplicate and the user decides not to download again **/ - bool addDownload(QNetworkReply *reply, const MOBase::ModRepositoryFileInfo *fileInfo); + bool addDownload(QNetworkReply* reply, const MOBase::ModRepositoryFileInfo* fileInfo); /** * @brief download from an already open network connection * * @param reply the network reply to download from - * @param fileName the name to use for the file. This may be overridden by the name in the fileInfo-structure or if the http stream specifies a name + * @param fileName the name to use for the file. This may be overridden by the name in + *the fileInfo-structure or if the http stream specifies a name * @param fileInfo information previously retrieved from the nexus network - * @return true if the download was started, false if it wasn't. The latter currently only happens if there is a duplicate and the user decides not to download again + * @return true if the download was started, false if it wasn't. The latter currently + *only happens if there is a duplicate and the user decides not to download again **/ - bool addDownload(QNetworkReply *reply, const QStringList &URLs, const QString &fileName, QString gameName, int modID, int fileID = 0, const MOBase::ModRepositoryFileInfo *fileInfo = new MOBase::ModRepositoryFileInfo()); + bool addDownload(QNetworkReply* reply, const QStringList& URLs, + const QString& fileName, QString gameName, int modID, int fileID = 0, + const MOBase::ModRepositoryFileInfo* fileInfo = + new MOBase::ModRepositoryFileInfo()); /** * @brief start a download using a nxm-link @@ -226,26 +246,30 @@ class DownloadManager : public QObject * starts a download using a nxm-link. The download manager will first query the nexus * page for file information. * @param url a nxm link looking like this: nxm://skyrim/mods/1234/files/4711 - * @todo the game name encoded into the link is currently ignored, all downloads are incorrectly assumed to be for the identified game + * @todo the game name encoded into the link is currently ignored, all downloads are + *incorrectly assumed to be for the identified game **/ - void addNXMDownload(const QString &url); + void addNXMDownload(const QString& url); /** - * @brief retrieve the total number of downloads, both finished and unfinished including downloads from previous sessions + * @brief retrieve the total number of downloads, both finished and unfinished + *including downloads from previous sessions * * @return total number of downloads **/ int numTotalDownloads() const; /** - * @brief retrieve number of pending downloads (nexus downloads for which we don't know the name and url yet) + * @brief retrieve number of pending downloads (nexus downloads for which we don't + * know the name and url yet) * @return number of pending downloads */ int numPendingDownloads() const; /** * @brief retrieve the info of a pending download - * @param index index of the pending download (index in the range [0, numPendingDownloads()[) + * @param index index of the pending download (index in the range [0, + * numPendingDownloads()[) * @return pair of modid, fileid */ std::tuple getPendingDownload(int index); @@ -369,7 +393,7 @@ class DownloadManager : public QObject * @param index index of the file to look up * @return the nexus mod information **/ - const MOBase::ModRepositoryFileInfo *getFileInfo(int index) const; + const MOBase::ModRepositoryFileInfo* getFileInfo(int index) const; /** * @brief mark a download as installed @@ -394,23 +418,26 @@ class DownloadManager : public QObject */ void refreshList(); -public: // IDownloadManager interface: - - int startDownloadURLs(const QStringList &urls); +public: // IDownloadManager interface: + int startDownloadURLs(const QStringList& urls); int startDownloadNexusFile(int modID, int fileID); QString downloadPath(int id); - boost::signals2::connection onDownloadComplete(const std::function& callback); - boost::signals2::connection onDownloadPaused(const std::function& callback); - boost::signals2::connection onDownloadFailed(const std::function& callback); - boost::signals2::connection onDownloadRemoved(const std::function& callback); + boost::signals2::connection + onDownloadComplete(const std::function& callback); + boost::signals2::connection + onDownloadPaused(const std::function& callback); + boost::signals2::connection + onDownloadFailed(const std::function& callback); + boost::signals2::connection + onDownloadRemoved(const std::function& callback); /** * @brief retrieve a download index from the filename * @param fileName file to look up * @return index of that download or -1 if it wasn't found */ - int indexByName(const QString &fileName) const; + int indexByName(const QString& fileName) const; int indexByInfo(const DownloadInfo* info) const; void pauseAll(); @@ -431,7 +458,7 @@ class DownloadManager : public QObject * * @param message the message to display **/ - void showMessage(const QString &message); + void showMessage(const QString& message); /** * @brief emitted whenever the state of a download changes @@ -441,9 +468,10 @@ class DownloadManager : public QObject void stateChanged(int row, DownloadManager::DownloadState state); /** - * @brief emitted whenever a download completes successfully, reporting the download speed for the server used + * @brief emitted whenever a download completes successfully, reporting the download + * speed for the server used */ - void downloadSpeed(const QString &serverName, int bytesPerSecond); + void downloadSpeed(const QString& serverName, int bytesPerSecond); /** * @brief emitted whenever a new download is added to the list @@ -456,7 +484,8 @@ public slots: * @brief removes the specified download * * @param index index of the download to remove - * @param deleteFile if true, the file will also be deleted from disc, otherwise it is only marked as hidden. + * @param deleteFile if true, the file will also be deleted from disc, otherwise it is + *only marked as hidden. **/ void removeDownload(int index, bool deleteFile); @@ -467,7 +496,8 @@ public slots: void restoreDownload(int index); /** - * @brief cancel the specified download. This will lead to the corresponding file to be deleted + * @brief cancel the specified download. This will lead to the corresponding file to + *be deleted * * @param index index of the download to cancel **/ @@ -489,19 +519,25 @@ public slots: void openInDownloadsFolder(int index); - void nxmDescriptionAvailable(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID); + void nxmDescriptionAvailable(QString gameName, int modID, QVariant userData, + QVariant resultData, int requestID); - void nxmFilesAvailable(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID); + void nxmFilesAvailable(QString gameName, int modID, QVariant userData, + QVariant resultData, int requestID); - void nxmFileInfoAvailable(QString gameName, int modID, int fileID, QVariant userData, QVariant resultData, int requestID); + void nxmFileInfoAvailable(QString gameName, int modID, int fileID, QVariant userData, + QVariant resultData, int requestID); - void nxmDownloadURLsAvailable(QString gameName, int modID, int fileID, QVariant userData, QVariant resultData, int requestID); + void nxmDownloadURLsAvailable(QString gameName, int modID, int fileID, + QVariant userData, QVariant resultData, int requestID); - void nxmFileInfoFromMd5Available(QString gameName, QVariant userData, QVariant resultData, int requestID); + void nxmFileInfoFromMd5Available(QString gameName, QVariant userData, + QVariant resultData, int requestID); - void nxmRequestFailed(QString gameName, int modID, int fileID, QVariant userData, int requestID, int errorCode, const QString &errorString); + void nxmRequestFailed(QString gameName, int modID, int fileID, QVariant userData, + int requestID, int errorCode, const QString& errorString); - void managedGameChanged(MOBase::IPluginGame const *gamePlugin); + void managedGameChanged(MOBase::IPluginGame const* gamePlugin); private slots: @@ -510,16 +546,14 @@ private slots: void downloadFinished(int index = 0); void downloadError(QNetworkReply::NetworkError error); void metaDataChanged(); - void directoryChanged(const QString &dirctory); + void directoryChanged(const QString& dirctory); void checkDownloadTimeout(); private: - - void createMetaFile(DownloadInfo *info); + void createMetaFile(DownloadInfo* info); DownloadManager::DownloadInfo* getDownloadInfo(QString fileName); public: - /** Get a unique filename for a download. * * This allows you multiple versions of download files, useful if the file @@ -529,11 +563,10 @@ private slots: * * @return Unique(ish) name */ - QString getDownloadFileName(const QString &baseName, bool rename = false) const; + QString getDownloadFileName(const QString& baseName, bool rename = false) const; private: - - void startDownload(QNetworkReply *reply, DownloadInfo *newDownload, bool resume); + void startDownload(QNetworkReply* reply, DownloadInfo* newDownload, bool resume); void resumeDownloadInt(int index); /** @@ -541,12 +574,15 @@ private slots: * * @param url the url to download from * @param fileInfo information previously retrieved from the mod page - * @return true if the download was started, false if it wasn't. The latter currently only happens if there is a duplicate and the user decides not to download again + * @return true if the download was started, false if it wasn't. The latter currently + *only happens if there is a duplicate and the user decides not to download again **/ - bool addDownload(const QStringList &URLs, QString gameName, int modID, int fileID, const MOBase::ModRepositoryFileInfo *fileInfo); + bool addDownload(const QStringList& URLs, QString gameName, int modID, int fileID, + const MOBase::ModRepositoryFileInfo* fileInfo); - // important: the caller has to lock the list-mutex, otherwise the DownloadInfo-pointer might get invalidated at any time - DownloadInfo *findDownload(QObject *reply, int *index = nullptr) const; + // important: the caller has to lock the list-mutex, otherwise the + // DownloadInfo-pointer might get invalidated at any time + DownloadInfo* findDownload(QObject* reply, int* index = nullptr) const; void removeFile(int index, bool deleteFile); @@ -554,27 +590,25 @@ private slots: bool ByName(int LHS, int RHS); - QString getFileNameFromNetworkReply(QNetworkReply *reply); + QString getFileNameFromNetworkReply(QNetworkReply* reply); - void setState(DownloadInfo *info, DownloadManager::DownloadState state); + void setState(DownloadInfo* info, DownloadManager::DownloadState state); - DownloadInfo *downloadInfoByID(unsigned int id); + DownloadInfo* downloadInfoByID(unsigned int id); void removePending(QString gameName, int modID, int fileID); static QString getFileTypeString(int fileType); - void writeData(DownloadInfo *info); + void writeData(DownloadInfo* info); private: - static const int AUTOMATIC_RETRIES = 3; private: + NexusInterface* m_NexusInterface; - NexusInterface *m_NexusInterface; - - OrganizerCore *m_OrganizerCore; + OrganizerCore* m_OrganizerCore; QWidget* m_ParentWidget; QVector> m_PendingDownloads; @@ -592,21 +626,20 @@ private slots: SignalDownloadCallback m_DownloadFailed; SignalDownloadCallback m_DownloadRemoved; - //The dirWatcher is actually triggering off normal Mo operations such as deleting downloads or editing .meta files - //so it needs to be disabled during operations that are known to cause the creation or deletion of files in the Downloads folder. - //Notably using QSettings to edit a file creates a temporarily .lock file that causes the Watcher to trigger multiple listRefreshes freezing the ui. + // The dirWatcher is actually triggering off normal Mo operations such as deleting + // downloads or editing .meta files so it needs to be disabled during operations that + // are known to cause the creation or deletion of files in the Downloads folder. + // Notably using QSettings to edit a file creates a temporarily .lock file that causes + // the Watcher to trigger multiple listRefreshes freezing the ui. static int m_DirWatcherDisabler; - std::map m_DownloadFails; bool m_ShowHidden; - MOBase::IPluginGame const *m_ManagedGame; + MOBase::IPluginGame const* m_ManagedGame; QTimer m_TimeoutTimer; }; - - -#endif // DOWNLOADMANAGER_H +#endif // DOWNLOADMANAGER_H diff --git a/src/downloadmanagerproxy.cpp b/src/downloadmanagerproxy.cpp index 95099044e..ab4ed83bb 100644 --- a/src/downloadmanagerproxy.cpp +++ b/src/downloadmanagerproxy.cpp @@ -1,13 +1,15 @@ #include "downloadmanagerproxy.h" -#include "proxyutils.h" #include "organizerproxy.h" +#include "proxyutils.h" using namespace MOBase; using namespace MOShared; -DownloadManagerProxy::DownloadManagerProxy(OrganizerProxy* oproxy, DownloadManager* downloadManager) : - m_OrganizerProxy(oproxy), m_Proxied(downloadManager) { } +DownloadManagerProxy::DownloadManagerProxy(OrganizerProxy* oproxy, + DownloadManager* downloadManager) + : m_OrganizerProxy(oproxy), m_Proxied(downloadManager) +{} DownloadManagerProxy::~DownloadManagerProxy() { @@ -16,10 +18,14 @@ DownloadManagerProxy::~DownloadManagerProxy() void DownloadManagerProxy::connectSignals() { - m_Connections.push_back(m_Proxied->onDownloadComplete(callSignalIfPluginActive(m_OrganizerProxy, m_DownloadComplete))); - m_Connections.push_back(m_Proxied->onDownloadFailed(callSignalIfPluginActive(m_OrganizerProxy, m_DownloadFailed))); - m_Connections.push_back(m_Proxied->onDownloadRemoved(callSignalIfPluginActive(m_OrganizerProxy, m_DownloadRemoved))); - m_Connections.push_back(m_Proxied->onDownloadPaused(callSignalIfPluginActive(m_OrganizerProxy, m_DownloadPaused))); + m_Connections.push_back(m_Proxied->onDownloadComplete( + callSignalIfPluginActive(m_OrganizerProxy, m_DownloadComplete))); + m_Connections.push_back(m_Proxied->onDownloadFailed( + callSignalIfPluginActive(m_OrganizerProxy, m_DownloadFailed))); + m_Connections.push_back(m_Proxied->onDownloadRemoved( + callSignalIfPluginActive(m_OrganizerProxy, m_DownloadRemoved))); + m_Connections.push_back(m_Proxied->onDownloadPaused( + callSignalIfPluginActive(m_OrganizerProxy, m_DownloadPaused))); } void DownloadManagerProxy::disconnectSignals() diff --git a/src/downloadmanagerproxy.h b/src/downloadmanagerproxy.h index 729fde91c..dde4efe18 100644 --- a/src/downloadmanagerproxy.h +++ b/src/downloadmanagerproxy.h @@ -1,8 +1,8 @@ #ifndef DOWNLOADMANAGERPROXY_H #define DOWNLOADMANAGERPROXY_H -#include #include "downloadmanager.h" +#include class OrganizerProxy; @@ -10,7 +10,6 @@ class DownloadManagerProxy : public MOBase::IDownloadManager { public: - DownloadManagerProxy(OrganizerProxy* oproxy, DownloadManager* downloadManager); virtual ~DownloadManagerProxy(); @@ -24,7 +23,6 @@ class DownloadManagerProxy : public MOBase::IDownloadManager bool onDownloadRemoved(const std::function& callback) override; private: - friend class OrganizerProxy; // See OrganizerProxy::connectSignals(). @@ -42,4 +40,4 @@ class DownloadManagerProxy : public MOBase::IDownloadManager std::vector m_Connections; }; -#endif // ORGANIZERPROXY_H +#endif // ORGANIZERPROXY_H diff --git a/src/downloadstab.cpp b/src/downloadstab.cpp index 8a6573bd3..bf3ea9ad0 100644 --- a/src/downloadstab.cpp +++ b/src/downloadstab.cpp @@ -5,16 +5,15 @@ #include "ui_mainwindow.h" DownloadsTab::DownloadsTab(OrganizerCore& core, Ui::MainWindow* mwui) - : m_core(core), ui{ - mwui->btnRefreshDownloads, mwui->downloadView, mwui->showHiddenBox, - mwui->downloadFilterEdit} + : m_core(core), ui{mwui->btnRefreshDownloads, mwui->downloadView, + mwui->showHiddenBox, mwui->downloadFilterEdit} { - DownloadList *sourceModel = new DownloadList(m_core, ui.list); + DownloadList* sourceModel = new DownloadList(m_core, ui.list); ui.list->setModel(sourceModel); ui.list->setManager(m_core.downloadManager()); - ui.list->setItemDelegate(new DownloadProgressDelegate( - m_core.downloadManager(), ui.list)); + ui.list->setItemDelegate( + new DownloadProgressDelegate(m_core.downloadManager(), ui.list)); update(); @@ -24,19 +23,33 @@ DownloadsTab::DownloadsTab(OrganizerCore& core, Ui::MainWindow* mwui) return sourceModel->lessThanPredicate(left, right); }); - connect(ui.refresh, &QPushButton::clicked, [&]{ refresh(); }); + connect(ui.refresh, &QPushButton::clicked, [&] { + refresh(); + }); connect(ui.list, SIGNAL(installDownload(int)), &m_core, SLOT(installDownload(int))); - connect(ui.list, SIGNAL(queryInfo(int)), m_core.downloadManager(), SLOT(queryInfo(int))); - connect(ui.list, SIGNAL(queryInfoMd5(int)), m_core.downloadManager(), SLOT(queryInfoMd5(int))); - connect(ui.list, SIGNAL(visitOnNexus(int)), m_core.downloadManager(), SLOT(visitOnNexus(int))); - connect(ui.list, SIGNAL(openFile(int)), m_core.downloadManager(), SLOT(openFile(int))); - connect(ui.list, SIGNAL(openMetaFile(int)), m_core.downloadManager(), SLOT(openMetaFile(int))); - connect(ui.list, SIGNAL(openInDownloadsFolder(int)), m_core.downloadManager(), SLOT(openInDownloadsFolder(int))); - connect(ui.list, SIGNAL(removeDownload(int, bool)), m_core.downloadManager(), SLOT(removeDownload(int, bool))); - connect(ui.list, SIGNAL(restoreDownload(int)), m_core.downloadManager(), SLOT(restoreDownload(int))); - connect(ui.list, SIGNAL(cancelDownload(int)), m_core.downloadManager(), SLOT(cancelDownload(int))); - connect(ui.list, SIGNAL(pauseDownload(int)), m_core.downloadManager(), SLOT(pauseDownload(int))); - connect(ui.list, &DownloadListView::resumeDownload, [&](int i){ resumeDownload(i); }); + connect(ui.list, SIGNAL(queryInfo(int)), m_core.downloadManager(), + SLOT(queryInfo(int))); + connect(ui.list, SIGNAL(queryInfoMd5(int)), m_core.downloadManager(), + SLOT(queryInfoMd5(int))); + connect(ui.list, SIGNAL(visitOnNexus(int)), m_core.downloadManager(), + SLOT(visitOnNexus(int))); + connect(ui.list, SIGNAL(openFile(int)), m_core.downloadManager(), + SLOT(openFile(int))); + connect(ui.list, SIGNAL(openMetaFile(int)), m_core.downloadManager(), + SLOT(openMetaFile(int))); + connect(ui.list, SIGNAL(openInDownloadsFolder(int)), m_core.downloadManager(), + SLOT(openInDownloadsFolder(int))); + connect(ui.list, SIGNAL(removeDownload(int, bool)), m_core.downloadManager(), + SLOT(removeDownload(int, bool))); + connect(ui.list, SIGNAL(restoreDownload(int)), m_core.downloadManager(), + SLOT(restoreDownload(int))); + connect(ui.list, SIGNAL(cancelDownload(int)), m_core.downloadManager(), + SLOT(cancelDownload(int))); + connect(ui.list, SIGNAL(pauseDownload(int)), m_core.downloadManager(), + SLOT(pauseDownload(int))); + connect(ui.list, &DownloadListView::resumeDownload, [&](int i) { + resumeDownload(i); + }); } void DownloadsTab::update() diff --git a/src/downloadstab.h b/src/downloadstab.h index f39f4ba58..f0b85d918 100644 --- a/src/downloadstab.h +++ b/src/downloadstab.h @@ -3,7 +3,10 @@ #include -namespace Ui { class MainWindow; } +namespace Ui +{ +class MainWindow; +} class OrganizerCore; class DownloadListView; @@ -33,4 +36,4 @@ class DownloadsTab : public QObject void resumeDownload(int downloadIndex); }; -#endif // MODORGANIZER_DOWNLOADTAB_INCLUDED +#endif // MODORGANIZER_DOWNLOADTAB_INCLUDED diff --git a/src/editexecutablesdialog.cpp b/src/editexecutablesdialog.cpp index ea7d20de9..8a01b9f03 100644 --- a/src/editexecutablesdialog.cpp +++ b/src/editexecutablesdialog.cpp @@ -18,17 +18,17 @@ along with Mod Organizer. If not, see . */ #include "editexecutablesdialog.h" -#include "ui_editexecutablesdialog.h" #include "filedialogmemory.h" -#include "modlist.h" #include "forcedloaddialog.h" +#include "modlist.h" #include "organizercore.h" #include "spawn.h" +#include "ui_editexecutablesdialog.h" #include #include -#include #include +#include using namespace MOBase; using namespace MOShared; @@ -36,32 +36,25 @@ using namespace MOShared; class IgnoreChanges { public: - IgnoreChanges(EditExecutablesDialog* d) - : m_dialog(d) + IgnoreChanges(EditExecutablesDialog* d) : m_dialog(d) { m_dialog->m_settingUI = true; } - ~IgnoreChanges() - { - m_dialog->m_settingUI = false; - } + ~IgnoreChanges() { m_dialog->m_settingUI = false; } - IgnoreChanges(const IgnoreChanges&) = delete; + IgnoreChanges(const IgnoreChanges&) = delete; IgnoreChanges& operator=(const IgnoreChanges&) = delete; private: EditExecutablesDialog* m_dialog; }; - -EditExecutablesDialog::EditExecutablesDialog(OrganizerCore& oc, int sel, QWidget* parent) - : TutorableDialog("EditExecutables", parent) - , ui(new Ui::EditExecutablesDialog) - , m_organizerCore(oc) - , m_originalExecutables(*oc.executablesList()) - , m_executablesList(*oc.executablesList()) - , m_settingUI(false) +EditExecutablesDialog::EditExecutablesDialog(OrganizerCore& oc, int sel, + QWidget* parent) + : TutorableDialog("EditExecutables", parent), ui(new Ui::EditExecutablesDialog), + m_organizerCore(oc), m_originalExecutables(*oc.executablesList()), + m_executablesList(*oc.executablesList()), m_settingUI(false) { ui->setupUi(this); ui->splitter->setSizes({200, 1}); @@ -71,15 +64,12 @@ EditExecutablesDialog::EditExecutablesDialog(OrganizerCore& oc, int sel, QWidget loadCustomOverwrites(); loadForcedLibraries(); - QStringList modNames; for (auto&& m : m_organizerCore.modList()->allMods()) { auto mod = ModInfo::getByName(m); - if (!mod->hasAnyOfTheseFlags({ ModInfo::FLAG_FOREIGN, - ModInfo::FLAG_BACKUP, - ModInfo::FLAG_OVERWRITE, - ModInfo::FLAG_SEPARATOR })) { + if (!mod->hasAnyOfTheseFlags({ModInfo::FLAG_FOREIGN, ModInfo::FLAG_BACKUP, + ModInfo::FLAG_OVERWRITE, ModInfo::FLAG_SEPARATOR})) { ui->mods->addItem(m); modNames.push_back(m); } @@ -90,7 +80,6 @@ EditExecutablesDialog::EditExecutablesDialog(OrganizerCore& oc, int sel, QWidget c->setCompletionMode(QCompleter::UnfilteredPopupCompletion); ui->mods->setCompleter(c); - fillList(); setDirty(false); @@ -99,20 +88,42 @@ EditExecutablesDialog::EditExecutablesDialog(OrganizerCore& oc, int sel, QWidget } auto* m = new QMenu; - m->addAction(tr("Add from file..."), [&]{ addFromFile(); }); - m->addAction(tr("Add empty"), [&]{ addEmpty(); }); - m->addAction(tr("Clone selected"), [&]{ clone(); }); + m->addAction(tr("Add from file..."), [&] { + addFromFile(); + }); + m->addAction(tr("Add empty"), [&] { + addEmpty(); + }); + m->addAction(tr("Clone selected"), [&] { + clone(); + }); ui->add->setMenu(m); // some widgets need to do more than just save() and have their own handler - connect(ui->binary, &QLineEdit::textChanged, [&]{ save(); }); - connect(ui->workingDirectory, &QLineEdit::textChanged, [&]{ save(); }); - connect(ui->arguments, &QLineEdit::textChanged, [&]{ save(); }); - connect(ui->steamAppID, &QLineEdit::textChanged, [&]{ save(); }); - connect(ui->mods, &QComboBox::currentTextChanged, [&]{ save(); }); - connect(ui->useApplicationIcon, &QCheckBox::toggled, [&]{ save(); }); - connect(ui->hide, &QCheckBox::toggled, [&]{ save(); }); - connect(ui->list->model(), &QAbstractItemModel::rowsMoved, [&]{ saveOrder(); }); + connect(ui->binary, &QLineEdit::textChanged, [&] { + save(); + }); + connect(ui->workingDirectory, &QLineEdit::textChanged, [&] { + save(); + }); + connect(ui->arguments, &QLineEdit::textChanged, [&] { + save(); + }); + connect(ui->steamAppID, &QLineEdit::textChanged, [&] { + save(); + }); + connect(ui->mods, &QComboBox::currentTextChanged, [&] { + save(); + }); + connect(ui->useApplicationIcon, &QCheckBox::toggled, [&] { + save(); + }); + connect(ui->hide, &QCheckBox::toggled, [&] { + save(); + }); + connect(ui->list->model(), &QAbstractItemModel::rowsMoved, [&] { + saveOrder(); + }); } EditExecutablesDialog::~EditExecutablesDialog() = default; @@ -141,10 +152,8 @@ void EditExecutablesDialog::loadForcedLibraries() const auto* p = m_organizerCore.currentProfile(); for (const auto& e : m_executablesList) { - m_forcedLibraries.set( - e.title(), - p->forcedLibrariesEnabled(e.title()), - p->determineForcedLibraries(e.title())); + m_forcedLibraries.set(e.title(), p->forcedLibrariesEnabled(e.title()), + p->determineForcedLibraries(e.title())); } } @@ -199,16 +208,15 @@ bool EditExecutablesDialog::checkOutputMods(const ExecutablesList& exes) if (modName && modName->enabled) { if (modName->value.isEmpty()) { - QMessageBox::critical( - this, tr("Empty output mod"), - tr("The output mod for %2 is empty.").arg(e.title())); + QMessageBox::critical(this, tr("Empty output mod"), + tr("The output mod for %2 is empty.").arg(e.title())); return false; } else if (ModInfo::getIndex(modName->value) == UINT_MAX) { - QMessageBox::critical( - this, tr("Output mod not found"), - tr("The output mod '%1' for %2 does not exist.") - .arg(modName->value).arg(e.title())); + QMessageBox::critical(this, tr("Output mod not found"), + tr("The output mod '%1' for %2 does not exist.") + .arg(modName->value) + .arg(e.title())); return false; } @@ -236,13 +244,13 @@ bool EditExecutablesDialog::commitChanges() // set the new custom overwrites and forced libraries for (const auto& e : newExecutables) { - if (auto modName=m_customOverwrites.find(e.title())) { + if (auto modName = m_customOverwrites.find(e.title())) { if (modName && modName->enabled) { profile->storeSetting("custom_overwrites", e.title(), modName->value); } } - if (auto libraryList=m_forcedLibraries.find(e.title())) { + if (auto libraryList = m_forcedLibraries.find(e.title())) { if (libraryList && !libraryList->value.empty()) { profile->setForcedLibrariesEnabled(e.title(), libraryList->enabled); profile->storeForcedLibraries(e.title(), libraryList->value); @@ -260,7 +268,7 @@ bool EditExecutablesDialog::commitChanges() void EditExecutablesDialog::setDirty(bool b) { - if (auto* button=ui->buttons->button(QDialogButtonBox::Apply)) { + if (auto* button = ui->buttons->button(QDialogButtonBox::Apply)) { button->setEnabled(b); } } @@ -268,8 +276,8 @@ void EditExecutablesDialog::setDirty(bool b) void EditExecutablesDialog::selectIndex(int i) { if (i >= 0 && i < ui->list->count()) { - ui->list->selectionModel()->setCurrentIndex( - ui->list->model()->index(i, 0), QItemSelectionModel::ClearAndSelect); + ui->list->selectionModel()->setCurrentIndex(ui->list->model()->index(i, 0), + QItemSelectionModel::ClearAndSelect); } } @@ -292,7 +300,7 @@ Executable* EditExecutablesDialog::selectedExe() } const auto& title = item->text(); - auto itor = m_executablesList.find(title); + auto itor = m_executablesList.find(title); if (itor == m_executablesList.end()) { return nullptr; @@ -305,7 +313,7 @@ void EditExecutablesDialog::fillList() { ui->list->clear(); - for(const auto& exe : m_executablesList) { + for (const auto& exe : m_executablesList) { ui->list->addItem(createListItem(exe)); } @@ -322,8 +330,7 @@ QListWidgetItem* EditExecutablesDialog::createListItem(const Executable& exe) return new QListWidgetItem(exe.title()); } -void EditExecutablesDialog::updateUI( - const QListWidgetItem* item, const Executable* e) +void EditExecutablesDialog::updateUI(const QListWidgetItem* item, const Executable* e) { // the ui is currently being set, ignore changes IgnoreChanges c(this); @@ -337,8 +344,7 @@ void EditExecutablesDialog::updateUI( setButtons(item, e); } -void EditExecutablesDialog::setButtons( - const QListWidgetItem* item, const Executable* e) +void EditExecutablesDialog::setButtons(const QListWidgetItem* item, const Executable* e) { // add and remove are always enabled @@ -405,10 +411,9 @@ void EditExecutablesDialog::setEdits(const Executable& e) modIndex = ui->mods->findText(modName->value); if (modIndex == -1) { - log::warn( - "executable '{}' uses mod '{}' as a custom overwrite, but that mod " - "doesn't exist", - e.title(), modName->value); + log::warn("executable '{}' uses mod '{}' as a custom overwrite, but that mod " + "doesn't exist", + e.title(), modName->value); } } @@ -420,7 +425,7 @@ void EditExecutablesDialog::setEdits(const Executable& e) } { - const auto libraryList = m_forcedLibraries.find(e.title()); + const auto libraryList = m_forcedLibraries.find(e.title()); const bool hasForcedLibraries = (libraryList && libraryList->enabled); ui->forceLoadLibraries->setChecked(hasForcedLibraries); @@ -557,10 +562,10 @@ void EditExecutablesDialog::on_reset_clicked() { const auto title = tr("Reset plugin executables"); - const auto text = tr( - "This will restore all the executables provided by the game plugin. If " - "there are existing executables with the same names, they will be " - "automatically renamed and left unchanged."); + const auto text = + tr("This will restore all the executables provided by the game plugin. If " + "there are existing executables with the same names, they will be " + "automatically renamed and left unchanged."); const auto buttons = QMessageBox::Ok | QMessageBox::Cancel; @@ -596,7 +601,6 @@ void EditExecutablesDialog::on_remove_clicked() const int currentRow = ui->list->row(item); delete item; - m_customOverwrites.remove(exe->title()); m_forcedLibraries.remove(exe->title()); @@ -604,7 +608,6 @@ void EditExecutablesDialog::on_remove_clicked() // exe pointer m_executablesList.remove(exe->title()); - // reselecting the same row as before, or the last one if (currentRow >= ui->list->count()) { // that was the last item, select the new list item, if any @@ -678,7 +681,7 @@ void EditExecutablesDialog::on_title_textChanged(const QString& original) // once the executable is saved, the list item must be changed to match the // new name - if (auto* i=selectedItem()) { + if (auto* i = selectedItem()) { i->setText(s); } } @@ -790,7 +793,7 @@ void EditExecutablesDialog::setBinary(const QFileInfo& binary) // setting title if some variation of "New Executable" if (ui->title->text().startsWith(tr("New Executable"), Qt::CaseInsensitive)) { - const auto prefix = binary.completeBaseName(); + const auto prefix = binary.completeBaseName(); const auto newTitle = m_executablesList.makeNonConflictingTitle(prefix); if (newTitle) { @@ -802,8 +805,8 @@ void EditExecutablesDialog::setBinary(const QFileInfo& binary) void EditExecutablesDialog::on_browseWorkingDirectory_clicked() { QString dirName = FileDialogMemory::getExistingDirectory( - "editExecutableDirectory", this, tr("Select a directory"), - ui->workingDirectory->text()); + "editExecutableDirectory", this, tr("Select a directory"), + ui->workingDirectory->text()); if (dirName.isNull()) { // cancelled @@ -823,7 +826,7 @@ void EditExecutablesDialog::on_configureLibraries_clicked() ForcedLoadDialog dialog(m_organizerCore.managedGame(), this); - if (auto libraryList=m_forcedLibraries.find(e->title())) { + if (auto libraryList = m_forcedLibraries.find(e->title())) { dialog.setValues(libraryList->value); } @@ -849,12 +852,10 @@ void EditExecutablesDialog::on_buttons_clicked(QAbstractButton* b) QFileInfo EditExecutablesDialog::browseBinary(const QString& initial) { const QString Filters = - tr("Executables (*.exe *.bat *.jar)") + ";;" + - tr("All Files (*.*)"); + tr("Executables (*.exe *.bat *.jar)") + ";;" + tr("All Files (*.*)"); const auto f = FileDialogMemory::getOpenFileName( - "editExecutableBinary", this, tr("Select an executable"), - initial, Filters); + "editExecutableBinary", this, tr("Select an executable"), initial, Filters); if (f.isNull()) { return {}; @@ -869,10 +870,10 @@ void EditExecutablesDialog::setJarBinary(const QFileInfo& binary) if (java.isEmpty()) { QMessageBox::information( - this, tr("Java required"), - tr("MO requires Java to run this application. If you already " - "have it installed, select javaw.exe from that installation as " - "the binary.")); + this, tr("Java required"), + tr("MO requires Java to run this application. If you already " + "have it installed, select javaw.exe from that installation as " + "the binary.")); } { @@ -881,7 +882,8 @@ void EditExecutablesDialog::setJarBinary(const QFileInfo& binary) ui->binary->setText(java); ui->workingDirectory->setText(QDir::toNativeSeparators(binary.absolutePath())); - ui->arguments->setText("-jar \"" + QDir::toNativeSeparators(binary.absoluteFilePath()) + "\""); + ui->arguments->setText("-jar \"" + + QDir::toNativeSeparators(binary.absoluteFilePath()) + "\""); } save(); diff --git a/src/editexecutablesdialog.h b/src/editexecutablesdialog.h index 8da53e09e..1d0bd4a0d 100644 --- a/src/editexecutablesdialog.h +++ b/src/editexecutablesdialog.h @@ -1,243 +1,239 @@ -/* -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 EDITEXECUTABLESDIALOG_H -#define EDITEXECUTABLESDIALOG_H - -#include "tutorabledialog.h" -#include -#include "executableslist.h" -#include "profile.h" -#include "iplugingame.h" -#include -#include -#include - -namespace Ui { - class EditExecutablesDialog; -} - -class ModList; -class OrganizerCore; - -/** helper class to manage custom overwrites within the edit executables - * dialog, stores a T and a bool in map indexed by a QString - **/ -template -class ToggableMap -{ -public: - struct Value - { - bool enabled; - T value; - - Value(bool b, T&& v) - : enabled(b), value(std::forward(v)) - { - } - }; - - /** - * returns the Value associated with the given title, or empty - **/ - std::optional find(const QString& title) const - { - auto itor = m_map.find(title); - if (itor == m_map.end()) { - return {}; - } - - return itor->second; - } - - /** - * sets the given value, adds it if not found - **/ - void set(QString title, bool b, T value) - { - m_map.insert_or_assign(std::move(title), Value(b, std::move(value))); - } - - /** - * sets whether the given value is enabled, inserts it if not found - **/ - void setEnabled(const QString& title, bool b) - { - auto itor = m_map.find(title); - - if (itor == m_map.end()) { - m_map.emplace(title, Value(b, {})); - } else { - itor->second.enabled = b; - } - } - - /** - * sets the given value, inserts it enabled if not found - **/ - void setValue(const QString& title, T value) - { - auto itor = m_map.find(title); - - if (itor == m_map.end()) { - m_map.emplace(title, Value(true, std::move(value))); - } else { - itor->second.value = std::move(value); - } - } - - /** - * renames the given value, ignored if not found - **/ - void rename(const QString& oldTitle, QString newTitle) - { - auto itor = m_map.find(oldTitle); - if (itor == m_map.end()) { - return; - } - - // move to new title, erase old - m_map.emplace(std::move(newTitle), std::move(itor->second)); - m_map.erase(itor); - } - - /** - * removes the given value, ignored if not found - **/ - void remove(const QString& title) - { - auto itor = m_map.find(title); - if (itor == m_map.end()) { - return; - } - - m_map.erase(itor); - } - -private: - std::map m_map; -}; - - -/** - * @brief Dialog to manage the list of executables - **/ -class EditExecutablesDialog : public MOBase::TutorableDialog -{ - Q_OBJECT; - friend class IgnoreChanges; - -public: - using CustomOverwrites = ToggableMap; - using ForcedLibraries = ToggableMap>; - - explicit EditExecutablesDialog( - OrganizerCore& oc, int selection=-1, QWidget* parent=nullptr); - - ~EditExecutablesDialog(); - - // also saves and restores geometry - // - int exec() override; - - ExecutablesList getExecutablesList() const; - const CustomOverwrites& getCustomOverwrites() const; - const ForcedLibraries& getForcedLibraries() const; - -private slots: - void on_list_itemSelectionChanged(); - - void on_reset_clicked(); - void on_add_clicked(); - void on_remove_clicked(); - void on_up_clicked(); - void on_down_clicked(); - - void on_title_textChanged(const QString& s); - void on_title_editingFinished(); - void on_overwriteSteamAppID_toggled(bool checked); - void on_createFilesInMod_toggled(bool checked); - void on_forceLoadLibraries_toggled(bool checked); - - void on_browseBinary_clicked(); - void on_browseWorkingDirectory_clicked(); - void on_configureLibraries_clicked(); - - void on_buttons_clicked(QAbstractButton* b); - -private: - std::unique_ptr ui; - OrganizerCore& m_organizerCore; - - // copy of the original executables, used to clear the current settings when - // committing changes - const ExecutablesList m_originalExecutables; - - // current executable list - ExecutablesList m_executablesList; - - // custom overwrites set in the dialog - CustomOverwrites m_customOverwrites; - - // forced libraries set in the dialog - ForcedLibraries m_forcedLibraries; - - // remembers the last executable title that made sense, reverts to this when - // the widget loses focus if it's empty - QString m_lastGoodTitle; - - // true when the change events being triggered are in response to loading - // the executable's data into the UI, not from a user change - bool m_settingUI; - - - void loadCustomOverwrites(); - void loadForcedLibraries(); - - QListWidgetItem* selectedItem(); - Executable* selectedExe(); - - void fillList(); - QListWidgetItem* createListItem(const Executable& exe); - void updateUI(const QListWidgetItem* item, const Executable* e); - void clearEdits(); - void setEdits(const Executable& e); - void setButtons(const QListWidgetItem* item, const Executable* e); - void save(); - void saveOrder(); - bool canMove(const QListWidgetItem* item, int direction); - void move(QListWidgetItem* item, int direction); - bool isTitleConflicting(const QString& s); - bool commitChanges(); - void setDirty(bool b); - void selectIndex(int i); - bool checkOutputMods(const ExecutablesList& exes); - - void addFromFile(); - void addEmpty(); - void clone(); - void addNew(Executable e); - - QFileInfo browseBinary(const QString& initial); - void setBinary(const QFileInfo& binary); - void setJarBinary(const QFileInfo& binary); -}; - -#endif // EDITEXECUTABLESDIALOG_H +/* +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 EDITEXECUTABLESDIALOG_H +#define EDITEXECUTABLESDIALOG_H + +#include "executableslist.h" +#include "iplugingame.h" +#include "profile.h" +#include "tutorabledialog.h" +#include +#include +#include +#include + +namespace Ui +{ +class EditExecutablesDialog; +} + +class ModList; +class OrganizerCore; + +/** helper class to manage custom overwrites within the edit executables + * dialog, stores a T and a bool in map indexed by a QString + **/ +template +class ToggableMap +{ +public: + struct Value + { + bool enabled; + T value; + + Value(bool b, T&& v) : enabled(b), value(std::forward(v)) {} + }; + + /** + * returns the Value associated with the given title, or empty + **/ + std::optional find(const QString& title) const + { + auto itor = m_map.find(title); + if (itor == m_map.end()) { + return {}; + } + + return itor->second; + } + + /** + * sets the given value, adds it if not found + **/ + void set(QString title, bool b, T value) + { + m_map.insert_or_assign(std::move(title), Value(b, std::move(value))); + } + + /** + * sets whether the given value is enabled, inserts it if not found + **/ + void setEnabled(const QString& title, bool b) + { + auto itor = m_map.find(title); + + if (itor == m_map.end()) { + m_map.emplace(title, Value(b, {})); + } else { + itor->second.enabled = b; + } + } + + /** + * sets the given value, inserts it enabled if not found + **/ + void setValue(const QString& title, T value) + { + auto itor = m_map.find(title); + + if (itor == m_map.end()) { + m_map.emplace(title, Value(true, std::move(value))); + } else { + itor->second.value = std::move(value); + } + } + + /** + * renames the given value, ignored if not found + **/ + void rename(const QString& oldTitle, QString newTitle) + { + auto itor = m_map.find(oldTitle); + if (itor == m_map.end()) { + return; + } + + // move to new title, erase old + m_map.emplace(std::move(newTitle), std::move(itor->second)); + m_map.erase(itor); + } + + /** + * removes the given value, ignored if not found + **/ + void remove(const QString& title) + { + auto itor = m_map.find(title); + if (itor == m_map.end()) { + return; + } + + m_map.erase(itor); + } + +private: + std::map m_map; +}; + +/** + * @brief Dialog to manage the list of executables + **/ +class EditExecutablesDialog : public MOBase::TutorableDialog +{ + Q_OBJECT; + friend class IgnoreChanges; + +public: + using CustomOverwrites = ToggableMap; + using ForcedLibraries = ToggableMap>; + + explicit EditExecutablesDialog(OrganizerCore& oc, int selection = -1, + QWidget* parent = nullptr); + + ~EditExecutablesDialog(); + + // also saves and restores geometry + // + int exec() override; + + ExecutablesList getExecutablesList() const; + const CustomOverwrites& getCustomOverwrites() const; + const ForcedLibraries& getForcedLibraries() const; + +private slots: + void on_list_itemSelectionChanged(); + + void on_reset_clicked(); + void on_add_clicked(); + void on_remove_clicked(); + void on_up_clicked(); + void on_down_clicked(); + + void on_title_textChanged(const QString& s); + void on_title_editingFinished(); + void on_overwriteSteamAppID_toggled(bool checked); + void on_createFilesInMod_toggled(bool checked); + void on_forceLoadLibraries_toggled(bool checked); + + void on_browseBinary_clicked(); + void on_browseWorkingDirectory_clicked(); + void on_configureLibraries_clicked(); + + void on_buttons_clicked(QAbstractButton* b); + +private: + std::unique_ptr ui; + OrganizerCore& m_organizerCore; + + // copy of the original executables, used to clear the current settings when + // committing changes + const ExecutablesList m_originalExecutables; + + // current executable list + ExecutablesList m_executablesList; + + // custom overwrites set in the dialog + CustomOverwrites m_customOverwrites; + + // forced libraries set in the dialog + ForcedLibraries m_forcedLibraries; + + // remembers the last executable title that made sense, reverts to this when + // the widget loses focus if it's empty + QString m_lastGoodTitle; + + // true when the change events being triggered are in response to loading + // the executable's data into the UI, not from a user change + bool m_settingUI; + + void loadCustomOverwrites(); + void loadForcedLibraries(); + + QListWidgetItem* selectedItem(); + Executable* selectedExe(); + + void fillList(); + QListWidgetItem* createListItem(const Executable& exe); + void updateUI(const QListWidgetItem* item, const Executable* e); + void clearEdits(); + void setEdits(const Executable& e); + void setButtons(const QListWidgetItem* item, const Executable* e); + void save(); + void saveOrder(); + bool canMove(const QListWidgetItem* item, int direction); + void move(QListWidgetItem* item, int direction); + bool isTitleConflicting(const QString& s); + bool commitChanges(); + void setDirty(bool b); + void selectIndex(int i); + bool checkOutputMods(const ExecutablesList& exes); + + void addFromFile(); + void addEmpty(); + void clone(); + void addNew(Executable e); + + QFileInfo browseBinary(const QString& initial); + void setBinary(const QFileInfo& binary); + void setJarBinary(const QFileInfo& binary); +}; + +#endif // EDITEXECUTABLESDIALOG_H diff --git a/src/env.cpp b/src/env.cpp index 818ce8fbb..9e67ef884 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -1,10 +1,10 @@ #include "env.h" +#include "envdump.h" #include "envmetrics.h" #include "envmodule.h" #include "envsecurity.h" #include "envshortcut.h" #include "envwindows.h" -#include "envdump.h" #include "settings.h" #include "shared/util.h" #include @@ -15,8 +15,7 @@ namespace env using namespace MOBase; -Console::Console() - : m_hasConsole(false), m_in(nullptr), m_out(nullptr), m_err(nullptr) +Console::Console() : m_hasConsole(false), m_in(nullptr), m_out(nullptr), m_err(nullptr) { // try to attach to parent if (!AttachConsole(ATTACH_PARENT_PROCESS)) { @@ -63,11 +62,9 @@ Console::~Console() } } - -ModuleNotification::ModuleNotification(QObject* o, std::function f) - : m_cookie(nullptr), m_object(o), m_f(std::move(f)) -{ -} +ModuleNotification::ModuleNotification(QObject* o, std::function f) + : m_cookie(nullptr), m_object(o), m_f(std::move(f)) +{} ModuleNotification::~ModuleNotification() { @@ -75,9 +72,7 @@ ModuleNotification::~ModuleNotification() return; } - typedef NTSTATUS NTAPI LdrUnregisterDllNotificationType( - PVOID Cookie - ); + typedef NTSTATUS NTAPI LdrUnregisterDllNotificationType(PVOID Cookie); LibraryPtr ntdll(LoadLibraryW(L"ntdll.dll")); @@ -86,8 +81,9 @@ ModuleNotification::~ModuleNotification() return; } - auto* LdrUnregisterDllNotification = reinterpret_cast( - GetProcAddress(ntdll.get(), "LdrUnregisterDllNotification")); + auto* LdrUnregisterDllNotification = + reinterpret_cast( + GetProcAddress(ntdll.get(), "LdrUnregisterDllNotification")); if (!LdrUnregisterDllNotification) { log::error("LdrUnregisterDllNotification not found in ntdll.dll"); @@ -123,23 +119,23 @@ void ModuleNotification::fire(QString path, std::size_t fileSize) // so this queues the callback in the main thread if (m_f) { - QMetaObject::invokeMethod(m_object, [path, fileSize, f=m_f] { - f(Module(path, fileSize)); - }, Qt::QueuedConnection); + QMetaObject::invokeMethod( + m_object, + [path, fileSize, f = m_f] { + f(Module(path, fileSize)); + }, + Qt::QueuedConnection); } } - -Environment::Environment() -{ -} +Environment::Environment() {} // anchor Environment::~Environment() = default; const std::vector& Environment::loadedModules() const { - if (m_modules.empty()){ + if (m_modules.empty()) { m_modules = getLoadedModules(); } @@ -190,24 +186,19 @@ QString Environment::timezone() const } auto offsetString = [](int o) { - return - QString("%1%2:%3") - .arg(o < 0 ? "" : "+") - .arg(QString::number(o / 60), 2, QChar::fromLatin1('0')) - .arg(QString::number(o % 60), 2, QChar::fromLatin1('0')); + return QString("%1%2:%3") + .arg(o < 0 ? "" : "+") + .arg(QString::number(o / 60), 2, QChar::fromLatin1('0')) + .arg(QString::number(o % 60), 2, QChar::fromLatin1('0')); }; - const auto stdName = QString::fromWCharArray(tz.StandardName); + const auto stdName = QString::fromWCharArray(tz.StandardName); const auto stdOffset = -(tz.Bias + tz.StandardBias); - const auto std = QString("%1, %2") - .arg(stdName) - .arg(offsetString(stdOffset)); + const auto std = QString("%1, %2").arg(stdName).arg(offsetString(stdOffset)); - const auto dstName = QString::fromWCharArray(tz.DaylightName); + const auto dstName = QString::fromWCharArray(tz.DaylightName); const auto dstOffset = -(tz.Bias + tz.DaylightBias); - const auto dst = QString("%1, %2") - .arg(dstName) - .arg(offsetString(dstOffset)); + const auto dst = QString("%1, %2").arg(dstName).arg(offsetString(dstOffset)); QString s; @@ -220,57 +211,55 @@ QString Environment::timezone() const return s; } -std::unique_ptr Environment::onModuleLoaded( - QObject* o, std::function f) +std::unique_ptr +Environment::onModuleLoaded(QObject* o, std::function f) { - typedef struct _UNICODE_STRING { + typedef struct _UNICODE_STRING + { USHORT Length; USHORT MaximumLength; - PWSTR Buffer; + PWSTR Buffer; } UNICODE_STRING, *PUNICODE_STRING; typedef const PUNICODE_STRING PCUNICODE_STRING; - typedef struct _LDR_DLL_LOADED_NOTIFICATION_DATA { - ULONG Flags; //Reserved. - PCUNICODE_STRING FullDllName; //The full path name of the DLL module. - PCUNICODE_STRING BaseDllName; //The base file name of the DLL module. - PVOID DllBase; //A pointer to the base address for the DLL in memory. - ULONG SizeOfImage; //The size of the DLL image, in bytes. + typedef struct _LDR_DLL_LOADED_NOTIFICATION_DATA + { + ULONG Flags; // Reserved. + PCUNICODE_STRING FullDllName; // The full path name of the DLL module. + PCUNICODE_STRING BaseDllName; // The base file name of the DLL module. + PVOID DllBase; // A pointer to the base address for the DLL in memory. + ULONG SizeOfImage; // The size of the DLL image, in bytes. } LDR_DLL_LOADED_NOTIFICATION_DATA, *PLDR_DLL_LOADED_NOTIFICATION_DATA; - typedef struct _LDR_DLL_UNLOADED_NOTIFICATION_DATA { - ULONG Flags; //Reserved. - PCUNICODE_STRING FullDllName; //The full path name of the DLL module. - PCUNICODE_STRING BaseDllName; //The base file name of the DLL module. - PVOID DllBase; //A pointer to the base address for the DLL in memory. - ULONG SizeOfImage; //The size of the DLL image, in bytes. + typedef struct _LDR_DLL_UNLOADED_NOTIFICATION_DATA + { + ULONG Flags; // Reserved. + PCUNICODE_STRING FullDllName; // The full path name of the DLL module. + PCUNICODE_STRING BaseDllName; // The base file name of the DLL module. + PVOID DllBase; // A pointer to the base address for the DLL in memory. + ULONG SizeOfImage; // The size of the DLL image, in bytes. } LDR_DLL_UNLOADED_NOTIFICATION_DATA, *PLDR_DLL_UNLOADED_NOTIFICATION_DATA; - typedef union _LDR_DLL_NOTIFICATION_DATA { + typedef union _LDR_DLL_NOTIFICATION_DATA + { LDR_DLL_LOADED_NOTIFICATION_DATA Loaded; LDR_DLL_UNLOADED_NOTIFICATION_DATA Unloaded; } LDR_DLL_NOTIFICATION_DATA, *PLDR_DLL_NOTIFICATION_DATA; typedef VOID CALLBACK LDR_DLL_NOTIFICATION_FUNCTION( - ULONG NotificationReason, - const PLDR_DLL_NOTIFICATION_DATA NotificationData, - PVOID Context - ); + ULONG NotificationReason, const PLDR_DLL_NOTIFICATION_DATA NotificationData, + PVOID Context); typedef LDR_DLL_NOTIFICATION_FUNCTION* PLDR_DLL_NOTIFICATION_FUNCTION; typedef NTSTATUS NTAPI LdrRegisterDllNotificationType( - ULONG Flags, - PLDR_DLL_NOTIFICATION_FUNCTION NotificationFunction, - PVOID Context, - PVOID *Cookie - ); + ULONG Flags, PLDR_DLL_NOTIFICATION_FUNCTION NotificationFunction, PVOID Context, + PVOID * Cookie); - const ULONG LDR_DLL_NOTIFICATION_REASON_LOADED = 1; + const ULONG LDR_DLL_NOTIFICATION_REASON_LOADED = 1; const ULONG LDR_DLL_NOTIFICATION_REASON_UNLOADED = 2; - // loading ntdll.dll, the function will be found with GetProcAddress() LibraryPtr ntdll(LoadLibraryW(L"ntdll.dll")); @@ -280,33 +269,32 @@ std::unique_ptr Environment::onModuleLoaded( } auto* LdrRegisterDllNotification = reinterpret_cast( - GetProcAddress(ntdll.get(), "LdrRegisterDllNotification")); + GetProcAddress(ntdll.get(), "LdrRegisterDllNotification")); if (!LdrRegisterDllNotification) { log::error("LdrRegisterDllNotification not found in ntdll.dll"); return {}; } - auto context = std::make_unique(o, f); void* cookie = nullptr; - auto OnDllLoaded = [](ULONG reason, const PLDR_DLL_NOTIFICATION_DATA data, void* context) { + auto OnDllLoaded = [](ULONG reason, const PLDR_DLL_NOTIFICATION_DATA data, + void* context) { if (reason == LDR_DLL_NOTIFICATION_REASON_LOADED) { if (data && data->Loaded.FullDllName) { if (context) { static_cast(context)->fire( - QString::fromWCharArray( - data->Loaded.FullDllName->Buffer, - data->Loaded.FullDllName->Length / sizeof(wchar_t)), - data->Loaded.SizeOfImage); + QString::fromWCharArray(data->Loaded.FullDllName->Buffer, + data->Loaded.FullDllName->Length / + sizeof(wchar_t)), + data->Loaded.SizeOfImage); } } } }; - const auto r = LdrRegisterDllNotification( - 0, OnDllLoaded, context.get(), &cookie); + const auto r = LdrRegisterDllNotification(0, OnDllLoaded, context.get(), &cookie); if (r != 0) { log::error("failed to register for module notifications, error {}", r); @@ -356,9 +344,8 @@ void Environment::dump(const Settings& s) const } const auto r = metrics().desktopGeometry(); - log::debug( - "desktop geometry: ({},{})-({},{})", - r.left(), r.top(), r.right(), r.bottom()); + log::debug("desktop geometry: ({},{})-({},{})", r.left(), r.top(), r.right(), + r.bottom()); dumpDisks(s); } @@ -379,11 +366,8 @@ void Environment::dumpDisks(const Settings& s) const // remember rootPaths.insert(si.rootPath()); - log::debug( - " . {} free={} MB{}", - si.rootPath(), - (si.bytesFree() / 1000 / 1000), - (si.isReadOnly() ? " (readonly)" : "")); + log::debug(" . {} free={} MB{}", si.rootPath(), (si.bytesFree() / 1000 / 1000), + (si.isReadOnly() ? " (readonly)" : "")); }; log::debug("drives:"); @@ -398,7 +382,6 @@ void Environment::dumpDisks(const Settings& s) const dump(QCoreApplication::applicationDirPath()); } - QString path() { return get("PATH"); @@ -426,19 +409,17 @@ void setPath(const QString& s) QString get(const QString& name) { std::size_t bufferSize = 4000; - auto buffer = std::make_unique(bufferSize); + auto buffer = std::make_unique(bufferSize); - DWORD realSize = ::GetEnvironmentVariableW( - name.toStdWString().c_str(), - buffer.get(), static_cast(bufferSize)); + DWORD realSize = ::GetEnvironmentVariableW(name.toStdWString().c_str(), buffer.get(), + static_cast(bufferSize)); if (realSize > bufferSize) { bufferSize = realSize; - buffer = std::make_unique(bufferSize); + buffer = std::make_unique(bufferSize); - realSize = ::GetEnvironmentVariableW( - name.toStdWString().c_str(), - buffer.get(), static_cast(bufferSize)); + realSize = ::GetEnvironmentVariableW(name.toStdWString().c_str(), buffer.get(), + static_cast(bufferSize)); } if (realSize == 0) { @@ -446,9 +427,8 @@ QString get(const QString& name) // don't log if not found if (e != ERROR_ENVVAR_NOT_FOUND) { - log::error( - "failed to get environment variable '{}', {}", - name, formatSystemMessage(e)); + log::error("failed to get environment variable '{}', {}", name, + formatSystemMessage(e)); } return {}; @@ -462,16 +442,12 @@ void set(const QString& n, const QString& v) ::SetEnvironmentVariableW(n.toStdWString().c_str(), v.toStdWString().c_str()); } - -Service::Service(QString name) - : Service(std::move(name), StartType::None, Status::None) -{ -} +Service::Service(QString name) : Service(std::move(name), StartType::None, Status::None) +{} Service::Service(QString name, StartType st, Status s) - : m_name(std::move(name)), m_startType(st), m_status(s) -{ -} + : m_name(std::move(name)), m_startType(st), m_status(s) +{} const QString& Service::name() const { @@ -496,29 +472,27 @@ Service::Status Service::status() const QString Service::toString() const { return QString("service '%1', start=%2, status=%3") - .arg(m_name) - .arg(env::toString(m_startType)) - .arg(env::toString(m_status)); + .arg(m_name) + .arg(env::toString(m_startType)) + .arg(env::toString(m_status)); } - QString toString(Service::StartType st) { using ST = Service::StartType; - switch (st) - { - case ST::None: - return "none"; + switch (st) { + case ST::None: + return "none"; - case ST::Disabled: - return "disabled"; + case ST::Disabled: + return "disabled"; - case ST::Enabled: - return "enabled"; + case ST::Enabled: + return "enabled"; - default: - return QString("unknown %1").arg(static_cast(st)); + default: + return QString("unknown %1").arg(static_cast(st)); } } @@ -526,19 +500,18 @@ QString toString(Service::Status st) { using S = Service::Status; - switch (st) - { - case S::None: - return "none"; + switch (st) { + case S::None: + return "none"; - case S::Stopped: - return "stopped"; + case S::Stopped: + return "stopped"; - case S::Running: - return "running"; + case S::Running: + return "running"; - default: - return QString("unknown %1").arg(static_cast(st)); + default: + return QString("unknown %1").arg(static_cast(st)); } } @@ -550,9 +523,8 @@ Service::StartType getServiceStartType(SC_HANDLE s, const QString& name) const auto e = GetLastError(); if (e != ERROR_INSUFFICIENT_BUFFER) { - log::error( - "QueryServiceConfig() for size for '{}' failed, {}", - name, GetLastError()); + log::error("QueryServiceConfig() for size for '{}' failed, {}", name, + GetLastError()); return Service::StartType::None; } @@ -560,41 +532,33 @@ Service::StartType getServiceStartType(SC_HANDLE s, const QString& name) const auto size = needed; MallocPtr config( - static_cast(std::malloc(size))); + static_cast(std::malloc(size))); if (!QueryServiceConfig(s, config.get(), size, &needed)) { const auto e = GetLastError(); - log::error( - "QueryServiceConfig() for '{}' failed", name, formatSystemMessage(e)); + log::error("QueryServiceConfig() for '{}' failed", name, formatSystemMessage(e)); return Service::StartType::None; } + switch (config->dwStartType) { + case SERVICE_AUTO_START: // fall-through + case SERVICE_BOOT_START: + case SERVICE_DEMAND_START: + case SERVICE_SYSTEM_START: { + return Service::StartType::Enabled; + } - switch (config->dwStartType) - { - case SERVICE_AUTO_START: // fall-through - case SERVICE_BOOT_START: - case SERVICE_DEMAND_START: - case SERVICE_SYSTEM_START: - { - return Service::StartType::Enabled; - } - - case SERVICE_DISABLED: - { - return Service::StartType::Disabled; - } + case SERVICE_DISABLED: { + return Service::StartType::Disabled; + } - default: - { - log::error( - "unknown service start type {} for '{}'", - config->dwStartType, name); + default: { + log::error("unknown service start type {} for '{}'", config->dwStartType, name); - return Service::StartType::None; - } + return Service::StartType::None; + } } } @@ -606,9 +570,8 @@ Service::Status getServiceStatus(SC_HANDLE s, const QString& name) const auto e = GetLastError(); if (e != ERROR_INSUFFICIENT_BUFFER) { - log::error( - "QueryServiceStatusEx() for size for '{}' failed, {}", - name, GetLastError()); + log::error("QueryServiceStatusEx() for size for '{}' failed, {}", name, + GetLastError()); return Service::Status::None; } @@ -616,56 +579,47 @@ Service::Status getServiceStatus(SC_HANDLE s, const QString& name) const auto size = needed; MallocPtr status( - static_cast(std::malloc(size))); + static_cast(std::malloc(size))); const auto r = QueryServiceStatusEx( - s, SC_STATUS_PROCESS_INFO, reinterpret_cast(status.get()), - size, &needed); + s, SC_STATUS_PROCESS_INFO, reinterpret_cast(status.get()), size, &needed); if (!r) { const auto e = GetLastError(); - log::error( - "QueryServiceStatusEx() failed for '{}', {}", - name, formatSystemMessage(e)); + log::error("QueryServiceStatusEx() failed for '{}', {}", name, + formatSystemMessage(e)); return Service::Status::None; } + switch (status->dwCurrentState) { + case SERVICE_START_PENDING: // fall-through + case SERVICE_CONTINUE_PENDING: + case SERVICE_RUNNING: { + return Service::Status::Running; + } - switch (status->dwCurrentState) - { - case SERVICE_START_PENDING: // fall-through - case SERVICE_CONTINUE_PENDING: - case SERVICE_RUNNING: - { - return Service::Status::Running; - } - - case SERVICE_STOPPED: // fall-through - case SERVICE_STOP_PENDING: - case SERVICE_PAUSE_PENDING: - case SERVICE_PAUSED: - { - return Service::Status::Stopped; - } + case SERVICE_STOPPED: // fall-through + case SERVICE_STOP_PENDING: + case SERVICE_PAUSE_PENDING: + case SERVICE_PAUSED: { + return Service::Status::Stopped; + } - default: - { - log::error( - "unknown service status {} for '{}'", - status->dwCurrentState, name); + default: { + log::error("unknown service status {} for '{}'", status->dwCurrentState, name); - return Service::Status::None; - } + return Service::Status::None; + } } } Service getService(const QString& name) { // service manager - const LocalPtr scm(OpenSCManager( - NULL, NULL, SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG)); + const LocalPtr scm( + OpenSCManager(NULL, NULL, SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG)); if (!scm) { const auto e = GetLastError(); @@ -674,9 +628,8 @@ Service getService(const QString& name) } // service - const LocalPtr s(OpenService( - scm.get(), name.toStdWString().c_str(), - SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG)); + const LocalPtr s(OpenService(scm.get(), name.toStdWString().c_str(), + SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG)); if (!s) { const auto e = GetLastError(); @@ -685,20 +638,19 @@ Service getService(const QString& name) } const auto startType = getServiceStartType(s.get(), name); - const auto status = getServiceStatus(s.get(), name); + const auto status = getServiceStatus(s.get(), name); return {name, startType, status}; } - std::optional getAssocString(const QFileInfo& file, ASSOCSTR astr) { const auto ext = L"." + file.suffix().toStdWString(); // getting buffer size DWORD bufferSize = 0; - auto r = AssocQueryStringW( - ASSOCF_INIT_IGNOREUNKNOWN, astr, ext.c_str(), L"open", nullptr, &bufferSize); + auto r = AssocQueryStringW(ASSOCF_INIT_IGNOREUNKNOWN, astr, ext.c_str(), L"open", + nullptr, &bufferSize); // returns S_FALSE when giving back the buffer size, so that's actually the // expected return value @@ -707,9 +659,8 @@ std::optional getAssocString(const QFileInfo& file, ASSOCSTR astr) if (r == HRESULT_FROM_WIN32(ERROR_NO_ASSOCIATION)) { log::error("file '{}' has no associated executable", file.absoluteFilePath()); } else { - log::error( - "can't get buffer size for AssocQueryStringW(), {}", - formatSystemMessage(r)); + log::error("can't get buffer size for AssocQueryStringW(), {}", + formatSystemMessage(r)); } return {}; } @@ -718,13 +669,12 @@ std::optional getAssocString(const QFileInfo& file, ASSOCSTR astr) auto buffer = std::make_unique(bufferSize + 1); std::fill(buffer.get(), buffer.get() + bufferSize + 1, 0); - r = AssocQueryStringW( - ASSOCF_INIT_IGNOREUNKNOWN, astr, ext.c_str(), L"open", buffer.get(), &bufferSize); + r = AssocQueryStringW(ASSOCF_INIT_IGNOREUNKNOWN, astr, ext.c_str(), L"open", + buffer.get(), &bufferSize); if (FAILED(r)) { - log::error( - "failed to get exe associated with '{}', {}", - file.suffix(), formatSystemMessage(r)); + log::error("failed to get exe associated with '{}', {}", file.suffix(), + formatSystemMessage(r)); return {}; } @@ -751,7 +701,7 @@ QString formatCommandLine(const QFileInfo& targetInfo, const QString& cmd) // first one is the filename const auto wpath = targetInfo.absoluteFilePath().toStdWString(); - args[0] = reinterpret_cast(wpath.c_str()); + args[0] = reinterpret_cast(wpath.c_str()); // remaining are "" std::fill(args.begin() + 1, args.end(), reinterpret_cast(L"")); @@ -761,20 +711,17 @@ QString formatCommandLine(const QFileInfo& targetInfo, const QString& cmd) const auto wcmd = cmd.toStdWString(); - const auto n = ::FormatMessageW( - FORMAT_MESSAGE_ALLOCATE_BUFFER | - FORMAT_MESSAGE_ARGUMENT_ARRAY | - FORMAT_MESSAGE_FROM_STRING, - wcmd.c_str(), 0, 0, - reinterpret_cast(&buffer), - 0, reinterpret_cast(&args[0])); + const auto n = + ::FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_ARGUMENT_ARRAY | + FORMAT_MESSAGE_FROM_STRING, + wcmd.c_str(), 0, 0, reinterpret_cast(&buffer), 0, + reinterpret_cast(&args[0])); - if (n == 0 || !buffer){ + if (n == 0 || !buffer) { const auto e = GetLastError(); - log::error( - "failed to format command line '{}' with path '{}', {}", - cmd, targetInfo.absoluteFilePath(), formatSystemMessage(e)); + log::error("failed to format command line '{}' with path '{}', {}", cmd, + targetInfo.absoluteFilePath(), formatSystemMessage(e)); return {}; } @@ -788,12 +735,12 @@ QString formatCommandLine(const QFileInfo& targetInfo, const QString& cmd) std::pair splitExeAndArguments(const QString& cmd) { int exeBegin = 0; - int exeEnd = -1; + int exeEnd = -1; - if (cmd[0] == '"'){ + if (cmd[0] == '"') { // surrounded by double-quotes, so find the next one exeBegin = 1; - exeEnd = cmd.indexOf('"', exeBegin); + exeEnd = cmd.indexOf('"', exeBegin); if (exeEnd == -1) { log::error("missing terminating double-quote in command line '{}'", cmd); @@ -807,7 +754,7 @@ std::pair splitExeAndArguments(const QString& cmd) } } - QString exe = cmd.mid(exeBegin, exeEnd - exeBegin).trimmed(); + QString exe = cmd.mid(exeBegin, exeEnd - exeBegin).trimmed(); QString args = cmd.mid(exeEnd + 1).trimmed(); return {std::move(exe), std::move(args)}; @@ -815,9 +762,8 @@ std::pair splitExeAndArguments(const QString& cmd) Association getAssociation(const QFileInfo& targetInfo) { - log::debug( - "getting association for '{}', extension is '.{}'", - targetInfo.absoluteFilePath(), targetInfo.suffix()); + log::debug("getting association for '{}', extension is '.{}'", + targetInfo.absoluteFilePath(), targetInfo.suffix()); const auto cmd = getAssocString(targetInfo, ASSOCSTR_COMMAND); if (!cmd) { @@ -828,9 +774,8 @@ Association getAssociation(const QFileInfo& targetInfo) QString formattedCmd = formatCommandLine(targetInfo, *cmd); if (formattedCmd.isEmpty()) { - log::error( - "command line associated with '{}' is empty", - targetInfo.absoluteFilePath()); + log::error("command line associated with '{}' is empty", + targetInfo.absoluteFilePath()); return {}; } @@ -847,7 +792,6 @@ Association getAssociation(const QFileInfo& targetInfo) return {QFileInfo(p.first), *cmd, p.second}; } - struct RegistryKeyCloser { using pointer = HKEY; @@ -866,10 +810,9 @@ RegistryKeyPtr openRegistryKey(HKEY parent, const wchar_t* name) { HKEY subkey = 0; - auto r = ::RegOpenKeyExW( - parent, name, - 0, KEY_SET_VALUE|KEY_ENUMERATE_SUB_KEYS|KEY_QUERY_VALUE, - &subkey); + auto r = ::RegOpenKeyExW(parent, name, 0, + KEY_SET_VALUE | KEY_ENUMERATE_SUB_KEYS | KEY_QUERY_VALUE, + &subkey); if (r != ERROR_SUCCESS) { return {}; @@ -880,12 +823,12 @@ RegistryKeyPtr openRegistryKey(HKEY parent, const wchar_t* name) bool keyHasValues(HKEY key) { - auto name = std::make_unique(1000 + 1); + auto name = std::make_unique(1000 + 1); DWORD nameSize = 1000; // note that RegEnumValueW() also enumerates the default value if it exists - auto r = ::RegEnumValueW( - key, 0, name.get(), &nameSize, nullptr, nullptr, nullptr, nullptr); + auto r = ::RegEnumValueW(key, 0, name.get(), &nameSize, nullptr, nullptr, nullptr, + nullptr); if (r != ERROR_NO_MORE_ITEMS) { return true; @@ -895,18 +838,17 @@ bool keyHasValues(HKEY key) return false; } -bool forEachSubKey(HKEY key, std::function f) +bool forEachSubKey(HKEY key, std::function f) { - auto name = std::make_unique(1000 + 1); + auto name = std::make_unique(1000 + 1); DWORD nameSize = 1000; DWORD i = 0; // something would be really wrong if it had more than 100 keys - while (i < 100) - { - auto r = ::RegEnumKeyExW( - key, i, name.get(), &nameSize, nullptr, nullptr, nullptr, nullptr); + while (i < 100) { + auto r = ::RegEnumKeyExW(key, i, name.get(), &nameSize, nullptr, nullptr, nullptr, + nullptr); if (r == ERROR_NO_MORE_ITEMS) { // no more subkeys @@ -991,8 +933,7 @@ void deleteRegistryKeyIfEmpty(const QString& name) bool registryValueExists(const QString& keyName, const QString& valueName) { - auto key = openRegistryKey( - HKEY_CURRENT_USER, keyName.toStdWString().c_str()); + auto key = openRegistryKey(HKEY_CURRENT_USER, keyName.toStdWString().c_str()); if (!key) { return false; @@ -1000,25 +941,22 @@ bool registryValueExists(const QString& keyName, const QString& valueName) DWORD type = 0; - auto r = ::RegQueryValueExW( - key.get(), valueName.toStdWString().c_str(), - nullptr, &type, nullptr, nullptr); + auto r = ::RegQueryValueExW(key.get(), valueName.toStdWString().c_str(), nullptr, + &type, nullptr, nullptr); return (r == ERROR_SUCCESS); } - // returns the filename of the given process or the current one // -std::filesystem::path processPath(HANDLE process=INVALID_HANDLE_VALUE) +std::filesystem::path processPath(HANDLE process = INVALID_HANDLE_VALUE) { // double the buffer size 10 times const int MaxTries = 10; DWORD bufferSize = MAX_PATH; - for (int tries=0; tries(bufferSize + 1); std::fill(buffer.get(), buffer.get() + bufferSize + 1, 0); @@ -1063,7 +1001,7 @@ std::filesystem::path processPath(HANDLE process=INVALID_HANDLE_VALUE) return {}; } -std::wstring processFilename(HANDLE process=INVALID_HANDLE_VALUE) +std::wstring processFilename(HANDLE process = INVALID_HANDLE_VALUE) { const auto p = processPath(process); if (p.empty()) { @@ -1092,9 +1030,8 @@ DWORD findOtherPid() // same one auto filename = processFilename(); if (filename.empty()) { - std::wcerr - << L"can't get current process filename, defaulting to " - << defaultName << L"\n"; + std::wcerr << L"can't get current process filename, defaulting to " << defaultName + << L"\n"; filename = defaultName; } else { @@ -1115,25 +1052,23 @@ DWORD findOtherPid() } } - std::wclog - << L"no process with this filename\n" - << L"MO may not be running, or it may be running as administrator\n" - << L"you can try running this again as administrator\n"; + std::wclog << L"no process with this filename\n" + << L"MO may not be running, or it may be running as administrator\n" + << L"you can try running this again as administrator\n"; return 0; } std::wstring tempDir() { - const DWORD bufferSize = MAX_PATH + 1; + const DWORD bufferSize = MAX_PATH + 1; wchar_t buffer[bufferSize + 1] = {}; const auto written = GetTempPathW(bufferSize, buffer); if (written == 0) { const auto e = GetLastError(); - std::wcerr - << L"failed to get temp path, " << formatSystemMessage(e) << L"\n"; + std::wcerr << L"failed to get temp path, " << formatSystemMessage(e) << L"\n"; return {}; } @@ -1144,13 +1079,10 @@ std::wstring tempDir() std::wstring safeVersion() { - try - { + try { // this can throw return MOShared::createVersionInfo().displayString(3).toStdWString() + L"-"; - } - catch(...) - { + } catch (...) { return {}; } } @@ -1162,32 +1094,28 @@ HandlePtr tempFile(const std::wstring dir) // UTC time and date will be in the filename const auto now = std::time(0); - const auto tm = std::gmtime(&now); + const auto tm = std::gmtime(&now); // "ModOrganizer-YYYYMMDDThhmmss.dmp", with a possible "-i" appended, where // i can go until MaxTries std::wostringstream oss; - oss - << L"ModOrganizer-" << safeVersion() - << std::setw(4) << (1900 + tm->tm_year) - << std::setw(2) << std::setfill(L'0') << (tm->tm_mon + 1) - << std::setw(2) << std::setfill(L'0') << tm->tm_mday << "T" - << std::setw(2) << std::setfill(L'0') << tm->tm_hour - << std::setw(2) << std::setfill(L'0') << tm->tm_min - << std::setw(2) << std::setfill(L'0') << tm->tm_sec; + oss << L"ModOrganizer-" << safeVersion() << std::setw(4) << (1900 + tm->tm_year) + << std::setw(2) << std::setfill(L'0') << (tm->tm_mon + 1) << std::setw(2) + << std::setfill(L'0') << tm->tm_mday << "T" << std::setw(2) << std::setfill(L'0') + << tm->tm_hour << std::setw(2) << std::setfill(L'0') << tm->tm_min << std::setw(2) + << std::setfill(L'0') << tm->tm_sec; const std::wstring prefix = oss.str(); - const std::wstring ext = L".dmp"; + const std::wstring ext = L".dmp"; // first path to try, without counter in it std::wstring path = dir + L"\\" + prefix + ext; - for (int i=0; i; - // used by HMenuPtr, calls DestroyMenu() as the deleter // struct HMenuFreer @@ -46,7 +44,6 @@ struct HMenuFreer using HMenuPtr = std::unique_ptr; - // used by LibraryPtr, calls FreeLibrary as the deleter // struct LibraryFreer @@ -63,7 +60,6 @@ struct LibraryFreer using LibraryPtr = std::unique_ptr; - // used by COMPtr, calls Release() as the deleter // struct COMReleaser @@ -79,21 +75,16 @@ struct COMReleaser template using COMPtr = std::unique_ptr; - // used by MallocPtr, calls std::free() as the deleter // struct MallocFreer { - void operator()(void* p) - { - std::free(p); - } + void operator()(void* p) { std::free(p); } }; template using MallocPtr = std::unique_ptr; - // used by LocalPtr, calls LocalFree() as the deleter // template @@ -101,16 +92,12 @@ struct LocalFreer { using pointer = T; - void operator()(T p) - { - ::LocalFree(p); - } + void operator()(T p) { ::LocalFree(p); } }; template using LocalPtr = std::unique_ptr>; - // used by the CoTaskMemPtr, calls CoTaskMemFree() as the deleter // template @@ -118,16 +105,12 @@ struct CoTaskMemFreer { using pointer = T; - void operator()(T p) - { - ::CoTaskMemFree(p); - } + void operator()(T p) { ::CoTaskMemFree(p); } }; template using CoTaskMemPtr = std::unique_ptr>; - // creates a console in the constructor and destroys it in the destructor, // also redirects standard streams // @@ -152,17 +135,16 @@ class Console FILE* m_err; }; - class ModuleNotification { public: - ModuleNotification(QObject* o, std::function f); + ModuleNotification(QObject* o, std::function f); ~ModuleNotification(); - ModuleNotification(const ModuleNotification&) = delete; + ModuleNotification(const ModuleNotification&) = delete; ModuleNotification& operator=(const ModuleNotification&) = delete; - ModuleNotification(ModuleNotification&&) = default; + ModuleNotification(ModuleNotification&&) = default; ModuleNotification& operator=(ModuleNotification&&) = default; void setCookie(void* c); @@ -172,10 +154,9 @@ class ModuleNotification void* m_cookie; QObject* m_object; std::set m_loaded; - std::function m_f; + std::function m_f; }; - // represents the process's environment // class Environment @@ -211,8 +192,8 @@ class Environment // will call `f` on the same thread `o` is running on every time a module // is loaded in the process // - std::unique_ptr onModuleLoaded( - QObject* o, std::function f); + std::unique_ptr onModuleLoaded(QObject* o, + std::function f); // logs the environment // @@ -229,7 +210,6 @@ class Environment void dumpDisks(const Settings& s) const; }; - // environment variables // QString get(const QString& name); @@ -240,7 +220,6 @@ QString appendToPath(const QString& s); QString prependToPath(const QString& s); void setPath(const QString& s); - class Service { public: @@ -258,7 +237,6 @@ class Service Running }; - explicit Service(QString name); Service(QString name, StartType st, Status s); @@ -276,12 +254,10 @@ class Service Status m_status; }; - Service getService(const QString& name); QString toString(Service::StartType st); QString toString(Service::Status st); - struct Association { // path to the executable associated with the file @@ -300,7 +276,6 @@ struct Association // Association getAssociation(const QFileInfo& file); - // returns whether the given value exists // bool registryValueExists(const QString& key, const QString& value); @@ -313,6 +288,6 @@ void deleteRegistryKeyIfEmpty(const QString& name); // std::filesystem::path thisProcessPath(); -} // namespace env +} // namespace env -#endif // ENV_ENV_H +#endif // ENV_ENV_H diff --git a/src/envdump.h b/src/envdump.h index 355df7836..91da52800 100644 --- a/src/envdump.h +++ b/src/envdump.h @@ -12,7 +12,6 @@ enum class CoreDumpTypes Full }; - CoreDumpTypes coreDumpTypeFromString(const std::string& s); std::string toString(CoreDumpTypes type); @@ -25,6 +24,6 @@ bool coredump(const wchar_t* dir, CoreDumpTypes type); // bool coredumpOther(CoreDumpTypes type); -} // namespace +} // namespace env #endif // MODORGANIZER_ENVDUMP_INCLUDED diff --git a/src/envfs.cpp b/src/envfs.cpp index dfea65987..fe84fd17f 100644 --- a/src/envfs.cpp +++ b/src/envfs.cpp @@ -1,19 +1,21 @@ #include "envfs.h" #include "env.h" #include "shared/util.h" -#include #include +#include using namespace MOBase; -typedef struct _UNICODE_STRING { +typedef struct _UNICODE_STRING +{ USHORT Length; USHORT MaximumLength; PWSTR Buffer; } UNICODE_STRING, *PUNICODE_STRING; -typedef const UNICODE_STRING *PCUNICODE_STRING; +typedef const UNICODE_STRING* PCUNICODE_STRING; -typedef struct _OBJECT_ATTRIBUTES { +typedef struct _OBJECT_ATTRIBUTES +{ ULONG Length; HANDLE RootDirectory; PUNICODE_STRING ObjectName; @@ -22,23 +24,22 @@ typedef struct _OBJECT_ATTRIBUTES { PVOID SecurityQualityOfService; } OBJECT_ATTRIBUTES, *POBJECT_ATTRIBUTES; - -typedef struct _FILE_DIRECTORY_INFORMATION { - ULONG NextEntryOffset; - ULONG FileIndex; +typedef struct _FILE_DIRECTORY_INFORMATION +{ + ULONG NextEntryOffset; + ULONG FileIndex; LARGE_INTEGER CreationTime; LARGE_INTEGER LastAccessTime; LARGE_INTEGER LastWriteTime; LARGE_INTEGER ChangeTime; LARGE_INTEGER EndOfFile; LARGE_INTEGER AllocationSize; - ULONG FileAttributes; - ULONG FileNameLength; - WCHAR FileName[1]; + ULONG FileAttributes; + ULONG FileNameLength; + WCHAR FileName[1]; } FILE_DIRECTORY_INFORMATION, *PFILE_DIRECTORY_INFORMATION; - -#define FILE_SHARE_VALID_FLAGS 0x00000007 +#define FILE_SHARE_VALID_FLAGS 0x00000007 // copied from ntstatus.h #define STATUS_SUCCESS ((NTSTATUS)0x00000000L) @@ -48,72 +49,71 @@ typedef struct _FILE_DIRECTORY_INFORMATION { typedef struct _IO_STATUS_BLOCK IO_STATUS_BLOCK; -typedef struct _IO_STATUS_BLOCK *PIO_STATUS_BLOCK; +typedef struct _IO_STATUS_BLOCK* PIO_STATUS_BLOCK; // typedef VOID (NTAPI *PIO_APC_ROUTINE )(__in PVOID ApcContext, __in // PIO_STATUS_BLOCK IoStatusBlock, __in ULONG Reserved); -typedef VOID(NTAPI *PIO_APC_ROUTINE)(PVOID ApcContext, - PIO_STATUS_BLOCK IoStatusBlock, - ULONG Reserved); - +typedef VOID(NTAPI* PIO_APC_ROUTINE)(PVOID ApcContext, PIO_STATUS_BLOCK IoStatusBlock, + ULONG Reserved); -typedef enum _FILE_INFORMATION_CLASS { +typedef enum _FILE_INFORMATION_CLASS +{ FileDirectoryInformation = 1 } FILE_INFORMATION_CLASS; -typedef NTSTATUS(WINAPI *NtQueryDirectoryFile_type)( - HANDLE, HANDLE, PIO_APC_ROUTINE, PVOID, PIO_STATUS_BLOCK, PVOID, ULONG, - FILE_INFORMATION_CLASS, BOOLEAN, PUNICODE_STRING, BOOLEAN); - -typedef NTSTATUS(WINAPI *NtOpenFile_type)(PHANDLE, ACCESS_MASK, - POBJECT_ATTRIBUTES, PIO_STATUS_BLOCK, - ULONG, ULONG); +typedef NTSTATUS(WINAPI* NtQueryDirectoryFile_type)(HANDLE, HANDLE, PIO_APC_ROUTINE, + PVOID, PIO_STATUS_BLOCK, PVOID, + ULONG, FILE_INFORMATION_CLASS, + BOOLEAN, PUNICODE_STRING, BOOLEAN); -typedef NTSTATUS(WINAPI *NtClose_type)(HANDLE); +typedef NTSTATUS(WINAPI* NtOpenFile_type)(PHANDLE, ACCESS_MASK, POBJECT_ATTRIBUTES, + PIO_STATUS_BLOCK, ULONG, ULONG); +typedef NTSTATUS(WINAPI* NtClose_type)(HANDLE); -NtOpenFile_type NtOpenFile = nullptr; +NtOpenFile_type NtOpenFile = nullptr; NtQueryDirectoryFile_type NtQueryDirectoryFile = nullptr; -extern NtClose_type NtClose = nullptr; +extern NtClose_type NtClose = nullptr; +#define FILE_DIRECTORY_FILE 0x00000001 +#define FILE_WRITE_THROUGH 0x00000002 +#define FILE_SEQUENTIAL_ONLY 0x00000004 +#define FILE_NO_INTERMEDIATE_BUFFERING 0x00000008 -#define FILE_DIRECTORY_FILE 0x00000001 -#define FILE_WRITE_THROUGH 0x00000002 -#define FILE_SEQUENTIAL_ONLY 0x00000004 -#define FILE_NO_INTERMEDIATE_BUFFERING 0x00000008 +#define FILE_SYNCHRONOUS_IO_ALERT 0x00000010 +#define FILE_SYNCHRONOUS_IO_NONALERT 0x00000020 +#define FILE_NON_DIRECTORY_FILE 0x00000040 +#define FILE_CREATE_TREE_CONNECTION 0x00000080 -#define FILE_SYNCHRONOUS_IO_ALERT 0x00000010 -#define FILE_SYNCHRONOUS_IO_NONALERT 0x00000020 -#define FILE_NON_DIRECTORY_FILE 0x00000040 -#define FILE_CREATE_TREE_CONNECTION 0x00000080 +#define FILE_COMPLETE_IF_OPLOCKED 0x00000100 +#define FILE_NO_EA_KNOWLEDGE 0x00000200 +#define FILE_OPEN_REMOTE_INSTANCE 0x00000400 +#define FILE_RANDOM_ACCESS 0x00000800 -#define FILE_COMPLETE_IF_OPLOCKED 0x00000100 -#define FILE_NO_EA_KNOWLEDGE 0x00000200 -#define FILE_OPEN_REMOTE_INSTANCE 0x00000400 -#define FILE_RANDOM_ACCESS 0x00000800 - -#define FILE_DELETE_ON_CLOSE 0x00001000 -#define FILE_OPEN_BY_FILE_ID 0x00002000 -#define FILE_OPEN_FOR_BACKUP_INTENT 0x00004000 -#define FILE_NO_COMPRESSION 0x00008000 +#define FILE_DELETE_ON_CLOSE 0x00001000 +#define FILE_OPEN_BY_FILE_ID 0x00002000 +#define FILE_OPEN_FOR_BACKUP_INTENT 0x00004000 +#define FILE_NO_COMPRESSION 0x00008000 #if (_WIN32_WINNT >= _WIN32_WINNT_WIN7) -#define FILE_OPEN_REQUIRING_OPLOCK 0x00010000 +#define FILE_OPEN_REQUIRING_OPLOCK 0x00010000 #endif -#define FILE_RESERVE_OPFILTER 0x00100000 -#define FILE_OPEN_REPARSE_POINT 0x00200000 -#define FILE_OPEN_NO_RECALL 0x00400000 -#define FILE_OPEN_FOR_FREE_SPACE_QUERY 0x00800000 +#define FILE_RESERVE_OPFILTER 0x00100000 +#define FILE_OPEN_REPARSE_POINT 0x00200000 +#define FILE_OPEN_NO_RECALL 0x00400000 +#define FILE_OPEN_FOR_FREE_SPACE_QUERY 0x00800000 -#define FILE_VALID_OPTION_FLAGS 0x00ffffff -#define FILE_VALID_PIPE_OPTION_FLAGS 0x00000032 -#define FILE_VALID_MAILSLOT_OPTION_FLAGS 0x00000032 -#define FILE_VALID_SET_FLAGS 0x00000036 +#define FILE_VALID_OPTION_FLAGS 0x00ffffff +#define FILE_VALID_PIPE_OPTION_FLAGS 0x00000032 +#define FILE_VALID_MAILSLOT_OPTION_FLAGS 0x00000032 +#define FILE_VALID_SET_FLAGS 0x00000036 -typedef struct _IO_STATUS_BLOCK { +typedef struct _IO_STATUS_BLOCK +{ #pragma warning(push) -#pragma warning(disable: 4201) // we'll always use the Microsoft compiler - union { +#pragma warning(disable : 4201) // we'll always use the Microsoft compiler + union + { NTSTATUS Status; PVOID Pointer; } DUMMYUNIONNAME; @@ -122,8 +122,6 @@ typedef struct _IO_STATUS_BLOCK { ULONG_PTR Information; } IO_STATUS_BLOCK, *PIO_STATUS_BLOCK; - - namespace env { @@ -151,25 +149,14 @@ QString toString(POBJECT_ATTRIBUTES poa) return QString::fromWCharArray(sv.data(), static_cast(sv.size())); } - class HandleCloserThread { public: - HandleCloserThread() - : m_ready(false) - { - m_handles.reserve(50000); - } + HandleCloserThread() : m_ready(false) { m_handles.reserve(50000); } - void shrink() - { - m_handles.shrink_to_fit(); - } + void shrink() { m_handles.shrink_to_fit(); } - void add(HANDLE h) - { - m_handles.push_back(h); - } + void add(HANDLE h) { m_handles.push_back(h); } void wakeup() { @@ -186,7 +173,9 @@ class HandleCloserThread MOShared::SetThisThreadName("HandleCloserThread"); std::unique_lock lock(m_mutex); - m_cv.wait(lock, [&]{ return m_ready; }); + m_cv.wait(lock, [&] { + return m_ready; + }); closeHandles(); } @@ -216,24 +205,23 @@ void setHandleCloserThreadCount(std::size_t n) g_handleClosers.setMax(n); } -void forEachEntryImpl( - void* cx, HandleCloserThread& hc, std::vector>& buffers, - POBJECT_ATTRIBUTES poa, std::size_t depth, - DirStartF* dirStartF, DirEndF* dirEndF, FileF* fileF) +void forEachEntryImpl(void* cx, HandleCloserThread& hc, + std::vector>& buffers, + POBJECT_ATTRIBUTES poa, std::size_t depth, DirStartF* dirStartF, + DirEndF* dirEndF, FileF* fileF) { IO_STATUS_BLOCK iosb; UNICODE_STRING ObjectName; - OBJECT_ATTRIBUTES oa = { sizeof(oa), 0, &ObjectName }; + OBJECT_ATTRIBUTES oa = {sizeof(oa), 0, &ObjectName}; NTSTATUS status; - status = NtOpenFile( - &oa.RootDirectory, FILE_GENERIC_READ, poa, &iosb, FILE_SHARE_VALID_FLAGS, - FILE_SYNCHRONOUS_IO_NONALERT|FILE_OPEN_FOR_BACKUP_INTENT); + status = NtOpenFile(&oa.RootDirectory, FILE_GENERIC_READ, poa, &iosb, + FILE_SHARE_VALID_FLAGS, + FILE_SYNCHRONOUS_IO_NONALERT | FILE_OPEN_FOR_BACKUP_INTENT); if (status < 0) { - log::error( - "failed to open directory '{}': {}", - toString(poa), formatNtMessage(status)); + log::error("failed to open directory '{}': {}", toString(poa), + formatNtMessage(status)); return; } @@ -256,16 +244,15 @@ void forEachEntryImpl( }; for (;;) { - status = NtQueryDirectoryFile( - oa.RootDirectory, NULL, NULL, NULL, &iosb, - buffer, AllocSize, FileDirectoryInformation, FALSE, NULL, FALSE); + status = + NtQueryDirectoryFile(oa.RootDirectory, NULL, NULL, NULL, &iosb, buffer, + AllocSize, FileDirectoryInformation, FALSE, NULL, FALSE); if (status == STATUS_NO_MORE_FILES) { break; } else if (status < 0) { - log::error( - "failed to read directory '{}': {}", - toString(poa), formatNtMessage(status)); + log::error("failed to read directory '{}': {}", toString(poa), + formatNtMessage(status)); break; } @@ -301,12 +288,13 @@ void forEachEntryImpl( if (DirInfo->FileAttributes & FILE_ATTRIBUTE_DIRECTORY) { if (dirStartF && dirEndF) { dirStartF(cx, toStringView(&oa)); - forEachEntryImpl(cx, hc, buffers, &oa, depth+1, dirStartF, dirEndF, fileF); + forEachEntryImpl(cx, hc, buffers, &oa, depth + 1, dirStartF, dirEndF, + fileF); dirEndF(cx, toStringView(&oa)); } } else { FILETIME ft; - ft.dwLowDateTime = DirInfo->LastWriteTime.LowPart; + ft.dwLowDateTime = DirInfo->LastWriteTime.LowPart; ft.dwHighDateTime = DirInfo->LastWriteTime.HighPart; fileF(cx, toStringView(&oa), ft, DirInfo->AllocationSize.QuadPart); @@ -324,9 +312,9 @@ void forEachEntryImpl( std::wstring makeNtPath(const std::wstring& path) { - constexpr const wchar_t* nt_prefix = L"\\??\\"; + constexpr const wchar_t* nt_prefix = L"\\??\\"; constexpr const wchar_t* nt_unc_prefix = L"\\??\\UNC\\"; - constexpr const wchar_t* share_prefix = L"\\\\"; + constexpr const wchar_t* share_prefix = L"\\\\"; if (path.starts_with(nt_prefix)) { // already an nt path @@ -340,38 +328,36 @@ std::wstring makeNtPath(const std::wstring& path) } } -void DirectoryWalker::forEachEntry( - const std::wstring& path, void* cx, - DirStartF* dirStartF, DirEndF* dirEndF, FileF* fileF) +void DirectoryWalker::forEachEntry(const std::wstring& path, void* cx, + DirStartF* dirStartF, DirEndF* dirEndF, FileF* fileF) { auto& hc = g_handleClosers.request(); if (!NtOpenFile) { LibraryPtr m(::LoadLibraryW(L"ntdll.dll")); NtOpenFile = (NtOpenFile_type)::GetProcAddress(m.get(), "NtOpenFile"); - NtQueryDirectoryFile = (NtQueryDirectoryFile_type)::GetProcAddress(m.get(), "NtQueryDirectoryFile"); + NtQueryDirectoryFile = + (NtQueryDirectoryFile_type)::GetProcAddress(m.get(), "NtQueryDirectoryFile"); NtClose = (NtClose_type)::GetProcAddress(m.get(), "NtClose"); } const std::wstring ntpath = makeNtPath(path); UNICODE_STRING ObjectName = {}; - ObjectName.Buffer = const_cast(ntpath.c_str()); - ObjectName.Length = (USHORT)ntpath.size() * sizeof(wchar_t); - ObjectName.MaximumLength = ObjectName.Length; + ObjectName.Buffer = const_cast(ntpath.c_str()); + ObjectName.Length = (USHORT)ntpath.size() * sizeof(wchar_t); + ObjectName.MaximumLength = ObjectName.Length; OBJECT_ATTRIBUTES oa = {}; - oa.Length = sizeof(oa); - oa.ObjectName = &ObjectName; + oa.Length = sizeof(oa); + oa.ObjectName = &ObjectName; forEachEntryImpl(cx, hc, m_buffers, &oa, 0, dirStartF, dirEndF, fileF); hc.wakeup(); } - -void forEachEntry( - const std::wstring& path, void* cx, - DirStartF* dirStartF, DirEndF* dirEndF, FileF* fileF) +void forEachEntry(const std::wstring& path, void* cx, DirStartF* dirStartF, + DirEndF* dirEndF, FileF* fileF) { DirectoryWalker().forEachEntry(path, cx, dirStartF, dirEndF, fileF); } @@ -388,45 +374,39 @@ Directory getFilesAndDirs(const std::wstring& path) Context cx; cx.current.push(&root); - env::forEachEntry(path, &cx, - [](void* pcx, std::wstring_view path) { - Context* cx = (Context*)pcx; + env::forEachEntry( + path, &cx, + [](void* pcx, std::wstring_view path) { + Context* cx = (Context*)pcx; - cx->current.top()->dirs.push_back(Directory(path)); - cx->current.push(&cx->current.top()->dirs.back()); - }, + cx->current.top()->dirs.push_back(Directory(path)); + cx->current.push(&cx->current.top()->dirs.back()); + }, - [](void* pcx, std::wstring_view path) { - Context* cx = (Context*)pcx; - cx->current.pop(); - }, + [](void* pcx, std::wstring_view path) { + Context* cx = (Context*)pcx; + cx->current.pop(); + }, - [](void* pcx, std::wstring_view path, FILETIME ft, uint64_t s) { - Context* cx = (Context*)pcx; + [](void* pcx, std::wstring_view path, FILETIME ft, uint64_t s) { + Context* cx = (Context*)pcx; - cx->current.top()->files.push_back(File(path, ft, s)); - } - ); + cx->current.top()->files.push_back(File(path, ft, s)); + }); return root; } -File::File(std::wstring_view n, FILETIME ft, uint64_t s) : - name(n.begin(), n.end()), - lcname(MOShared::ToLowerCopy(name)), - lastModified(ft), size(s) -{ -} +File::File(std::wstring_view n, FILETIME ft, uint64_t s) + : name(n.begin(), n.end()), lcname(MOShared::ToLowerCopy(name)), lastModified(ft), + size(s) +{} -Directory::Directory() -{ -} +Directory::Directory() {} Directory::Directory(std::wstring_view n) - : name(n.begin(), n.end()), lcname(MOShared::ToLowerCopy(name)) -{ -} - + : name(n.begin(), n.end()), lcname(MOShared::ToLowerCopy(name)) +{} void getFilesAndDirsWithFindImpl(const std::wstring& path, Directory& d) { @@ -434,9 +414,9 @@ void getFilesAndDirsWithFindImpl(const std::wstring& path, Directory& d) WIN32_FIND_DATAW findData; - HANDLE searchHandle = ::FindFirstFileExW( - searchString.c_str(), FindExInfoBasic, &findData, FindExSearchNameMatch, - nullptr, FIND_FIRST_EX_LARGE_FETCH); + HANDLE searchHandle = + ::FindFirstFileExW(searchString.c_str(), FindExInfoBasic, &findData, + FindExSearchNameMatch, nullptr, FIND_FIRST_EX_LARGE_FETCH); if (searchHandle != INVALID_HANDLE_VALUE) { BOOL result = true; @@ -444,17 +424,16 @@ void getFilesAndDirsWithFindImpl(const std::wstring& path, Directory& d) while (result) { if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { if ((wcscmp(findData.cFileName, L".") != 0) && - (wcscmp(findData.cFileName, L"..") != 0)) { + (wcscmp(findData.cFileName, L"..") != 0)) { const std::wstring newPath = path + L"\\" + findData.cFileName; d.dirs.push_back(Directory(findData.cFileName)); getFilesAndDirsWithFindImpl(newPath, d.dirs.back()); } } else { const auto size = - (findData.nFileSizeHigh * (MAXDWORD+1)) + findData.nFileSizeLow; + (findData.nFileSizeHigh * (MAXDWORD + 1)) + findData.nFileSizeLow; - d.files.push_back(File( - findData.cFileName, findData.ftLastWriteTime, size)); + d.files.push_back(File(findData.cFileName, findData.ftLastWriteTime, size)); } result = ::FindNextFileW(searchHandle, &findData); @@ -471,4 +450,4 @@ Directory getFilesAndDirsWithFind(const std::wstring& path) return d; } -} // namespace +} // namespace env diff --git a/src/envfs.h b/src/envfs.h index 62540e18a..4ee1fab10 100644 --- a/src/envfs.h +++ b/src/envfs.h @@ -29,25 +29,15 @@ struct Directory Directory(std::wstring_view name); }; - template class ThreadPool { public: - ThreadPool(std::size_t max=1) - { - setMax(max); - } + ThreadPool(std::size_t max = 1) { setMax(max); } - ~ThreadPool() - { - stopAndJoin(); - } + ~ThreadPool() { stopAndJoin(); } - void setMax(std::size_t n) - { - m_threads.resize(n); - } + void setMax(std::size_t n) { m_threads.resize(n); } void stopAndJoin() { @@ -124,10 +114,11 @@ class ThreadPool std::atomic stop; - ThreadInfo() - : busy(true), ready(false), stop(false) + ThreadInfo() : busy(true), ready(false), stop(false) { - thread = MOShared::startSafeThread([&]{ run(); }); + thread = MOShared::startSafeThread([&] { + run(); + }); } ~ThreadInfo() @@ -155,7 +146,9 @@ class ThreadPool while (!stop) { std::unique_lock lock(mutex); - cv.wait(lock, [&]{ return ready; }); + cv.wait(lock, [&] { + return ready; + }); if (stop) { break; @@ -164,7 +157,7 @@ class ThreadPool o.run(); ready = false; - busy = false; + busy = false; } } }; @@ -172,33 +165,28 @@ class ThreadPool std::list m_threads; }; - -using DirStartF = void (void*, std::wstring_view); -using DirEndF = void (void*, std::wstring_view); -using FileF = void (void*, std::wstring_view, FILETIME, uint64_t); +using DirStartF = void(void*, std::wstring_view); +using DirEndF = void(void*, std::wstring_view); +using FileF = void(void*, std::wstring_view, FILETIME, uint64_t); void setHandleCloserThreadCount(std::size_t n); - class DirectoryWalker { public: - void forEachEntry( - const std::wstring& path, void* cx, - DirStartF* dirStartF, DirEndF* dirEndF, FileF* fileF); + void forEachEntry(const std::wstring& path, void* cx, DirStartF* dirStartF, + DirEndF* dirEndF, FileF* fileF); private: std::vector> m_buffers; }; - -void forEachEntry( - const std::wstring& path, void* cx, - DirStartF* dirStartF, DirEndF* dirEndF, FileF* fileF); +void forEachEntry(const std::wstring& path, void* cx, DirStartF* dirStartF, + DirEndF* dirEndF, FileF* fileF); Directory getFilesAndDirs(const std::wstring& path); Directory getFilesAndDirsWithFind(const std::wstring& path); -} // namespace +} // namespace env -#endif // ENV_ENVFS_H +#endif // ENV_ENVFS_H diff --git a/src/envmetrics.cpp b/src/envmetrics.cpp index b47ca2f5b..645645e40 100644 --- a/src/envmetrics.cpp +++ b/src/envmetrics.cpp @@ -1,10 +1,10 @@ #include "envmetrics.h" #include "env.h" +#include #include -#include #include +#include #include -#include namespace env { @@ -46,14 +46,13 @@ HMONITOR findMonitor(const QString& name) auto& data = *reinterpret_cast(lp); MONITORINFOEX mi = {}; - mi.cbSize = sizeof(mi); + mi.cbSize = sizeof(mi); // monitor info will include the name if (!GetMonitorInfoW(hm, &mi)) { const auto e = GetLastError(); - log::error( - "GetMonitorInfo() failed for '{}', {}", - data.name, formatSystemMessage(e)); + log::error("GetMonitorInfo() failed for '{}', {}", data.name, + formatSystemMessage(e)); // error for this monitor, but continue return TRUE; @@ -69,7 +68,6 @@ HMONITOR findMonitor(const QString& name) return TRUE; }; - // for each monitor EnumDisplayMonitors(0, nullptr, callback, reinterpret_cast(&data)); @@ -82,11 +80,11 @@ HMONITOR findMonitor(const QString& name) int getDpi(const QString& monitorDevice) { using GetDpiForMonitorFunction = - HRESULT WINAPI (HMONITOR, MONITOR_DPI_TYPE, UINT*, UINT*); + HRESULT WINAPI(HMONITOR, MONITOR_DPI_TYPE, UINT*, UINT*); static LibraryPtr shcore; static GetDpiForMonitorFunction* GetDpiForMonitor = nullptr; - static bool checked = false; + static bool checked = false; if (!checked) { // try to find GetDpiForMonitor() from shcored.dll @@ -96,7 +94,7 @@ int getDpi(const QString& monitorDevice) if (shcore) { // windows 8.1+ only GetDpiForMonitor = reinterpret_cast( - GetProcAddress(shcore.get(), "GetDpiForMonitor")); + GetProcAddress(shcore.get(), "GetDpiForMonitor")); } checked = true; @@ -107,7 +105,6 @@ int getDpi(const QString& monitorDevice) return getDesktopDpi(); } - // there's no way to get an HMONITOR from a device name, so all monitors // will have to be enumerated and their name checked HMONITOR hm = findMonitor(monitorDevice); @@ -116,13 +113,12 @@ int getDpi(const QString& monitorDevice) return 0; } - UINT dpiX=0, dpiY=0; + UINT dpiX = 0, dpiY = 0; const auto r = GetDpiForMonitor(hm, MDT_EFFECTIVE_DPI, &dpiX, &dpiY); if (FAILED(r)) { - log::error( - "GetDpiForMonitor() failed for '{}', {}", - monitorDevice, formatSystemMessage(r)); + log::error("GetDpiForMonitor() failed for '{}', {}", monitorDevice, + formatSystemMessage(r)); return 0; } @@ -131,12 +127,9 @@ int getDpi(const QString& monitorDevice) return dpiX; } - -Display::Display(QString adapter, QString monitorDevice, bool primary) : - m_adapter(std::move(adapter)), - m_monitorDevice(std::move(monitorDevice)), - m_primary(primary), - m_resX(0), m_resY(0), m_dpi(0), m_refreshRate(0) +Display::Display(QString adapter, QString monitorDevice, bool primary) + : m_adapter(std::move(adapter)), m_monitorDevice(std::move(monitorDevice)), + m_primary(primary), m_resX(0), m_resY(0), m_dpi(0), m_refreshRate(0) { getSettings(); m_dpi = getDpi(m_monitorDevice); @@ -180,18 +173,18 @@ int Display::refreshRate() const QString Display::toString() const { return QString("%1*%2 %3hz dpi=%4 on %5%6") - .arg(m_resX) - .arg(m_resY) - .arg(m_refreshRate) - .arg(m_dpi) - .arg(m_adapter) - .arg(m_primary ? " (primary)" : ""); + .arg(m_resX) + .arg(m_resY) + .arg(m_refreshRate) + .arg(m_dpi) + .arg(m_adapter) + .arg(m_primary ? " (primary)" : ""); } void Display::getSettings() { DEVMODEW dm = {}; - dm.dmSize = sizeof(dm); + dm.dmSize = sizeof(dm); const auto wsDevice = m_monitorDevice.toStdWString(); @@ -215,7 +208,6 @@ void Display::getSettings() } } - Metrics::Metrics() { getDisplays(); @@ -240,9 +232,9 @@ QRect Metrics::desktopGeometry() const void Metrics::getDisplays() { // don't bother if it goes over 100 - for (int i=0; i<100; ++i) { + for (int i = 0; i < 100; ++i) { DISPLAY_DEVICEW device = {}; - device.cb = sizeof(device); + device.cb = sizeof(device); if (!EnumDisplayDevicesW(nullptr, i, &device, 0)) { // no more @@ -256,11 +248,10 @@ void Metrics::getDisplays() continue; } - m_displays.emplace_back( - QString::fromWCharArray(device.DeviceString), - QString::fromWCharArray(device.DeviceName), - (device.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE)); + m_displays.emplace_back(QString::fromWCharArray(device.DeviceString), + QString::fromWCharArray(device.DeviceName), + (device.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE)); } } -} // namespace +} // namespace env diff --git a/src/envmetrics.h b/src/envmetrics.h index 8dfdb0878..578c42341 100644 --- a/src/envmetrics.h +++ b/src/envmetrics.h @@ -54,7 +54,6 @@ class Display void getSettings(); }; - // holds various information about Windows metrics // class Metrics @@ -76,6 +75,6 @@ class Metrics void getDisplays(); }; -} // namespace +} // namespace env -#endif // ENV_METRICS_H +#endif // ENV_METRICS_H diff --git a/src/envmodule.cpp b/src/envmodule.cpp index 5b1e7621d..2bba4c419 100644 --- a/src/envmodule.cpp +++ b/src/envmodule.cpp @@ -1,7 +1,7 @@ #include "envmodule.h" #include "env.h" -#include #include +#include namespace env { @@ -13,14 +13,13 @@ using namespace MOBase; // while adding to the startup time constexpr bool UseMD5 = false; - Module::Module(QString path, std::size_t fileSize) - : m_path(std::move(path)), m_fileSize(fileSize) + : m_path(std::move(path)), m_fileSize(fileSize) { const auto fi = getFileInfo(); - m_version = getVersion(fi.ffi); - m_timestamp = getTimestamp(fi.ffi); + m_version = getVersion(fi.ffi); + m_timestamp = getTimestamp(fi.ffi); m_versionString = fi.fileDescription; if (UseMD5) { @@ -113,7 +112,7 @@ Module::FileInfo Module::getFileInfo() const const auto wspath = m_path.toStdWString(); // getting version info size - DWORD dummy = 0; + DWORD dummy = 0; const DWORD size = GetFileVersionInfoSizeW(wspath.c_str(), &dummy); if (size == 0) { @@ -130,9 +129,8 @@ Module::FileInfo Module::getFileInfo() const return {}; } - log::debug( - "GetFileVersionInfoSizeW() failed on '{}', {}", - m_path, formatSystemMessage(e)); + log::debug("GetFileVersionInfoSizeW() failed on '{}', {}", m_path, + formatSystemMessage(e)); return {}; } @@ -143,9 +141,8 @@ Module::FileInfo Module::getFileInfo() const if (!GetFileVersionInfoW(wspath.c_str(), 0, size, buffer.get())) { const auto e = GetLastError(); - log::error( - "GetFileVersionInfoW() failed on '{}', {}", - m_path, formatSystemMessage(e)); + log::error("GetFileVersionInfoW() failed on '{}', {}", m_path, + formatSystemMessage(e)); return {}; } @@ -154,7 +151,7 @@ Module::FileInfo Module::getFileInfo() const // set of strings FileInfo fi; - fi.ffi = getFixedFileInfo(buffer.get()); + fi.ffi = getFixedFileInfo(buffer.get()); fi.fileDescription = getFileDescription(buffer.get()); return fi; @@ -162,7 +159,7 @@ Module::FileInfo Module::getFileInfo() const VS_FIXEDFILEINFO Module::getFixedFileInfo(std::byte* buffer) const { - void* valuePointer = nullptr; + void* valuePointer = nullptr; unsigned int valueSize = 0; // the fixed version info is in the root @@ -177,9 +174,7 @@ VS_FIXEDFILEINFO Module::getFixedFileInfo(std::byte* buffer) const // signature is always 0xfeef04bd if (fi->dwSignature != 0xfeef04bd) { - log::error( - "bad file info signature {:#x} for '{}'", - fi->dwSignature, m_path); + log::error("bad file info signature {:#x} for '{}'", fi->dwSignature, m_path); return {}; } @@ -195,12 +190,12 @@ QString Module::getFileDescription(std::byte* buffer) const WORD wCodePage; }; - void* valuePointer = nullptr; + void* valuePointer = nullptr; unsigned int valueSize = 0; // getting list of available languages - auto ret = VerQueryValueW( - buffer, L"\\VarFileInfo\\Translation", &valuePointer, &valueSize); + auto ret = + VerQueryValueW(buffer, L"\\VarFileInfo\\Translation", &valuePointer, &valueSize); if (!ret || !valuePointer || valueSize == 0) { log::error("VerQueryValueW() for translations failed on '{}'", m_path); @@ -217,11 +212,11 @@ QString Module::getFileDescription(std::byte* buffer) const const auto* lcp = static_cast(valuePointer); const auto subBlock = QString("\\StringFileInfo\\%1%2\\FileVersion") - .arg(lcp->wLanguage, 4, 16, QChar('0')) - .arg(lcp->wCodePage, 4, 16, QChar('0')); + .arg(lcp->wLanguage, 4, 16, QChar('0')) + .arg(lcp->wCodePage, 4, 16, QChar('0')); - ret = VerQueryValueW( - buffer, subBlock.toStdWString().c_str(), &valuePointer, &valueSize); + ret = VerQueryValueW(buffer, subBlock.toStdWString().c_str(), &valuePointer, + &valueSize); if (!ret || !valuePointer || valueSize == 0) { // not an error, no file version @@ -229,8 +224,7 @@ QString Module::getFileDescription(std::byte* buffer) const } // valueSize includes the null terminator - return QString::fromWCharArray( - static_cast(valuePointer), valueSize - 1); + return QString::fromWCharArray(static_cast(valuePointer), valueSize - 1); } QString Module::getVersion(const VS_FIXEDFILEINFO& fi) const @@ -239,17 +233,16 @@ QString Module::getVersion(const VS_FIXEDFILEINFO& fi) const return {}; } - const DWORD major = (fi.dwFileVersionMS >> 16 ) & 0xffff; - const DWORD minor = (fi.dwFileVersionMS >> 0 ) & 0xffff; - const DWORD maintenance = (fi.dwFileVersionLS >> 16 ) & 0xffff; - const DWORD build = (fi.dwFileVersionLS >> 0 ) & 0xffff; + const DWORD major = (fi.dwFileVersionMS >> 16) & 0xffff; + const DWORD minor = (fi.dwFileVersionMS >> 0) & 0xffff; + const DWORD maintenance = (fi.dwFileVersionLS >> 16) & 0xffff; + const DWORD build = (fi.dwFileVersionLS >> 0) & 0xffff; if (major == 0 && minor == 0 && maintenance == 0 && build == 0) { return {}; } - return QString("%1.%2.%3.%4") - .arg(major).arg(minor).arg(maintenance).arg(build); + return QString("%1.%2.%3.%4").arg(major).arg(minor).arg(maintenance).arg(build); } QDateTime Module::getTimestamp(const VS_FIXEDFILEINFO& fi) const @@ -261,16 +254,15 @@ QDateTime Module::getTimestamp(const VS_FIXEDFILEINFO& fi) const // time on the file // opening the file - HandlePtr h(CreateFileW( - m_path.toStdWString().c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, - OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0)); + HandlePtr h(CreateFileW(m_path.toStdWString().c_str(), GENERIC_READ, + FILE_SHARE_READ, nullptr, OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, 0)); if (h.get() == INVALID_HANDLE_VALUE) { const auto e = GetLastError(); - log::debug( - "can't open file '{}' for timestamp, {}", - m_path, formatSystemMessage(e)); + log::debug("can't open file '{}' for timestamp, {}", m_path, + formatSystemMessage(e)); return {}; } @@ -279,50 +271,42 @@ QDateTime Module::getTimestamp(const VS_FIXEDFILEINFO& fi) const if (!GetFileTime(h.get(), &ft, nullptr, nullptr)) { const auto e = GetLastError(); - log::error( - "can't get file time for '{}', {}", - m_path, formatSystemMessage(e)); + log::error("can't get file time for '{}', {}", m_path, formatSystemMessage(e)); return {}; } } else { // use the time from the file info ft.dwHighDateTime = fi.dwFileDateMS; - ft.dwLowDateTime = fi.dwFileDateLS; + ft.dwLowDateTime = fi.dwFileDateLS; } - // converting to SYSTEMTIME SYSTEMTIME utc = {}; if (!FileTimeToSystemTime(&ft, &utc)) { log::error( - "FileTimeToSystemTime() failed on timestamp high={:#x} low={:#x} for '{}'", - ft.dwHighDateTime, ft.dwLowDateTime, m_path); + "FileTimeToSystemTime() failed on timestamp high={:#x} low={:#x} for '{}'", + ft.dwHighDateTime, ft.dwLowDateTime, m_path); return {}; } - return QDateTime( - QDate(utc.wYear, utc.wMonth, utc.wDay), - QTime(utc.wHour, utc.wMinute, utc.wSecond, utc.wMilliseconds)); + return QDateTime(QDate(utc.wYear, utc.wMonth, utc.wDay), + QTime(utc.wHour, utc.wMinute, utc.wSecond, utc.wMilliseconds)); } bool Module::interesting() const { static const auto windir = []() -> QString { - try - { - return QDir::toNativeSeparators( - MOBase::getKnownFolder(FOLDERID_Windows).path()) + "\\"; - } - catch(...) - { + try { + return QDir::toNativeSeparators(MOBase::getKnownFolder(FOLDERID_Windows).path()) + + "\\"; + } catch (...) { return "c:\\windows\\"; } }(); - if (m_path.startsWith(windir, Qt::CaseInsensitive)) { return false; } @@ -330,15 +314,10 @@ bool Module::interesting() const return true; } - QString Module::getMD5() const { static const std::set ignore = { - "\\windows\\", - "\\program files\\", - "\\program files (x86)\\", - "\\programdata\\" - }; + "\\windows\\", "\\program files\\", "\\program files (x86)\\", "\\programdata\\"}; // don't calculate md5 for system files, it's not really relevant and // it takes a while @@ -366,21 +345,13 @@ QString Module::getMD5() const return hash.result().toHex(); } +Process::Process() : Process(0, 0, {}) {} -Process::Process() - : Process(0, 0, {}) -{ -} - -Process::Process(HANDLE h) - : Process(::GetProcessId(h), 0, {}) -{ -} +Process::Process(HANDLE h) : Process(::GetProcessId(h), 0, {}) {} Process::Process(DWORD pid, DWORD ppid, QString name) - : m_pid(pid), m_ppid(ppid), m_name(std::move(name)) -{ -} + : m_pid(pid), m_ppid(ppid), m_name(std::move(name)) +{} bool Process::isValid() const { @@ -413,9 +384,9 @@ const QString& Process::name() const HandlePtr Process::openHandleForWait() const { const auto rights = - PROCESS_QUERY_LIMITED_INFORMATION | // exit code, image name, etc. - SYNCHRONIZE | // wait functions - PROCESS_SET_QUOTA | PROCESS_TERMINATE; // add to job + PROCESS_QUERY_LIMITED_INFORMATION | // exit code, image name, etc. + SYNCHRONIZE | // wait functions + PROCESS_SET_QUOTA | PROCESS_TERMINATE; // add to job // don't log errors, failure can happen if the process doesn't exist return HandlePtr(OpenProcess(rights, FALSE, m_pid)); @@ -426,8 +397,7 @@ HandlePtr Process::openHandleForWait() const // bool Process::canAccess() const { - HandlePtr h(OpenProcess( - PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, m_pid)); + HandlePtr h(OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, m_pid)); if (!h) { const auto e = GetLastError(); @@ -454,25 +424,22 @@ const std::vector& Process::children() const return m_children; } - std::vector getLoadedModules() { - HandlePtr snapshot(CreateToolhelp32Snapshot( - TH32CS_SNAPMODULE32 | TH32CS_SNAPMODULE, GetCurrentProcessId())); + HandlePtr snapshot(CreateToolhelp32Snapshot(TH32CS_SNAPMODULE32 | TH32CS_SNAPMODULE, + GetCurrentProcessId())); - if (snapshot.get() == INVALID_HANDLE_VALUE) - { + if (snapshot.get() == INVALID_HANDLE_VALUE) { const auto e = GetLastError(); log::error("CreateToolhelp32Snapshot() failed, {}", formatSystemMessage(e)); return {}; } MODULEENTRY32 me = {}; - me.dwSize = sizeof(me); + me.dwSize = sizeof(me); // first module, this shouldn't fail because there's at least the executable - if (!Module32First(snapshot.get(), &me)) - { + if (!Module32First(snapshot.get(), &me)) { const auto e = GetLastError(); log::error("Module32First() failed, {}", formatSystemMessage(e)); return {}; @@ -480,8 +447,7 @@ std::vector getLoadedModules() std::vector v; - for (;;) - { + for (;;) { const auto path = QString::fromWCharArray(me.szExePath); if (!path.isEmpty()) { v.push_back(Module(path, me.modBaseSize)); @@ -503,26 +469,24 @@ std::vector getLoadedModules() // sorting by display name std::sort(v.begin(), v.end(), [](auto&& a, auto&& b) { return (a.displayPath().compare(b.displayPath(), Qt::CaseInsensitive) < 0); - }); + }); return v; } - template void forEachRunningProcess(F&& f) { HandlePtr snapshot(CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)); - if (snapshot.get() == INVALID_HANDLE_VALUE) - { + if (snapshot.get() == INVALID_HANDLE_VALUE) { const auto e = GetLastError(); log::error("CreateToolhelp32Snapshot() failed, {}", formatSystemMessage(e)); return; } PROCESSENTRY32 entry = {}; - entry.dwSize = sizeof(entry); + entry.dwSize = sizeof(entry); // first process, this shouldn't fail because there's at least one process // running @@ -532,15 +496,13 @@ void forEachRunningProcess(F&& f) return; } - for (;;) - { + for (;;) { if (!f(entry)) { break; } // next process - if (!Process32Next(snapshot.get(), &entry)) - { + if (!Process32Next(snapshot.get(), &entry)) { const auto e = GetLastError(); // no more processes is not an error @@ -557,10 +519,8 @@ std::vector getRunningProcesses() std::vector v; forEachRunningProcess([&](auto&& entry) { - v.push_back(Process( - entry.th32ProcessID, - entry.th32ParentProcessID, - QString::fromStdWString(entry.szExeFile))); + v.push_back(Process(entry.th32ProcessID, entry.th32ParentProcessID, + QString::fromStdWString(entry.szExeFile))); return true; }); @@ -580,13 +540,12 @@ void findChildren(Process& parent, const std::vector& processes) } } - Process getProcessTreeFromProcess(HANDLE h) { Process root; const auto parentPID = ::GetProcessId(h); - const auto v = getRunningProcesses(); + const auto v = getRunningProcesses(); for (auto&& p : v) { if (p.pid() == parentPID) { @@ -600,7 +559,6 @@ Process getProcessTreeFromProcess(HANDLE h) return root; } - std::vector processesInJob(HANDLE h) { const int MaxTries = 5; @@ -609,18 +567,17 @@ std::vector processesInJob(HANDLE h) DWORD maxIds = 100; // for logging - DWORD lastCount=0, lastAssigned=0; + DWORD lastCount = 0, lastAssigned = 0; - - for (int tries=0; tries buffer(std::malloc(bufferSize)); auto* ids = static_cast(buffer.get()); - const auto r = QueryInformationJobObject( - h, JobObjectBasicProcessIdList, ids, bufferSize, nullptr); + const auto r = QueryInformationJobObject(h, JobObjectBasicProcessIdList, ids, + bufferSize, nullptr); if (!r) { const auto e = GetLastError(); @@ -632,7 +589,7 @@ std::vector processesInJob(HANDLE h) if (ids->NumberOfProcessIdsInList >= ids->NumberOfAssignedProcesses) { std::vector v; - for (DWORD i=0; iNumberOfProcessIdsInList; ++i) { + for (DWORD i = 0; i < ids->NumberOfProcessIdsInList; ++i) { v.push_back(ids->ProcessIdList[i]); } @@ -643,18 +600,17 @@ std::vector processesInJob(HANDLE h) maxIds *= 2; // for logging - lastCount = ids->NumberOfProcessIdsInList; + lastCount = ids->NumberOfProcessIdsInList; lastAssigned = ids->NumberOfAssignedProcesses; } - log::error( - "failed to get processes in job, can't get a buffer large enough, " - "{}/{} ids", lastCount, lastAssigned); + log::error("failed to get processes in job, can't get a buffer large enough, " + "{}/{} ids", + lastCount, lastAssigned); return {}; } - void findChildProcesses(Process& parent, std::vector& processes) { // find all processes that are direct children of `parent` @@ -687,10 +643,8 @@ Process getProcessTreeFromJob(HANDLE h) forEachRunningProcess([&](auto&& entry) { for (auto&& id : ids) { if (entry.th32ProcessID == id) { - ps.push_back(Process( - entry.th32ProcessID, - entry.th32ParentProcessID, - QString::fromStdWString(entry.szExeFile))); + ps.push_back(Process(entry.th32ProcessID, entry.th32ParentProcessID, + QString::fromStdWString(entry.szExeFile))); break; } @@ -705,7 +659,7 @@ Process getProcessTreeFromJob(HANDLE h) // getting processes whose parent is not in the list for (auto&& possibleRoot : ps) { const auto ppid = possibleRoot.ppid(); - bool found = false; + bool found = false; for (auto&& p : ps) { if (p.pid() == ppid) { @@ -756,8 +710,8 @@ bool isJobHandle(HANDLE h) { JOBOBJECT_BASIC_ACCOUNTING_INFORMATION info = {}; - const auto r = ::QueryInformationJobObject( - h, JobObjectBasicAccountingInformation, &info, sizeof(info), nullptr); + const auto r = ::QueryInformationJobObject(h, JobObjectBasicAccountingInformation, + &info, sizeof(info), nullptr); return r; } @@ -792,7 +746,7 @@ QString getProcessName(HANDLE process) return badName; } - const DWORD bufferSize = MAX_PATH; + const DWORD bufferSize = MAX_PATH; wchar_t buffer[bufferSize + 1] = {}; const auto realSize = ::GetProcessImageFileNameW(process, buffer, bufferSize); @@ -829,10 +783,9 @@ DWORD getProcessParentID(DWORD pid) return ppid; } - DWORD getProcessParentID(HANDLE handle) { return getProcessParentID(GetProcessId(handle)); } -} // namespace +} // namespace env diff --git a/src/envmodule.h b/src/envmodule.h index 4b85d737c..1bf99d5be 100644 --- a/src/envmodule.h +++ b/src/envmodule.h @@ -1,8 +1,8 @@ #ifndef ENV_MODULE_H #define ENV_MODULE_H -#include #include +#include namespace env { @@ -23,7 +23,6 @@ struct HandleCloser using HandlePtr = std::unique_ptr; - // represents one module // class Module @@ -116,7 +115,6 @@ class Module QString getFileDescription(std::byte* buffer) const; }; - // represents one process // class Process @@ -149,7 +147,6 @@ class Process std::vector m_children; }; - std::vector getRunningProcesses(); std::vector getLoadedModules(); @@ -163,6 +160,6 @@ QString getProcessName(HANDLE process); DWORD getProcessParentID(DWORD pid); DWORD getProcessParentID(HANDLE handle); -} // namespace env +} // namespace env -#endif // ENV_MODULE_H +#endif // ENV_MODULE_H diff --git a/src/envsecurity.cpp b/src/envsecurity.cpp index fd65b483c..914a70ba3 100644 --- a/src/envsecurity.cpp +++ b/src/envsecurity.cpp @@ -1,13 +1,13 @@ #include "envsecurity.h" #include "env.h" #include "envmodule.h" -#include #include +#include #include -#include #include #include +#include #pragma comment(lib, "Wbemuuid.lib") #include @@ -23,18 +23,16 @@ using namespace MOBase; class WMI { public: - class failed {}; + class failed + {}; WMI(const std::string& ns) { - try - { + try { createLocator(); createService(ns); setSecurity(); - } - catch(failed&) - { + } catch (failed&) { } } @@ -50,13 +48,12 @@ class WMI return; } - for (;;) - { + for (;;) { COMPtr object; { IWbemClassObject* rawObject = nullptr; - ULONG count = 0; + ULONG count = 0; auto ret = enumerator->Next(WBEM_INFINITE, 1, &rawObject, &count); if (count == 0 || !rawObject) { @@ -83,14 +80,12 @@ class WMI { void* rawLocator = nullptr; - const auto ret = CoCreateInstance( - CLSID_WbemLocator, nullptr, CLSCTX_INPROC_SERVER, - IID_IWbemLocator, &rawLocator); + const auto ret = CoCreateInstance(CLSID_WbemLocator, nullptr, CLSCTX_INPROC_SERVER, + IID_IWbemLocator, &rawLocator); if (FAILED(ret) || !rawLocator) { - log::error( - "CoCreateInstance for WbemLocator failed, {}", - formatSystemMessage(ret)); + log::error("CoCreateInstance for WbemLocator failed, {}", + formatSystemMessage(ret)); throw failed(); } @@ -102,16 +97,14 @@ class WMI { IWbemServices* rawService = nullptr; - const auto res = m_locator->ConnectServer( - _bstr_t(ns.c_str()), - nullptr, nullptr, nullptr, 0, nullptr, nullptr, - &rawService); + const auto res = + m_locator->ConnectServer(_bstr_t(ns.c_str()), nullptr, nullptr, nullptr, 0, + nullptr, nullptr, &rawService); if (FAILED(res) || !rawService) { // don't log as error, seems to happen often for some people - log::debug( - "locator->ConnectServer() failed for namespace '{}', {}", - ns, formatSystemMessage(res)); + log::debug("locator->ConnectServer() failed for namespace '{}', {}", ns, + formatSystemMessage(res)); throw failed(); } @@ -121,31 +114,25 @@ class WMI void setSecurity() { - auto ret = CoSetProxyBlanket( - m_service.get(), RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, nullptr, - RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, 0, EOAC_NONE); + auto ret = CoSetProxyBlanket(m_service.get(), RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, + nullptr, RPC_C_AUTHN_LEVEL_CALL, + RPC_C_IMP_LEVEL_IMPERSONATE, 0, EOAC_NONE); - if (FAILED(ret)) - { + if (FAILED(ret)) { log::error("CoSetProxyBlanket() failed, {}", formatSystemMessage(ret)); throw failed(); } } - COMPtr getEnumerator( - const std::string& query) + COMPtr getEnumerator(const std::string& query) { IEnumWbemClassObject* rawEnumerator = NULL; auto ret = m_service->ExecQuery( - bstr_t("WQL"), - bstr_t(query.c_str()), - WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, - NULL, - &rawEnumerator); - - if (FAILED(ret) || !rawEnumerator) - { + bstr_t("WQL"), bstr_t(query.c_str()), + WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, NULL, &rawEnumerator); + + if (FAILED(ret) || !rawEnumerator) { log::error("query '{}' failed, {}", query, formatSystemMessage(ret)); return {}; } @@ -154,14 +141,11 @@ class WMI } }; - -SecurityProduct::SecurityProduct( - QUuid guid, QString name, int provider, - bool active, bool upToDate) : - m_guid(std::move(guid)), m_name(std::move(name)), m_provider(provider), - m_active(active), m_upToDate(upToDate) -{ -} +SecurityProduct::SecurityProduct(QUuid guid, QString name, int provider, bool active, + bool upToDate) + : m_guid(std::move(guid)), m_name(std::move(name)), m_provider(provider), + m_active(active), m_upToDate(upToDate) +{} const QUuid& SecurityProduct::guid() const { @@ -250,12 +234,10 @@ QString SecurityProduct::providerToString() const return ps.join("|"); } - std::optional handleProduct(IWbemClassObject* o) { VARIANT prop; - // guid auto ret = o->Get(L"instanceGuid", 0, &prop, 0, 0); if (FAILED(ret)) { @@ -271,7 +253,6 @@ std::optional handleProduct(IWbemClassObject* o) const QUuid guid(QString::fromWCharArray(prop.bstrVal)); VariantClear(&prop); - // display name QString displayName; ret = o->Get(L"displayName", 0, &prop, 0, 0); @@ -286,10 +267,9 @@ std::optional handleProduct(IWbemClassObject* o) VariantClear(&prop); - // product state DWORD state = 0; - ret = o->Get(L"productState", 0, &prop, 0, 0); + ret = o->Get(L"productState", 0, &prop, 0, 0); if (FAILED(ret)) { log::error("failed to get productState, {}", formatSystemMessage(ret)); @@ -309,12 +289,11 @@ std::optional handleProduct(IWbemClassObject* o) VariantClear(&prop); - - const auto provider = static_cast((state >> 16) & 0xff); - const auto scanner = (state >> 8) & 0xff; + const auto provider = static_cast((state >> 16) & 0xff); + const auto scanner = (state >> 8) & 0xff; const auto definitions = state & 0xff; - const bool active = ((scanner & 0x10) != 0); + const bool active = ((scanner & 0x10) != 0); const bool upToDate = (definitions == 0); return SecurityProduct(guid, displayName, provider, active, upToDate); @@ -328,7 +307,7 @@ std::vector getSecurityProductsFromWMI() std::map map; auto f = [&](auto* o) { - if (auto p=handleProduct(o)) { + if (auto p = handleProduct(o)) { map.emplace(p->guid(), std::move(*p)); } }; @@ -365,14 +344,12 @@ std::optional getWindowsFirewall() { void* rawPolicy = nullptr; - hr = CoCreateInstance( - __uuidof(NetFwPolicy2), nullptr, CLSCTX_INPROC_SERVER, - __uuidof(INetFwPolicy2), &rawPolicy); + hr = CoCreateInstance(__uuidof(NetFwPolicy2), nullptr, CLSCTX_INPROC_SERVER, + __uuidof(INetFwPolicy2), &rawPolicy); if (FAILED(hr) || !rawPolicy) { - log::error( - "CoCreateInstance for NetFwPolicy2 failed, {}", - formatSystemMessage(hr)); + log::error("CoCreateInstance for NetFwPolicy2 failed, {}", + formatSystemMessage(hr)); return {}; } @@ -384,8 +361,7 @@ std::optional getWindowsFirewall() if (policy) { hr = policy->get_FirewallEnabled(NET_FW_PROFILE2_PUBLIC, &enabledVariant); - if (FAILED(hr)) - { + if (FAILED(hr)) { // EPT_S_NOT_REGISTERED is "There are no more endpoints available from the // endpoint mapper", which seems to happen sometimes on Windows 7 when the // firewall has been disabled, so treat it as such and don't log it @@ -408,58 +384,49 @@ std::optional getWindowsFirewall() return {}; } - return SecurityProduct( - {}, "Windows Firewall", WSC_SECURITY_PROVIDER_FIREWALL, true, true); + return SecurityProduct({}, "Windows Firewall", WSC_SECURITY_PROVIDER_FIREWALL, true, + true); } - std::vector getSecurityProducts() { std::vector v; { auto fromWMI = getSecurityProductsFromWMI(); - v.insert( - v.end(), - std::make_move_iterator(fromWMI.begin()), - std::make_move_iterator(fromWMI.end())); + v.insert(v.end(), std::make_move_iterator(fromWMI.begin()), + std::make_move_iterator(fromWMI.end())); } - if (auto p=getWindowsFirewall()) { + if (auto p = getWindowsFirewall()) { v.push_back(std::move(*p)); } return v; } - class failed { public: failed(DWORD e, QString what) - : m_what(what + ", " + QString::fromStdWString(formatSystemMessage(e))) - { - } + : m_what(what + ", " + QString::fromStdWString(formatSystemMessage(e))) + {} - QString what() const - { - return m_what; - } + QString what() const { return m_what; } private: QString m_what; }; - MallocPtr getSecurityDescriptor(const QString& path) { const auto wpath = path.toStdWString(); - BOOL ret = FALSE; + BOOL ret = FALSE; DWORD length = 0; - ret = ::GetFileSecurityW( - wpath.c_str(), DACL_SECURITY_INFORMATION|OWNER_SECURITY_INFORMATION, - nullptr, 0, &length); + ret = ::GetFileSecurityW(wpath.c_str(), + DACL_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION, + nullptr, 0, &length); if (!ret || length == 0) { const auto e = GetLastError(); @@ -478,13 +445,13 @@ MallocPtr getSecurityDescriptor(const QString& path) } MallocPtr sd( - static_cast(std::malloc(length))); + static_cast(std::malloc(length))); std::memset(sd.get(), 0, length); - ret = ::GetFileSecurityW( - wpath.c_str(), DACL_SECURITY_INFORMATION|OWNER_SECURITY_INFORMATION, - sd.get(), length, &length); + ret = ::GetFileSecurityW(wpath.c_str(), + DACL_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION, + sd.get(), length, &length); if (!ret) { const auto e = GetLastError(); @@ -496,9 +463,9 @@ MallocPtr getSecurityDescriptor(const QString& path) PACL getDacl(SECURITY_DESCRIPTOR* sd) { - BOOL present = FALSE; + BOOL present = FALSE; BOOL daclDefaulted = FALSE; - PACL acl = nullptr; + PACL acl = nullptr; BOOL ret = ::GetSecurityDescriptorDacl(sd, &present, &acl, &daclDefaulted); @@ -531,7 +498,7 @@ PSID getFileOwner(SECURITY_DESCRIPTOR* sd) MallocPtr getCurrentUser() { - HANDLE hnd = ::GetCurrentProcess(); + HANDLE hnd = ::GetCurrentProcess(); HANDLE rawToken = 0; BOOL ret = ::OpenProcessToken(hnd, TOKEN_QUERY, &rawToken); @@ -543,7 +510,7 @@ MallocPtr getCurrentUser() HandlePtr token(rawToken); DWORD retsize = 0; - ret = ::GetTokenInformation(token.get(), TokenUser, 0, 0, &retsize); + ret = ::GetTokenInformation(token.get(), TokenUser, 0, 0, &retsize); if (!ret) { const auto e = GetLastError(); @@ -553,8 +520,8 @@ MallocPtr getCurrentUser() } MallocPtr tokenBuffer(std::malloc(retsize)); - ret = ::GetTokenInformation( - token.get(), TokenUser, tokenBuffer.get(), retsize, &retsize); + ret = ::GetTokenInformation(token.get(), TokenUser, tokenBuffer.get(), retsize, + &retsize); if (!ret) { const auto e = GetLastError(); @@ -562,7 +529,7 @@ MallocPtr getCurrentUser() } PSID tokenSid = ((PTOKEN_USER)(tokenBuffer.get()))->User.Sid; - DWORD sidLen = ::GetLengthSid(tokenSid); + DWORD sidLen = ::GetLengthSid(tokenSid); MallocPtr currentUserSID((SID*)(malloc(sidLen))); ret = ::CopySid(sidLen, currentUserSID.get(), tokenSid); @@ -581,7 +548,7 @@ ACCESS_MASK getEffectiveRights(ACL* dacl, PSID sid) BuildTrusteeWithSid(&trustee, sid); ACCESS_MASK access = 0; - DWORD ret = ::GetEffectiveRightsFromAclW(dacl, &trustee, &access); + DWORD ret = ::GetEffectiveRightsFromAclW(dacl, &trustee, &access); if (ret != ERROR_SUCCESS) { throw failed(ret, "GetEffectiveRightsFromAclW()"); @@ -592,11 +559,11 @@ ACCESS_MASK getEffectiveRights(ACL* dacl, PSID sid) QString getUsername(PSID owner) { - DWORD nameSize=0, domainSize=0; + DWORD nameSize = 0, domainSize = 0; auto use = SidTypeUnknown; - BOOL ret = LookupAccountSidW( - nullptr, owner, nullptr, &nameSize, nullptr, &domainSize, &use); + BOOL ret = + LookupAccountSidW(nullptr, owner, nullptr, &nameSize, nullptr, &domainSize, &use); if (!ret) { const auto e = GetLastError(); @@ -606,18 +573,18 @@ QString getUsername(PSID owner) } } - auto wsName = std::make_unique(nameSize); + auto wsName = std::make_unique(nameSize); auto wsDomain = std::make_unique(domainSize); - ret = LookupAccountSidW( - nullptr, owner, wsName.get(), &nameSize, wsDomain.get(), &domainSize, &use); + ret = LookupAccountSidW(nullptr, owner, wsName.get(), &nameSize, wsDomain.get(), + &domainSize, &use); if (!ret) { const auto e = GetLastError(); throw failed(e, "LookupAccountSid()"); } - const QString name = QString::fromWCharArray(wsName.get(), nameSize); + const QString name = QString::fromWCharArray(wsName.get(), nameSize); const QString domain = QString::fromWCharArray(wsDomain.get(), domainSize); if (!name.isEmpty() && !domain.isEmpty()) { @@ -720,10 +687,9 @@ FileRights makeFileRights(ACCESS_MASK m) } // 0x001f01ff - const auto normalRights = - STANDARD_RIGHTS_ALL | - FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_GENERIC_EXECUTE | - FILE_DELETE_CHILD; + const auto normalRights = STANDARD_RIGHTS_ALL | FILE_GENERIC_READ | + FILE_GENERIC_WRITE | FILE_GENERIC_EXECUTE | + FILE_DELETE_CHILD; if (m == normalRights) { fr.normalRights = true; @@ -736,13 +702,12 @@ FileSecurity getFileSecurity(const QString& path) { FileSecurity fs; - try - { - auto sd = getSecurityDescriptor(path); - auto dacl = getDacl(sd.get()); + try { + auto sd = getSecurityDescriptor(path); + auto dacl = getDacl(sd.get()); auto currentUser = getCurrentUser(); - auto owner = getFileOwner(sd.get()); - auto access = getEffectiveRights(dacl, currentUser.get()); + auto owner = getFileOwner(sd.get()); + auto access = getEffectiveRights(dacl, currentUser.get()); fs.rights = makeFileRights(access); @@ -751,12 +716,10 @@ FileSecurity getFileSecurity(const QString& path) } else { fs.owner = getUsername(owner); } - } - catch(failed& f) - { + } catch (failed& f) { fs.error = f.what(); } return fs; } -} // namespace +} // namespace env diff --git a/src/envsecurity.h b/src/envsecurity.h index 5f9e53322..ca69b1a21 100644 --- a/src/envsecurity.h +++ b/src/envsecurity.h @@ -1,8 +1,8 @@ #ifndef ENV_SECURITY_H #define ENV_SECURITY_H -#include #include +#include namespace env { @@ -12,9 +12,7 @@ namespace env class SecurityProduct { public: - SecurityProduct( - QUuid guid, QString name, int provider, - bool active, bool upToDate); + SecurityProduct(QUuid guid, QString name, int provider, bool active, bool upToDate); // guid // @@ -50,14 +48,12 @@ class SecurityProduct QString providerToString() const; }; - std::vector getSecurityProducts(); - struct FileRights { QStringList list; - bool hasExecute = false; + bool hasExecute = false; bool normalRights = false; }; @@ -70,6 +66,6 @@ struct FileSecurity FileSecurity getFileSecurity(const QString& file); -} // namespace env +} // namespace env -#endif // ENV_SECURITY_H +#endif // ENV_SECURITY_H diff --git a/src/envshell.cpp b/src/envshell.cpp index f3146b9f3..3d0f774e9 100644 --- a/src/envshell.cpp +++ b/src/envshell.cpp @@ -9,46 +9,34 @@ namespace env using namespace MOBase; const int QCM_FIRST = 1; -const int QCM_LAST = 0x7ff; +const int QCM_LAST = 0x7ff; class MenuFailed : public std::runtime_error { public: MenuFailed(HRESULT r, const std::string& what) - : runtime_error(fmt::format( - "{}, {}", - what, QString::fromStdWString(formatSystemMessage(r)).toStdString())) - { - } + : runtime_error( + fmt::format("{}, {}", what, + QString::fromStdWString(formatSystemMessage(r)).toStdString())) + {} }; - class DummyMenu { public: - DummyMenu(QString s) - : m_what(s) - { - } + DummyMenu(QString s) : m_what(s) {} - const QString& what() const - { - return m_what; - } + const QString& what() const { return m_what; } private: QString m_what; }; - struct IdlsFreer { const std::vector& v; - IdlsFreer(const std::vector& v) - : v(v) - { - } + IdlsFreer(const std::vector& v) : v(v) {} ~IdlsFreer() { @@ -58,19 +46,16 @@ struct IdlsFreer } }; - class WndProcFilter : public QAbstractNativeEventFilter { public: - using function_type = std::function< - bool (HWND hwnd, UINT m, WPARAM wp, LPARAM lp, LRESULT* out)>; + using function_type = + std::function; - WndProcFilter(function_type f) - : m_f(std::move(f)) - { - } + WndProcFilter(function_type f) : m_f(std::move(f)) {} - bool nativeEventFilter(const QByteArray& eventType, void* message, qintptr* result) override + bool nativeEventFilter(const QByteArray& eventType, void* message, + qintptr* result) override { MSG* msg = (MSG*)message; if (!msg) { @@ -92,9 +77,6 @@ class WndProcFilter : public QAbstractNativeEventFilter function_type m_f; }; - - - HWND getHWND(QMainWindow* mw) { if (mw) { @@ -107,9 +89,8 @@ HWND getHWND(QMainWindow* mw) // adapted from // https://devblogs.microsoft.com/oldnewthing/20040928-00/?p=37723 // -HRESULT IContextMenu_GetCommandString( - IContextMenu *pcm, UINT_PTR idCmd, UINT uFlags, - UINT *pwReserved, LPWSTR pszName, UINT cchMax) +HRESULT IContextMenu_GetCommandString(IContextMenu* pcm, UINT_PTR idCmd, UINT uFlags, + UINT* pwReserved, LPWSTR pszName, UINT cchMax) { // Callers are expected to be using Unicode. if (!(uFlags & GCS_UNICODE)) { @@ -130,8 +111,7 @@ HRESULT IContextMenu_GetCommandString( // doing anything. pszName[0] = L'\0'; - HRESULT hr = pcm->GetCommandString( - idCmd, uFlags, pwReserved, (LPSTR)pszName, cchMax); + HRESULT hr = pcm->GetCommandString(idCmd, uFlags, pwReserved, (LPSTR)pszName, cchMax); if (SUCCEEDED(hr) && pszName[0] == L'\0') { // Rats, a buggy IContextMenu handler that returned success @@ -143,14 +123,13 @@ HRESULT IContextMenu_GetCommandString( // try again with ANSI � pad the buffer with one extra character // to compensate for context menu handlers that overflow by // one character. - LPSTR pszAnsi = (LPSTR)LocalAlloc( - LMEM_FIXED, (cchMax + 1) * sizeof(CHAR)); + LPSTR pszAnsi = (LPSTR)LocalAlloc(LMEM_FIXED, (cchMax + 1) * sizeof(CHAR)); if (pszAnsi) { pszAnsi[0] = '\0'; - hr = pcm->GetCommandString( - idCmd, uFlags & ~GCS_UNICODE, pwReserved, pszAnsi, cchMax); + hr = pcm->GetCommandString(idCmd, uFlags & ~GCS_UNICODE, pwReserved, pszAnsi, + cchMax); if (SUCCEEDED(hr) && pszAnsi[0] == '\0') { // Rats, a buggy IContextMenu handler that returned success @@ -174,11 +153,7 @@ HRESULT IContextMenu_GetCommandString( return hr; } - -ShellMenu::ShellMenu(QMainWindow* mw) - : m_mw(mw) -{ -} +ShellMenu::ShellMenu(QMainWindow* mw) : m_mw(mw) {} void ShellMenu::addFile(QFileInfo fi) { @@ -197,22 +172,21 @@ void ShellMenu::exec(const QPoint& pos) return; } - try - { + try { const auto hwnd = getHWND(m_mw); auto filter = std::make_unique( - [&](HWND h, UINT m, WPARAM wp, LPARAM lp, LRESULT* out) { - return wndProc(h, m, wp, lp, out); - }); + [&](HWND h, UINT m, WPARAM wp, LPARAM lp, LRESULT* out) { + return wndProc(h, m, wp, lp, out); + }); QCoreApplication::instance()->installNativeEventFilter(filter.get()); - const int cmd = TrackPopupMenuEx( - menu, TPM_RETURNCMD, pos.x(), pos.y(), hwnd, nullptr); + const int cmd = + TrackPopupMenuEx(menu, TPM_RETURNCMD, pos.x(), pos.y(), hwnd, nullptr); if (m_mw) { - if (auto* sb=m_mw->statusBar()) { + if (auto* sb = m_mw->statusBar()) { sb->clearMessage(); } } @@ -222,17 +196,12 @@ void ShellMenu::exec(const QPoint& pos) } invoke(pos, cmd - QCM_FIRST); - } - catch(MenuFailed& e) - { + } catch (MenuFailed& e) { if (m_files.size() == 1) { - log::error( - "can't exec shell menu for '{}': {}", - QDir::toNativeSeparators(m_files[0].absoluteFilePath()), e.what()); + log::error("can't exec shell menu for '{}': {}", + QDir::toNativeSeparators(m_files[0].absoluteFilePath()), e.what()); } else { - log::error( - "can't exec shell menu for {} files: {}", - m_files.size(), e.what()); + log::error("can't exec shell menu for {} files: {}", m_files.size(), e.what()); } } } @@ -279,21 +248,21 @@ bool ShellMenu::wndProc(HWND h, UINT m, WPARAM wp, LPARAM lp, LRESULT* out) // adapted from // https://devblogs.microsoft.com/oldnewthing/20040928-00/?p=37723 // -void ShellMenu::onMenuSelect( - HWND hwnd, HMENU hmenu, int item, HMENU hmenuPopup, UINT flags) +void ShellMenu::onMenuSelect(HWND hwnd, HMENU hmenu, int item, HMENU hmenuPopup, + UINT flags) { if (m_cm && item >= QCM_FIRST && item <= QCM_LAST) { WCHAR szBuf[MAX_PATH]; - const auto r = IContextMenu_GetCommandString( - m_cm.get(), item - QCM_FIRST, GCS_HELPTEXTW, NULL, szBuf, MAX_PATH); + const auto r = IContextMenu_GetCommandString(m_cm.get(), item - QCM_FIRST, + GCS_HELPTEXTW, NULL, szBuf, MAX_PATH); if (FAILED(r)) { lstrcpynW(szBuf, L"No help available.", MAX_PATH); } if (m_mw) { - if (auto* sb=m_mw->statusBar()) { + if (auto* sb = m_mw->statusBar()) { sb->showMessage(QString::fromWCharArray(szBuf)); } } @@ -307,8 +276,7 @@ void ShellMenu::create() return; } - try - { + try { auto idls = createIdls(m_files); if (idls.empty()) { @@ -322,21 +290,14 @@ void ShellMenu::create() createContextMenu(array.get()); createPopupMenu(m_cm.get()); - } - catch(DummyMenu& dm) - { + } catch (DummyMenu& dm) { m_menu = createDummyMenu(dm.what()); - } - catch(MenuFailed& e) - { + } catch (MenuFailed& e) { if (m_files.size() == 1) { - log::error( - "can't create shell menu for '{}': {}", - QDir::toNativeSeparators(m_files[0].absoluteFilePath()), e.what()); + log::error("can't create shell menu for '{}': {}", + QDir::toNativeSeparators(m_files[0].absoluteFilePath()), e.what()); } else { - log::error( - "can't create shell menu for {} files: {}", - m_files.size(), e.what()); + log::error("can't create shell menu for {} files: {}", m_files.size(), e.what()); } m_menu = createDummyMenu(QObject::tr("No menu available")); @@ -345,8 +306,7 @@ void ShellMenu::create() HMenuPtr ShellMenu::createDummyMenu(const QString& what) { - try - { + try { HMENU menu = CreatePopupMenu(); if (!menu) { const auto e = GetLastError(); @@ -359,9 +319,7 @@ HMenuPtr ShellMenu::createDummyMenu(const QString& what) } return HMenuPtr(menu); - } - catch(MenuFailed& e) - { + } catch (MenuFailed& e) { log::error("{}", what); log::error("additionally, creating the dummy menu failed: {}", e.what()); @@ -369,8 +327,7 @@ HMenuPtr ShellMenu::createDummyMenu(const QString& what) } } -std::vector ShellMenu::createIdls( - const std::vector& files) +std::vector ShellMenu::createIdls(const std::vector& files) { std::vector idls; std::optional parent; @@ -382,14 +339,13 @@ std::vector ShellMenu::createIdls( parent = f.absoluteDir(); } else { if (*parent != f.absoluteDir()) { - throw DummyMenu(QObject::tr( - "Selected files must be in the same directory")); + throw DummyMenu(QObject::tr("Selected files must be in the same directory")); } } - auto item = createShellItem(path); + auto item = createShellItem(path); auto pidlist = getPersistIDList(item.get()); - auto absIdl = getIDList(pidlist.get()); + auto absIdl = getIDList(pidlist.get()); idls.push_back(absIdl.release()); } @@ -397,12 +353,11 @@ std::vector ShellMenu::createIdls( return idls; } -COMPtr ShellMenu::createItemArray( - std::vector& idls) +COMPtr ShellMenu::createItemArray(std::vector& idls) { IShellItemArray* array = nullptr; - auto r = SHCreateShellItemArrayFromIDLists( - static_cast(idls.size()), &idls[0], &array); + auto r = SHCreateShellItemArrayFromIDLists(static_cast(idls.size()), &idls[0], + &array); if (FAILED(r)) { throw MenuFailed(r, "SHCreateShellItemArrayFromIDLists failed"); @@ -415,8 +370,8 @@ void ShellMenu::createContextMenu(IShellItemArray* array) { IContextMenu* cm = nullptr; - auto r = array->BindToHandler( - nullptr, BHID_SFUIObject, IID_IContextMenu, (void**)&cm); + auto r = + array->BindToHandler(nullptr, BHID_SFUIObject, IID_IContextMenu, (void**)&cm); if (FAILED(r)) { throw MenuFailed(r, "BindToHandler failed"); @@ -447,8 +402,7 @@ void ShellMenu::createPopupMenu(IContextMenu* cm) throw MenuFailed(e, "CreatePopupMenu failed"); } - const auto r = cm->QueryContextMenu( - hmenu, 0, QCM_FIRST, QCM_LAST, CMF_EXTENDEDVERBS); + const auto r = cm->QueryContextMenu(hmenu, 0, QCM_FIRST, QCM_LAST, CMF_EXTENDEDVERBS); if (FAILED(r)) { throw MenuFailed(r, "QueryContextMenu failed"); @@ -461,8 +415,8 @@ COMPtr ShellMenu::createShellItem(const std::wstring& path) { IShellItem* item = nullptr; - auto r = SHCreateItemFromParsingName( - path.c_str(), nullptr, IID_IShellItem, (void**)&item); + auto r = + SHCreateItemFromParsingName(path.c_str(), nullptr, IID_IShellItem, (void**)&item); if (FAILED(r)) { throw MenuFailed(r, "SHCreateItemFromParsingName failed"); @@ -474,7 +428,7 @@ COMPtr ShellMenu::createShellItem(const std::wstring& path) COMPtr ShellMenu::getPersistIDList(IShellItem* item) { IPersistIDList* idl = nullptr; - auto r = item->QueryInterface(IID_IPersistIDList, (void**)&idl); + auto r = item->QueryInterface(IID_IPersistIDList, (void**)&idl); if (FAILED(r)) { throw MenuFailed(r, "QueryInterface IID_IPersistIDList failed"); @@ -486,7 +440,7 @@ COMPtr ShellMenu::getPersistIDList(IShellItem* item) CoTaskMemPtr ShellMenu::getIDList(IPersistIDList* pidlist) { LPITEMIDLIST absIdl = nullptr; - auto r = pidlist->GetIDList(&absIdl); + auto r = pidlist->GetIDList(&absIdl); if (FAILED(r)) { throw MenuFailed(r, "GetIDList failed"); @@ -501,12 +455,12 @@ void ShellMenu::invoke(const QPoint& p, int cmd) CMINVOKECOMMANDINFOEX info = {}; - info.cbSize = sizeof(info); - info.fMask = CMIC_MASK_UNICODE | CMIC_MASK_PTINVOKE; - info.hwnd = hwnd; - info.lpVerb = MAKEINTRESOURCEA(cmd); - info.lpVerbW = MAKEINTRESOURCEW(cmd); - info.nShow = SW_SHOWNORMAL; + info.cbSize = sizeof(info); + info.fMask = CMIC_MASK_UNICODE | CMIC_MASK_PTINVOKE; + info.hwnd = hwnd; + info.lpVerb = MAKEINTRESOURCEA(cmd); + info.lpVerbW = MAKEINTRESOURCEW(cmd); + info.nShow = SW_SHOWNORMAL; info.ptInvoke = {p.x(), p.y()}; // note: this calls the query version because the Qt even loop hasn't run @@ -528,11 +482,8 @@ void ShellMenu::invoke(const QPoint& p, int cmd) } } - -ShellMenuCollection::ShellMenuCollection(QMainWindow* mw) - : m_mw(mw), m_active(nullptr) -{ -} +ShellMenuCollection::ShellMenuCollection(QMainWindow* mw) : m_mw(mw), m_active(nullptr) +{} void ShellMenuCollection::addDetails(QString s) { @@ -550,9 +501,7 @@ void ShellMenuCollection::exec(const QPoint& pos) if (!menu) { const auto e = GetLastError(); - log::error( - "CreatePopupMenu for merged menus failed, {}", - formatSystemMessage(e)); + log::error("CreatePopupMenu for merged menus failed, {}", formatSystemMessage(e)); return; } @@ -560,22 +509,19 @@ void ShellMenuCollection::exec(const QPoint& pos) if (!m_details.empty()) { for (auto&& d : m_details) { const auto s = d.toStdWString(); - const auto r = AppendMenuW(menu, MF_STRING|MF_DISABLED, 0, s.c_str()); + const auto r = AppendMenuW(menu, MF_STRING | MF_DISABLED, 0, s.c_str()); if (!r) { const auto e = GetLastError(); - log::error( - "AppendMenuW failed for details '{}', {}", - d, formatSystemMessage(e)); + log::error("AppendMenuW failed for details '{}', {}", d, + formatSystemMessage(e)); } } const auto r = AppendMenuW(menu, MF_SEPARATOR, 0, nullptr); if (!r) { const auto e = GetLastError(); - log::error( - "AppendMenuW failed for separator, {}", - formatSystemMessage(e)); + log::error("AppendMenuW failed for separator, {}", formatSystemMessage(e)); } } @@ -585,16 +531,15 @@ void ShellMenuCollection::exec(const QPoint& pos) continue; } - const auto r = AppendMenuW( - menu, MF_POPUP | MF_STRING, - reinterpret_cast(hmenu), m.name.toStdWString().c_str()); + const auto r = + AppendMenuW(menu, MF_POPUP | MF_STRING, reinterpret_cast(hmenu), + m.name.toStdWString().c_str()); if (!r) { const auto e = GetLastError(); - log::error( - "AppendMenuW failed for merged menu {}, {}", - m.name, formatSystemMessage(e)); + log::error("AppendMenuW failed for merged menu {}, {}", m.name, + formatSystemMessage(e)); continue; } @@ -603,17 +548,17 @@ void ShellMenuCollection::exec(const QPoint& pos) auto hwnd = getHWND(m_mw); auto filter = std::make_unique( - [&](HWND h, UINT m, WPARAM wp, LPARAM lp, LRESULT* out) { - return wndProc(h, m, wp, lp, out); - }); + [&](HWND h, UINT m, WPARAM wp, LPARAM lp, LRESULT* out) { + return wndProc(h, m, wp, lp, out); + }); QCoreApplication::instance()->installNativeEventFilter(filter.get()); - const int cmd = TrackPopupMenuEx( - menu, TPM_RETURNCMD, pos.x(), pos.y(), hwnd, nullptr); + const int cmd = + TrackPopupMenuEx(menu, TPM_RETURNCMD, pos.x(), pos.y(), hwnd, nullptr); if (m_mw) { - if (auto* sb=m_mw->statusBar()) { + if (auto* sb = m_mw->statusBar()) { sb->clearMessage(); } } @@ -633,12 +578,11 @@ void ShellMenuCollection::exec(const QPoint& pos) m_active->menu.invoke(pos, realCmd); } -bool ShellMenuCollection::wndProc( - HWND h, UINT m, WPARAM wp, LPARAM lp, LRESULT* out) +bool ShellMenuCollection::wndProc(HWND h, UINT m, WPARAM wp, LPARAM lp, LRESULT* out) { if (m == WM_MENUSELECT) { auto* oldActive = m_active; - m_active = nullptr; + m_active = nullptr; HANDLE_WM_MENUSELECT(h, wp, lp, onMenuSelect); @@ -662,8 +606,8 @@ bool ShellMenuCollection::wndProc( return m_active->menu.wndProc(h, m, wp, lp, out); } -void ShellMenuCollection::onMenuSelect( - HWND hwnd, HMENU hmenu, int item, HMENU hmenuPopup, UINT flags) +void ShellMenuCollection::onMenuSelect(HWND hwnd, HMENU hmenu, int item, + HMENU hmenuPopup, UINT flags) { for (auto&& m : m_menus) { if (m.menu.getMenu() == hmenuPopup) { @@ -673,4 +617,4 @@ void ShellMenuCollection::onMenuSelect( } } -} // namespace +} // namespace env diff --git a/src/envshell.h b/src/envshell.h index f9245a371..f47c58c88 100644 --- a/src/envshell.h +++ b/src/envshell.h @@ -14,10 +14,10 @@ class ShellMenu ShellMenu(QMainWindow* mw); // noncopyable - ShellMenu(const ShellMenu&) = delete; + ShellMenu(const ShellMenu&) = delete; ShellMenu& operator=(const ShellMenu&) = delete; - ShellMenu(ShellMenu&&) = default; - ShellMenu& operator=(ShellMenu&&) = default; + ShellMenu(ShellMenu&&) = default; + ShellMenu& operator=(ShellMenu&&) = default; void addFile(QFileInfo fi); int fileCount() const; @@ -48,11 +48,9 @@ class ShellMenu CoTaskMemPtr getIDList(IPersistIDList* pidlist); HMenuPtr createDummyMenu(const QString& what); - void onMenuSelect( - HWND hwnd, HMENU hmenu, int item, HMENU hmenuPopup, UINT flags); + void onMenuSelect(HWND hwnd, HMENU hmenu, int item, HMENU hmenuPopup, UINT flags); }; - class ShellMenuCollection { public: @@ -77,10 +75,9 @@ class ShellMenuCollection bool wndProc(HWND hwnd, UINT m, WPARAM wp, LPARAM lp, LRESULT* out); - void onMenuSelect( - HWND hwnd, HMENU hmenu, int item, HMENU hmenuPopup, UINT flags); + void onMenuSelect(HWND hwnd, HMENU hmenu, int item, HMENU hmenuPopup, UINT flags); }; -} // namespace +} // namespace env -#endif // ENV_SHELL_H +#endif // ENV_SHELL_H diff --git a/src/envshortcut.cpp b/src/envshortcut.cpp index 4c9f0679b..204435b76 100644 --- a/src/envshortcut.cpp +++ b/src/envshortcut.cpp @@ -1,10 +1,10 @@ #include "envshortcut.h" #include "env.h" #include "executableslist.h" -#include "instancemanager.h" #include "filesystemutilities.h" -#include +#include "instancemanager.h" #include +#include namespace env { @@ -14,15 +14,9 @@ using namespace MOBase; class ShellLinkException { public: - ShellLinkException(QString s) - : m_what(std::move(s)) - { - } + ShellLinkException(QString s) : m_what(std::move(s)) {} - const QString& what() const - { - return m_what; - } + const QString& what() const { return m_what; } private: QString m_what; @@ -99,9 +93,7 @@ class ShellLinkWrapper void throwOnFail(HRESULT r, const QString& s) { if (FAILED(r)) { - throw ShellLinkException(QString("%1, %2") - .arg(s) - .arg(formatSystemMessage(r))); + throw ShellLinkException(QString("%1, %2").arg(s).arg(formatSystemMessage(r))); } } @@ -109,9 +101,8 @@ class ShellLinkWrapper { void* link = nullptr; - const auto r = CoCreateInstance( - CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, - IID_IShellLink, &link); + const auto r = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, + IID_IShellLink, &link); throwOnFail(r, "failed to create IShellLink instance"); @@ -137,23 +128,18 @@ class ShellLinkWrapper } }; +Shortcut::Shortcut() : m_iconIndex(0) {} -Shortcut::Shortcut() - : m_iconIndex(0) -{ -} - -Shortcut::Shortcut(const Executable& exe) - : Shortcut() +Shortcut::Shortcut(const Executable& exe) : Shortcut() { const auto i = *InstanceManager::singleton().currentInstance(); - m_name = MOBase::sanitizeFileName(exe.title()); + m_name = MOBase::sanitizeFileName(exe.title()); m_target = QFileInfo(qApp->applicationFilePath()).absoluteFilePath(); m_arguments = QString("\"moshortcut://%1:%2\"") - .arg(i.isPortable() ? "" : i.displayName()) - .arg(exe.title()); + .arg(i.isPortable() ? "" : i.displayName()) + .arg(exe.title()); m_description = QString("Run %1 with ModOrganizer").arg(exe.title()); @@ -190,7 +176,7 @@ Shortcut& Shortcut::description(const QString& s) Shortcut& Shortcut::icon(const QString& s, int index) { - m_icon = s; + m_icon = s; m_iconIndex = index; return *this; } @@ -222,21 +208,15 @@ bool Shortcut::toggle(Locations loc) bool Shortcut::add(Locations loc) { - log::debug( - "adding shortcut to {}:\n" - " . name: '{}'\n" - " . target: '{}'\n" - " . arguments: '{}'\n" - " . description: '{}'\n" - " . icon: '{}' @ {}\n" - " . working directory: '{}'", - toString(loc), - m_name, - m_target, - m_arguments, - m_description, - m_icon, m_iconIndex, - m_workingDirectory); + log::debug("adding shortcut to {}:\n" + " . name: '{}'\n" + " . target: '{}'\n" + " . arguments: '{}'\n" + " . description: '{}'\n" + " . icon: '{}' @ {}\n" + " . working directory: '{}'", + toString(loc), m_name, m_target, m_arguments, m_description, m_icon, + m_iconIndex, m_workingDirectory); if (m_target.isEmpty()) { log::error("shortcut: target is empty"); @@ -250,8 +230,7 @@ bool Shortcut::add(Locations loc) log::debug("shorcut file will be saved at '{}'", path); - try - { + try { ShellLinkWrapper link; link.setPath(m_target); @@ -263,9 +242,7 @@ bool Shortcut::add(Locations loc) link.save(path); return true; - } - catch(ShellLinkException& e) - { + } catch (ShellLinkException& e) { log::error("{}\nshortcut file was not saved", e.what()); } @@ -291,9 +268,7 @@ bool Shortcut::remove(Locations loc) if (!MOBase::shellDelete({path})) { const auto e = ::GetLastError(); - log::error( - "failed to remove shortcut '{}', {}", - path, formatSystemMessage(e)); + log::error("failed to remove shortcut '{}', {}", path, formatSystemMessage(e)); return false; } @@ -320,26 +295,22 @@ QString Shortcut::shortcutDirectory(Locations loc) const { QString dir; - try - { - switch (loc) - { - case Desktop: - dir = MOBase::getDesktopDirectory(); - break; - - case StartMenu: - dir = MOBase::getStartMenuDirectory(); - break; - - case None: - default: - log::error("shortcut: bad location {}", loc); - break; + try { + switch (loc) { + case Desktop: + dir = MOBase::getDesktopDirectory(); + break; + + case StartMenu: + dir = MOBase::getStartMenuDirectory(); + break; + + case None: + default: + log::error("shortcut: bad location {}", loc); + break; } - } - catch(std::exception&) - { + } catch (std::exception&) { } return QDir::toNativeSeparators(dir); @@ -355,23 +326,21 @@ QString Shortcut::shortcutFilename() const return m_name + ".lnk"; } - QString toString(Shortcut::Locations loc) { - switch (loc) - { - case Shortcut::None: - return "none"; + switch (loc) { + case Shortcut::None: + return "none"; - case Shortcut::Desktop: - return "desktop"; + case Shortcut::Desktop: + return "desktop"; - case Shortcut::StartMenu: - return "start menu"; + case Shortcut::StartMenu: + return "start menu"; - default: - return QString("? (%1)").arg(static_cast(loc)); + default: + return QString("? (%1)").arg(static_cast(loc)); } } -} // namespace +} // namespace env diff --git a/src/envshortcut.h b/src/envshortcut.h index a05528d9c..0cea14016 100644 --- a/src/envshortcut.h +++ b/src/envshortcut.h @@ -26,7 +26,6 @@ class Shortcut StartMenu }; - // empty shortcut // Shortcut(); @@ -53,13 +52,12 @@ class Shortcut // path to a binary that contains the icon and its index // - Shortcut& icon(const QString& s, int index=0); + Shortcut& icon(const QString& s, int index = 0); // "start in" option for this shortcut // Shortcut& workingDirectory(const QString& s); - // returns whether this shortcut already exists at the given location; this // does not check whether the shortcut parameters are different, it merely if // the .lnk file exists @@ -100,11 +98,10 @@ class Shortcut QString shortcutFilename() const; }; - // returns a string representation of the given location // QString toString(Shortcut::Locations loc); -} // namespace +} // namespace env -#endif // ENV_SHORTCUT_H +#endif // ENV_SHORTCUT_H diff --git a/src/envwindows.cpp b/src/envwindows.cpp index a2019fabc..d62047fa8 100644 --- a/src/envwindows.cpp +++ b/src/envwindows.cpp @@ -1,8 +1,8 @@ #include "envwindows.h" #include "env.h" #include "envmodule.h" -#include #include +#include namespace env { @@ -19,10 +19,10 @@ WindowsInfo::WindowsInfo() return; } else { m_reported = getReportedVersion(ntdll.get()); - m_real = getRealVersion(ntdll.get()); + m_real = getRealVersion(ntdll.get()); } - m_release = getRelease(); + m_release = getRelease(); m_elevated = getElevated(); } @@ -61,7 +61,7 @@ QString WindowsInfo::toString() const QStringList sl; const QString reported = m_reported.toString(); - const QString real = m_real.toString(); + const QString real = m_real.toString(); // version sl.push_back("version " + reported); @@ -113,17 +113,17 @@ WindowsInfo::Version WindowsInfo::getReportedVersion(HINSTANCE ntdll) const // // there's still RtlGetVersion() though - using RtlGetVersionType = NTSTATUS (NTAPI)(PRTL_OSVERSIONINFOW); + using RtlGetVersionType = NTSTATUS(NTAPI)(PRTL_OSVERSIONINFOW); - auto* RtlGetVersion = reinterpret_cast( - GetProcAddress(ntdll, "RtlGetVersion")); + auto* RtlGetVersion = + reinterpret_cast(GetProcAddress(ntdll, "RtlGetVersion")); if (!RtlGetVersion) { log::error("RtlGetVersion() not found in ntdll.dll"); return {}; } - OSVERSIONINFOEX vi = {}; + OSVERSIONINFOEX vi = {}; vi.dwOSVersionInfoSize = sizeof(vi); // this apparently never fails @@ -140,17 +140,17 @@ WindowsInfo::Version WindowsInfo::getRealVersion(HINSTANCE ntdll) const // RtlGetNtVersionNumbers() is an undocumented function that seems to work // fine, but it might not in the future - using RtlGetNtVersionNumbersType = void (NTAPI)(DWORD*, DWORD*, DWORD*); + using RtlGetNtVersionNumbersType = void(NTAPI)(DWORD*, DWORD*, DWORD*); auto* RtlGetNtVersionNumbers = reinterpret_cast( - GetProcAddress(ntdll, "RtlGetNtVersionNumbers")); + GetProcAddress(ntdll, "RtlGetNtVersionNumbers")); if (!RtlGetNtVersionNumbers) { log::error("RtlGetNtVersionNumbers not found in ntdll.dll"); return {}; } - DWORD major=0, minor=0, build=0; + DWORD major = 0, minor = 0, build = 0; RtlGetNtVersionNumbers(&major, &minor, &build); // for whatever reason, the build number has 0xf0000000 set @@ -168,8 +168,8 @@ WindowsInfo::Release WindowsInfo::getRelease() const // any of the other versions fail to work QSettings settings( - R"(HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion)", - QSettings::NativeFormat); + R"(HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion)", + QSettings::NativeFormat); Release r; @@ -201,12 +201,12 @@ std::optional WindowsInfo::getElevated() const { HANDLE rawToken = 0; - if (!OpenProcessToken(GetCurrentProcess( ), TOKEN_QUERY, &rawToken)) { + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &rawToken)) { const auto e = GetLastError(); - log::error( - "while trying to check if process is elevated, " - "OpenProcessToken() failed: {}", formatSystemMessage(e)); + log::error("while trying to check if process is elevated, " + "OpenProcessToken() failed: {}", + formatSystemMessage(e)); return {}; } @@ -215,14 +215,14 @@ std::optional WindowsInfo::getElevated() const } TOKEN_ELEVATION e = {}; - DWORD size = sizeof(TOKEN_ELEVATION); + DWORD size = sizeof(TOKEN_ELEVATION); if (!GetTokenInformation(token.get(), TokenElevation, &e, sizeof(e), &size)) { const auto e = GetLastError(); - log::error( - "while trying to check if process is elevated, " - "GetTokenInformation() failed: {}", formatSystemMessage(e)); + log::error("while trying to check if process is elevated, " + "GetTokenInformation() failed: {}", + formatSystemMessage(e)); return {}; } @@ -230,4 +230,4 @@ std::optional WindowsInfo::getElevated() const return (e.TokenIsElevated != 0); } -} // namespace +} // namespace env diff --git a/src/envwindows.h b/src/envwindows.h index 61ef609d7..7076bcc2b 100644 --- a/src/envwindows.h +++ b/src/envwindows.h @@ -14,7 +14,7 @@ class WindowsInfo public: struct Version { - DWORD major=0, minor=0, build=0; + DWORD major = 0, minor = 0, build = 0; QString toString() const { @@ -23,16 +23,10 @@ class WindowsInfo friend bool operator==(const Version& a, const Version& b) { - return - a.major == b.major && - a.minor == b.minor && - a.build == b.build; + return a.major == b.major && a.minor == b.minor && a.build == b.build; } - friend bool operator!=(const Version& a, const Version& b) - { - return !(a == b); - } + friend bool operator!=(const Version& a, const Version& b) { return !(a == b); } }; struct Release @@ -46,13 +40,9 @@ class WindowsInfo // some sub-build number, undocumented, may be empty DWORD UBR; - Release() - : UBR(0) - { - } + Release() : UBR(0) {} }; - WindowsInfo(); // tries to guess whether this process is running in compatibility mode @@ -102,6 +92,6 @@ class WindowsInfo std::optional getElevated() const; }; -} // namespace +} // namespace env -#endif // ENV_WINDOWS_H +#endif // ENV_WINDOWS_H diff --git a/src/executableslist.cpp b/src/executableslist.cpp index 137f38dc1..c683bdcbb 100644 --- a/src/executableslist.cpp +++ b/src/executableslist.cpp @@ -1,491 +1,483 @@ -/* -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 "executableslist.h" - -#include "iplugingame.h" -#include "utility.h" -#include "settings.h" -#include - -#include -#include -#include -#include - - -#include - - -using namespace MOBase; - -ExecutablesList::iterator ExecutablesList::begin() -{ - return m_Executables.begin(); -} - -ExecutablesList::const_iterator ExecutablesList::begin() const -{ - return m_Executables.begin(); -} - -ExecutablesList::iterator ExecutablesList::end() -{ - return m_Executables.end(); -} - -ExecutablesList::const_iterator ExecutablesList::end() const -{ - return m_Executables.end(); -} - -std::size_t ExecutablesList::size() const -{ - return m_Executables.size(); -} - -bool ExecutablesList::empty() const -{ - return m_Executables.empty(); -} - -void ExecutablesList::load(const MOBase::IPluginGame* game, const Settings& s) -{ - log::debug("loading executables"); - - m_Executables.clear(); - - // whether the executable list in the .ini is still using the old custom - // executables from 2.2.0, see upgradeFromCustom() - bool needsUpgrade = false; - - for (auto& map : s.executables()) { - Executable::Flags flags; - - if (map["toolbar"].toBool()) - flags |= Executable::ShowInToolbar; - - if (map["ownicon"].toBool()) - flags |= Executable::UseApplicationIcon; - - if (map["hide"].toBool()) - flags |= Executable::Hide; - - if (map.contains("custom")) { - // the "custom" setting only exists in older versions - needsUpgrade = true; - } - - setExecutable(Executable() - .title(map["title"].toString()) - .binaryInfo(QFileInfo(map["binary"].toString())) - .arguments(map["arguments"].toString()) - .steamAppID(map["steamAppID"].toString()) - .workingDirectory(map["workingDirectory"].toString()) - .flags(flags)); - } - - addFromPlugin(game, IgnoreExisting); - - if (needsUpgrade) - upgradeFromCustom(game); - - dump(); -} - -void ExecutablesList::store(Settings& s) -{ - std::vector> v; - - for (const auto& item : *this) { - std::map map; - - map["title"] = item.title(); - map["toolbar"] = item.isShownOnToolbar(); - map["ownicon"] = item.usesOwnIcon(); - map["hide"] = item.hide(); - map["binary"] = item.binaryInfo().filePath(); - map["arguments"] = item.arguments(); - map["workingDirectory"] = item.workingDirectory(); - map["steamAppID"] = item.steamAppID(); - - v.push_back(std::move(map)); - } - - s.setExecutables(v); -} - -std::vector ExecutablesList::getPluginExecutables( - MOBase::IPluginGame const *game) const -{ - Q_ASSERT(game != nullptr); - - std::vector v; - - for (const ExecutableInfo &info : game->executables()) { - if (!info.isValid()) { - continue; - } - - v.push_back({info, Executable::UseApplicationIcon}); - } - - const QFileInfo eppBin(QCoreApplication::applicationDirPath() + "/explorer++/Explorer++.exe"); - - if (eppBin.exists()) { - const auto args = QString("\"%1\"") - .arg(QDir::toNativeSeparators(game->dataDirectory().absolutePath())); - - const auto exe = Executable() - .title("Explore Virtual Folder") - .binaryInfo(eppBin) - .arguments(args) - .workingDirectory(eppBin.absolutePath()) - .flags(Executable::UseApplicationIcon); - - v.push_back(exe); - } - - return v; -} - -void ExecutablesList::resetFromPlugin(MOBase::IPluginGame const *game) -{ - log::debug("resetting plugin executables"); - - Q_ASSERT(game != nullptr); - - for (const auto& exe : getPluginExecutables(game)) { - setExecutable(exe, MoveExisting); - } -} - -void ExecutablesList::addFromPlugin(IPluginGame const *game, SetFlags flags) -{ - Q_ASSERT(game != nullptr); - - for (const auto& exe : getPluginExecutables(game)) { - setExecutable(exe, flags); - } -} - -const Executable &ExecutablesList::get(const QString &title) const -{ - for (const auto& exe : m_Executables) { - if (exe.title() == title) { - return exe; - } - } - - throw std::runtime_error(QString("executable not found: %1").arg(title).toLocal8Bit().constData()); -} - -Executable &ExecutablesList::get(const QString &title) -{ - return const_cast(std::as_const(*this).get(title)); -} - -Executable &ExecutablesList::getByBinary(const QFileInfo &info) -{ - for (Executable &exe : m_Executables) { - if (exe.binaryInfo() == info) { - return exe; - } - } - throw std::runtime_error("invalid info"); -} - -ExecutablesList::iterator ExecutablesList::find(const QString &title, bool ci) -{ - const auto cif = ci ? Qt::CaseInsensitive : Qt::CaseSensitive; - - return std::find_if(begin(), end(), [&](auto&& e) { - return (e.title().compare(title, cif) == 0); - }); -} - -ExecutablesList::const_iterator ExecutablesList::find(const QString &title, bool ci) const -{ - const auto cif = ci ? Qt::CaseInsensitive : Qt::CaseSensitive; - - return std::find_if(begin(), end(), [&](auto&& e) { - return (e.title().compare(title, cif) == 0); - }); -} - -bool ExecutablesList::titleExists(const QString &title) const -{ - auto test = [&] (const Executable &exe) { return exe.title() == title; }; - return std::find_if(m_Executables.begin(), m_Executables.end(), test) != m_Executables.end(); -} - -void ExecutablesList::setExecutable(const Executable &exe) -{ - setExecutable(exe, MergeExisting); -} - -void ExecutablesList::setExecutable(const Executable &exe, SetFlags flags) -{ - auto itor = find(exe.title()); - - if (itor != end()) { - if (flags == IgnoreExisting) { - return; - } - - if (flags == MoveExisting) { - const auto newTitle = makeNonConflictingTitle(exe.title()); - if (!newTitle) { - log::error( - "executable '{}' was in the way but could not be renamed", - exe.title()); - - return; - } - - log::warn( - "executable '{}' was in the way and was renamed to '{}'", - itor->title(), *newTitle); - - itor->title(*newTitle); - itor = end(); - } - } - - if (itor == m_Executables.end()) { - m_Executables.push_back(exe); - } else { - itor->mergeFrom(exe); - } -} - -void ExecutablesList::remove(const QString &title) -{ - auto itor = find(title); - if (itor != m_Executables.end()) { - m_Executables.erase(itor); - } -} - -std::optional ExecutablesList::makeNonConflictingTitle( - const QString& prefix) -{ - const int max = 100; - - QString title = prefix; - - for (int i=1; ibinaryInfo().exists()) { - itor->binaryInfo(exe.binaryInfo()); - } - - if (itor->workingDirectory().isEmpty()) { - itor->workingDirectory(exe.workingDirectory()); - } - } -} - -void ExecutablesList::dump() const -{ - for (const auto& e : m_Executables) { - QStringList flags; - - if (e.flags() & Executable::ShowInToolbar) { - flags.push_back("toolbar"); - } - - if (e.flags() & Executable::UseApplicationIcon) { - flags.push_back("icon"); - } - - if (e.flags() & Executable::Hide) { - flags.push_back("hide"); - } - - log::debug( - " . executable '{}'\n" - " binary: {}\n" - " arguments: {}\n" - " steam ID: {}\n" - " directory: {}\n" - " flags: {} ({})", - e.title(), e.binaryInfo().filePath(), e.arguments(), - e.steamAppID(), e.workingDirectory(), flags.join("|"), e.flags()); - } -} - - -Executable::Executable(QString title) - : m_title(title) -{ -} - -Executable::Executable(const MOBase::ExecutableInfo& info, Flags flags) : - m_title(info.title()), - m_binaryInfo(info.binary()), - m_arguments(info.arguments().join(" ")), - m_steamAppID(info.steamAppID()), - m_workingDirectory(info.workingDirectory().path()), - m_flags(flags) -{ -} - -const QString& Executable::title() const -{ - return m_title; -} - -const QFileInfo& Executable::binaryInfo() const -{ - return m_binaryInfo; -} - -const QString& Executable::arguments() const -{ - return m_arguments; -} - -const QString& Executable::steamAppID() const -{ - return m_steamAppID; -} - -const QString& Executable::workingDirectory() const -{ - return m_workingDirectory; -} - -Executable::Flags Executable::flags() const -{ - return m_flags; -} - -Executable& Executable::title(const QString& s) -{ - m_title = s; - return *this; -} - -Executable& Executable::binaryInfo(const QFileInfo& fi) -{ - m_binaryInfo = fi; - return *this; -} - -Executable& Executable::arguments(const QString& s) -{ - m_arguments = s; - return *this; -} - -Executable& Executable::steamAppID(const QString& s) -{ - m_steamAppID = s; - return *this; -} - -Executable& Executable::workingDirectory(const QString& s) -{ - m_workingDirectory = s; - return *this; -} - -Executable& Executable::flags(Flags f) -{ - m_flags = f; - return *this; -} - -bool Executable::isShownOnToolbar() const -{ - return m_flags.testFlag(ShowInToolbar); -} - -void Executable::setShownOnToolbar(bool state) -{ - if (state) { - m_flags |= ShowInToolbar; - } else { - m_flags &= ~ShowInToolbar; - } -} - -bool Executable::usesOwnIcon() const -{ - return m_flags.testFlag(UseApplicationIcon); -} - -bool Executable::hide() const -{ - return m_flags.testFlag(Hide); -} - -void Executable::mergeFrom(const Executable& other) -{ - // this happens after executables are loaded from settings and plugin - // executables are being added, or when users are modifying executables - - m_title = other.title(); - m_binaryInfo = other.binaryInfo(); - m_arguments = other.arguments(); - m_steamAppID = other.steamAppID(); - m_workingDirectory = other.workingDirectory(); - m_flags = other.flags(); -} +/* +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 "executableslist.h" + +#include "iplugingame.h" +#include "settings.h" +#include "utility.h" +#include + +#include +#include +#include +#include + +#include + +using namespace MOBase; + +ExecutablesList::iterator ExecutablesList::begin() +{ + return m_Executables.begin(); +} + +ExecutablesList::const_iterator ExecutablesList::begin() const +{ + return m_Executables.begin(); +} + +ExecutablesList::iterator ExecutablesList::end() +{ + return m_Executables.end(); +} + +ExecutablesList::const_iterator ExecutablesList::end() const +{ + return m_Executables.end(); +} + +std::size_t ExecutablesList::size() const +{ + return m_Executables.size(); +} + +bool ExecutablesList::empty() const +{ + return m_Executables.empty(); +} + +void ExecutablesList::load(const MOBase::IPluginGame* game, const Settings& s) +{ + log::debug("loading executables"); + + m_Executables.clear(); + + // whether the executable list in the .ini is still using the old custom + // executables from 2.2.0, see upgradeFromCustom() + bool needsUpgrade = false; + + for (auto& map : s.executables()) { + Executable::Flags flags; + + if (map["toolbar"].toBool()) + flags |= Executable::ShowInToolbar; + + if (map["ownicon"].toBool()) + flags |= Executable::UseApplicationIcon; + + if (map["hide"].toBool()) + flags |= Executable::Hide; + + if (map.contains("custom")) { + // the "custom" setting only exists in older versions + needsUpgrade = true; + } + + setExecutable(Executable() + .title(map["title"].toString()) + .binaryInfo(QFileInfo(map["binary"].toString())) + .arguments(map["arguments"].toString()) + .steamAppID(map["steamAppID"].toString()) + .workingDirectory(map["workingDirectory"].toString()) + .flags(flags)); + } + + addFromPlugin(game, IgnoreExisting); + + if (needsUpgrade) + upgradeFromCustom(game); + + dump(); +} + +void ExecutablesList::store(Settings& s) +{ + std::vector> v; + + for (const auto& item : *this) { + std::map map; + + map["title"] = item.title(); + map["toolbar"] = item.isShownOnToolbar(); + map["ownicon"] = item.usesOwnIcon(); + map["hide"] = item.hide(); + map["binary"] = item.binaryInfo().filePath(); + map["arguments"] = item.arguments(); + map["workingDirectory"] = item.workingDirectory(); + map["steamAppID"] = item.steamAppID(); + + v.push_back(std::move(map)); + } + + s.setExecutables(v); +} + +std::vector +ExecutablesList::getPluginExecutables(MOBase::IPluginGame const* game) const +{ + Q_ASSERT(game != nullptr); + + std::vector v; + + for (const ExecutableInfo& info : game->executables()) { + if (!info.isValid()) { + continue; + } + + v.push_back({info, Executable::UseApplicationIcon}); + } + + const QFileInfo eppBin(QCoreApplication::applicationDirPath() + + "/explorer++/Explorer++.exe"); + + if (eppBin.exists()) { + const auto args = QString("\"%1\"").arg( + QDir::toNativeSeparators(game->dataDirectory().absolutePath())); + + const auto exe = Executable() + .title("Explore Virtual Folder") + .binaryInfo(eppBin) + .arguments(args) + .workingDirectory(eppBin.absolutePath()) + .flags(Executable::UseApplicationIcon); + + v.push_back(exe); + } + + return v; +} + +void ExecutablesList::resetFromPlugin(MOBase::IPluginGame const* game) +{ + log::debug("resetting plugin executables"); + + Q_ASSERT(game != nullptr); + + for (const auto& exe : getPluginExecutables(game)) { + setExecutable(exe, MoveExisting); + } +} + +void ExecutablesList::addFromPlugin(IPluginGame const* game, SetFlags flags) +{ + Q_ASSERT(game != nullptr); + + for (const auto& exe : getPluginExecutables(game)) { + setExecutable(exe, flags); + } +} + +const Executable& ExecutablesList::get(const QString& title) const +{ + for (const auto& exe : m_Executables) { + if (exe.title() == title) { + return exe; + } + } + + throw std::runtime_error( + QString("executable not found: %1").arg(title).toLocal8Bit().constData()); +} + +Executable& ExecutablesList::get(const QString& title) +{ + return const_cast(std::as_const(*this).get(title)); +} + +Executable& ExecutablesList::getByBinary(const QFileInfo& info) +{ + for (Executable& exe : m_Executables) { + if (exe.binaryInfo() == info) { + return exe; + } + } + throw std::runtime_error("invalid info"); +} + +ExecutablesList::iterator ExecutablesList::find(const QString& title, bool ci) +{ + const auto cif = ci ? Qt::CaseInsensitive : Qt::CaseSensitive; + + return std::find_if(begin(), end(), [&](auto&& e) { + return (e.title().compare(title, cif) == 0); + }); +} + +ExecutablesList::const_iterator ExecutablesList::find(const QString& title, + bool ci) const +{ + const auto cif = ci ? Qt::CaseInsensitive : Qt::CaseSensitive; + + return std::find_if(begin(), end(), [&](auto&& e) { + return (e.title().compare(title, cif) == 0); + }); +} + +bool ExecutablesList::titleExists(const QString& title) const +{ + auto test = [&](const Executable& exe) { + return exe.title() == title; + }; + return std::find_if(m_Executables.begin(), m_Executables.end(), test) != + m_Executables.end(); +} + +void ExecutablesList::setExecutable(const Executable& exe) +{ + setExecutable(exe, MergeExisting); +} + +void ExecutablesList::setExecutable(const Executable& exe, SetFlags flags) +{ + auto itor = find(exe.title()); + + if (itor != end()) { + if (flags == IgnoreExisting) { + return; + } + + if (flags == MoveExisting) { + const auto newTitle = makeNonConflictingTitle(exe.title()); + if (!newTitle) { + log::error("executable '{}' was in the way but could not be renamed", + exe.title()); + + return; + } + + log::warn("executable '{}' was in the way and was renamed to '{}'", itor->title(), + *newTitle); + + itor->title(*newTitle); + itor = end(); + } + } + + if (itor == m_Executables.end()) { + m_Executables.push_back(exe); + } else { + itor->mergeFrom(exe); + } +} + +void ExecutablesList::remove(const QString& title) +{ + auto itor = find(title); + if (itor != m_Executables.end()) { + m_Executables.erase(itor); + } +} + +std::optional ExecutablesList::makeNonConflictingTitle(const QString& prefix) +{ + const int max = 100; + + QString title = prefix; + + for (int i = 1; i < max; ++i) { + if (!titleExists(title)) { + return title; + } + + title = prefix + QString(" (%1)").arg(i); + } + + log::error("ran out of executable titles for prefix '{}'", prefix); + return {}; +} + +void ExecutablesList::upgradeFromCustom(MOBase::IPluginGame const* game) +{ + log::debug("upgrading executables list"); + + Q_ASSERT(game != nullptr); + + // prior to 2.2.1, plugin executables were special in the sense that they + // did not store certain settings, like the binary info and working directory; + // those were filled in when MO started, but never saved + // + // this interferes with the new executables list, which is completely + // customizable, because plugin executables are only added to the list when + // they don't exist at all and are ignored otherwise, leaving some of the + // fields completely blank + // + // when the "custom" setting is found in the .ini file (see load()), it is + // assumed that the older scheme is still present; in that case, the plugin + // executables force their binary info and working directory into the existing + // executables one last time + // + // from that point on, plugin executables are ignored unless they're + // completely missing from the list, allowing users to customize them as they + // want + + for (const auto& exe : getPluginExecutables(game)) { + auto itor = find(exe.title()); + if (itor == end()) { + continue; + } + + if (!itor->binaryInfo().exists()) { + itor->binaryInfo(exe.binaryInfo()); + } + + if (itor->workingDirectory().isEmpty()) { + itor->workingDirectory(exe.workingDirectory()); + } + } +} + +void ExecutablesList::dump() const +{ + for (const auto& e : m_Executables) { + QStringList flags; + + if (e.flags() & Executable::ShowInToolbar) { + flags.push_back("toolbar"); + } + + if (e.flags() & Executable::UseApplicationIcon) { + flags.push_back("icon"); + } + + if (e.flags() & Executable::Hide) { + flags.push_back("hide"); + } + + log::debug(" . executable '{}'\n" + " binary: {}\n" + " arguments: {}\n" + " steam ID: {}\n" + " directory: {}\n" + " flags: {} ({})", + e.title(), e.binaryInfo().filePath(), e.arguments(), e.steamAppID(), + e.workingDirectory(), flags.join("|"), e.flags()); + } +} + +Executable::Executable(QString title) : m_title(title) {} + +Executable::Executable(const MOBase::ExecutableInfo& info, Flags flags) + : m_title(info.title()), m_binaryInfo(info.binary()), + m_arguments(info.arguments().join(" ")), m_steamAppID(info.steamAppID()), + m_workingDirectory(info.workingDirectory().path()), m_flags(flags) +{} + +const QString& Executable::title() const +{ + return m_title; +} + +const QFileInfo& Executable::binaryInfo() const +{ + return m_binaryInfo; +} + +const QString& Executable::arguments() const +{ + return m_arguments; +} + +const QString& Executable::steamAppID() const +{ + return m_steamAppID; +} + +const QString& Executable::workingDirectory() const +{ + return m_workingDirectory; +} + +Executable::Flags Executable::flags() const +{ + return m_flags; +} + +Executable& Executable::title(const QString& s) +{ + m_title = s; + return *this; +} + +Executable& Executable::binaryInfo(const QFileInfo& fi) +{ + m_binaryInfo = fi; + return *this; +} + +Executable& Executable::arguments(const QString& s) +{ + m_arguments = s; + return *this; +} + +Executable& Executable::steamAppID(const QString& s) +{ + m_steamAppID = s; + return *this; +} + +Executable& Executable::workingDirectory(const QString& s) +{ + m_workingDirectory = s; + return *this; +} + +Executable& Executable::flags(Flags f) +{ + m_flags = f; + return *this; +} + +bool Executable::isShownOnToolbar() const +{ + return m_flags.testFlag(ShowInToolbar); +} + +void Executable::setShownOnToolbar(bool state) +{ + if (state) { + m_flags |= ShowInToolbar; + } else { + m_flags &= ~ShowInToolbar; + } +} + +bool Executable::usesOwnIcon() const +{ + return m_flags.testFlag(UseApplicationIcon); +} + +bool Executable::hide() const +{ + return m_flags.testFlag(Hide); +} + +void Executable::mergeFrom(const Executable& other) +{ + // this happens after executables are loaded from settings and plugin + // executables are being added, or when users are modifying executables + + m_title = other.title(); + m_binaryInfo = other.binaryInfo(); + m_arguments = other.arguments(); + m_steamAppID = other.steamAppID(); + m_workingDirectory = other.workingDirectory(); + m_flags = other.flags(); +} diff --git a/src/executableslist.h b/src/executableslist.h index 97d395004..21a30056c 100644 --- a/src/executableslist.h +++ b/src/executableslist.h @@ -1,228 +1,230 @@ -/* -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 EXECUTABLESLIST_H -#define EXECUTABLESLIST_H - -#include "executableinfo.h" - -#include -#include - -#include -#include - -namespace MOBase { class IPluginGame; class ExecutableInfo; } -class Settings; - -/*! - * @brief Information about an executable - **/ -class Executable -{ -public: - enum Flag - { - ShowInToolbar = 0x02, - UseApplicationIcon = 0x04, - Hide = 0x08 - }; - - Q_DECLARE_FLAGS(Flags, Flag); - - Executable(QString title={}); - - /** - * @brief Executable from plugin - */ - Executable(const MOBase::ExecutableInfo& info, Flags flags); - - const QString& title() const; - const QFileInfo& binaryInfo() const; - const QString& arguments() const; - const QString& steamAppID() const; - const QString& workingDirectory() const; - Flags flags() const; - - Executable& title(const QString& s); - Executable& binaryInfo(const QFileInfo& fi); - Executable& arguments(const QString& s); - Executable& steamAppID(const QString& s); - Executable& workingDirectory(const QString& s); - Executable& flags(Flags f); - - bool isShownOnToolbar() const; - void setShownOnToolbar(bool state); - bool usesOwnIcon() const; - bool hide() const; - - void mergeFrom(const Executable& other); - -private: - QString m_title; - QFileInfo m_binaryInfo; - QString m_arguments; - QString m_steamAppID; - QString m_workingDirectory; - Flags m_flags; -}; - - -/*! - * @brief List of executables configured to by started from MO - **/ -class ExecutablesList { -public: - using vector_type = std::vector; - using iterator = vector_type::iterator; - using const_iterator = vector_type::const_iterator; - - /** - * standard container interface - */ - iterator begin(); - const_iterator begin() const; - iterator end(); - const_iterator end() const; - std::size_t size() const; - bool empty() const; - - /** - * @brief initializes the list from the settings and the given plugin - **/ - void load(const MOBase::IPluginGame* game, const Settings& settings); - - /** - * @brief re-adds all the executables from the plugin and renames existing - * executables that are in the way - **/ - void resetFromPlugin(MOBase::IPluginGame const *game); - - /** - * @brief writes the current list to the settings - */ - void store(Settings& settings); - - /** - * @brief get an executable by name - * - * @param title the title of the executable to look up - * @return the executable - * @exception runtime_error will throw an exception if the executable is not found - **/ - const Executable &get(const QString &title) const; - - /** - * @brief find an executable by its name - * - * @param title the title of the executable to look up - * @return the executable - * @exception runtime_error will throw an exception if the name is not correct - **/ - Executable &get(const QString &title); - - /** - * @brief find an executable by a fileinfo structure - * @param info the info object to search for - * @return the executable - * @exception runtime_error will throw an exception if the name is not correct - */ - Executable &getByBinary(const QFileInfo &info); - - /** - * @brief returns an iterator for the given executable by title, or end() - */ - iterator find(const QString &title, bool caseSensitive=true); - const_iterator find(const QString &title, bool caseSensitive=true) const; - - /** - * @brief determine if an executable exists - * @param title the title of the executable to look up - * @return true if the executable exists, false otherwise - **/ - bool titleExists(const QString &title) const; - - /** - * @brief add a new executable to the list - * @param executable - */ - void setExecutable(const Executable &executable); - - /** - * @brief remove the executable with the specified file name. This needs to - * be an absolute file path - * - * @param title title of the executable to remove - * @note if the executable name is invalid, nothing happens. There is no way - * to determine if this was successful - **/ - void remove(const QString &title); - - /** - * returns a title that starts with the given prefix and does not clash with - * an existing executable, may fail - */ - std::optional makeNonConflictingTitle(const QString& prefix); - -private: - enum SetFlags - { - // executables having the same name as existing ones are ignored - IgnoreExisting = 1, - - // executables having the same name are merged - MergeExisting, - - // an existing executable with the same name is renamed - MoveExisting - }; - - std::vector m_Executables; - - - /** - * @brief add the executables preconfigured for this game - **/ - void addFromPlugin(MOBase::IPluginGame const *game, SetFlags flags); - - /** - * @brief add a new executable to the list - * @param executable - */ - void setExecutable(const Executable &exe, SetFlags flags); - - /** - * returns the executables provided by the game plugin - **/ - std::vector getPluginExecutables( - MOBase::IPluginGame const *game) const; - - /** - * called when MO is still using the old custom executables from 2.2.0 - **/ - void upgradeFromCustom(const MOBase::IPluginGame* game); - - // logs all executables - // - void dump() const; -}; - -Q_DECLARE_OPERATORS_FOR_FLAGS(Executable::Flags) - -#endif // EXECUTABLESLIST_H +/* +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 EXECUTABLESLIST_H +#define EXECUTABLESLIST_H + +#include "executableinfo.h" + +#include +#include + +#include +#include + +namespace MOBase +{ +class IPluginGame; +class ExecutableInfo; +} // namespace MOBase +class Settings; + +/*! + * @brief Information about an executable + **/ +class Executable +{ +public: + enum Flag + { + ShowInToolbar = 0x02, + UseApplicationIcon = 0x04, + Hide = 0x08 + }; + + Q_DECLARE_FLAGS(Flags, Flag); + + Executable(QString title = {}); + + /** + * @brief Executable from plugin + */ + Executable(const MOBase::ExecutableInfo& info, Flags flags); + + const QString& title() const; + const QFileInfo& binaryInfo() const; + const QString& arguments() const; + const QString& steamAppID() const; + const QString& workingDirectory() const; + Flags flags() const; + + Executable& title(const QString& s); + Executable& binaryInfo(const QFileInfo& fi); + Executable& arguments(const QString& s); + Executable& steamAppID(const QString& s); + Executable& workingDirectory(const QString& s); + Executable& flags(Flags f); + + bool isShownOnToolbar() const; + void setShownOnToolbar(bool state); + bool usesOwnIcon() const; + bool hide() const; + + void mergeFrom(const Executable& other); + +private: + QString m_title; + QFileInfo m_binaryInfo; + QString m_arguments; + QString m_steamAppID; + QString m_workingDirectory; + Flags m_flags; +}; + +/*! + * @brief List of executables configured to by started from MO + **/ +class ExecutablesList +{ +public: + using vector_type = std::vector; + using iterator = vector_type::iterator; + using const_iterator = vector_type::const_iterator; + + /** + * standard container interface + */ + iterator begin(); + const_iterator begin() const; + iterator end(); + const_iterator end() const; + std::size_t size() const; + bool empty() const; + + /** + * @brief initializes the list from the settings and the given plugin + **/ + void load(const MOBase::IPluginGame* game, const Settings& settings); + + /** + * @brief re-adds all the executables from the plugin and renames existing + * executables that are in the way + **/ + void resetFromPlugin(MOBase::IPluginGame const* game); + + /** + * @brief writes the current list to the settings + */ + void store(Settings& settings); + + /** + * @brief get an executable by name + * + * @param title the title of the executable to look up + * @return the executable + * @exception runtime_error will throw an exception if the executable is not found + **/ + const Executable& get(const QString& title) const; + + /** + * @brief find an executable by its name + * + * @param title the title of the executable to look up + * @return the executable + * @exception runtime_error will throw an exception if the name is not correct + **/ + Executable& get(const QString& title); + + /** + * @brief find an executable by a fileinfo structure + * @param info the info object to search for + * @return the executable + * @exception runtime_error will throw an exception if the name is not correct + */ + Executable& getByBinary(const QFileInfo& info); + + /** + * @brief returns an iterator for the given executable by title, or end() + */ + iterator find(const QString& title, bool caseSensitive = true); + const_iterator find(const QString& title, bool caseSensitive = true) const; + + /** + * @brief determine if an executable exists + * @param title the title of the executable to look up + * @return true if the executable exists, false otherwise + **/ + bool titleExists(const QString& title) const; + + /** + * @brief add a new executable to the list + * @param executable + */ + void setExecutable(const Executable& executable); + + /** + * @brief remove the executable with the specified file name. This needs to + * be an absolute file path + * + * @param title title of the executable to remove + * @note if the executable name is invalid, nothing happens. There is no way + * to determine if this was successful + **/ + void remove(const QString& title); + + /** + * returns a title that starts with the given prefix and does not clash with + * an existing executable, may fail + */ + std::optional makeNonConflictingTitle(const QString& prefix); + +private: + enum SetFlags + { + // executables having the same name as existing ones are ignored + IgnoreExisting = 1, + + // executables having the same name are merged + MergeExisting, + + // an existing executable with the same name is renamed + MoveExisting + }; + + std::vector m_Executables; + + /** + * @brief add the executables preconfigured for this game + **/ + void addFromPlugin(MOBase::IPluginGame const* game, SetFlags flags); + + /** + * @brief add a new executable to the list + * @param executable + */ + void setExecutable(const Executable& exe, SetFlags flags); + + /** + * returns the executables provided by the game plugin + **/ + std::vector getPluginExecutables(MOBase::IPluginGame const* game) const; + + /** + * called when MO is still using the old custom executables from 2.2.0 + **/ + void upgradeFromCustom(const MOBase::IPluginGame* game); + + // logs all executables + // + void dump() const; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(Executable::Flags) + +#endif // EXECUTABLESLIST_H diff --git a/src/filedialogmemory.cpp b/src/filedialogmemory.cpp index 8cfeb6b55..721fca84a 100644 --- a/src/filedialogmemory.cpp +++ b/src/filedialogmemory.cpp @@ -1,82 +1,83 @@ -/* -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 "filedialogmemory.h" -#include "settings.h" -#include - -static std::map g_Cache; - -void FileDialogMemory::save(Settings& s) -{ - s.paths().setRecent(g_Cache); -} - -void FileDialogMemory::restore(const Settings& s) -{ - g_Cache = s.paths().recent(); -} - -QString FileDialogMemory::getOpenFileName( - const QString &dirID, QWidget *parent, const QString &caption, - const QString &dir, const QString &filter, QString *selectedFilter, - QFileDialog::Options options) -{ - QString currentDir = dir; - - if (currentDir.isEmpty()) { - auto itor = g_Cache.find(dirID); - if (itor != g_Cache.end()) { - currentDir = itor->second; - } - } - - QString result = QFileDialog::getOpenFileName( - parent, caption, currentDir, filter, selectedFilter, options); - - if (!result.isNull()) { - g_Cache[dirID] = QFileInfo(result).path(); - } - - return result; -} - - -QString FileDialogMemory::getExistingDirectory( - const QString &dirID, QWidget *parent, const QString &caption, - const QString &dir, QFileDialog::Options options) -{ - QString currentDir = dir; - - if (currentDir.isEmpty()) { - auto itor = g_Cache.find(dirID); - if (itor != g_Cache.end()) { - currentDir = itor->second; - } - } - - QString result = QFileDialog::getExistingDirectory( - parent, caption, currentDir, options); - - if (!result.isNull()) { - g_Cache[dirID] = result; - } - - return result; -} +/* +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 "filedialogmemory.h" +#include "settings.h" +#include + +static std::map g_Cache; + +void FileDialogMemory::save(Settings& s) +{ + s.paths().setRecent(g_Cache); +} + +void FileDialogMemory::restore(const Settings& s) +{ + g_Cache = s.paths().recent(); +} + +QString FileDialogMemory::getOpenFileName(const QString& dirID, QWidget* parent, + const QString& caption, const QString& dir, + const QString& filter, + QString* selectedFilter, + QFileDialog::Options options) +{ + QString currentDir = dir; + + if (currentDir.isEmpty()) { + auto itor = g_Cache.find(dirID); + if (itor != g_Cache.end()) { + currentDir = itor->second; + } + } + + QString result = QFileDialog::getOpenFileName(parent, caption, currentDir, filter, + selectedFilter, options); + + if (!result.isNull()) { + g_Cache[dirID] = QFileInfo(result).path(); + } + + return result; +} + +QString FileDialogMemory::getExistingDirectory(const QString& dirID, QWidget* parent, + const QString& caption, + const QString& dir, + QFileDialog::Options options) +{ + QString currentDir = dir; + + if (currentDir.isEmpty()) { + auto itor = g_Cache.find(dirID); + if (itor != g_Cache.end()) { + currentDir = itor->second; + } + } + + QString result = + QFileDialog::getExistingDirectory(parent, caption, currentDir, options); + + if (!result.isNull()) { + g_Cache[dirID] = result; + } + + return result; +} diff --git a/src/filedialogmemory.h b/src/filedialogmemory.h index 11b156ede..464ac3bc9 100644 --- a/src/filedialogmemory.h +++ b/src/filedialogmemory.h @@ -1,49 +1,51 @@ -/* -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 FILEDIALOGMEMORY_H -#define FILEDIALOGMEMORY_H - - -#include -#include -#include - -class Settings; - -class FileDialogMemory -{ -public: - FileDialogMemory() = delete; - - static void save(Settings& settings); - static void restore(const Settings& settings); - - static QString getOpenFileName( - const QString &dirID, QWidget *parent = 0, const QString &caption = QString(), - const QString &dir = QString(), const QString &filter = QString(), - QString *selectedFilter = 0, QFileDialog::Options options = QFileDialog::Option(0)); - - static QString getExistingDirectory( - const QString &dirID, QWidget *parent = 0, const QString &caption = QString(), - const QString &dir = QString(), - QFileDialog::Options options = QFileDialog::ShowDirsOnly); -}; - -#endif // FILEDIALOGMEMORY_H +/* +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 FILEDIALOGMEMORY_H +#define FILEDIALOGMEMORY_H + +#include +#include +#include + +class Settings; + +class FileDialogMemory +{ +public: + FileDialogMemory() = delete; + + static void save(Settings& settings); + static void restore(const Settings& settings); + + static QString getOpenFileName(const QString& dirID, QWidget* parent = 0, + const QString& caption = QString(), + const QString& dir = QString(), + const QString& filter = QString(), + QString* selectedFilter = 0, + QFileDialog::Options options = QFileDialog::Option(0)); + + static QString + getExistingDirectory(const QString& dirID, QWidget* parent = 0, + const QString& caption = QString(), + const QString& dir = QString(), + QFileDialog::Options options = QFileDialog::ShowDirsOnly); +}; + +#endif // FILEDIALOGMEMORY_H diff --git a/src/filerenamer.cpp b/src/filerenamer.cpp index dd5876b60..c6172dc6a 100644 --- a/src/filerenamer.cpp +++ b/src/filerenamer.cpp @@ -1,23 +1,24 @@ #include "filerenamer.h" -#include -#include -#include #include +#include +#include +#include using namespace MOBase; FileRenamer::FileRenamer(QWidget* parent, QFlags flags) - : m_parent(parent), m_flags(flags) + : m_parent(parent), m_flags(flags) { // sanity check for flags - if ((m_flags & (HIDE|UNHIDE)) == 0) { + if ((m_flags & (HIDE | UNHIDE)) == 0) { log::error("renameFile() missing hide flag"); // doesn't really matter, it's just for text m_flags = HIDE; } } -FileRenamer::RenameResults FileRenamer::rename(const QString& oldName, const QString& newName) +FileRenamer::RenameResults FileRenamer::rename(const QString& oldName, + const QString& newName) { log::debug("renaming {} to {}", oldName, newName); @@ -28,42 +29,42 @@ FileRenamer::RenameResults FileRenamer::rename(const QString& oldName, const QSt auto answer = confirmReplace(newName); switch (answer) { - case DECISION_SKIP: { - // user wants to skip this file - log::debug("skipping {}", oldName); - return RESULT_SKIP; - } - - case DECISION_REPLACE: { - log::debug("removing {}", newName); + case DECISION_SKIP: { + // user wants to skip this file + log::debug("skipping {}", oldName); + return RESULT_SKIP; + } - // user wants to replace the file, so remove it - const auto r = shell::Delete(QFileInfo(newName)); + case DECISION_REPLACE: { + log::debug("removing {}", newName); - if (!r.success()) { - log::error("failed to remove '{}': {}", newName, r.toString()); + // user wants to replace the file, so remove it + const auto r = shell::Delete(QFileInfo(newName)); - // removal failed, warn the user and allow canceling - if (!removeFailed(newName, r)) { - log::debug("canceling {}", oldName); - // user wants to cancel - return RESULT_CANCEL; - } + if (!r.success()) { + log::error("failed to remove '{}': {}", newName, r.toString()); - // ignore this file and continue on - log::debug("skipping {}", oldName); - return RESULT_SKIP; + // removal failed, warn the user and allow canceling + if (!removeFailed(newName, r)) { + log::debug("canceling {}", oldName); + // user wants to cancel + return RESULT_CANCEL; } - break; + // ignore this file and continue on + log::debug("skipping {}", oldName); + return RESULT_SKIP; } - case DECISION_CANCEL: // fall-through - default: { - // user wants to stop - log::debug("canceling"); - return RESULT_CANCEL; - } + break; + } + + case DECISION_CANCEL: // fall-through + default: { + // user wants to stop + log::debug("canceling"); + return RESULT_CANCEL; + } } } @@ -71,9 +72,7 @@ FileRenamer::RenameResults FileRenamer::rename(const QString& oldName, const QSt const auto r = shell::Rename(QFileInfo(oldName), QFileInfo(newName)); if (!r.success()) { - log::error( - "failed to rename '{}' to '{}': {}", - oldName, newName, r.toString()); + log::error("failed to rename '{}' to '{}': {}", oldName, newName, r.toString()); // renaming failed, warn the user and allow canceling if (!renameFailed(oldName, newName, r)) { @@ -98,8 +97,7 @@ FileRenamer::RenameDecision FileRenamer::confirmReplace(const QString& newName) // user wants to silently replace all log::debug("user has selected replace all"); return DECISION_REPLACE; - } - else if (m_flags & REPLACE_NONE) { + } else if (m_flags & REPLACE_NONE) { // user wants to silently skip all log::debug("user has selected replace none"); return DECISION_SKIP; @@ -108,10 +106,11 @@ FileRenamer::RenameDecision FileRenamer::confirmReplace(const QString& newName) QString text; if (m_flags & HIDE) { - text = QObject::tr("The hidden file \"%1\" already exists. Replace it?").arg(newName); - } - else if (m_flags & UNHIDE) { - text = QObject::tr("The visible file \"%1\" already exists. Replace it?").arg(newName); + text = + QObject::tr("The hidden file \"%1\" already exists. Replace it?").arg(newName); + } else if (m_flags & UNHIDE) { + text = + QObject::tr("The visible file \"%1\" already exists. Replace it?").arg(newName); } auto buttons = QMessageBox::Yes | QMessageBox::No; @@ -120,34 +119,34 @@ FileRenamer::RenameDecision FileRenamer::confirmReplace(const QString& newName) buttons |= QMessageBox::YesToAll | QMessageBox::NoToAll | QMessageBox::Cancel; } - const auto answer = QMessageBox::question( - m_parent, QObject::tr("Replace file?"), text, buttons); + const auto answer = + QMessageBox::question(m_parent, QObject::tr("Replace file?"), text, buttons); switch (answer) { - case QMessageBox::Yes: - log::debug("user wants to replace"); - return DECISION_REPLACE; - - case QMessageBox::No: - log::debug("user wants to skip"); - return DECISION_SKIP; - - case QMessageBox::YesToAll: - log::debug("user wants to replace all"); - // remember the answer - m_flags |= REPLACE_ALL; - return DECISION_REPLACE; - - case QMessageBox::NoToAll: - log::debug("user wants to replace none"); - // remember the answer - m_flags |= REPLACE_NONE; - return DECISION_SKIP; - - case QMessageBox::Cancel: // fall-through - default: - log::debug("user wants to cancel"); - return DECISION_CANCEL; + case QMessageBox::Yes: + log::debug("user wants to replace"); + return DECISION_REPLACE; + + case QMessageBox::No: + log::debug("user wants to skip"); + return DECISION_SKIP; + + case QMessageBox::YesToAll: + log::debug("user wants to replace all"); + // remember the answer + m_flags |= REPLACE_ALL; + return DECISION_REPLACE; + + case QMessageBox::NoToAll: + log::debug("user wants to replace none"); + // remember the answer + m_flags |= REPLACE_NONE; + return DECISION_SKIP; + + case QMessageBox::Cancel: // fall-through + default: + log::debug("user wants to cancel"); + return DECISION_CANCEL; } } @@ -160,10 +159,8 @@ bool FileRenamer::removeFailed(const QString& name, const shell::Result& r) } const auto answer = QMessageBox::critical( - m_parent, - QObject::tr("File operation failed"), - QObject::tr("Failed to remove \"%1\": %2").arg(name).arg(r.toString()), - buttons); + m_parent, QObject::tr("File operation failed"), + QObject::tr("Failed to remove \"%1\": %2").arg(name).arg(r.toString()), buttons); if (answer == QMessageBox::Cancel) { // user wants to stop @@ -176,8 +173,8 @@ bool FileRenamer::removeFailed(const QString& name, const shell::Result& r) return true; } -bool FileRenamer::renameFailed( - const QString& oldName, const QString& newName, const shell::Result& r) +bool FileRenamer::renameFailed(const QString& oldName, const QString& newName, + const shell::Result& r) { QMessageBox::StandardButtons buttons = QMessageBox::Ok; if (m_flags & MULTIPLE) { @@ -185,17 +182,15 @@ bool FileRenamer::renameFailed( buttons |= QMessageBox::Cancel; } - const auto answer = QMessageBox::critical( - m_parent, - QObject::tr("File operation failed"), - QObject::tr( - "Failed to rename file: %1.\r\n\r\n" - "Source:\r\n\"%2\"\r\n\r\n" - "Destination:\r\n\"%3\"") - .arg(r.toString()) - .arg(QDir::toNativeSeparators(oldName)) - .arg(QDir::toNativeSeparators(newName)), - buttons); + const auto answer = + QMessageBox::critical(m_parent, QObject::tr("File operation failed"), + QObject::tr("Failed to rename file: %1.\r\n\r\n" + "Source:\r\n\"%2\"\r\n\r\n" + "Destination:\r\n\"%3\"") + .arg(r.toString()) + .arg(QDir::toNativeSeparators(oldName)) + .arg(QDir::toNativeSeparators(newName)), + buttons); if (answer == QMessageBox::Cancel) { // user wants to stop diff --git a/src/filerenamer.h b/src/filerenamer.h index 5583ecbd2..a848a7fb7 100644 --- a/src/filerenamer.h +++ b/src/filerenamer.h @@ -3,142 +3,142 @@ #include -namespace MOBase::shell { class Result; } +namespace MOBase::shell +{ +class Result; +} /** -* Renames individual files and handles dialog boxes to confirm replacements and -* failures with the user -**/ + * Renames individual files and handles dialog boxes to confirm replacements and + * failures with the user + **/ class FileRenamer { public: /** - * controls appearance and replacement behaviour; if RENAME_REPLACE_ALL and - * RENAME_REPLACE_NONE are not provided, the user will have the option to - * choose on the first replacement - **/ + * controls appearance and replacement behaviour; if RENAME_REPLACE_ALL and + * RENAME_REPLACE_NONE are not provided, the user will have the option to + * choose on the first replacement + **/ enum RenameFlags { /** - * this renamer will be used on multiple files, so display additional - * buttons to replace all and for canceling - **/ - MULTIPLE = 0x01, + * this renamer will be used on multiple files, so display additional + * buttons to replace all and for canceling + **/ + MULTIPLE = 0x01, /** - * customizes some of the text shown on dialog to mention that files are - * being hidden - **/ - HIDE = 0x02, + * customizes some of the text shown on dialog to mention that files are + * being hidden + **/ + HIDE = 0x02, /** - * customizes some of the text shown on dialog to mention that files are - * being unhidden - **/ - UNHIDE = 0x04, + * customizes some of the text shown on dialog to mention that files are + * being unhidden + **/ + UNHIDE = 0x04, /** - * silently replaces all existing files - **/ - REPLACE_ALL = 0x08, + * silently replaces all existing files + **/ + REPLACE_ALL = 0x08, /** - * silently skips all existing files - **/ - REPLACE_NONE = 0x10, + * silently skips all existing files + **/ + REPLACE_NONE = 0x10, }; - /** result of a single rename - * - **/ + * + **/ enum RenameResults { /** - * the user skipped this file - */ + * the user skipped this file + */ RESULT_SKIP, /** - * the file was successfully renamed - */ + * the file was successfully renamed + */ RESULT_OK, /** - * the user wants to cancel - */ + * the user wants to cancel + */ RESULT_CANCEL }; - /** - * @param parent Parent widget for dialog boxes - **/ + * @param parent Parent widget for dialog boxes + **/ FileRenamer(QWidget* parent, QFlags flags); /** - * renames the given file - * @param oldName current filename - * @param newName new filename - * @return whether the file was renamed, skipped or the user wants to cancel - **/ + * renames the given file + * @param oldName current filename + * @param newName new filename + * @return whether the file was renamed, skipped or the user wants to cancel + **/ RenameResults rename(const QString& oldName, const QString& newName); private: /** - *user's decision when replacing - **/ + *user's decision when replacing + **/ enum RenameDecision { /** - * replace the file - **/ + * replace the file + **/ DECISION_REPLACE, /** - * skip the file - **/ + * skip the file + **/ DECISION_SKIP, /** - * cancel the whole thing - **/ + * cancel the whole thing + **/ DECISION_CANCEL }; /** - * parent widget for dialog boxes - **/ + * parent widget for dialog boxes + **/ QWidget* m_parent; /** - * flags - **/ + * flags + **/ QFlags m_flags; /** - * asks the user to replace an existing file, may return early if the user - * has already selected to replace all/none - * @return whether to replace, skip or cancel - **/ + * asks the user to replace an existing file, may return early if the user + * has already selected to replace all/none + * @return whether to replace, skip or cancel + **/ RenameDecision confirmReplace(const QString& newName); /** - * removal of a file failed, ask the user to continue or cancel - * @param name The name of the file that failed to be removed - * @return true to continue, false to stop - **/ + * removal of a file failed, ask the user to continue or cancel + * @param name The name of the file that failed to be removed + * @return true to continue, false to stop + **/ bool removeFailed(const QString& name, const MOBase::shell::Result& r); /** - * renaming a file failed, ask the user to continue or cancel - * @param oldName current filename - * @param newName new filename - * @return true to continue, false to stop - **/ - bool renameFailed( - const QString& oldName, const QString& newName, - const MOBase::shell::Result& r); + * renaming a file failed, ask the user to continue or cancel + * @param oldName current filename + * @param newName new filename + * @return true to continue, false to stop + **/ + bool renameFailed(const QString& oldName, const QString& newName, + const MOBase::shell::Result& r); }; -#endif // FILERENAMER_H +#endif // FILERENAMER_H diff --git a/src/filetree.cpp b/src/filetree.cpp index 02c85fdd0..f043448f4 100644 --- a/src/filetree.cpp +++ b/src/filetree.cpp @@ -1,10 +1,10 @@ #include "filetree.h" -#include "filetreemodel.h" +#include "envshell.h" #include "filetreeitem.h" +#include "filetreemodel.h" #include "organizercore.h" -#include "envshell.h" -#include "shared/fileentry.h" #include "shared/directoryentry.h" +#include "shared/fileentry.h" #include "shared/filesorigin.h" #include #include @@ -12,11 +12,10 @@ using namespace MOShared; using namespace MOBase; - bool canPreviewFile(const PluginContainer& pc, const FileEntry& file) { - return canPreviewFile( - pc, file.isFromArchive(), QString::fromStdWString(file.getName())); + return canPreviewFile(pc, file.isFromArchive(), + QString::fromStdWString(file.getName())); } bool canRunFile(const FileEntry& file) @@ -31,21 +30,18 @@ bool canOpenFile(const FileEntry& file) bool isHidden(const FileEntry& file) { - return (QString::fromStdWString(file.getName()).endsWith(ModInfo::s_HiddenExt, Qt::CaseInsensitive)); + return (QString::fromStdWString(file.getName()) + .endsWith(ModInfo::s_HiddenExt, Qt::CaseInsensitive)); } bool canExploreFile(const FileEntry& file); bool canHideFile(const FileEntry& file); bool canUnhideFile(const FileEntry& file); - class MenuItem { public: - MenuItem(QString s={}) - : m_action(new QAction(std::move(s))) - { - } + MenuItem(QString s = {}) : m_action(new QAction(std::move(s))) {} MenuItem& caption(const QString& s) { @@ -120,9 +116,8 @@ class MenuItem } }; - FileTree::FileTree(OrganizerCore& core, PluginContainer& pc, QTreeView* tree) - : m_core(core), m_plugins(pc), m_tree(tree), m_model(new FileTreeModel(core)) + : m_core(core), m_plugins(pc), m_tree(tree), m_model(new FileTreeModel(core)) { m_tree->sortByColumn(0, Qt::AscendingOrder); m_tree->setModel(m_model); @@ -130,21 +125,21 @@ FileTree::FileTree(OrganizerCore& core, PluginContainer& pc, QTreeView* tree) MOBase::setCustomizableColumns(m_tree); - connect( - m_tree, &QTreeView::customContextMenuRequested, - [&](auto pos){ onContextMenu(pos); }); + connect(m_tree, &QTreeView::customContextMenuRequested, [&](auto pos) { + onContextMenu(pos); + }); - connect( - m_tree, &QTreeView::expanded, - [&](auto&& index){ onExpandedChanged(index, true); }); + connect(m_tree, &QTreeView::expanded, [&](auto&& index) { + onExpandedChanged(index, true); + }); - connect( - m_tree, &QTreeView::collapsed, - [&](auto&& index){ onExpandedChanged(index, false); }); + connect(m_tree, &QTreeView::collapsed, [&](auto&& index) { + onExpandedChanged(index, false); + }); - connect( - m_tree, &QTreeView::activated, - [&](auto&& index){ onItemActivated(index); }); + connect(m_tree, &QTreeView::activated, [&](auto&& index) { + onItemActivated(index); + }); } FileTreeModel* FileTree::model() @@ -200,10 +195,10 @@ void FileTree::open(FileTreeItem* item) const QFileInfo targetInfo(path); m_core.processRunner() - .setFromFile(m_tree->window(), targetInfo) - .setHooked(false) - .setWaitForCompletion(ProcessRunner::TriggerRefresh) - .run(); + .setFromFile(m_tree->window(), targetInfo) + .setHooked(false) + .setWaitForCompletion(ProcessRunner::TriggerRefresh) + .run(); } void FileTree::openHooked(FileTreeItem* item) @@ -224,10 +219,10 @@ void FileTree::openHooked(FileTreeItem* item) const QFileInfo targetInfo(path); m_core.processRunner() - .setFromFile(m_tree->window(), targetInfo) - .setHooked(true) - .setWaitForCompletion(ProcessRunner::TriggerRefresh) - .run(); + .setFromFile(m_tree->window(), targetInfo) + .setHooked(true) + .setWaitForCompletion(ProcessRunner::TriggerRefresh) + .run(); } void FileTree::preview(FileTreeItem* item) @@ -256,8 +251,7 @@ void FileTree::activate(FileTreeItem* item) return; } - const auto tryPreview = - m_core.settings().interface().doubleClicksOpenPreviews(); + const auto tryPreview = m_core.settings().interface().doubleClicksOpenPreviews(); if (tryPreview) { const QFileInfo fi(item->realPath()); @@ -284,38 +278,34 @@ void FileTree::addAsExecutable(FileTreeItem* item) const QFileInfo target(path); const auto fec = spawn::getFileExecutionContext(m_tree->window(), target); - switch (fec.type) - { - case spawn::FileExecutionTypes::Executable: - { - const QString name = QInputDialog::getText( - m_tree->window(), tr("Enter Name"), - tr("Enter a name for the executable"), QLineEdit::Normal, - target.completeBaseName()); - - if (!name.isEmpty()) { - //Note: If this already exists, you'll lose custom settings - m_core.executablesList()->setExecutable(Executable() - .title(name) - .binaryInfo(fec.binary) - .arguments(fec.arguments) - .workingDirectory(target.absolutePath())); - - emit executablesChanged(); - } - - break; + switch (fec.type) { + case spawn::FileExecutionTypes::Executable: { + const QString name = QInputDialog::getText( + m_tree->window(), tr("Enter Name"), tr("Enter a name for the executable"), + QLineEdit::Normal, target.completeBaseName()); + + if (!name.isEmpty()) { + // Note: If this already exists, you'll lose custom settings + m_core.executablesList()->setExecutable( + Executable() + .title(name) + .binaryInfo(fec.binary) + .arguments(fec.arguments) + .workingDirectory(target.absolutePath())); + + emit executablesChanged(); } - case spawn::FileExecutionTypes::Other: // fall-through - default: - { - QMessageBox::information( - m_tree->window(), tr("Not an executable"), - tr("This is not a recognized executable.")); + break; + } + + case spawn::FileExecutionTypes::Other: // fall-through + default: { + QMessageBox::information(m_tree->window(), tr("Not an executable"), + tr("This is not a recognized executable.")); - break; - } + break; + } } } @@ -357,7 +347,7 @@ void FileTree::openModInfo(FileTreeItem* item) } const auto& origin = m_core.directoryStructure()->getOriginByID(originID); - const auto& name = QString::fromStdWString(origin.getName()); + const auto& name = QString::fromStdWString(origin.getName()); unsigned int index = ModInfo::getIndex(name); if (index == UINT_MAX) { @@ -386,9 +376,8 @@ void FileTree::toggleVisibility(bool visible, FileTreeItem* item) if (visible) { if (!currentName.endsWith(ModInfo::s_HiddenExt)) { - log::error( - "cannot unhide '{}', doesn't end with '{}'", - currentName, ModInfo::s_HiddenExt); + log::error("cannot unhide '{}', doesn't end with '{}'", currentName, + ModInfo::s_HiddenExt); return; } @@ -396,9 +385,8 @@ void FileTree::toggleVisibility(bool visible, FileTreeItem* item) newName = currentName.left(currentName.size() - ModInfo::s_HiddenExt.size()); } else { if (currentName.endsWith(ModInfo::s_HiddenExt)) { - log::error( - "cannot hide '{}', already ends with '{}'", - currentName, ModInfo::s_HiddenExt); + log::error("cannot hide '{}', already ends with '{}'", currentName, + ModInfo::s_HiddenExt); return; } @@ -408,9 +396,8 @@ void FileTree::toggleVisibility(bool visible, FileTreeItem* item) log::debug("attempting to rename '{}' to '{}'", currentName, newName); - FileRenamer renamer( - m_tree->window(), - (visible ? FileRenamer::UNHIDE : FileRenamer::HIDE)); + FileRenamer renamer(m_tree->window(), + (visible ? FileRenamer::UNHIDE : FileRenamer::HIDE)); if (renamer.rename(currentName, newName) == FileRenamer::RESULT_OK) { emit originModified(item->originID()); @@ -428,7 +415,8 @@ void FileTree::unhide(FileTreeItem* item) toggleVisibility(true, item); } -class DumpFailed {}; +class DumpFailed +{}; void FileTree::dumpToFile() const { @@ -445,7 +433,7 @@ void FileTree::dumpToFile() const void FileTree::onExpandedChanged(const QModelIndex& index, bool expanded) { - if (auto* item=m_model->itemFromIndex(proxiedIndex(index))) { + if (auto* item = m_model->itemFromIndex(proxiedIndex(index))) { item->setExpanded(expanded); } } @@ -460,7 +448,7 @@ void FileTree::onItemActivated(const QModelIndex& index) activate(item); } -void FileTree::onContextMenu(const QPoint &pos) +void FileTree::onContextMenu(const QPoint& pos) { const auto m = QApplication::keyboardModifiers(); @@ -473,11 +461,9 @@ void FileTree::onContextMenu(const QPoint &pos) const auto index = m_tree->indexAt(pos); if (!m_tree->selectionModel()->isSelected(index)) { - m_tree->selectionModel()->select( - index, - QItemSelectionModel::ClearAndSelect | - QItemSelectionModel::Rows | - QItemSelectionModel::Current); + m_tree->selectionModel()->select(index, QItemSelectionModel::ClearAndSelect | + QItemSelectionModel::Rows | + QItemSelectionModel::Current); } // if no shell menu was available, continue on and show the regular @@ -489,12 +475,12 @@ void FileTree::onContextMenu(const QPoint &pos) QMenu menu; - if (auto* item=singleSelection()) { + if (auto* item = singleSelection()) { if (item->isDirectory()) { addDirectoryMenus(menu, *item); } else { const auto file = m_core.directoryStructure()->searchFile( - item->dataRelativeFilePath().toStdWString(), nullptr); + item->dataRelativeFilePath().toStdWString(), nullptr); if (file) { addFileMenus(menu, *file, item->originID()); @@ -512,7 +498,7 @@ QMainWindow* getMainWindow(QWidget* w) QWidget* p = w; while (p) { - if (auto* mw=dynamic_cast(p)) { + if (auto* mw = dynamic_cast(p)) { return mw; } @@ -528,7 +514,7 @@ bool FileTree::showShellMenu(QPoint pos) // menus by origin std::map menus; - int totalFiles = 0; + int totalFiles = 0; bool warnOnEmpty = true; for (auto&& index : m_tree->selectionModel()->selectedRows()) { @@ -540,9 +526,7 @@ bool FileTree::showShellMenu(QPoint pos) if (item->isDirectory()) { warnOnEmpty = false; - log::warn( - "directories do not have shell menus; '{}' selected", - item->filename()); + log::warn("directories do not have shell menus; '{}' selected", item->filename()); continue; } @@ -550,9 +534,8 @@ bool FileTree::showShellMenu(QPoint pos) if (item->isFromArchive()) { warnOnEmpty = false; - log::warn( - "files from archives do not have shell menus; '{}' selected", - item->filename()); + log::warn("files from archives do not have shell menus; '{}' selected", + item->filename()); continue; } @@ -563,9 +546,8 @@ bool FileTree::showShellMenu(QPoint pos) } if (!QFile::exists(item->realPath())) { - log::error("{}", - tr("File '%1' does not exist, you may need to refresh.") - .arg(item->realPath())); + log::error("{}", tr("File '%1' does not exist, you may need to refresh.") + .arg(item->realPath())); } itor->second.addFile(QFileInfo(item->realPath())); @@ -573,21 +555,19 @@ bool FileTree::showShellMenu(QPoint pos) if (item->isConflicted()) { const auto file = m_core.directoryStructure()->searchFile( - item->dataRelativeFilePath().toStdWString(), nullptr); + item->dataRelativeFilePath().toStdWString(), nullptr); if (!file) { - log::error( - "file '{}' not found, data path={}, real path={}", - item->filename(), item->dataRelativeFilePath(), item->realPath()); + log::error("file '{}' not found, data path={}, real path={}", item->filename(), + item->dataRelativeFilePath(), item->realPath()); continue; } const auto alts = file->getAlternatives(); if (alts.empty()) { - log::warn( - "file '{}' has no alternative origins but is marked as conflicted", - item->dataRelativeFilePath()); + log::warn("file '{}' has no alternative origins but is marked as conflicted", + item->dataRelativeFilePath()); } for (auto&& alt : alts) { @@ -598,17 +578,15 @@ bool FileTree::showShellMenu(QPoint pos) const auto fullPath = file->getFullPath(alt.originID()); if (fullPath.empty()) { - log::error( - "file {} not found in origin {}", - item->dataRelativeFilePath(), alt.originID()); + log::error("file {} not found in origin {}", item->dataRelativeFilePath(), + alt.originID()); continue; } if (!QFile::exists(QString::fromStdWString(fullPath))) { - log::error("{}", - tr("File '%1' does not exist, you may need to refresh.") - .arg(QString::fromStdWString(fullPath))); + log::error("{}", tr("File '%1' does not exist, you may need to refresh.") + .arg(QString::fromStdWString(fullPath))); } itor->second.addFile(QFileInfo(QString::fromStdWString(fullPath))); @@ -624,8 +602,7 @@ bool FileTree::showShellMenu(QPoint pos) } return false; - } - else if (menus.size() == 1) { + } else if (menus.size() == 1) { auto& menu = menus.begin()->second; menu.exec(m_tree->viewport()->mapToGlobal(pos)); } else { @@ -676,40 +653,50 @@ void FileTree::addFileMenus(QMenu& menu, const FileEntry& file, int originID) const QFileInfo target(QString::fromStdWString(file.getFullPath())); MenuItem(tr("&Add as Executable")) - .callback([&]{ addAsExecutable(); }) - .hint(tr("Add this file to the executables list")) - .disabledHint(tr("This file is not executable")) - .enabled(getFileExecutionType(target) == FileExecutionTypes::Executable) - .addTo(menu); + .callback([&] { + addAsExecutable(); + }) + .hint(tr("Add this file to the executables list")) + .disabledHint(tr("This file is not executable")) + .enabled(getFileExecutionType(target) == FileExecutionTypes::Executable) + .addTo(menu); MenuItem(tr("Reveal in E&xplorer")) - .callback([&]{ exploreOrigin(); }) - .hint(tr("Opens the file in Explorer")) - .disabledHint(tr("This file is in an archive")) - .enabled(!file.isFromArchive()) - .addTo(menu); + .callback([&] { + exploreOrigin(); + }) + .hint(tr("Opens the file in Explorer")) + .disabledHint(tr("This file is in an archive")) + .enabled(!file.isFromArchive()) + .addTo(menu); MenuItem(tr("Open &Mod Info")) - .callback([&]{ openModInfo(); }) - .hint(tr("Opens the Mod Info Window")) - .disabledHint(tr("This file is not in a managed mod")) - .enabled(originID != 0) - .addTo(menu); + .callback([&] { + openModInfo(); + }) + .hint(tr("Opens the Mod Info Window")) + .disabledHint(tr("This file is not in a managed mod")) + .enabled(originID != 0) + .addTo(menu); if (isHidden(file)) { MenuItem(tr("&Un-Hide")) - .callback([&]{ unhide(); }) - .hint(tr("Un-hides the file")) - .disabledHint(tr("This file is in an archive")) - .enabled(!file.isFromArchive()) - .addTo(menu); + .callback([&] { + unhide(); + }) + .hint(tr("Un-hides the file")) + .disabledHint(tr("This file is in an archive")) + .enabled(!file.isFromArchive()) + .addTo(menu); } else { MenuItem(tr("&Hide")) - .callback([&]{ hide(); }) - .hint(tr("Hides the file")) - .disabledHint(tr("This file is in an archive")) - .enabled(!file.isFromArchive()) - .addTo(menu); + .callback([&] { + hide(); + }) + .hint(tr("Hides the file")) + .disabledHint(tr("This file is in an archive")) + .enabled(!file.isFromArchive()) + .addTo(menu); } } @@ -722,43 +709,48 @@ void FileTree::addOpenMenus(QMenu& menu, const MOShared::FileEntry& file) const QFileInfo target(QString::fromStdWString(file.getFullPath())); if (getFileExecutionType(target) == FileExecutionTypes::Executable) { - openMenu - .caption(tr("&Execute")) - .callback([&]{ open(); }) - .hint(tr("Launches this program")) - .disabledHint(tr("This file is in an archive")) - .enabled(!file.isFromArchive()); - - openHookedMenu - .caption(tr("Execute with &VFS")) - .callback([&]{ openHooked(); }) - .hint(tr("Launches this program hooked to the VFS")) - .disabledHint(tr("This file is in an archive")) - .enabled(!file.isFromArchive()); + openMenu.caption(tr("&Execute")) + .callback([&] { + open(); + }) + .hint(tr("Launches this program")) + .disabledHint(tr("This file is in an archive")) + .enabled(!file.isFromArchive()); + + openHookedMenu.caption(tr("Execute with &VFS")) + .callback([&] { + openHooked(); + }) + .hint(tr("Launches this program hooked to the VFS")) + .disabledHint(tr("This file is in an archive")) + .enabled(!file.isFromArchive()); } else { - openMenu - .caption(tr("&Open")) - .callback([&]{ open(); }) - .hint(tr("Opens this file with its default handler")) - .disabledHint(tr("This file is in an archive")) - .enabled(!file.isFromArchive()); - - openHookedMenu - .caption(tr("Open with &VFS")) - .callback([&]{ openHooked(); }) - .hint(tr("Opens this file with its default handler hooked to the VFS")) - .disabledHint(tr("This file is in an archive")) - .enabled(!file.isFromArchive()); + openMenu.caption(tr("&Open")) + .callback([&] { + open(); + }) + .hint(tr("Opens this file with its default handler")) + .disabledHint(tr("This file is in an archive")) + .enabled(!file.isFromArchive()); + + openHookedMenu.caption(tr("Open with &VFS")) + .callback([&] { + openHooked(); + }) + .hint(tr("Opens this file with its default handler hooked to the VFS")) + .disabledHint(tr("This file is in an archive")) + .enabled(!file.isFromArchive()); } MenuItem previewMenu(tr("&Preview")); previewMenu - .callback([&]{ preview(); }) - .hint(tr("Previews this file within Mod Organizer")) - .disabledHint(tr( - "This file is in an archive or has no preview handler " - "associated with it")) - .enabled(canPreviewFile(m_plugins, file)); + .callback([&] { + preview(); + }) + .hint(tr("Previews this file within Mod Organizer")) + .disabledHint(tr("This file is in an archive or has no preview handler " + "associated with it")) + .enabled(canPreviewFile(m_plugins, file)); if (m_core.settings().interface().doubleClicksOpenPreviews()) { previewMenu.addTo(menu); @@ -771,7 +763,7 @@ void FileTree::addOpenMenus(QMenu& menu, const MOShared::FileEntry& file) } // bold the first enabled option, only first three are considered - for (int i=0; i<3; ++i) { + for (int i = 0; i < 3; ++i) { if (i >= menu.actions().size()) { break; } @@ -792,29 +784,37 @@ void FileTree::addCommonMenus(QMenu& menu) menu.addSeparator(); MenuItem(tr("&Save Tree to Text File...")) - .callback([&]{ dumpToFile(); }) - .hint(tr("Writes the list of files to a text file")) - .addTo(menu); + .callback([&] { + dumpToFile(); + }) + .hint(tr("Writes the list of files to a text file")) + .addTo(menu); MenuItem(tr("&Refresh")) - .callback([&]{ refresh(); }) - .hint(tr("Refreshes the list")) - .addTo(menu); + .callback([&] { + refresh(); + }) + .hint(tr("Refreshes the list")) + .addTo(menu); MenuItem(tr("Ex&pand All")) - .callback([&]{ expandAll(); }) - .addTo(menu); + .callback([&] { + expandAll(); + }) + .addTo(menu); MenuItem(tr("&Collapse All")) - .callback([&]{ collapseAll(); }) - .addTo(menu); + .callback([&] { + collapseAll(); + }) + .addTo(menu); } QModelIndex FileTree::proxiedIndex(const QModelIndex& index) { auto* model = m_tree->model(); - if (auto* proxy=dynamic_cast(model)) { + if (auto* proxy = dynamic_cast(model)) { return proxy->mapToSource(index); } else { return index; diff --git a/src/filetree.h b/src/filetree.h index 8d71abb34..d9597322a 100644 --- a/src/filetree.h +++ b/src/filetree.h @@ -4,7 +4,10 @@ #include "modinfo.h" #include "modinfodialogfwd.h" -namespace MOShared { class FileEntry; } +namespace MOShared +{ +class FileEntry; +} class OrganizerCore; class PluginContainer; @@ -28,17 +31,17 @@ class FileTree : public QObject void expandAll(); void collapseAll(); - void open(FileTreeItem* item=nullptr); - void openHooked(FileTreeItem* item=nullptr); - void preview(FileTreeItem* item=nullptr); - void activate(FileTreeItem* item=nullptr); + void open(FileTreeItem* item = nullptr); + void openHooked(FileTreeItem* item = nullptr); + void preview(FileTreeItem* item = nullptr); + void activate(FileTreeItem* item = nullptr); - void addAsExecutable(FileTreeItem* item=nullptr); - void exploreOrigin(FileTreeItem* item=nullptr); - void openModInfo(FileTreeItem* item=nullptr); + void addAsExecutable(FileTreeItem* item = nullptr); + void exploreOrigin(FileTreeItem* item = nullptr); + void openModInfo(FileTreeItem* item = nullptr); - void hide(FileTreeItem* item=nullptr); - void unhide(FileTreeItem* item=nullptr); + void hide(FileTreeItem* item = nullptr); + void unhide(FileTreeItem* item = nullptr); void dumpToFile() const; @@ -57,7 +60,7 @@ class FileTree : public QObject void onExpandedChanged(const QModelIndex& index, bool expanded); void onItemActivated(const QModelIndex& index); - void onContextMenu(const QPoint &pos); + void onContextMenu(const QPoint& pos); bool showShellMenu(QPoint pos); void addDirectoryMenus(QMenu& menu, FileTreeItem& item); @@ -65,9 +68,9 @@ class FileTree : public QObject void addOpenMenus(QMenu& menu, const MOShared::FileEntry& file); void addCommonMenus(QMenu& menu); - void toggleVisibility(bool b, FileTreeItem* item=nullptr); + void toggleVisibility(bool b, FileTreeItem* item = nullptr); QModelIndex proxiedIndex(const QModelIndex& index); }; -#endif // MODORGANIZER_FILETREE_INCLUDED +#endif // MODORGANIZER_FILETREE_INCLUDED diff --git a/src/filetreeitem.cpp b/src/filetreeitem.cpp index 7997974f8..16c040940 100644 --- a/src/filetreeitem.cpp +++ b/src/filetreeitem.cpp @@ -1,8 +1,8 @@ #include "filetreeitem.h" #include "filetreemodel.h" #include "modinfo.h" -#include "shared/util.h" #include "modinfodialogfwd.h" +#include "shared/util.h" #include #include @@ -16,7 +16,7 @@ const QString& directoryFileType() { static const QString name = [] { const DWORD flags = SHGFI_TYPENAME; - SHFILEINFOW sfi = {}; + SHFILEINFOW sfi = {}; // "." for the current directory, which should always exist const auto r = SHGetFileInfoW(L".", 0, &sfi, sizeof(sfi), flags); @@ -24,9 +24,8 @@ const QString& directoryFileType() if (!r) { const auto e = GetLastError(); - log::error( - "SHGetFileInfoW failed for folder file type, {}", - formatSystemMessage(e)); + log::error("SHGetFileInfoW failed for folder file type, {}", + formatSystemMessage(e)); return QString("File folder"); } else { @@ -41,7 +40,7 @@ const QString& cachedFileTypeNoExtension() { static const QString name = [] { const DWORD flags = SHGFI_TYPENAME | SHGFI_USEFILEATTRIBUTES; - SHFILEINFOW sfi = {}; + SHFILEINFOW sfi = {}; // dummy filename with no extension const auto r = SHGetFileInfoW(L"file", 0, &sfi, sizeof(sfi), flags); @@ -49,9 +48,8 @@ const QString& cachedFileTypeNoExtension() if (!r) { const auto e = GetLastError(); - log::error( - "SHGetFileInfoW failed for file without extension, {}", - formatSystemMessage(e)); + log::error("SHGetFileInfoW failed for file without extension, {}", + formatSystemMessage(e)); return QString("File"); } else { @@ -80,7 +78,6 @@ const QString& cachedFileType(const std::wstring& file, bool isOnFilesystem) return itor->second; } - DWORD flags = SHGFI_TYPENAME; if (!isOnFilesystem) { @@ -90,16 +87,14 @@ const QString& cachedFileType(const std::wstring& file, bool isOnFilesystem) } SHFILEINFOW sfi = {}; - const auto r = SHGetFileInfoW(file.c_str(), 0, &sfi, sizeof(sfi), flags); + const auto r = SHGetFileInfoW(file.c_str(), 0, &sfi, sizeof(sfi), flags); QString s; if (!r) { const auto e = GetLastError(); - log::error( - "SHGetFileInfoW failed for '{}', {}", - file, formatSystemMessage(e)); + log::error("SHGetFileInfoW failed for '{}', {}", file, formatSystemMessage(e)); s = cachedFileTypeNoExtension(); } else { @@ -109,51 +104,41 @@ const QString& cachedFileType(const std::wstring& file, bool isOnFilesystem) return map.emplace(sv, s).first->second; } - - -FileTreeItem::FileTreeItem( - FileTreeModel* model, FileTreeItem* parent, - std::wstring dataRelativeParentPath, bool isDirectory, std::wstring file) : - m_model(model), m_parent(parent), m_indexGuess(NoIndexGuess), - m_virtualParentPath(QString::fromStdWString(dataRelativeParentPath)), - m_wsFile(file), - m_wsLcFile(ToLowerCopy(file)), - m_key(m_wsLcFile), - m_file(QString::fromStdWString(file)), - m_isDirectory(isDirectory), - m_originID(-1), - m_flags(NoFlags), - m_loaded(false), - m_expanded(false), - m_sortingStale(true) -{ -} - -FileTreeItem::Ptr FileTreeItem::createFile( - FileTreeModel* model, FileTreeItem* parent, - std::wstring dataRelativeParentPath, std::wstring file) +FileTreeItem::FileTreeItem(FileTreeModel* model, FileTreeItem* parent, + std::wstring dataRelativeParentPath, bool isDirectory, + std::wstring file) + : m_model(model), m_parent(parent), m_indexGuess(NoIndexGuess), + m_virtualParentPath(QString::fromStdWString(dataRelativeParentPath)), + m_wsFile(file), m_wsLcFile(ToLowerCopy(file)), m_key(m_wsLcFile), + m_file(QString::fromStdWString(file)), m_isDirectory(isDirectory), m_originID(-1), + m_flags(NoFlags), m_loaded(false), m_expanded(false), m_sortingStale(true) +{} + +FileTreeItem::Ptr FileTreeItem::createFile(FileTreeModel* model, FileTreeItem* parent, + std::wstring dataRelativeParentPath, + std::wstring file) { return std::unique_ptr(new FileTreeItem( - model, parent, std::move(dataRelativeParentPath), false, std::move(file))); + model, parent, std::move(dataRelativeParentPath), false, std::move(file))); } -FileTreeItem::Ptr FileTreeItem::createDirectory( - FileTreeModel* model, FileTreeItem* parent, - std::wstring dataRelativeParentPath, std::wstring file) +FileTreeItem::Ptr FileTreeItem::createDirectory(FileTreeModel* model, + FileTreeItem* parent, + std::wstring dataRelativeParentPath, + std::wstring file) { return std::unique_ptr(new FileTreeItem( - model, parent, std::move(dataRelativeParentPath), true, std::move(file))); + model, parent, std::move(dataRelativeParentPath), true, std::move(file))); } -void FileTreeItem::setOrigin( - int originID, const std::wstring& realPath, Flags flags, - const std::wstring& mod) +void FileTreeItem::setOrigin(int originID, const std::wstring& realPath, Flags flags, + const std::wstring& mod) { - m_originID = originID; + m_originID = originID; m_wsRealPath = realPath; - m_realPath = QString::fromStdWString(realPath); - m_flags = flags; - m_mod = QString::fromStdWString(mod); + m_realPath = QString::fromStdWString(realPath); + m_flags = flags; + m_mod = QString::fromStdWString(mod); m_fileSize.reset(); m_lastModified.reset(); @@ -164,9 +149,8 @@ void FileTreeItem::setOrigin( void FileTreeItem::insert(FileTreeItem::Ptr child, std::size_t at) { if (at > m_children.size()) { - log::error( - "{}: can't insert child {} at {}, out of range", - debugName(), child->debugName(), at); + log::error("{}: can't insert child {} at {}, out of range", debugName(), + child->debugName(), at); return; } @@ -193,12 +177,11 @@ void FileTreeItem::remove(std::size_t from, std::size_t n) } auto begin = m_children.begin() + from; - auto end = begin + n; + auto end = begin + n; m_children.erase(begin, end); } - template int threeWayCompare(T&& a, T&& b) { @@ -218,31 +201,26 @@ class FileTreeItem::Sorter public: static int compare(int column, const FileTreeItem* a, const FileTreeItem* b) { - switch (column) - { - case FileTreeModel::FileName: - return naturalCompare(a->m_file, b->m_file); - - case FileTreeModel::ModName: - return naturalCompare(a->m_mod, b->m_mod); - - case FileTreeModel::FileType: - return naturalCompare( - a->fileType().value_or(QString()), - b->fileType().value_or(QString())); - - case FileTreeModel::FileSize: - return threeWayCompare( - a->fileSize().value_or(0), - b->fileSize().value_or(0)); - - case FileTreeModel::LastModified: - return threeWayCompare( - a->lastModified().value_or(QDateTime()), - b->lastModified().value_or(QDateTime())); - - default: - return 0; + switch (column) { + case FileTreeModel::FileName: + return naturalCompare(a->m_file, b->m_file); + + case FileTreeModel::ModName: + return naturalCompare(a->m_mod, b->m_mod); + + case FileTreeModel::FileType: + return naturalCompare(a->fileType().value_or(QString()), + b->fileType().value_or(QString())); + + case FileTreeModel::FileSize: + return threeWayCompare(a->fileSize().value_or(0), b->fileSize().value_or(0)); + + case FileTreeModel::LastModified: + return threeWayCompare(a->lastModified().value_or(QDateTime()), + b->lastModified().value_or(QDateTime())); + + default: + return 0; } } }; @@ -271,7 +249,7 @@ void FileTreeItem::sort(int column, Qt::SortOrder order, bool force) } if (m_sortingStale || force) { - //log::debug("sorting is stale for {}, sorting now", debugName()); + // log::debug("sorting is stale for {}, sorting now", debugName()); m_sortingStale = false; std::sort(m_children.begin(), m_children.end(), [&](auto&& a, auto&& b) { @@ -448,7 +426,7 @@ bool FileTreeItem::areChildrenVisible() const QString FileTreeItem::debugName() const { return QString("%1(ld=%2,cs=%3)") - .arg(virtualPath()) - .arg(m_loaded) - .arg(m_children.size()); + .arg(virtualPath()) + .arg(m_loaded) + .arg(m_children.size()); } diff --git a/src/filetreeitem.h b/src/filetreeitem.h index 750e4719f..ea2fd308e 100644 --- a/src/filetreeitem.h +++ b/src/filetreeitem.h @@ -11,7 +11,7 @@ class FileTreeItem class Sorter; public: - using Ptr = std::unique_ptr; + using Ptr = std::unique_ptr; using Children = std::vector; enum Flag @@ -21,25 +21,21 @@ class FileTreeItem Conflicted = 0x02 }; - Q_DECLARE_FLAGS(Flags, Flag); - static Ptr createFile( - FileTreeModel* model, FileTreeItem* parent, - std::wstring dataRelativeParentPath, std::wstring file); + static Ptr createFile(FileTreeModel* model, FileTreeItem* parent, + std::wstring dataRelativeParentPath, std::wstring file); - static Ptr createDirectory( - FileTreeModel* model, FileTreeItem* parent, - std::wstring dataRelativeParentPath, std::wstring file); + static Ptr createDirectory(FileTreeModel* model, FileTreeItem* parent, + std::wstring dataRelativeParentPath, std::wstring file); - FileTreeItem(const FileTreeItem&) = delete; + FileTreeItem(const FileTreeItem&) = delete; FileTreeItem& operator=(const FileTreeItem&) = delete; - FileTreeItem(FileTreeItem&&) = default; - FileTreeItem& operator=(FileTreeItem&&) = default; + FileTreeItem(FileTreeItem&&) = default; + FileTreeItem& operator=(FileTreeItem&&) = default; - void setOrigin( - int originID, const std::wstring& realPath, - Flags flags, const std::wstring& mod); + void setOrigin(int originID, const std::wstring& realPath, Flags flags, + const std::wstring& mod); void add(Ptr child) { @@ -53,7 +49,7 @@ class FileTreeItem void insert(Itor begin, Itor end, std::size_t at) { std::size_t nextRowGuess = m_children.size(); - for (auto itor=begin; itor!=end; ++itor) { + for (auto itor = begin; itor != end; ++itor) { (*itor)->m_indexGuess = nextRowGuess++; } @@ -69,10 +65,7 @@ class FileTreeItem m_loaded = false; } - const Children& children() const - { - return m_children; - } + const Children& children() const { return m_children; } int childIndex(const FileTreeItem& item) const { @@ -82,7 +75,7 @@ class FileTreeItem } } - for (std::size_t i=0; i(i); @@ -95,47 +88,23 @@ class FileTreeItem void sort(int column, Qt::SortOrder order, bool force); void makeSortingStale(); - FileTreeItem* parent() - { - return m_parent; - } + FileTreeItem* parent() { return m_parent; } - int originID() const - { - return m_originID; - } + int originID() const { return m_originID; } - const QString& virtualParentPath() const - { - return m_virtualParentPath; - } + const QString& virtualParentPath() const { return m_virtualParentPath; } QString virtualPath() const; - const QString& filename() const - { - return m_file; - } + const QString& filename() const { return m_file; } - const std::wstring& filenameWs() const - { - return m_wsFile; - } + const std::wstring& filenameWs() const { return m_wsFile; } - const std::wstring& filenameWsLowerCase() const - { - return m_wsLcFile; - } + const std::wstring& filenameWsLowerCase() const { return m_wsLcFile; } - const MOShared::DirectoryEntryFileKey& key() const - { - return m_key; - } + const MOShared::DirectoryEntryFileKey& key() const { return m_key; } - const QString& mod() const - { - return m_mod; - } + const QString& mod() const { return m_mod; } QFont font() const; @@ -148,44 +117,26 @@ class FileTreeItem return m_compressedFileSize.value; } - void setFileSize(uint64_t size) - { - m_fileSize.override(size); - } + void setFileSize(uint64_t size) { m_fileSize.override(size); } void setCompressedFileSize(uint64_t compressedSize) { m_compressedFileSize.override(compressedSize); } - const QString& realPath() const - { - return m_realPath; - } + const QString& realPath() const { return m_realPath; } - const QString& dataRelativeParentPath() const - { - return m_virtualParentPath; - } + const QString& dataRelativeParentPath() const { return m_virtualParentPath; } QString dataRelativeFilePath() const; QFileIconProvider::IconType icon() const; - bool isDirectory() const - { - return m_isDirectory; - } + bool isDirectory() const { return m_isDirectory; } - bool isFromArchive() const - { - return (m_flags & FromArchive); - } + bool isFromArchive() const { return (m_flags & FromArchive); } - bool isConflicted() const - { - return (m_flags & Conflicted); - } + bool isConflicted() const { return (m_flags & Conflicted); } bool isHidden() const; @@ -202,16 +153,9 @@ class FileTreeItem return true; } + void setLoaded(bool b) { m_loaded = b; } - void setLoaded(bool b) - { - m_loaded = b; - } - - bool isLoaded() const - { - return m_loaded; - } + bool isLoaded() const { return m_loaded; } void unload(); @@ -228,10 +172,7 @@ class FileTreeItem } } - bool isStrictlyExpanded() const - { - return m_expanded; - } + bool isStrictlyExpanded() const { return m_expanded; } bool areChildrenVisible() const; @@ -242,46 +183,42 @@ class FileTreeItem struct Cached { std::optional value; - bool failed = false; + bool failed = false; bool overridden = false; - bool empty() const - { - return !failed && !value; - } + bool empty() const { return !failed && !value; } void set(T t) { - value = std::move(t); - failed = false; + value = std::move(t); + failed = false; overridden = false; } void override(T t) { - value = std::move(t); - failed = false; + value = std::move(t); + failed = false; overridden = true; } void fail() { - value = {}; - failed = true; + value = {}; + failed = true; overridden = false; } void reset() { if (!overridden) { - value = {}; + value = {}; failed = false; } } }; - static constexpr std::size_t NoIndexGuess = - std::numeric_limits::max(); + static constexpr std::size_t NoIndexGuess = std::numeric_limits::max(); FileTreeModel* m_model; FileTreeItem* m_parent; @@ -309,13 +246,12 @@ class FileTreeItem bool m_sortingStale; Children m_children; - - FileTreeItem( - FileTreeModel* model, FileTreeItem* parent, - std::wstring dataRelativeParentPath, bool isDirectory, std::wstring file); + FileTreeItem(FileTreeModel* model, FileTreeItem* parent, + std::wstring dataRelativeParentPath, bool isDirectory, + std::wstring file); void getFileType() const; void queueSort(); }; -#endif // MODORGANIZER_FILETREEITEM_INCLUDED +#endif // MODORGANIZER_FILETREEITEM_INCLUDED diff --git a/src/filetreemodel.cpp b/src/filetreemodel.cpp index bd9cd0677..9d63a7828 100644 --- a/src/filetreemodel.cpp +++ b/src/filetreemodel.cpp @@ -1,9 +1,9 @@ #include "filetreemodel.h" #include "organizercore.h" -#include "shared/filesorigin.h" -#include "shared/util.h" #include "shared/directoryentry.h" #include "shared/fileentry.h" +#include "shared/filesorigin.h" +#include "shared/util.h" #include #include @@ -15,7 +15,6 @@ QString UnmanagedModName(); #define trace(f) - // about queueRemoveItem(), queueSortItems() and the `forFetching` parameter // // update() can be called when refreshing the tree or expanding a node; @@ -52,7 +51,6 @@ QString UnmanagedModName(); // (such as when removeDisappearingFiles()) because they cannot happen while // fetching - // tracks a contiguous range in the model to avoid calling begin*Rows(), etc. // for every single item that's added/removed // @@ -62,10 +60,9 @@ class FileTreeModel::Range // note that file ranges can start from an index higher than 0 if there are // directories // - Range(FileTreeModel* model, FileTreeItem& parentItem, int start=0) - : m_model(model), m_parentItem(parentItem), m_first(-1), m_current(start) - { - } + Range(FileTreeModel* model, FileTreeItem& parentItem, int start = 0) + : m_model(model), m_parentItem(parentItem), m_first(-1), m_current(start) + {} // includes the current index in the range // @@ -80,23 +77,17 @@ class FileTreeModel::Range // moves to the next row // - void next() - { - ++m_current; - } + void next() { ++m_current; } // returns the current row // - int current() const - { - return m_current; - } + int current() const { return m_current; } // manually set this range // void set(int first, int last) { - m_first = first; + m_first = first; m_current = last; } @@ -110,7 +101,7 @@ class FileTreeModel::Range return; } - const auto last = m_current - 1; + const auto last = m_current - 1; const auto parentIndex = m_model->indexFromItem(m_parentItem); // make sure the number of items is the same as the size of this range @@ -120,10 +111,9 @@ class FileTreeModel::Range m_model->beginInsertRows(parentIndex, m_first, last); - m_parentItem.insert( - std::make_move_iterator(toAdd.begin()), - std::make_move_iterator(toAdd.end()), - static_cast(m_first)); + m_parentItem.insert(std::make_move_iterator(toAdd.begin()), + std::make_move_iterator(toAdd.end()), + static_cast(m_first)); m_model->endInsertRows(); @@ -137,16 +127,15 @@ class FileTreeModel::Range FileTreeItem::Children::const_iterator remove() { if (m_first >= 0) { - const auto last = m_current - 1; + const auto last = m_current - 1; const auto parentIndex = m_model->indexFromItem(m_parentItem); trace(log::debug("Range::remove() {} to {}", m_first, last)); m_model->beginRemoveRows(parentIndex, m_first, last); - m_parentItem.remove( - static_cast(m_first), - static_cast(last - m_first + 1)); + m_parentItem.remove(static_cast(m_first), + static_cast(last - m_first + 1)); m_model->endRemoveRows(); @@ -181,7 +170,6 @@ class FileTreeModel::Range int m_current; }; - FileTreeItem* getItem(const QModelIndex& index) { return static_cast(index.internalPointer()); @@ -192,18 +180,23 @@ void* makeInternalPointer(FileTreeItem* item) return item; } - -FileTreeModel::FileTreeModel(OrganizerCore& core, QObject* parent) : - QAbstractItemModel(parent), m_core(core), m_enabled(true), - m_root(FileTreeItem::createDirectory(this, nullptr, L"", L"")), - m_flags(NoFlags), m_fullyLoaded(false), m_sortingEnabled(true) +FileTreeModel::FileTreeModel(OrganizerCore& core, QObject* parent) + : QAbstractItemModel(parent), m_core(core), m_enabled(true), + m_root(FileTreeItem::createDirectory(this, nullptr, L"", L"")), m_flags(NoFlags), + m_fullyLoaded(false), m_sortingEnabled(true) { m_root->setExpanded(true); m_sortTimer.setSingleShot(true); - connect(&m_removeTimer, &QTimer::timeout, [&]{ removeItems(); }); - connect(&m_sortTimer, &QTimer::timeout, [&]{ sortItems(); }); - connect(&m_iconPendingTimer, &QTimer::timeout, [&]{ updatePendingIcons(); }); + connect(&m_removeTimer, &QTimer::timeout, [&] { + removeItems(); + }); + connect(&m_sortTimer, &QTimer::timeout, [&] { + sortItems(); + }); + connect(&m_iconPendingTimer, &QTimer::timeout, [&] { + updatePendingIcons(); + }); } void FileTreeModel::refresh() @@ -230,7 +223,7 @@ void FileTreeModel::recursiveFetchMore(const QModelIndex& m) doFetchMore(m, false, false); } - for (int i=0; i= parentItem->children().size()) { log::error("row {} out of range for {}", row, parentItem->debugName()); return {}; @@ -309,7 +301,7 @@ QModelIndex FileTreeModel::parent(const QModelIndex& index) const int FileTreeModel::rowCount(const QModelIndex& parent) const { - if (auto* item=itemFromIndex(parent)) { + if (auto* item = itemFromIndex(parent)) { return static_cast(item->children().size()); } @@ -327,7 +319,7 @@ bool FileTreeModel::hasChildren(const QModelIndex& parent) const return false; } - if (auto* item=itemFromIndex(parent)) { + if (auto* item = itemFromIndex(parent)) { if (parent.column() <= 0) { return item->hasChildren(); } @@ -342,7 +334,7 @@ bool FileTreeModel::canFetchMore(const QModelIndex& parent) const return false; } - if (auto* item=itemFromIndex(parent)) { + if (auto* item = itemFromIndex(parent)) { return !item->isLoaded(); } @@ -354,8 +346,7 @@ void FileTreeModel::fetchMore(const QModelIndex& parent) doFetchMore(parent, true, true); } -void FileTreeModel::doFetchMore( - const QModelIndex& parent, bool forFetch, bool doSort) +void FileTreeModel::doFetchMore(const QModelIndex& parent, bool forFetch, bool doSort) { FileTreeItem* item = itemFromIndex(parent); if (!item) { @@ -364,8 +355,8 @@ void FileTreeModel::doFetchMore( const auto path = item->dataRelativeFilePath(); - auto* parentEntry = m_core.directoryStructure() - ->findSubDirectoryRecursive(path.toStdWString()); + auto* parentEntry = + m_core.directoryStructure()->findSubDirectoryRecursive(path.toStdWString()); if (!parentEntry) { log::error("FileTreeModel::fetchMore(): directory '{}' not found", path); @@ -382,58 +373,52 @@ void FileTreeModel::doFetchMore( QVariant FileTreeModel::data(const QModelIndex& index, int role) const { - switch (role) - { - case Qt::DisplayRole: - { - if (auto* item=itemFromIndex(index)) { - return displayData(item, index.column()); - } - - break; + switch (role) { + case Qt::DisplayRole: { + if (auto* item = itemFromIndex(index)) { + return displayData(item, index.column()); } - case Qt::FontRole: - { - if (auto* item=itemFromIndex(index)) { - return item->font(); - } + break; + } - break; + case Qt::FontRole: { + if (auto* item = itemFromIndex(index)) { + return item->font(); } - case Qt::ToolTipRole: - { - if (auto* item=itemFromIndex(index)) { - return makeTooltip(*item); - } + break; + } - return {}; + case Qt::ToolTipRole: { + if (auto* item = itemFromIndex(index)) { + return makeTooltip(*item); } - case Qt::ForegroundRole: - { - if (index.column() == 1) { - if (auto* item=itemFromIndex(index)) { - if (item->isConflicted()) { - return QBrush(Qt::red); - } + return {}; + } + + case Qt::ForegroundRole: { + if (index.column() == 1) { + if (auto* item = itemFromIndex(index)) { + if (item->isConflicted()) { + return QBrush(Qt::red); } } - - break; } - case Qt::DecorationRole: - { - if (index.column() == 0) { - if (auto* item=itemFromIndex(index)) { - return makeIcon(*item, index); - } - } + break; + } - break; + case Qt::DecorationRole: { + if (index.column() == 0) { + if (auto* item = itemFromIndex(index)) { + return makeIcon(*item, index); + } } + + break; + } } return {}; @@ -442,8 +427,7 @@ QVariant FileTreeModel::data(const QModelIndex& index, int role) const QVariant FileTreeModel::headerData(int i, Qt::Orientation ori, int role) const { static const std::array names = { - tr("Name"), tr("Mod"), tr("Type"), tr("Size"), tr("Date modified") - }; + tr("Name"), tr("Mod"), tr("Type"), tr("Size"), tr("Date modified")}; if (role == Qt::DisplayRole) { if (i >= 0 && i < static_cast(names.size())) { @@ -458,7 +442,7 @@ Qt::ItemFlags FileTreeModel::flags(const QModelIndex& index) const { auto f = QAbstractItemModel::flags(index); - if (auto* item=itemFromIndex(index)) { + if (auto* item = itemFromIndex(index)) { if (!item->hasChildren()) { f |= Qt::ItemNeverHasChildren; } @@ -471,14 +455,13 @@ void FileTreeModel::sortItem(FileTreeItem& item, bool force) { emit layoutAboutToBeChanged({}, QAbstractItemModel::VerticalSortHint); - const auto oldList = persistentIndexList(); std::vector> oldItems; const auto itemCount = oldList.size(); oldItems.reserve(static_cast(itemCount)); - for (int i=0; i(i)]; newList.append(indexFromItem(*pair.first, pair.second)); } @@ -501,7 +484,7 @@ void FileTreeModel::sortItem(FileTreeItem& item, bool force) void FileTreeModel::sort(int column, Qt::SortOrder order) { m_sort.column = column; - m_sort.order = order; + m_sort.order = order; sortItem(*m_root, true); } @@ -519,9 +502,8 @@ FileTreeItem* FileTreeModel::itemFromIndex(const QModelIndex& index) const } if (index.row() < 0 || index.row() >= parentItem->children().size()) { - log::error( - "FileTreeModel::itemFromIndex(): row {} is out of range for {}", - index.row(), parentItem->debugName()); + log::error("FileTreeModel::itemFromIndex(): row {} is out of range for {}", + index.row(), parentItem->debugName()); return nullptr; } @@ -538,9 +520,8 @@ QModelIndex FileTreeModel::indexFromItem(FileTreeItem& item, int col) const const int index = parent->childIndex(item); if (index == -1) { - log::error( - "FileTreeMode::indexFromItem(): item {} not found in parent", - item.debugName()); + log::error("FileTreeMode::indexFromItem(): item {} not found in parent", + item.debugName()); return {}; } @@ -548,9 +529,9 @@ QModelIndex FileTreeModel::indexFromItem(FileTreeItem& item, int col) const return createIndex(index, col, makeInternalPointer(parent)); } -void FileTreeModel::update( - FileTreeItem& parentItem, const MOShared::DirectoryEntry& parentEntry, - const std::wstring& parentPath, bool forFetching) +void FileTreeModel::update(FileTreeItem& parentItem, + const MOShared::DirectoryEntry& parentEntry, + const std::wstring& parentPath, bool forFetching) { trace(log::debug("updating {}", parentItem.debugName())); @@ -588,9 +569,10 @@ void FileTreeModel::update( } } -bool FileTreeModel::updateDirectories( - FileTreeItem& parentItem, const std::wstring& parentPath, - const MOShared::DirectoryEntry& parentEntry, bool forFetching) +bool FileTreeModel::updateDirectories(FileTreeItem& parentItem, + const std::wstring& parentPath, + const MOShared::DirectoryEntry& parentEntry, + bool forFetching) { // removeDisappearingDirectories() will add directories that are in the // tree and still on the filesystem to this set; addNewDirectories() will @@ -602,12 +584,12 @@ bool FileTreeModel::updateDirectories( } void FileTreeModel::removeDisappearingDirectories( - FileTreeItem& parentItem, const MOShared::DirectoryEntry& parentEntry, - const std::wstring& parentPath, std::unordered_set& seen, - bool forFetching) + FileTreeItem& parentItem, const MOShared::DirectoryEntry& parentEntry, + const std::wstring& parentPath, std::unordered_set& seen, + bool forFetching) { auto& children = parentItem.children(); - auto itor = children.begin(); + auto itor = children.begin(); // keeps track of the contiguous directories that need to be removed to // avoid calling beginRemoveRows(), etc. for each item @@ -688,10 +670,10 @@ void FileTreeModel::removeDisappearingDirectories( range.remove(); } -bool FileTreeModel::addNewDirectories( - FileTreeItem& parentItem, const MOShared::DirectoryEntry& parentEntry, - const std::wstring& parentPath, - const std::unordered_set& seen) +bool FileTreeModel::addNewDirectories(FileTreeItem& parentItem, + const MOShared::DirectoryEntry& parentEntry, + const std::wstring& parentPath, + const std::unordered_set& seen) { // keeps track of the contiguous directories that need to be added to // avoid calling beginAddRows(), etc. for each item @@ -715,7 +697,8 @@ bool FileTreeModel::addNewDirectories( } else { if (!shouldShowFolder(*d, nullptr)) { // this is a new directory, but it doesn't contain anything interesting - trace(log::debug("new dir {}, empty and pruned", QString::fromStdWString(d->getName()))); + trace(log::debug("new dir {}, empty and pruned", + QString::fromStdWString(d->getName()))); // act as if this directory doesn't exist at all continue; @@ -739,9 +722,9 @@ bool FileTreeModel::addNewDirectories( return added; } -bool FileTreeModel::updateFiles( - FileTreeItem& parentItem, const std::wstring& parentPath, - const MOShared::DirectoryEntry& parentEntry) +bool FileTreeModel::updateFiles(FileTreeItem& parentItem, + const std::wstring& parentPath, + const MOShared::DirectoryEntry& parentEntry) { // removeDisappearingFiles() will add files that are in the tree and still on // the filesystem to this set; addNewFiless() will use this to figure out if @@ -754,12 +737,13 @@ bool FileTreeModel::updateFiles( return addNewFiles(parentItem, parentEntry, parentPath, firstFileRow, seen); } -void FileTreeModel::removeDisappearingFiles( - FileTreeItem& parentItem, const MOShared::DirectoryEntry& parentEntry, - int& firstFileRow, std::unordered_set& seen) +void FileTreeModel::removeDisappearingFiles(FileTreeItem& parentItem, + const MOShared::DirectoryEntry& parentEntry, + int& firstFileRow, + std::unordered_set& seen) { auto& children = parentItem.children(); - auto itor = children.begin(); + auto itor = children.begin(); firstFileRow = -1; @@ -814,10 +798,10 @@ void FileTreeModel::removeDisappearingFiles( } } -bool FileTreeModel::addNewFiles( - FileTreeItem& parentItem, const MOShared::DirectoryEntry& parentEntry, - const std::wstring& parentPath, const int firstFileRow, - const std::unordered_set& seen) +bool FileTreeModel::addNewFiles(FileTreeItem& parentItem, + const MOShared::DirectoryEntry& parentEntry, + const std::wstring& parentPath, const int firstFileRow, + const std::unordered_set& seen) { // keeps track of the contiguous files that need to be added to // avoid calling beginAddRows(), etc. for each item @@ -838,9 +822,8 @@ bool FileTreeModel::addNewFiles( const auto file = parentEntry.getFileByIndex(fileIndex); if (!file) { - log::error( - "FileTreeModel::addNewFiles(): file index {} in path {} not found", - fileIndex, parentPath); + log::error("FileTreeModel::addNewFiles(): file index {} in path {} not found", + fileIndex, parentPath); return true; } @@ -855,7 +838,8 @@ bool FileTreeModel::addNewFiles( range.includeCurrent(); } else { // this is a new file, but it shouldn't be shown - trace(log::debug("new file {}, not shown", QString::fromStdWString(file->getName()))); + trace(log::debug("new file {}, not shown", + QString::fromStdWString(file->getName()))); return true; } } @@ -924,12 +908,11 @@ void FileTreeModel::sortItems() } } -FileTreeItem::Ptr FileTreeModel::createDirectoryItem( - FileTreeItem& parentItem, const std::wstring& parentPath, - const DirectoryEntry& d) +FileTreeItem::Ptr FileTreeModel::createDirectoryItem(FileTreeItem& parentItem, + const std::wstring& parentPath, + const DirectoryEntry& d) { - auto item = FileTreeItem::createDirectory( - this, &parentItem, parentPath, d.getName()); + auto item = FileTreeItem::createDirectory(this, &parentItem, parentPath, d.getName()); if (d.isEmpty()) { // if this directory is empty, mark the item as loaded so the expand @@ -940,12 +923,11 @@ FileTreeItem::Ptr FileTreeModel::createDirectoryItem( return item; } -FileTreeItem::Ptr FileTreeModel::createFileItem( - FileTreeItem& parentItem, const std::wstring& parentPath, - const FileEntry& file) +FileTreeItem::Ptr FileTreeModel::createFileItem(FileTreeItem& parentItem, + const std::wstring& parentPath, + const FileEntry& file) { - auto item = FileTreeItem::createFile( - this, &parentItem, parentPath, file.getName()); + auto item = FileTreeItem::createFile(this, &parentItem, parentPath, file.getName()); updateFileItem(*item, file); @@ -954,11 +936,10 @@ FileTreeItem::Ptr FileTreeModel::createFileItem( return item; } -void FileTreeModel::updateFileItem( - FileTreeItem& item, const MOShared::FileEntry& file) +void FileTreeModel::updateFileItem(FileTreeItem& item, const MOShared::FileEntry& file) { bool isArchive = false; - int originID = file.getOrigin(isArchive); + int originID = file.getOrigin(isArchive); FileTreeItem::Flags flags = FileTreeItem::NoFlags; @@ -970,8 +951,7 @@ void FileTreeModel::updateFileItem( flags |= FileTreeItem::Conflicted; } - item.setOrigin( - originID, file.getFullPath(), flags, makeModName(file, originID)); + item.setOrigin(originID, file.getFullPath(), flags, makeModName(file, originID)); if (file.getFileSize() != FileEntry::NoFileSize) { item.setFileSize(file.getFileSize()); @@ -997,8 +977,8 @@ bool FileTreeModel::shouldShowFile(const FileEntry& file) const return true; } -bool FileTreeModel::shouldShowFolder( - const DirectoryEntry& dir, const FileTreeItem* item) const +bool FileTreeModel::shouldShowFolder(const DirectoryEntry& dir, + const FileTreeItem* item) const { bool shouldPrune = m_flags.testFlag(PruneDirectories); @@ -1061,62 +1041,55 @@ bool FileTreeModel::shouldShowFolder( QVariant FileTreeModel::displayData(const FileTreeItem* item, int column) const { - switch (column) - { - case FileName: - { - return item->filename(); - } + switch (column) { + case FileName: { + return item->filename(); + } - case ModName: - { - return item->mod(); - } + case ModName: { + return item->mod(); + } - case FileType: - { - return item->fileType().value_or(QString()); - } + case FileType: { + return item->fileType().value_or(QString()); + } - case FileSize: - { - if (item->isDirectory()) { - return {}; - } else { - QString fs; + case FileSize: { + if (item->isDirectory()) { + return {}; + } else { + QString fs; - if (auto n=item->fileSize()) { - fs = localizedByteSize(*n); - } + if (auto n = item->fileSize()) { + fs = localizedByteSize(*n); + } - if (auto n=item->compressedFileSize()) { - return QString("%1 (%2)").arg(fs).arg(localizedByteSize(*n)); - } else { - return fs; - } + if (auto n = item->compressedFileSize()) { + return QString("%1 (%2)").arg(fs).arg(localizedByteSize(*n)); + } else { + return fs; } } + } - case LastModified: - { - if (auto d=item->lastModified()) { - if (d.has_value() && d.value().isValid()) { - return QLocale::system().toString(d.value(), QLocale::ShortFormat); - } + case LastModified: { + if (auto d = item->lastModified()) { + if (d.has_value() && d.value().isValid()) { + return QLocale::system().toString(d.value(), QLocale::ShortFormat); } - - return {}; } - default: - { - return {}; - } + return {}; + } + + default: { + return {}; + } } } -std::wstring FileTreeModel::makeModName( - const MOShared::FileEntry& file, int originID) const +std::wstring FileTreeModel::makeModName(const MOShared::FileEntry& file, + int originID) const { static const std::wstring Unmanaged = UnmanagedModName().toStdWString(); @@ -1150,34 +1123,26 @@ QString FileTreeModel::makeTooltip(const FileTreeItem& item) const } }; - if (item.isDirectory()) { - return - line(tr("Directory"), item.filename()) + - line(tr("Virtual path"), item.virtualPath()); + return line(tr("Directory"), item.filename()) + + line(tr("Virtual path"), item.virtualPath()); } - - static const QString ListStart = - "
      "; + static const QString ListStart = "
        "; static const QString ListEnd = "
      "; - - QString s = - line(tr("Virtual path"), item.virtualPath()) + - line(tr("Real path"), item.realPath()) + - line(tr("From"), item.mod()); - + QString s = line(tr("Virtual path"), item.virtualPath()) + + line(tr("Real path"), item.realPath()) + line(tr("From"), item.mod()); const auto file = m_core.directoryStructure()->searchFile( - item.dataRelativeFilePath().toStdWString(), nullptr); + item.dataRelativeFilePath().toStdWString(), nullptr); if (file) { const auto alternatives = file->getAlternatives(); @@ -1194,7 +1159,7 @@ QString FileTreeModel::makeTooltip(const FileTreeItem& item) const s += line(tr("Also in"), QString()) + ListStart; for (auto&& alt : list) { - s += "
    • " + alt +"
    • "; + s += "
    • " + alt + "
    • "; } s += ListEnd; @@ -1204,8 +1169,8 @@ QString FileTreeModel::makeTooltip(const FileTreeItem& item) const return s; } -QVariant FileTreeModel::makeIcon( - const FileTreeItem& item, const QModelIndex& index) const +QVariant FileTreeModel::makeIcon(const FileTreeItem& item, + const QModelIndex& index) const { if (item.isDirectory()) { return m_iconFetcher.genericDirectoryIcon(); @@ -1236,8 +1201,7 @@ void FileTreeModel::updatePendingIcons() } } -void FileTreeModel::removePendingIcons( - const QModelIndex& parent, int first, int last) +void FileTreeModel::removePendingIcons(const QModelIndex& parent, int first, int last) { auto itor = m_iconPending.begin(); diff --git a/src/filetreemodel.h b/src/filetreemodel.h index 73a18d914..fbb70c7a0 100644 --- a/src/filetreemodel.h +++ b/src/filetreemodel.h @@ -36,25 +36,18 @@ class FileTreeModel : public QAbstractItemModel struct SortInfo { - int column = 0; + int column = 0; Qt::SortOrder order = Qt::AscendingOrder; }; + FileTreeModel(OrganizerCore& core, QObject* parent = nullptr); - FileTreeModel(OrganizerCore& core, QObject* parent=nullptr); - - void setFlags(Flags f) - { - m_flags = f; - } + void setFlags(Flags f) { m_flags = f; } void refresh(); void clear(); - bool fullyLoaded() const - { - return m_fullyLoaded; - } + bool fullyLoaded() const { return m_fullyLoaded; } void ensureFullyLoaded(); @@ -64,20 +57,20 @@ class FileTreeModel : public QAbstractItemModel void aboutToExpandAll(); void expandedAll(); - const SortInfo& sortInfo() const; - QModelIndex index(int row, int col, const QModelIndex& parent={}) const override; + QModelIndex index(int row, int col, const QModelIndex& parent = {}) const override; QModelIndex parent(const QModelIndex& index) const override; - int rowCount(const QModelIndex& parent={}) const override; - int columnCount(const QModelIndex& parent={}) const override; - bool hasChildren(const QModelIndex& parent={}) const override; + int rowCount(const QModelIndex& parent = {}) const override; + int columnCount(const QModelIndex& parent = {}) const override; + bool hasChildren(const QModelIndex& parent = {}) const override; bool canFetchMore(const QModelIndex& parent) const override; void fetchMore(const QModelIndex& parent) override; - QVariant data(const QModelIndex& index, int role=Qt::DisplayRole) const override; - QVariant headerData(int i, Qt::Orientation ori, int role=Qt::DisplayRole) const override; + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + QVariant headerData(int i, Qt::Orientation ori, + int role = Qt::DisplayRole) const override; Qt::ItemFlags flags(const QModelIndex& index) const override; - void sort(int column, Qt::SortOrder order=Qt::AscendingOrder) override; + void sort(int column, Qt::SortOrder order = Qt::AscendingOrder) override; FileTreeItem* itemFromIndex(const QModelIndex& index) const; void sortItem(FileTreeItem& item, bool force); @@ -105,19 +98,13 @@ class FileTreeModel : public QAbstractItemModel QTimer m_removeTimer; QTimer m_sortTimer; - - bool showConflictsOnly() const - { - return (m_flags & ConflictsOnly); - } + bool showConflictsOnly() const { return (m_flags & ConflictsOnly); } bool showArchives() const; - // for `forFetching`, see top of filetreemodel.cpp - void update( - FileTreeItem& parentItem, const MOShared::DirectoryEntry& parentEntry, - const std::wstring& parentPath, bool forFetching); + void update(FileTreeItem& parentItem, const MOShared::DirectoryEntry& parentEntry, + const std::wstring& parentPath, bool forFetching); void doFetchMore(const QModelIndex& parent, bool forFetch, bool doSort); @@ -126,49 +113,45 @@ class FileTreeModel : public QAbstractItemModel void sortItems(); - // for `forFetching`, see top of filetreemodel.cpp - bool updateDirectories( - FileTreeItem& parentItem, const std::wstring& path, - const MOShared::DirectoryEntry& parentEntry, bool forFetching); + bool updateDirectories(FileTreeItem& parentItem, const std::wstring& path, + const MOShared::DirectoryEntry& parentEntry, bool forFetching); // for `forFetching`, see top of filetreemodel.cpp - void removeDisappearingDirectories( - FileTreeItem& parentItem, const MOShared::DirectoryEntry& parentEntry, - const std::wstring& parentPath, std::unordered_set& seen, - bool forFetching); - - bool addNewDirectories( - FileTreeItem& parentItem, const MOShared::DirectoryEntry& parentEntry, - const std::wstring& parentPath, - const std::unordered_set& seen); - - - bool updateFiles( - FileTreeItem& parentItem, const std::wstring& path, - const MOShared::DirectoryEntry& parentEntry); - - void removeDisappearingFiles( - FileTreeItem& parentItem, const MOShared::DirectoryEntry& parentEntry, - int& firstFileRow, std::unordered_set& seen); - - bool addNewFiles( - FileTreeItem& parentItem, const MOShared::DirectoryEntry& parentEntry, - const std::wstring& parentPath, int firstFileRow, - const std::unordered_set& seen); - - - FileTreeItem::Ptr createDirectoryItem( - FileTreeItem& parentItem, const std::wstring& parentPath, - const MOShared::DirectoryEntry& d); - - FileTreeItem::Ptr createFileItem( - FileTreeItem& parentItem, const std::wstring& parentPath, - const MOShared::FileEntry& file); + void removeDisappearingDirectories(FileTreeItem& parentItem, + const MOShared::DirectoryEntry& parentEntry, + const std::wstring& parentPath, + std::unordered_set& seen, + bool forFetching); + + bool addNewDirectories(FileTreeItem& parentItem, + const MOShared::DirectoryEntry& parentEntry, + const std::wstring& parentPath, + const std::unordered_set& seen); + + bool updateFiles(FileTreeItem& parentItem, const std::wstring& path, + const MOShared::DirectoryEntry& parentEntry); + + void removeDisappearingFiles(FileTreeItem& parentItem, + const MOShared::DirectoryEntry& parentEntry, + int& firstFileRow, + std::unordered_set& seen); + + bool addNewFiles(FileTreeItem& parentItem, + const MOShared::DirectoryEntry& parentEntry, + const std::wstring& parentPath, int firstFileRow, + const std::unordered_set& seen); + + FileTreeItem::Ptr createDirectoryItem(FileTreeItem& parentItem, + const std::wstring& parentPath, + const MOShared::DirectoryEntry& d); + + FileTreeItem::Ptr createFileItem(FileTreeItem& parentItem, + const std::wstring& parentPath, + const MOShared::FileEntry& file); void updateFileItem(FileTreeItem& item, const MOShared::FileEntry& file); - QVariant displayData(const FileTreeItem* item, int column) const; std::wstring makeModName(const MOShared::FileEntry& file, int originID) const; @@ -177,15 +160,16 @@ class FileTreeModel : public QAbstractItemModel void removePendingIcons(const QModelIndex& parent, int first, int last); bool shouldShowFile(const MOShared::FileEntry& file) const; - bool shouldShowFolder(const MOShared::DirectoryEntry& dir, const FileTreeItem* item) const; + bool shouldShowFolder(const MOShared::DirectoryEntry& dir, + const FileTreeItem* item) const; QString makeTooltip(const FileTreeItem& item) const; QVariant makeIcon(const FileTreeItem& item, const QModelIndex& index) const; - QModelIndex indexFromItem(FileTreeItem& item, int col=0) const; + QModelIndex indexFromItem(FileTreeItem& item, int col = 0) const; void recursiveFetchMore(const QModelIndex& m); }; Q_DECLARE_OPERATORS_FOR_FLAGS(FileTreeModel::Flags); Q_DECLARE_OPERATORS_FOR_FLAGS(FileTreeItem::Flags); -#endif // MODORGANIZER_FILETREEMODEL_INCLUDED +#endif // MODORGANIZER_FILETREEMODEL_INCLUDED diff --git a/src/filterlist.cpp b/src/filterlist.cpp index 50a8be71c..c88945f83 100644 --- a/src/filterlist.cpp +++ b/src/filterlist.cpp @@ -1,24 +1,23 @@ #include "filterlist.h" -#include "ui_mainwindow.h" #include "categories.h" #include "categoriesdialog.h" +#include "organizercore.h" #include "plugincontainer.h" #include "settings.h" -#include "organizercore.h" +#include "ui_mainwindow.h" #include using namespace MOBase; using CriteriaType = ModListSortProxy::CriteriaType; -using Criteria = ModListSortProxy::Criteria; +using Criteria = ModListSortProxy::Criteria; class FilterList::CriteriaItem : public QTreeWidgetItem { - static constexpr int IDRole = Qt::UserRole; + static constexpr int IDRole = Qt::UserRole; static constexpr int TypeRole = Qt::UserRole + 1; public: - static constexpr int StateRole = Qt::UserRole + 2; enum States @@ -33,7 +32,7 @@ class FilterList::CriteriaItem : public QTreeWidgetItem }; CriteriaItem(FilterList* list, QString name, CriteriaType type, int id) - : QTreeWidgetItem({"", name}), m_list(list), m_state(Inactive) + : QTreeWidgetItem({"", name}), m_list(list), m_state(Inactive) { setData(0, Qt::ToolTipRole, name); setData(0, TypeRole, type); @@ -46,15 +45,9 @@ class FilterList::CriteriaItem : public QTreeWidgetItem return static_cast(data(0, TypeRole).toInt()); } - int id() const - { - return data(0, IDRole).toInt(); - } + int id() const { return data(0, IDRole).toInt(); } - States state() const - { - return m_state; - } + States state() const { return m_state; } void setState(States s) { @@ -90,17 +83,16 @@ class FilterList::CriteriaItem : public QTreeWidgetItem return QTreeWidgetItem::data(column, role); } - void setData(int column, int role, const QVariant& value) { + void setData(int column, int role, const QVariant& value) + { if (role == StateRole) { setState(static_cast(value.toInt())); - } - else { + } else { QTreeWidgetItem::setData(column, role, value); } } private: - FilterList* m_list; States m_state; @@ -108,40 +100,34 @@ class FilterList::CriteriaItem : public QTreeWidgetItem { QIcon i; - switch (m_state) - { - case Inactive: - { - i = QIcon(":/MO/gui/unchecked-checkbox"); - break; - } + switch (m_state) { + case Inactive: { + i = QIcon(":/MO/gui/unchecked-checkbox"); + break; + } - case Active: - { - i = QIcon(":/MO/gui/checked-checkbox"); - break; - } + case Active: { + i = QIcon(":/MO/gui/checked-checkbox"); + break; + } - case Inverted: - { - i = QIcon(":/MO/gui/indeterminate-checkbox"); - break; - } + case Inverted: { + i = QIcon(":/MO/gui/indeterminate-checkbox"); + break; + } } setData(0, Qt::DecorationRole, i); } }; - class CriteriaItemFilter : public QObject { public: - using Callback = std::function; + using Callback = std::function; CriteriaItemFilter(QTreeWidget* tree, Callback f) - : QObject(tree), m_tree(tree), m_f(std::move(f)) - { - } + : QObject(tree), m_tree(tree), m_f(std::move(f)) + {} bool eventFilter(QObject* o, QEvent* e) override { @@ -151,7 +137,8 @@ class CriteriaItemFilter : public QObject // viewport only and keyboard events from the tree only if (m_f) { - if (e->type() == QEvent::MouseButtonPress || e->type() == QEvent::MouseButtonDblClick) { + if (e->type() == QEvent::MouseButtonPress || + e->type() == QEvent::MouseButtonDblClick) { if (handleMouse(static_cast(e))) { return true; } @@ -178,7 +165,7 @@ class CriteriaItemFilter : public QObject m_tree->setCurrentItem(item); - const auto dir = (e->button() == Qt::LeftButton ? 1 : - 1); + const auto dir = (e->button() == Qt::LeftButton ? 1 : -1); return m_f(item, dir); } @@ -192,7 +179,7 @@ class CriteriaItemFilter : public QObject } const auto shiftPressed = (e->modifiers() & Qt::ShiftModifier); - const auto dir = (shiftPressed ? -1 : 1); + const auto dir = (shiftPressed ? -1 : 1); return m_f(item, dir); } @@ -201,33 +188,45 @@ class CriteriaItemFilter : public QObject } }; - -FilterList::FilterList(Ui::MainWindow* ui, OrganizerCore& core, CategoryFactory& factory) - : ui(ui), m_core(core), m_factory(factory) +FilterList::FilterList(Ui::MainWindow* ui, OrganizerCore& core, + CategoryFactory& factory) + : ui(ui), m_core(core), m_factory(factory) { - auto* eventFilter = new CriteriaItemFilter( - ui->filters, [&](auto* item, int dir){ return cycleItem(item, dir); }); + auto* eventFilter = new CriteriaItemFilter(ui->filters, [&](auto* item, int dir) { + return cycleItem(item, dir); + }); ui->filters->installEventFilter(eventFilter); ui->filters->viewport()->installEventFilter(eventFilter); - connect(ui->filtersClear, &QPushButton::clicked, [&]{ clearSelection(); }); - connect(ui->filtersEdit, &QPushButton::clicked, [&]{ editCategories(); }); - connect(ui->filtersAnd, &QCheckBox::toggled, [&]{ onOptionsChanged(); }); - connect(ui->filtersOr, &QCheckBox::toggled, [&]{ onOptionsChanged(); }); - - connect( - ui->filtersSeparators, qOverload(&QComboBox::currentIndexChanged), - [&]{ onOptionsChanged(); }); + connect(ui->filtersClear, &QPushButton::clicked, [&] { + clearSelection(); + }); + connect(ui->filtersEdit, &QPushButton::clicked, [&] { + editCategories(); + }); + connect(ui->filtersAnd, &QCheckBox::toggled, [&] { + onOptionsChanged(); + }); + connect(ui->filtersOr, &QCheckBox::toggled, [&] { + onOptionsChanged(); + }); + + connect(ui->filtersSeparators, qOverload(&QComboBox::currentIndexChanged), [&] { + onOptionsChanged(); + }); ui->filters->header()->setMinimumSectionSize(0); ui->filters->header()->resizeSection(0, 23); ui->categoriesSplitter->setCollapsible(0, false); ui->categoriesSplitter->setCollapsible(1, false); - ui->filtersSeparators->addItem(tr("Filter separators"), ModListSortProxy::SeparatorFilter); - ui->filtersSeparators->addItem(tr("Show separators"), ModListSortProxy::SeparatorShow); - ui->filtersSeparators->addItem(tr("Hide separators"), ModListSortProxy::SeparatorHide); + ui->filtersSeparators->addItem(tr("Filter separators"), + ModListSortProxy::SeparatorFilter); + ui->filtersSeparators->addItem(tr("Show separators"), + ModListSortProxy::SeparatorShow); + ui->filtersSeparators->addItem(tr("Hide separators"), + ModListSortProxy::SeparatorHide); } void FilterList::restoreState(const Settings& s) @@ -250,9 +249,8 @@ void FilterList::saveState(Settings& s) const s.widgets().saveIndex(ui->filtersSeparators); } -QTreeWidgetItem* FilterList::addCriteriaItem( - QTreeWidgetItem *root, const QString &name, int categoryID, - CriteriaType type) +QTreeWidgetItem* FilterList::addCriteriaItem(QTreeWidgetItem* root, const QString& name, + int categoryID, CriteriaType type) { auto* item = new CriteriaItem(this, name, type, categoryID); @@ -265,23 +263,26 @@ QTreeWidgetItem* FilterList::addCriteriaItem( void FilterList::addContentCriteria() { - m_core.modDataContents().forEachContent([this](auto const& content) { - addCriteriaItem( - nullptr, QString("<%1>").arg(tr("Contains %1").arg(content.name())), - content.id(), ModListSortProxy::TypeContent); - }, true); + m_core.modDataContents().forEachContent( + [this](auto const& content) { + addCriteriaItem(nullptr, + QString("<%1>").arg(tr("Contains %1").arg(content.name())), + content.id(), ModListSortProxy::TypeContent); + }, + true); } -void FilterList::addCategoryCriteria(QTreeWidgetItem *root, const std::set &categoriesUsed, int targetID) +void FilterList::addCategoryCriteria(QTreeWidgetItem* root, + const std::set& categoriesUsed, int targetID) { const auto count = static_cast(m_factory.numCategories()); for (unsigned int i = 1; i < count; ++i) { if (m_factory.getParentID(i) == targetID) { int categoryID = m_factory.getCategoryID(i); if (categoriesUsed.find(categoryID) != categoriesUsed.end()) { - QTreeWidgetItem *item = - addCriteriaItem(root, m_factory.getCategoryName(i), - categoryID, ModListSortProxy::TypeCategory); + QTreeWidgetItem* item = + addCriteriaItem(root, m_factory.getCategoryName(i), categoryID, + ModListSortProxy::TypeCategory); if (m_factory.hasChildren(i)) { addCategoryCriteria(item, categoriesUsed, categoryID); } @@ -294,9 +295,8 @@ void FilterList::addSpecialCriteria(int type) { const auto sc = static_cast(type); - addCriteriaItem( - nullptr, m_factory.getSpecialCategoryName(sc), - type, ModListSortProxy::TypeSpecial); + addCriteriaItem(nullptr, m_factory.getSpecialCategoryName(sc), type, + ModListSortProxy::TypeSpecial); } void FilterList::refresh() @@ -368,7 +368,7 @@ void FilterList::setSelection(const std::vector& criteria) void FilterList::clearSelection() { - for (int i=0; ifilters->topLevelItemCount(); ++i) { + for (int i = 0; i < ui->filters->topLevelItemCount(); ++i) { auto* ci = dynamic_cast(ui->filters->topLevelItem(i)); if (!ci) { continue; @@ -403,16 +403,15 @@ std::vector FilterList::selectedCriteria() const { std::vector criteria; - for (int i=0; ifilters->topLevelItemCount(); ++i) { + for (int i = 0; i < ui->filters->topLevelItemCount(); ++i) { const auto* ci = dynamic_cast(ui->filters->topLevelItem(i)); if (!ci) { continue; } if (ci->state() != CriteriaItem::Inactive) { - criteria.push_back({ - ci->type(), ci->id(), (ci->state() == CriteriaItem::Inverted) - }); + criteria.push_back( + {ci->type(), ci->id(), (ci->state() == CriteriaItem::Inverted)}); } } @@ -436,11 +435,11 @@ void FilterList::editCategories() void FilterList::onOptionsChanged() { - const auto mode = ui->filtersAnd->isChecked() ? - ModListSortProxy::FilterAnd: ModListSortProxy::FilterOr; + const auto mode = ui->filtersAnd->isChecked() ? ModListSortProxy::FilterAnd + : ModListSortProxy::FilterOr; const auto separators = static_cast( - ui->filtersSeparators->currentData().toInt()); + ui->filtersSeparators->currentData().toInt()); emit optionsChanged(mode, separators); } diff --git a/src/filterlist.h b/src/filterlist.h index e8f5d001a..c28b08c0e 100644 --- a/src/filterlist.h +++ b/src/filterlist.h @@ -4,7 +4,10 @@ #include "modlistsortproxy.h" #include -namespace Ui { class MainWindow; }; +namespace Ui +{ +class MainWindow; +}; class CategoryFactory; class PluginContainer; class Settings; @@ -26,8 +29,8 @@ class FilterList : public QObject signals: void criteriaChanged(std::vector criteria); - void optionsChanged( - ModListSortProxy::FilterMode mode, ModListSortProxy::SeparatorsMode sep); + void optionsChanged(ModListSortProxy::FilterMode mode, + ModListSortProxy::SeparatorsMode sep); private: class CriteriaItem; @@ -45,14 +48,13 @@ class FilterList : public QObject std::vector selectedCriteria() const; bool cycleItem(QTreeWidgetItem* item, int direction); - QTreeWidgetItem* addCriteriaItem( - QTreeWidgetItem *root, const QString &name, int categoryID, - ModListSortProxy::CriteriaType type); + QTreeWidgetItem* addCriteriaItem(QTreeWidgetItem* root, const QString& name, + int categoryID, ModListSortProxy::CriteriaType type); void addContentCriteria(); - void addCategoryCriteria( - QTreeWidgetItem *root, const std::set &categoriesUsed, int targetID); + void addCategoryCriteria(QTreeWidgetItem* root, const std::set& categoriesUsed, + int targetID); void addSpecialCriteria(int type); }; -#endif // MODORGANIZER_CATEGORIESLIST_INCLUDED +#endif // MODORGANIZER_CATEGORIESLIST_INCLUDED diff --git a/src/forcedloaddialog.cpp b/src/forcedloaddialog.cpp index ab0bd5835..14011b1b5 100644 --- a/src/forcedloaddialog.cpp +++ b/src/forcedloaddialog.cpp @@ -8,25 +8,23 @@ using namespace MOBase; -ForcedLoadDialog::ForcedLoadDialog(const IPluginGame *game, QWidget *parent) : - QDialog(parent), - ui(new Ui::ForcedLoadDialog), - m_GamePlugin(game) +ForcedLoadDialog::ForcedLoadDialog(const IPluginGame* game, QWidget* parent) + : QDialog(parent), ui(new Ui::ForcedLoadDialog), m_GamePlugin(game) { - ui->setupUi(this); + ui->setupUi(this); } ForcedLoadDialog::~ForcedLoadDialog() { - delete ui; + delete ui; } -void ForcedLoadDialog::setValues(QList &values) +void ForcedLoadDialog::setValues(QList& values) { ui->tableWidget->clearContents(); for (int i = 0; i < values.count(); i++) { - ForcedLoadDialogWidget *item = new ForcedLoadDialogWidget(m_GamePlugin, this); + ForcedLoadDialogWidget* item = new ForcedLoadDialogWidget(m_GamePlugin, this); item->setEnabled(values[i].enabled()); item->setProcess(values[i].process()); item->setLibraryPath(values[i].library()); @@ -45,32 +43,28 @@ QList ForcedLoadDialog::values() for (int row = 0; row < ui->tableWidget->rowCount(); row++) { auto widget = (ForcedLoadDialogWidget*)ui->tableWidget->cellWidget(row, 0); results.append( - ExecutableForcedLoadSetting( - widget->getProcess(), - widget->getLibraryPath() - ).withEnabled(widget->getEnabled()) - .withForced(widget->getForced()) - ); + ExecutableForcedLoadSetting(widget->getProcess(), widget->getLibraryPath()) + .withEnabled(widget->getEnabled()) + .withForced(widget->getForced())); } return results; } - void ForcedLoadDialog::on_addRowButton_clicked() { int row = ui->tableWidget->rowCount(); ui->tableWidget->insertRow(row); - ForcedLoadDialogWidget *item = new ForcedLoadDialogWidget(m_GamePlugin, this); + ForcedLoadDialogWidget* item = new ForcedLoadDialogWidget(m_GamePlugin, this); ui->tableWidget->setCellWidget(row, 0, item); ui->tableWidget->resizeRowsToContents(); } void ForcedLoadDialog::on_deleteRowButton_clicked() { - for (auto rowIndex: ui->tableWidget->selectionModel()->selectedRows()) { - int row = rowIndex.row(); + for (auto rowIndex : ui->tableWidget->selectionModel()->selectedRows()) { + int row = rowIndex.row(); auto widget = (ForcedLoadDialogWidget*)ui->tableWidget->cellWidget(row, 0); - if (!widget->getForced()){ + if (!widget->getForced()) { ui->tableWidget->removeRow(row); } } diff --git a/src/forcedloaddialog.h b/src/forcedloaddialog.h index 6a88bccf0..ca57ab944 100644 --- a/src/forcedloaddialog.h +++ b/src/forcedloaddialog.h @@ -7,7 +7,8 @@ #include "executableinfo.h" #include "iplugingame.h" -namespace Ui { +namespace Ui +{ class ForcedLoadDialog; } @@ -16,10 +17,10 @@ class ForcedLoadDialog : public QDialog Q_OBJECT public: - explicit ForcedLoadDialog(const MOBase::IPluginGame *game, QWidget *parent = nullptr); + explicit ForcedLoadDialog(const MOBase::IPluginGame* game, QWidget* parent = nullptr); ~ForcedLoadDialog(); - void setValues(QList &values); + void setValues(QList& values); QList values(); private slots: @@ -27,8 +28,8 @@ private slots: void on_deleteRowButton_clicked(); private: - Ui::ForcedLoadDialog *ui; - const MOBase::IPluginGame *m_GamePlugin; + Ui::ForcedLoadDialog* ui; + const MOBase::IPluginGame* m_GamePlugin; }; -#endif // FORCEDLOADDIALOG_H +#endif // FORCEDLOADDIALOG_H diff --git a/src/forcedloaddialogwidget.cpp b/src/forcedloaddialogwidget.cpp index b84f785f5..c2e444372 100644 --- a/src/forcedloaddialogwidget.cpp +++ b/src/forcedloaddialogwidget.cpp @@ -1,22 +1,20 @@ #include "forcedloaddialogwidget.h" -#include "ui_forcedloaddialogwidget.h" #include "executableinfo.h" -#include +#include "ui_forcedloaddialogwidget.h" #include +#include using namespace MOBase; -ForcedLoadDialogWidget::ForcedLoadDialogWidget(const IPluginGame *game, QWidget *parent) : - QWidget(parent), - ui(new Ui::ForcedLoadDialogWidget), - m_GamePlugin(game) +ForcedLoadDialogWidget::ForcedLoadDialogWidget(const IPluginGame* game, QWidget* parent) + : QWidget(parent), ui(new Ui::ForcedLoadDialogWidget), m_GamePlugin(game) { - ui->setupUi(this); + ui->setupUi(this); } ForcedLoadDialogWidget::~ForcedLoadDialogWidget() { - delete ui; + delete ui; } bool ForcedLoadDialogWidget::getEnabled() @@ -53,12 +51,12 @@ void ForcedLoadDialogWidget::setForced(bool forced) ui->processEdit->setEnabled(!forced); } -void ForcedLoadDialogWidget::setLibraryPath(const QString &path) +void ForcedLoadDialogWidget::setLibraryPath(const QString& path) { ui->libraryPathEdit->setText(path); } -void ForcedLoadDialogWidget::setProcess(const QString &name) +void ForcedLoadDialogWidget::setProcess(const QString& name) { ui->processEdit->setText(name); } @@ -72,11 +70,13 @@ void ForcedLoadDialogWidget::on_libraryPathBrowseButton_clicked() { QDir gameDir(m_GamePlugin->gameDirectory()); QString startPath = gameDir.absolutePath(); - QString result = QFileDialog::getOpenFileName(nullptr, "Select a library...", startPath, "Dynamic link library (*.dll)", nullptr, QFileDialog::ReadOnly); + QString result = QFileDialog::getOpenFileName( + nullptr, "Select a library...", startPath, "Dynamic link library (*.dll)", + nullptr, QFileDialog::ReadOnly); if (!result.isEmpty()) { QFileInfo fileInfo(result); QString relativePath = gameDir.relativeFilePath(fileInfo.filePath()); - QString filePath = fileInfo.filePath(); + QString filePath = fileInfo.filePath(); if (!relativePath.startsWith("..")) { filePath = relativePath; } @@ -93,7 +93,9 @@ void ForcedLoadDialogWidget::on_processBrowseButton_clicked() { QDir gameDir(m_GamePlugin->gameDirectory()); QString startPath = gameDir.absolutePath(); - QString result = QFileDialog::getOpenFileName(nullptr, "Select a process...", startPath, "Executable (*.exe)", nullptr, QFileDialog::ReadOnly); + QString result = QFileDialog::getOpenFileName(nullptr, "Select a process...", + startPath, "Executable (*.exe)", + nullptr, QFileDialog::ReadOnly); if (!result.isEmpty()) { QFileInfo fileInfo(result); QString fileName = fileInfo.fileName(); diff --git a/src/forcedloaddialogwidget.h b/src/forcedloaddialogwidget.h index 239653c89..68ece9c0b 100644 --- a/src/forcedloaddialogwidget.h +++ b/src/forcedloaddialogwidget.h @@ -1,41 +1,42 @@ #ifndef FORCEDLOADDIALOGWIDGET_H #define FORCEDLOADDIALOGWIDGET_H -#include #include "iplugingame.h" +#include -namespace Ui { +namespace Ui +{ class ForcedLoadDialogWidget; } class ForcedLoadDialogWidget : public QWidget { - Q_OBJECT + Q_OBJECT public: - explicit ForcedLoadDialogWidget(const MOBase::IPluginGame *game, QWidget *parent = nullptr); - ~ForcedLoadDialogWidget(); + explicit ForcedLoadDialogWidget(const MOBase::IPluginGame* game, + QWidget* parent = nullptr); + ~ForcedLoadDialogWidget(); - bool getEnabled(); - bool getForced(); - QString getLibraryPath(); - QString getProcess(); + bool getEnabled(); + bool getForced(); + QString getLibraryPath(); + QString getProcess(); - void setEnabled(bool enabled); - void setForced(bool forced); - void setLibraryPath(const QString &path); - void setProcess(const QString &name); + void setEnabled(bool enabled); + void setForced(bool forced); + void setLibraryPath(const QString& path); + void setProcess(const QString& name); private slots: - void on_enabledBox_toggled(); - void on_libraryPathBrowseButton_clicked(); - void on_processBrowseButton_clicked(); + void on_enabledBox_toggled(); + void on_libraryPathBrowseButton_clicked(); + void on_processBrowseButton_clicked(); private: - Ui::ForcedLoadDialogWidget *ui; - bool m_Forced; - const MOBase::IPluginGame *m_GamePlugin; - + Ui::ForcedLoadDialogWidget* ui; + bool m_Forced; + const MOBase::IPluginGame* m_GamePlugin; }; -#endif // FORCEDLOADDIALOGWIDGET_H +#endif // FORCEDLOADDIALOGWIDGET_H diff --git a/src/genericicondelegate.cpp b/src/genericicondelegate.cpp index 7afaca3fd..1ee980ab3 100644 --- a/src/genericicondelegate.cpp +++ b/src/genericicondelegate.cpp @@ -1,29 +1,27 @@ -#include "genericicondelegate.h" -#include "pluginlist.h" -#include -#include - - -GenericIconDelegate::GenericIconDelegate(QTreeView* parent, int role, int logicalIndex, int compactSize) - : IconDelegate(parent, logicalIndex, compactSize) - , m_Role(role) -{ -} - -QList GenericIconDelegate::getIcons(const QModelIndex &index) const -{ - QList result; - if (index.isValid()) { - for (const QVariant &var : index.data(m_Role).toList()) { - if (!compact() || !var.toString().isEmpty()) { - result.append(var.toString()); - } - } - } - return result; -} - -size_t GenericIconDelegate::getNumIcons(const QModelIndex &index) const -{ - return index.data(m_Role).toList().count(); -} +#include "genericicondelegate.h" +#include "pluginlist.h" +#include +#include + +GenericIconDelegate::GenericIconDelegate(QTreeView* parent, int role, int logicalIndex, + int compactSize) + : IconDelegate(parent, logicalIndex, compactSize), m_Role(role) +{} + +QList GenericIconDelegate::getIcons(const QModelIndex& index) const +{ + QList result; + if (index.isValid()) { + for (const QVariant& var : index.data(m_Role).toList()) { + if (!compact() || !var.toString().isEmpty()) { + result.append(var.toString()); + } + } + } + return result; +} + +size_t GenericIconDelegate::getNumIcons(const QModelIndex& index) const +{ + return index.data(m_Role).toList().count(); +} diff --git a/src/genericicondelegate.h b/src/genericicondelegate.h index 52db2bb6e..028738f5c 100644 --- a/src/genericicondelegate.h +++ b/src/genericicondelegate.h @@ -1,35 +1,40 @@ -#ifndef GENERICICONDELEGATE_H -#define GENERICICONDELEGATE_H - -#include "icondelegate.h" - -/** - * @brief an icon delegate that takes the list of icons from a user-defines data role - */ -class GenericIconDelegate : public IconDelegate -{ -Q_OBJECT -public: - /** - * @brief constructor - * @param parent parent object - * @param role role of the itemmodel from which the icon list can be queried (as a QVariantList) - * @param logicalIndex logical index within the model. This is part of a "hack". Normally "empty" icons will be allocated the same - * space as a regular icon. This way the model can use empty icons as spacers and thus align same icons horizontally. - * Now, if you set the logical Index to a valid column and connect the columnResized slot to the sectionResized signal - * of the view, the delegate will turn off this behaviour if the column is smaller than "compactSize" - * @param compactSize see explanation of logicalIndex - */ - GenericIconDelegate(QTreeView* parent, int role = Qt::UserRole + 1, int logicalIndex = -1, int compactSize = 150); - -private: - virtual QList getIcons(const QModelIndex &index) const; - virtual size_t getNumIcons(const QModelIndex &index) const; -private: - int m_Role; - int m_LogicalIndex; - int m_CompactSize; - bool m_Compact; -}; - -#endif // GENERICICONDELEGATE_H +#ifndef GENERICICONDELEGATE_H +#define GENERICICONDELEGATE_H + +#include "icondelegate.h" + +/** + * @brief an icon delegate that takes the list of icons from a user-defines data role + */ +class GenericIconDelegate : public IconDelegate +{ + Q_OBJECT +public: + /** + * @brief constructor + * @param parent parent object + * @param role role of the itemmodel from which the icon list can be queried (as a + * QVariantList) + * @param logicalIndex logical index within the model. This is part of a "hack". + * Normally "empty" icons will be allocated the same space as a regular icon. This way + * the model can use empty icons as spacers and thus align same icons horizontally. + * Now, if you set the logical Index to a valid column and connect + * the columnResized slot to the sectionResized signal of the view, the delegate will + * turn off this behaviour if the column is smaller than "compactSize" + * @param compactSize see explanation of logicalIndex + */ + GenericIconDelegate(QTreeView* parent, int role = Qt::UserRole + 1, + int logicalIndex = -1, int compactSize = 150); + +private: + virtual QList getIcons(const QModelIndex& index) const; + virtual size_t getNumIcons(const QModelIndex& index) const; + +private: + int m_Role; + int m_LogicalIndex; + int m_CompactSize; + bool m_Compact; +}; + +#endif // GENERICICONDELEGATE_H diff --git a/src/glob_matching.h b/src/glob_matching.h index e7e7b6211..b47d30aca 100644 --- a/src/glob_matching.h +++ b/src/glob_matching.h @@ -6,208 +6,180 @@ #ifndef GLOB_MATCHING_H #define GLOB_MATCHING_H +#include #include #include -#include -namespace MOShared { +namespace MOShared +{ - /** - * Contraints string_traits to allow usage of both standard strings and QString in - * GlobPattern. - */ - namespace details { +/** + * Contraints string_traits to allow usage of both standard strings and QString in + * GlobPattern. + */ +namespace details +{ - template < - class CharT, - class Traits = std::char_traits, - class Allocator = std::allocator> - struct string_traits { - using string_type = std::basic_string; - using string_view = std::basic_string_view; + template , + class Allocator = std::allocator> + struct string_traits + { + using string_type = std::basic_string; + using string_view = std::basic_string_view; - static auto tolower(CharT c) { return std::tolower(c); } + static auto tolower(CharT c) { return std::tolower(c); } - static auto empty(string_view const& view) { return view.empty(); } - }; + static auto empty(string_view const& view) { return view.empty(); } + }; - template <> - struct string_traits { - using string_type = QString; - using string_view = QString; + template <> + struct string_traits + { + using string_type = QString; + using string_view = QString; - static auto tolower(QChar const& c) { return c.toLower(); } - static auto empty(string_view const& view) { return view.isEmpty(); } - }; + static auto tolower(QChar const& c) { return c.toLower(); } + static auto empty(string_view const& view) { return view.isEmpty(); } + }; - } +} // namespace details + +/** + * @brief Class that provides basic wildcard pattern matching. + * + * From https://gitlab.com/G_ka/playground/-/commits/master/include/wildcards.hpp + * + * Currently, this supports the following globbing character: + * - '*' matches zero or more characters. + * - '?' matches exactly one character. + * - '[abc]' matches one of 'a', 'b' or 'c'. + * + * Standard globbing feature not supported: + * - You cannot escape globbing characters with \. + * - You cannot use `[a-z]` to match any character from 'a' to 'z'. + * + * Custom class because the following alternatives have some issues: + * - QRegExp is a tad slow, and we need to convert everything to QString. + * - QDir::match is VERY slow. I think it converts the glob pattern to a + * QRegularExpression and then use it. + * - PatchMatchSpecW (Windows API) is fast but does not support some useful glob + * pattern (e.g., [ab]). + * + * Advantage of this over the above methods: + * - It is fast. Quick testing show that this is faster than PatchMatchSpecW. + * - It can be used on most string types (QString, std::string, std::wstring, etc.). + */ +template , + class Allocator = std::allocator> +class GlobPattern +{ +public: + using traits = details::string_traits; + + using string_type = typename traits::string_type; + using string_view_type = typename traits::string_view; + + struct card + { + // Relying on automatic conversion: + static constexpr CharT any = '?'; + static constexpr CharT any_repeat = '*'; + static constexpr CharT set_begin = '['; + static constexpr CharT set_end = ']'; + }; - /** - * @brief Class that provides basic wildcard pattern matching. - * - * From https://gitlab.com/G_ka/playground/-/commits/master/include/wildcards.hpp - * - * Currently, this supports the following globbing character: - * - '*' matches zero or more characters. - * - '?' matches exactly one character. - * - '[abc]' matches one of 'a', 'b' or 'c'. - * - * Standard globbing feature not supported: - * - You cannot escape globbing characters with \. - * - You cannot use `[a-z]` to match any character from 'a' to 'z'. - * - * Custom class because the following alternatives have some issues: - * - QRegExp is a tad slow, and we need to convert everything to QString. - * - QDir::match is VERY slow. I think it converts the glob pattern to a QRegularExpression and - * then use it. - * - PatchMatchSpecW (Windows API) is fast but does not support some useful glob pattern (e.g., - * [ab]). - * - * Advantage of this over the above methods: - * - It is fast. Quick testing show that this is faster than PatchMatchSpecW. - * - It can be used on most string types (QString, std::string, std::wstring, etc.). - */ - template < - class CharT, - class Traits = std::char_traits, - class Allocator = std::allocator> - class GlobPattern { - public: - - using traits = details::string_traits; - - using string_type = typename traits::string_type; - using string_view_type = typename traits::string_view; - - struct card { - // Relying on automatic conversion: - static constexpr CharT any = '?'; - static constexpr CharT any_repeat = '*'; - static constexpr CharT set_begin = '['; - static constexpr CharT set_end = ']'; - }; - - public: - - GlobPattern(string_view_type const& s) : v{ s } { } - - const string_type& native() const { return v; } - - constexpr bool match( - string_view_type const& str, - bool case_sensitive = false) - { - // Empty pattern can only match with empty sting - if (traits::empty(v)) - return traits::empty(str); - - auto pat_it = v.begin(); - auto pat_end = v.end(); - - auto str_it = str.begin(); - auto str_end = str.end(); - - auto anyrep_pos_pat = pat_end; - auto anyrep_pos_str = str_end; - - auto set_pos_pat = pat_end; - - while (str_it != str_end) - { - CharT current_pat = QChar(0); - CharT current_str = QChar(-1); - if (pat_it != pat_end) - { - current_pat = case_sensitive ? *pat_it : traits::tolower(*pat_it); - current_str = case_sensitive ? *str_it : traits::tolower(*str_it); - } - if (pat_it != pat_end && current_pat == card::set_begin) - { - set_pos_pat = pat_it; - pat_it++; - } - else if (pat_it != pat_end && current_pat == card::set_end) - { - if (anyrep_pos_pat != pat_end) - { - set_pos_pat = pat_end; - pat_it++; - } - else - { - return false; - } +public: + GlobPattern(string_view_type const& s) : v{s} {} - } - else if (set_pos_pat != pat_end) - { - if (current_pat == current_str) - { - set_pos_pat = pat_end; - pat_it = std::find(pat_it, pat_end, card::set_end) + 1; - str_it++; - } - else - { - if (pat_it == pat_end) - { - return false; - } - pat_it++; - } - } - else if (pat_it != pat_end && current_pat == current_str) - { - pat_it++; - str_it++; - } - else if (pat_it != pat_end && current_pat == card::any) - { - pat_it++; - str_it++; - } - else if (pat_it != pat_end && current_pat == card::any_repeat) - { - anyrep_pos_pat = pat_it; - anyrep_pos_str = str_it; - pat_it++; - } - else if (anyrep_pos_pat != pat_end) - { - pat_it = anyrep_pos_pat + 1; - str_it = anyrep_pos_str + 1; - anyrep_pos_str++; - } - else - { + const string_type& native() const { return v; } + + constexpr bool match(string_view_type const& str, bool case_sensitive = false) + { + // Empty pattern can only match with empty sting + if (traits::empty(v)) + return traits::empty(str); + + auto pat_it = v.begin(); + auto pat_end = v.end(); + + auto str_it = str.begin(); + auto str_end = str.end(); + + auto anyrep_pos_pat = pat_end; + auto anyrep_pos_str = str_end; + + auto set_pos_pat = pat_end; + + while (str_it != str_end) { + CharT current_pat = QChar(0); + CharT current_str = QChar(-1); + if (pat_it != pat_end) { + current_pat = case_sensitive ? *pat_it : traits::tolower(*pat_it); + current_str = case_sensitive ? *str_it : traits::tolower(*str_it); + } + if (pat_it != pat_end && current_pat == card::set_begin) { + set_pos_pat = pat_it; + pat_it++; + } else if (pat_it != pat_end && current_pat == card::set_end) { + if (anyrep_pos_pat != pat_end) { + set_pos_pat = pat_end; + pat_it++; + } else { + return false; + } + + } else if (set_pos_pat != pat_end) { + if (current_pat == current_str) { + set_pos_pat = pat_end; + pat_it = std::find(pat_it, pat_end, card::set_end) + 1; + str_it++; + } else { + if (pat_it == pat_end) { return false; } + pat_it++; } - while (pat_it != pat_end) - { - CharT cur = case_sensitive ? *pat_it : traits::tolower(*pat_it); - if (cur == card::any_repeat) - pat_it++; - else - break; - } - return pat_it == pat_end; + } else if (pat_it != pat_end && current_pat == current_str) { + pat_it++; + str_it++; + } else if (pat_it != pat_end && current_pat == card::any) { + pat_it++; + str_it++; + } else if (pat_it != pat_end && current_pat == card::any_repeat) { + anyrep_pos_pat = pat_it; + anyrep_pos_str = str_it; + pat_it++; + } else if (anyrep_pos_pat != pat_end) { + pat_it = anyrep_pos_pat + 1; + str_it = anyrep_pos_str + 1; + anyrep_pos_str++; + } else { + return false; } + } + while (pat_it != pat_end) { + CharT cur = case_sensitive ? *pat_it : traits::tolower(*pat_it); + if (cur == card::any_repeat) + pat_it++; + else + break; + } + return pat_it == pat_end; + } - private: - string_type v; - }; +private: + string_type v; +}; - template - GlobPattern(std::basic_string const&) +template +GlobPattern(std::basic_string const&) -> GlobPattern; - template - GlobPattern(CharT const*) -> GlobPattern; - - GlobPattern(QString const&) -> GlobPattern; +template +GlobPattern(CharT const*) -> GlobPattern; +GlobPattern(QString const&)->GlobPattern; -} // namespace wildcards +} // namespace MOShared #endif diff --git a/src/icondelegate.cpp b/src/icondelegate.cpp index 7205ba625..bec7d995e 100644 --- a/src/icondelegate.cpp +++ b/src/icondelegate.cpp @@ -1,87 +1,87 @@ -/* -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 "icondelegate.h" -#include -#include -#include -#include -#include -#include - -using namespace MOBase; - -IconDelegate::IconDelegate(QTreeView* view, int column, int compactSize) : - QStyledItemDelegate(view), m_column(column), m_compactSize(compactSize), m_compact(false) -{ - if (view) { - connect(view->header(), &QHeaderView::sectionResized, [=](int column, int, int size) { - if (column == m_column) { - m_compact = size < m_compactSize; - } - }); - } -} - -void IconDelegate::paintIcons( - QPainter *painter, const QStyleOptionViewItem &option, - const QModelIndex &index, const QList& icons) -{ - int x = 4; - painter->save(); - - int iconWidth = icons.size() > 0 ? ((option.rect.width() / icons.size()) - 4) : 16; - iconWidth = std::min(16, iconWidth); - - const int margin = (option.rect.height() - iconWidth) / 2; - - painter->translate(option.rect.topLeft()); - for (const QString &iconId : icons) { - if (iconId.isEmpty()) { - x += iconWidth + 4; - continue; - } - QPixmap icon; - QString fullIconId = QString("%1_%2").arg(iconId).arg(iconWidth); - if (!QPixmapCache::find(fullIconId, &icon)) { - icon = QIcon(iconId).pixmap(iconWidth, iconWidth); - if (icon.isNull()) { - log::warn("failed to load icon {}", iconId); - } - QPixmapCache::insert(fullIconId, icon); - } - painter->drawPixmap(x, margin, iconWidth, iconWidth, icon); - x += iconWidth + 4; - } - - painter->restore(); -} - -void IconDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const -{ - if (auto* w = qobject_cast(parent())) { - w->itemDelegate()->paint(painter, option, index); - } - else { - QStyledItemDelegate::paint(painter, option, index); - } - - paintIcons(painter, option, index, getIcons(index)); -} - +/* +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 "icondelegate.h" +#include +#include +#include +#include +#include +#include + +using namespace MOBase; + +IconDelegate::IconDelegate(QTreeView* view, int column, int compactSize) + : QStyledItemDelegate(view), m_column(column), m_compactSize(compactSize), + m_compact(false) +{ + if (view) { + connect(view->header(), &QHeaderView::sectionResized, + [=](int column, int, int size) { + if (column == m_column) { + m_compact = size < m_compactSize; + } + }); + } +} + +void IconDelegate::paintIcons(QPainter* painter, const QStyleOptionViewItem& option, + const QModelIndex& index, const QList& icons) +{ + int x = 4; + painter->save(); + + int iconWidth = icons.size() > 0 ? ((option.rect.width() / icons.size()) - 4) : 16; + iconWidth = std::min(16, iconWidth); + + const int margin = (option.rect.height() - iconWidth) / 2; + + painter->translate(option.rect.topLeft()); + for (const QString& iconId : icons) { + if (iconId.isEmpty()) { + x += iconWidth + 4; + continue; + } + QPixmap icon; + QString fullIconId = QString("%1_%2").arg(iconId).arg(iconWidth); + if (!QPixmapCache::find(fullIconId, &icon)) { + icon = QIcon(iconId).pixmap(iconWidth, iconWidth); + if (icon.isNull()) { + log::warn("failed to load icon {}", iconId); + } + QPixmapCache::insert(fullIconId, icon); + } + painter->drawPixmap(x, margin, iconWidth, iconWidth, icon); + x += iconWidth + 4; + } + + painter->restore(); +} + +void IconDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, + const QModelIndex& index) const +{ + if (auto* w = qobject_cast(parent())) { + w->itemDelegate()->paint(painter, option, index); + } else { + QStyledItemDelegate::paint(painter, option, index); + } + + paintIcons(painter, option, index, getIcons(index)); +} diff --git a/src/icondelegate.h b/src/icondelegate.h index a123470e5..658868049 100644 --- a/src/icondelegate.h +++ b/src/icondelegate.h @@ -1,57 +1,54 @@ -/* -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 ICONDELEGATE_H -#define ICONDELEGATE_H - -#include -#include -#include - - -class IconDelegate : public QStyledItemDelegate -{ - Q_OBJECT; - -public: - explicit IconDelegate(QTreeView* view, int column = -1, int compactSize = 100); - - void paint(QPainter *painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override; - -protected: - - // check if icons should be compacted or not - // - bool compact() const { return m_compact; } - - static void paintIcons( - QPainter *painter, const QStyleOptionViewItem &option, - const QModelIndex &index, const QList& icons); - - virtual QList getIcons(const QModelIndex &index) const = 0; - virtual size_t getNumIcons(const QModelIndex &index) const = 0; - -private: - - int m_column; - int m_compactSize; - bool m_compact; -}; - -#endif // ICONDELEGATE_H +/* +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 ICONDELEGATE_H +#define ICONDELEGATE_H + +#include +#include +#include + +class IconDelegate : public QStyledItemDelegate +{ + Q_OBJECT; + +public: + explicit IconDelegate(QTreeView* view, int column = -1, int compactSize = 100); + + void paint(QPainter* painter, const QStyleOptionViewItem& option, + const QModelIndex& index) const override; + +protected: + // check if icons should be compacted or not + // + bool compact() const { return m_compact; } + + static void paintIcons(QPainter* painter, const QStyleOptionViewItem& option, + const QModelIndex& index, const QList& icons); + + virtual QList getIcons(const QModelIndex& index) const = 0; + virtual size_t getNumIcons(const QModelIndex& index) const = 0; + +private: + int m_column; + int m_compactSize; + bool m_compact; +}; + +#endif // ICONDELEGATE_H diff --git a/src/iconfetcher.cpp b/src/iconfetcher.cpp index 32b0d58b4..c677424c8 100644 --- a/src/iconfetcher.cpp +++ b/src/iconfetcher.cpp @@ -1,11 +1,13 @@ #include "iconfetcher.h" -#include "thread_utils.h" #include "shared/util.h" +#include "thread_utils.h" void IconFetcher::Waiter::wait() { std::unique_lock lock(m_wakeUpMutex); - m_wakeUp.wait(lock, [&]{ return m_queueAvailable; }); + m_wakeUp.wait(lock, [&] { + return m_queueAvailable; + }); m_queueAvailable = false; } @@ -19,14 +21,14 @@ void IconFetcher::Waiter::wakeUp() m_wakeUp.notify_one(); } - -IconFetcher::IconFetcher() - : m_iconSize(GetSystemMetrics(SM_CXSMICON)), m_stop(false) +IconFetcher::IconFetcher() : m_iconSize(GetSystemMetrics(SM_CXSMICON)), m_stop(false) { - m_quickCache.file = getPixmapIcon(QFileIconProvider::File); + m_quickCache.file = getPixmapIcon(QFileIconProvider::File); m_quickCache.directory = getPixmapIcon(QFileIconProvider::Folder); - m_thread = MOShared::startSafeThread([&]{ threadFun(); }); + m_thread = MOShared::startSafeThread([&] { + threadFun(); + }); } IconFetcher::~IconFetcher() @@ -73,10 +75,9 @@ bool IconFetcher::hasOwnIcon(const QString& path) const static const QString lnk = ".lnk"; static const QString ico = ".ico"; - return - path.endsWith(exe, Qt::CaseInsensitive) || - path.endsWith(lnk, Qt::CaseInsensitive) || - path.endsWith(ico, Qt::CaseInsensitive); + return path.endsWith(exe, Qt::CaseInsensitive) || + path.endsWith(lnk, Qt::CaseInsensitive) || + path.endsWith(ico, Qt::CaseInsensitive); } void IconFetcher::threadFun() diff --git a/src/iconfetcher.h b/src/iconfetcher.h index 78857d057..3207a57ee 100644 --- a/src/iconfetcher.h +++ b/src/iconfetcher.h @@ -1,9 +1,9 @@ #ifndef MODORGANIZER_ICONFETCHER_INCLUDED #define MODORGANIZER_ICONFETCHER_INCLUDED -#include -#include #include #include +#include +#include class IconFetcher { @@ -45,7 +45,6 @@ class IconFetcher bool m_queueAvailable = false; }; - const int m_iconSize; QFileIconProvider m_provider; std::thread m_thread; @@ -56,7 +55,6 @@ class IconFetcher mutable Cache m_fileCache; mutable Waiter m_waiter; - bool hasOwnIcon(const QString& path) const; template @@ -74,4 +72,4 @@ class IconFetcher QVariant extensionIcon(const QStringView& ext) const; }; -#endif // MODORGANIZER_ICONFETCHER_INCLUDED +#endif // MODORGANIZER_ICONFETCHER_INCLUDED diff --git a/src/installationmanager.cpp b/src/installationmanager.cpp index f1eec4f13..50c726fdb 100644 --- a/src/installationmanager.cpp +++ b/src/installationmanager.cpp @@ -21,33 +21,32 @@ along with Mod Organizer. If not, see . #include "installationmanager.h" -#include "filesystemutilities.h" -#include "report.h" #include "categories.h" -#include "questionboxmemory.h" -#include "settings.h" -#include "queryoverwritedialog.h" -#include "messagedialog.h" -#include "iplugininstallersimple.h" +#include "filesystemutilities.h" #include "iplugininstallercustom.h" +#include "iplugininstallersimple.h" +#include "messagedialog.h" +#include "modinfo.h" #include "nexusinterface.h" +#include "queryoverwritedialog.h" +#include "questionboxmemory.h" +#include "report.h" #include "selectiondialog.h" -#include "modinfo.h" +#include "settings.h" #include #include -#include +#include +#include +#include +#include +#include #include -#include #include -#include +#include #include -#include #include -#include -#include -#include -#include +#include #include #include @@ -58,19 +57,16 @@ along with Mod Organizer. If not, see . #include "archivefiletree.h" - using namespace MOBase; using namespace MOShared; - -InstallationResult::InstallationResult(IPluginInstaller::EInstallResult result) : - m_result(result), m_name(), m_iniTweaks(false), m_backup(false), m_merged(false), m_replaced(false) -{ -} - +InstallationResult::InstallationResult(IPluginInstaller::EInstallResult result) + : m_result(result), m_name(), m_iniTweaks(false), m_backup(false), m_merged(false), + m_replaced(false) +{} template -static T resolveFunction(QLibrary &lib, const char *name) +static T resolveFunction(QLibrary& lib, const char* name) { T temp = reinterpret_cast(lib.resolve(name)); if (temp == nullptr) { @@ -82,10 +78,8 @@ static T resolveFunction(QLibrary &lib, const char *name) return temp; } -InstallationManager::InstallationManager() - : - m_ParentWidget(nullptr), - m_IsRunning(false) { +InstallationManager::InstallationManager() : m_ParentWidget(nullptr), m_IsRunning(false) +{ m_ArchiveHandler = CreateArchive(); if (!m_ArchiveHandler->isValid()) { throw MyException(getErrorString(m_ArchiveHandler->getLastError())); @@ -108,18 +102,16 @@ InstallationManager::InstallationManager() } }); - // Connect the query password slot - This is the only way I found to be able to query user - // from a separate thread. We use a BlockingQueuedConnection so that calling passwordRequested() - // will block until the end of the slot. - connect(this, &InstallationManager::passwordRequested, - this, &InstallationManager::queryPassword, Qt::BlockingQueuedConnection); + // Connect the query password slot - This is the only way I found to be able to query + // user from a separate thread. We use a BlockingQueuedConnection so that calling + // passwordRequested() will block until the end of the slot. + connect(this, &InstallationManager::passwordRequested, this, + &InstallationManager::queryPassword, Qt::BlockingQueuedConnection); } -InstallationManager::~InstallationManager() -{ -} +InstallationManager::~InstallationManager() {} -void InstallationManager::setParentWidget(QWidget *widget) +void InstallationManager::setParentWidget(QWidget* widget) { m_ParentWidget = widget; } @@ -129,12 +121,14 @@ void InstallationManager::setPluginContainer(const PluginContainer* pluginContai m_PluginContainer = pluginContainer; } -void InstallationManager::queryPassword() { +void InstallationManager::queryPassword() +{ m_Password = QInputDialog::getText(m_ParentWidget, tr("Password required"), - tr("Password"), QLineEdit::Password); + tr("Password"), QLineEdit::Password); } -bool InstallationManager::extractFiles(QString extractPath, QString title, bool showFilenames, bool silent) +bool InstallationManager::extractFiles(QString extractPath, QString title, + bool showFilenames, bool silent) { TimeThis tt("InstallationManager::extractFiles"); @@ -150,24 +144,19 @@ bool InstallationManager::extractFiles(QString extractPath, QString title, bool if (silent) { future = QtConcurrent::run([&]() -> bool { - return m_ArchiveHandler->extract( - extractPath.toStdWString(), - nullptr, - nullptr, - errorCallback - ); + return m_ArchiveHandler->extract(extractPath.toStdWString(), nullptr, nullptr, + errorCallback); }); future.waitForFinished(); - } - else { + } else { QProgressDialog* installationProgress = new QProgressDialog(m_ParentWidget); ON_BLOCK_EXIT([=]() { installationProgress->cancel(); installationProgress->hide(); installationProgress->deleteLater(); }); - installationProgress->setWindowFlags( - installationProgress->windowFlags() & (~Qt::WindowContextHelpButtonHint)); + installationProgress->setWindowFlags(installationProgress->windowFlags() & + (~Qt::WindowContextHelpButtonHint)); if (!title.isEmpty()) { installationProgress->setWindowTitle(title); } @@ -176,27 +165,32 @@ bool InstallationManager::extractFiles(QString extractPath, QString title, bool // Turn off auto-reset otherwize the progress dialog is reset before the end. This // is kind of annoying because updateProgress consider percentage of progression - // through the archive (pack), while we are waiting for extracting archive entries, so - // the percentage of in updateProgress is not really related to the percentage of files - // extracted... + // through the archive (pack), while we are waiting for extracting archive entries, + // so the percentage of in updateProgress is not really related to the percentage of + // files extracted... installationProgress->setAutoReset(false); - // Note: Using a loop with a progressUpdate() that only wake-up the loop. The event-loop - // will be used in a loop and not via exec() because connecting to QProgressDialog::setValue - // and using .exec() creates huge recursion that leads to stack-overflow. - // See https://bugreports.qt.io/browse/QTBUG-10561 + // Note: Using a loop with a progressUpdate() that only wake-up the loop. The + // event-loop will be used in a loop and not via exec() because connecting to + // QProgressDialog::setValue and using .exec() creates huge recursion that leads to + // stack-overflow. See https://bugreports.qt.io/browse/QTBUG-10561 QEventLoop loop; - connect(this, &InstallationManager::progressUpdate, &loop, &QEventLoop::wakeUp, Qt::QueuedConnection); + connect(this, &InstallationManager::progressUpdate, &loop, &QEventLoop::wakeUp, + Qt::QueuedConnection); - // Cancelling progress only cancel the extraction, we do not force exiting the event-loop: - connect(installationProgress, &QProgressDialog::canceled, [this]() { m_ArchiveHandler->cancel(); }); + // Cancelling progress only cancel the extraction, we do not force exiting the + // event-loop: + connect(installationProgress, &QProgressDialog::canceled, [this]() { + m_ArchiveHandler->cancel(); + }); std::mutex mutex; int currentProgress = 0; QString currentFileName; // The callbacks: - auto progressCallback = [this, ¤tProgress, &mutex](auto progressType, uint64_t current, uint64_t total) { + auto progressCallback = [this, ¤tProgress, &mutex]( + auto progressType, uint64_t current, uint64_t total) { if (progressType == Archive::ProgressType::EXTRACTION) { { std::scoped_lock guard(mutex); @@ -205,28 +199,25 @@ bool InstallationManager::extractFiles(QString extractPath, QString title, bool emit progressUpdate(); } }; - Archive::FileChangeCallback fileChangeCallback = [this, ¤tFileName, &mutex](auto changeType, std::wstring const& file) { - if (changeType == Archive::FileChangeType::EXTRACTION_START) { - { - std::scoped_lock guard(mutex); - currentFileName = QString::fromStdWString(file); - } - emit progressUpdate(); - } - }; + Archive::FileChangeCallback fileChangeCallback = + [this, ¤tFileName, &mutex](auto changeType, std::wstring const& file) { + if (changeType == Archive::FileChangeType::EXTRACTION_START) { + { + std::scoped_lock guard(mutex); + currentFileName = QString::fromStdWString(file); + } + emit progressUpdate(); + } + }; // unpack only the files we need for the installer QFutureWatcher futureWatcher; - connect(&futureWatcher, &QFutureWatcher::finished, - &loop, &QEventLoop::wakeUp, - Qt::QueuedConnection); + connect(&futureWatcher, &QFutureWatcher::finished, &loop, &QEventLoop::wakeUp, + Qt::QueuedConnection); futureWatcher.setFuture(QtConcurrent::run([&]() -> bool { - return m_ArchiveHandler->extract( - extractPath.toStdWString(), - progressCallback, - showFilenames ? fileChangeCallback : nullptr, - errorCallback - ); + return m_ArchiveHandler->extract(extractPath.toStdWString(), progressCallback, + showFilenames ? fileChangeCallback : nullptr, + errorCallback); })); installationProgress->setModal(true); @@ -253,31 +244,34 @@ bool InstallationManager::extractFiles(QString extractPath, QString title, bool if (m_ArchiveHandler->getLastError() == Archive::Error::ERROR_EXTRACT_CANCELLED) { if (!errorMessage.isEmpty()) { throw MyException(tr("Extraction failed: %1").arg(errorMessage)); - } - else { + } else { return false; } - } - else { - throw MyException(tr("Extraction failed: %1").arg(static_cast(m_ArchiveHandler->getLastError()))); + } else { + throw MyException(tr("Extraction failed: %1") + .arg(static_cast(m_ArchiveHandler->getLastError()))); } } return true; } -QString InstallationManager::extractFile(std::shared_ptr entry, bool silent) +QString InstallationManager::extractFile(std::shared_ptr entry, + bool silent) { - QStringList result = this->extractFiles({ entry }, silent); + QStringList result = this->extractFiles({entry}, silent); return result.isEmpty() ? QString() : result[0]; } -QStringList InstallationManager::extractFiles(std::vector> const& entries, bool silent) +QStringList InstallationManager::extractFiles( + std::vector> const& entries, bool silent) { // Remove the directory since mapToArchive would add them: std::vector> files; std::copy_if(entries.begin(), entries.end(), std::back_inserter(files), - [](auto const& entry) { return entry->isFile(); }); + [](auto const& entry) { + return entry->isFile(); + }); // Update the archive: ArchiveFileTree::mapToArchive(*m_ArchiveHandler, files); @@ -285,7 +279,7 @@ QStringList InstallationManager::extractFiles(std::vectorpath(); result.append(QDir::tempPath().append("/").append(path)); m_TempFilesToDelete.insert(path); @@ -298,11 +292,12 @@ QStringList InstallationManager::extractFiles(std::vector entry) +QString +InstallationManager::createFile(std::shared_ptr entry) { // Use QTemporaryFile to create the temporary file with the given template: - QTemporaryFile tempFile(QDir::cleanPath(QDir::tempPath() + QDir::separator() + "mo2-install")); + QTemporaryFile tempFile( + QDir::cleanPath(QDir::tempPath() + QDir::separator() + "mo2-install")); // Turn-off autoRemove otherwise the file is deleted when destructor is called: tempFile.setAutoRemove(false); @@ -323,10 +318,11 @@ QString InstallationManager::createFile(std::shared_ptr fileTree) +void InstallationManager::cleanCreatedFiles( + std::shared_ptr fileTree) { // We simply have to check if all the entries have fileTree as a parent: - for (auto it = std::begin(m_CreatedFiles); it != std::end(m_CreatedFiles); ) { + for (auto it = std::begin(m_CreatedFiles); it != std::end(m_CreatedFiles);) { // Find the parent - Could this be in FileTreeEntry? bool found = false; @@ -335,38 +331,38 @@ void InstallationManager::cleanCreatedFiles(std::shared_ptrparent(); } } } - // If the parent was not found, we remove the entry, otherwize we move to the next one: + // If the parent was not found, we remove the entry, otherwize we move to the next + // one: if (!found) { it = m_CreatedFiles.erase(it); - } - else { + } else { ++it; } } } - -IPluginInstaller::EInstallResult InstallationManager::installArchive(GuessedValue &modName, const QString &archiveName, int modId) +IPluginInstaller::EInstallResult +InstallationManager::installArchive(GuessedValue& modName, + const QString& archiveName, int modId) { - // in earlier versions the modName was copied here and the copy passed to install. I don't know why I did this and it causes - // a problem if this is called by the bundle installer and the bundled installer adds additional names that then end up being used, - // because the caller will then not have the right name. + // in earlier versions the modName was copied here and the copy passed to install. I + // don't know why I did this and it causes a problem if this is called by the bundle + // installer and the bundled installer adds additional names that then end up being + // used, because the caller will then not have the right name. return install(archiveName, modName, modId).result(); } - -QString InstallationManager::generateBackupName(const QString &directoryName) const +QString InstallationManager::generateBackupName(const QString& directoryName) const { QString backupName = directoryName + "_backup"; if (QDir(backupName).exists()) { - int idx = 2; + int idx = 2; QString temp = backupName + QString::number(idx); while (QDir(temp).exists()) { ++idx; @@ -377,21 +373,21 @@ QString InstallationManager::generateBackupName(const QString &directoryName) co return backupName; } - -InstallationResult InstallationManager::testOverwrite(GuessedValue &modName) +InstallationResult InstallationManager::testOverwrite(GuessedValue& modName) { - QString targetDirectory = QDir::fromNativeSeparators(m_ModsDirectory + "\\" + modName); + QString targetDirectory = + QDir::fromNativeSeparators(m_ModsDirectory + "\\" + modName); // this is only returned on success - InstallationResult result{ IPluginInstaller::RESULT_SUCCESS }; + InstallationResult result{IPluginInstaller::RESULT_SUCCESS}; while (QDir(targetDirectory).exists()) { - Settings &settings(Settings::instance()); + Settings& settings(Settings::instance()); const bool backup = settings.keepBackupOnInstall(); - QueryOverwriteDialog overwriteDialog( - m_ParentWidget, - backup ? QueryOverwriteDialog::BACKUP_YES : QueryOverwriteDialog::BACKUP_NO); + QueryOverwriteDialog overwriteDialog(m_ParentWidget, + backup ? QueryOverwriteDialog::BACKUP_YES + : QueryOverwriteDialog::BACKUP_NO); if (overwriteDialog.exec()) { settings.setKeepBackupOnInstall(overwriteDialog.backup()); @@ -400,22 +396,22 @@ InstallationResult InstallationManager::testOverwrite(GuessedValue &mod QString backupDirectory = generateBackupName(targetDirectory); if (!copyDir(targetDirectory, backupDirectory, false)) { reportError(tr("Failed to create backup")); - return { IPluginInstaller::RESULT_FAILED }; + return {IPluginInstaller::RESULT_FAILED}; } } - result.m_merged = overwriteDialog.action() == QueryOverwriteDialog::ACT_MERGE; + result.m_merged = overwriteDialog.action() == QueryOverwriteDialog::ACT_MERGE; result.m_replaced = overwriteDialog.action() == QueryOverwriteDialog::ACT_REPLACE; - result.m_backup = overwriteDialog.backup(); + result.m_backup = overwriteDialog.backup(); if (overwriteDialog.action() == QueryOverwriteDialog::ACT_RENAME) { - bool ok = false; + bool ok = false; QString name = QInputDialog::getText(m_ParentWidget, tr("Mod Name"), tr("Name"), QLineEdit::Normal, modName, &ok); if (ok && !name.isEmpty()) { modName.update(name, GUESS_USER); if (!ensureValidModName(modName)) { - return { IPluginInstaller::RESULT_FAILED }; + return {IPluginInstaller::RESULT_FAILED}; } targetDirectory = QDir::fromNativeSeparators(m_ModsDirectory) + "/" + modName; } @@ -438,8 +434,8 @@ InstallationResult InstallationManager::testOverwrite(GuessedValue &mod // remove the directory with all content, then recreate it empty shellDelete(QStringList(targetDirectory)); if (!QDir().mkdir(targetDirectory)) { - // windows may keep the directory around for a moment, preventing its re-creation. Not sure - // if this still happens with shellDelete + // windows may keep the directory around for a moment, preventing its + // re-creation. Not sure if this still happens with shellDelete Sleep(100); QDir().mkdir(targetDirectory); } @@ -453,11 +449,12 @@ InstallationResult InstallationManager::testOverwrite(GuessedValue &mod return result; } else if (overwriteDialog.action() == QueryOverwriteDialog::ACT_MERGE) { return result; - } else /* if (overwriteDialog.action() == QueryOverwriteDialog::ACT_NONE) */ { - return { IPluginInstaller::RESULT_CANCELED }; + } else /* if (overwriteDialog.action() == QueryOverwriteDialog::ACT_NONE) */ + { + return {IPluginInstaller::RESULT_CANCELED}; } } else { - return { IPluginInstaller::RESULT_CANCELED }; + return {IPluginInstaller::RESULT_CANCELED}; } } @@ -466,15 +463,16 @@ InstallationResult InstallationManager::testOverwrite(GuessedValue &mod return result; } - -bool InstallationManager::ensureValidModName(GuessedValue &name) const +bool InstallationManager::ensureValidModName(GuessedValue& name) const { while (name->isEmpty()) { bool ok; - name.update(QInputDialog::getText(m_ParentWidget, tr("Invalid name"), - tr("The name you entered is invalid, please enter a different one."), - QLineEdit::Normal, "", &ok), - GUESS_USER); + name.update( + QInputDialog::getText( + m_ParentWidget, tr("Invalid name"), + tr("The name you entered is invalid, please enter a different one."), + QLineEdit::Normal, "", &ok), + GUESS_USER); if (!ok) { return false; } @@ -482,13 +480,15 @@ bool InstallationManager::ensureValidModName(GuessedValue &name) const return true; } -InstallationResult InstallationManager::doInstall( - GuessedValue &modName, QString gameName, int modID, - const QString &version, const QString &newestVersion, - int categoryID, int fileCategoryID, const QString &repository) +InstallationResult InstallationManager::doInstall(GuessedValue& modName, + QString gameName, int modID, + const QString& version, + const QString& newestVersion, + int categoryID, int fileCategoryID, + const QString& repository) { if (!ensureValidModName(modName)) { - return { IPluginInstaller::RESULT_FAILED }; + return {IPluginInstaller::RESULT_FAILED}; } bool merge = false; @@ -500,17 +500,18 @@ InstallationResult InstallationManager::doInstall( result.m_name = modName; - QString targetDirectory = QDir(m_ModsDirectory + "/" + modName).canonicalPath(); + QString targetDirectory = QDir(m_ModsDirectory + "/" + modName).canonicalPath(); QString targetDirectoryNative = QDir::toNativeSeparators(targetDirectory); log::debug("installing to \"{}\"", targetDirectoryNative); if (!extractFiles(targetDirectory, "", true, false)) { - return { IPluginInstaller::RESULT_CANCELED }; + return {IPluginInstaller::RESULT_CANCELED}; } // Copy the created files: for (auto& p : m_CreatedFiles) { - QString destPath = QDir::cleanPath(targetDirectory + QDir::separator() + p.first->path()); + QString destPath = + QDir::cleanPath(targetDirectory + QDir::separator() + p.first->path()); log::debug("Moving {} to {}.", p.second, destPath); // We need to remove the path if it exists: @@ -528,7 +529,8 @@ InstallationResult InstallationManager::doInstall( QSettings settingsFile(targetDirectory + "/meta.ini", QSettings::IniFormat); - // overwrite settings only if they are actually are available or haven't been set before + // overwrite settings only if they are actually are available or haven't been set + // before if ((gameName != "") || !settingsFile.contains("gameName")) { settingsFile.setValue("gameName", gameName); } @@ -537,7 +539,8 @@ InstallationResult InstallationManager::doInstall( } if (!settingsFile.contains("version") || (!version.isEmpty() && - (!merge || (VersionInfo(version) >= VersionInfo(settingsFile.value("version").toString()))))) { + (!merge || (VersionInfo(version) >= + VersionInfo(settingsFile.value("version").toString()))))) { settingsFile.setValue("version", version); } if (!newestVersion.isEmpty() || !settingsFile.contains("newestVersion")) { @@ -552,8 +555,9 @@ InstallationResult InstallationManager::doInstall( settingsFile.setValue("repository", repository); if (!merge) { - // this does not clear the list we have in memory but the mod is going to have to be re-read anyway - // btw.: installedFiles were written with beginWriteArray but we can still clear it with beginGroup. nice + // this does not clear the list we have in memory but the mod is going to have to be + // re-read anyway btw.: installedFiles were written with beginWriteArray but we can + // still clear it with beginGroup. nice settingsFile.beginGroup("installedFiles"); settingsFile.remove(""); settingsFile.endGroup(); @@ -562,7 +566,6 @@ InstallationResult InstallationManager::doInstall( return result; } - bool InstallationManager::wasCancelled() const { return m_ArchiveHandler->getLastError() == Archive::Error::ERROR_EXTRACT_CANCELLED; @@ -573,7 +576,6 @@ bool InstallationManager::isRunning() const return m_IsRunning; } - void InstallationManager::postInstallCleanup() { // Clear the list of created files: @@ -582,21 +584,27 @@ void InstallationManager::postInstallCleanup() // Close the archive: m_ArchiveHandler->close(); - // directories we may want to remove. sorted from longest to shortest to ensure we remove subdirectories first. - auto longestFirst = [](const QString &LHS, const QString &RHS) -> bool { - if (LHS.size() != RHS.size()) return LHS.size() > RHS.size(); - else return LHS < RHS; - }; + // directories we may want to remove. sorted from longest to shortest to ensure we + // remove subdirectories first. + auto longestFirst = [](const QString& LHS, const QString& RHS) -> bool { + if (LHS.size() != RHS.size()) + return LHS.size() > RHS.size(); + else + return LHS < RHS; + }; - std::set> directoriesToRemove(longestFirst); + std::set> + directoriesToRemove(longestFirst); // clean up temp files - // TODO: this doesn't yet remove directories. Also, the files may be left there if this point isn't reached - for (const QString &tempFile : m_TempFilesToDelete) { + // TODO: this doesn't yet remove directories. Also, the files may be left there if + // this point isn't reached + for (const QString& tempFile : m_TempFilesToDelete) { QFileInfo fileInfo(QDir::tempPath() + "/" + tempFile); if (fileInfo.exists()) { if (!fileInfo.isReadable() || !fileInfo.isWritable()) { - QFile::setPermissions(fileInfo.absoluteFilePath(), QFile::ReadOther | QFile::WriteOther); + QFile::setPermissions(fileInfo.absoluteFilePath(), + QFile::ReadOther | QFile::WriteOther); } if (!QFile::remove(fileInfo.absoluteFilePath())) { log::warn("Unable to delete {}", fileInfo.absoluteFilePath()); @@ -607,17 +615,21 @@ void InstallationManager::postInstallCleanup() m_TempFilesToDelete.clear(); - // try to delete each directory we had temporary files in. the call fails for non-empty directories which is ok - for (const QString &dir : directoriesToRemove) { + // try to delete each directory we had temporary files in. the call fails for + // non-empty directories which is ok + for (const QString& dir : directoriesToRemove) { QDir().rmdir(dir); } } -InstallationResult InstallationManager::install( - const QString &fileName, GuessedValue &modName, int modID) +InstallationResult InstallationManager::install(const QString& fileName, + GuessedValue& modName, + int modID) { m_IsRunning = true; - ON_BLOCK_EXIT([this]() { m_IsRunning = false; }); + ON_BLOCK_EXIT([this]() { + m_IsRunning = false; + }); QFileInfo fileInfo(fileName); if (!getSupportedExtensions().contains(fileInfo.suffix(), Qt::CaseInsensitive)) { @@ -630,41 +642,43 @@ InstallationResult InstallationManager::install( modName.update(QFileInfo(fileName).completeBaseName(), GUESS_FALLBACK); // read out meta information from the download if available - QString gameName = ""; - QString version = ""; + QString gameName = ""; + QString version = ""; QString newestVersion = ""; - int categoryID = 0; - int fileCategoryID = 1; - QString repository = "Nexus"; + int categoryID = 0; + int fileCategoryID = 1; + QString repository = "Nexus"; QString metaName = fileName + ".meta"; if (QFile(metaName).exists()) { QSettings metaFile(metaName, QSettings::IniFormat); gameName = metaFile.value("gameName", "").toString(); - modID = metaFile.value("modID", 0).toInt(); + modID = metaFile.value("modID", 0).toInt(); QTextDocument doc; doc.setHtml(metaFile.value("name", "").toString()); modName.update(doc.toPlainText(), GUESS_FALLBACK); modName.update(metaFile.value("modName", "").toString(), GUESS_META); - version = metaFile.value("version", "").toString(); - newestVersion = metaFile.value("newestVersion", "").toString(); + version = metaFile.value("version", "").toString(); + newestVersion = metaFile.value("newestVersion", "").toString(); unsigned int categoryIndex = CategoryFactory::instance()->resolveNexusID( metaFile.value("category", 0).toInt()); - categoryID = CategoryFactory::instance()->getCategoryID(categoryIndex); - repository = metaFile.value("repository", "").toString(); + categoryID = CategoryFactory::instance()->getCategoryID(categoryIndex); + repository = metaFile.value("repository", "").toString(); fileCategoryID = metaFile.value("fileCategory", 1).toInt(); } if (version.isEmpty()) { QDateTime lastMod = fileInfo.lastModified(); - version = "d" + lastMod.toString("yyyy.M.d"); + version = "d" + lastMod.toString("yyyy.M.d"); } - { // guess the mod name and mod if from the file name if there was no meta information + { // guess the mod name and mod if from the file name if there was no meta + // information QString guessedModName; int guessedModID = modID; - NexusInterface::interpretNexusFileName(QFileInfo(fileName).fileName(), guessedModName, guessedModID, false); + NexusInterface::interpretNexusFileName(QFileInfo(fileName).fileName(), + guessedModName, guessedModID, false); if ((modID == 0) && (guessedModID != -1)) { modID = guessedModID; } else if (modID != guessedModID) { @@ -678,52 +692,54 @@ InstallationResult InstallationManager::install( if (fileInfo.dir() == QDir(m_DownloadsDirectory)) { m_CurrentFile = fileInfo.fileName(); } - log::debug("using mod name \"{}\" (id {}) -> {}", QString(modName), modID, m_CurrentFile); + log::debug("using mod name \"{}\" (id {}) -> {}", QString(modName), modID, + m_CurrentFile); - //If there's an archive already open, close it. This happens with the bundle - //installer when it uncompresses a split archive, then finds it has a real archive - //to deal with. + // If there's an archive already open, close it. This happens with the bundle + // installer when it uncompresses a split archive, then finds it has a real archive + // to deal with. m_ArchiveHandler->close(); // open the archive and construct the directory tree the installers work on - bool archiveOpen = m_ArchiveHandler->open( - fileName.toStdWString(), [this]() -> std::wstring { - m_Password = QString(); - - // Note: If we are not in the Qt event thread, we cannot use queryPassword() directly, - // so we emit passwordRequested() that is connected to queryPassword(). The connection is - // made using Qt::BlockingQueuedConnection, so the emit "call" is actually blocking. We - // cannot use emit if we are in the even thread, otherwize we have a deadlock. - if (QThread::currentThread() != QApplication::instance()->thread()) { - emit passwordRequested(); - } - else { - queryPassword(); - } - return m_Password.toStdWString(); - }); + bool archiveOpen = + m_ArchiveHandler->open(fileName.toStdWString(), [this]() -> std::wstring { + m_Password = QString(); + + // Note: If we are not in the Qt event thread, we cannot use queryPassword() + // directly, so we emit passwordRequested() that is connected to + // queryPassword(). The connection is made using Qt::BlockingQueuedConnection, + // so the emit "call" is actually blocking. We cannot use emit if we are in the + // even thread, otherwize we have a deadlock. + if (QThread::currentThread() != QApplication::instance()->thread()) { + emit passwordRequested(); + } else { + queryPassword(); + } + return m_Password.toStdWString(); + }); if (!archiveOpen) { - log::debug("integrated archiver can't open {}: {} ({})", - fileName, - getErrorString(m_ArchiveHandler->getLastError()), - m_ArchiveHandler->getLastError()); + log::debug("integrated archiver can't open {}: {} ({})", fileName, + getErrorString(m_ArchiveHandler->getLastError()), + m_ArchiveHandler->getLastError()); } ON_BLOCK_EXIT(std::bind(&InstallationManager::postInstallCleanup, this)); std::shared_ptr filesTree = - archiveOpen ? ArchiveFileTree::makeTree(*m_ArchiveHandler) : nullptr; + archiveOpen ? ArchiveFileTree::makeTree(*m_ArchiveHandler) : nullptr; auto installers = m_PluginContainer->plugins(); - std::sort(installers.begin(), installers.end(), [] (IPluginInstaller* lhs, IPluginInstaller* rhs) { - return lhs->priority() > rhs->priority(); - }); + std::sort(installers.begin(), installers.end(), + [](IPluginInstaller* lhs, IPluginInstaller* rhs) { + return lhs->priority() > rhs->priority(); + }); InstallationResult installResult(IPluginInstaller::RESULT_NOTATTEMPTED); - for (IPluginInstaller *installer : installers) { - // don't use inactive installers (installer can't be null here but vc static code analysis thinks it could) + for (IPluginInstaller* installer : installers) { + // don't use inactive installers (installer can't be null here but vc static code + // analysis thinks it could) if ((installer == nullptr) || !m_PluginContainer->isEnabled(installer)) { continue; } @@ -738,13 +754,13 @@ InstallationResult InstallationManager::install( } try { - { // simple case - IPluginInstallerSimple *installerSimple - = dynamic_cast(installer); - if ((installerSimple != nullptr) && (filesTree != nullptr) - && (installer->isArchiveSupported(filesTree))) { - installResult.m_result - = installerSimple->install(modName, filesTree, version, modID); + { // simple case + IPluginInstallerSimple* installerSimple = + dynamic_cast(installer); + if ((installerSimple != nullptr) && (filesTree != nullptr) && + (installer->isArchiveSupported(filesTree))) { + installResult.m_result = + installerSimple->install(modName, filesTree, version, modID); if (installResult) { // Downcast to an actual ArchiveFileTree and map to the archive. Test if @@ -752,7 +768,8 @@ InstallationResult InstallationManager::install( // did some bad stuff. ArchiveFileTree* p = dynamic_cast(filesTree.get()); if (p == nullptr) { - throw IncompatibilityException(tr("Invalid file tree returned by plugin.")); + throw IncompatibilityException( + tr("Invalid file tree returned by plugin.")); } // Detach the file tree (this ensure the parent is null and call to path() @@ -766,25 +783,23 @@ InstallationResult InstallationManager::install( // the simple installer only prepares the installation, the rest // works the same for all installers - installResult = doInstall(modName, gameName, modID, version, - newestVersion, categoryID, fileCategoryID, repository); + installResult = doInstall(modName, gameName, modID, version, newestVersion, + categoryID, fileCategoryID, repository); } } } - if (installResult.result() != IPluginInstaller::RESULT_CANCELED) { // custom case - IPluginInstallerCustom *installerCustom - = dynamic_cast(installer); - if ((installerCustom != nullptr) - && (((filesTree != nullptr) - && installer->isArchiveSupported(filesTree)) - || ((filesTree == nullptr) - && installerCustom->isArchiveSupported(fileName)))) { - std::set installerExt - = installerCustom->supportedExtensions(); + if (installResult.result() != IPluginInstaller::RESULT_CANCELED) { // custom case + IPluginInstallerCustom* installerCustom = + dynamic_cast(installer); + if ((installerCustom != nullptr) && + (((filesTree != nullptr) && installer->isArchiveSupported(filesTree)) || + ((filesTree == nullptr) && + installerCustom->isArchiveSupported(fileName)))) { + std::set installerExt = installerCustom->supportedExtensions(); if (installerExt.find(fileInfo.suffix()) != installerExt.end()) { - installResult.m_result - = installerCustom->install(modName, gameName, fileName, version, modID); + installResult.m_result = + installerCustom->install(modName, gameName, fileName, version, modID); unsigned int idx = ModInfo::getIndex(modName); if (idx != UINT_MAX) { ModInfo::Ptr info = ModInfo::getByIndex(idx); @@ -793,40 +808,42 @@ InstallationResult InstallationManager::install( } } } - } catch (const IncompatibilityException &e) { + } catch (const IncompatibilityException& e) { log::error("plugin \"{}\" incompatible: {}", installer->name(), e.what()); } // act upon the installation result. at this point the files have already been // extracted to the correct location switch (installResult.result()) { - case IPluginInstaller::RESULT_FAILED: { - QMessageBox::information(qApp->activeWindow(), tr("Installation failed"), - tr("Something went wrong while installing this mod."), - QMessageBox::Ok); - return installResult; - } break; - case IPluginInstaller::RESULT_SUCCESS: - case IPluginInstaller::RESULT_SUCCESSCANCEL: { - if (filesTree != nullptr) { - auto iniTweakEntry = filesTree->find("INI Tweaks", FileTreeEntry::DIRECTORY); - installResult.m_iniTweaks = iniTweakEntry != nullptr - && !iniTweakEntry->astree()->empty(); - } - installResult.m_result = IPluginInstaller::RESULT_SUCCESS; - return installResult; - } break; - case IPluginInstaller::RESULT_NOTATTEMPTED: - case IPluginInstaller::RESULT_MANUALREQUESTED: { - continue; + case IPluginInstaller::RESULT_FAILED: { + QMessageBox::information(qApp->activeWindow(), tr("Installation failed"), + tr("Something went wrong while installing this mod."), + QMessageBox::Ok); + return installResult; + } break; + case IPluginInstaller::RESULT_SUCCESS: + case IPluginInstaller::RESULT_SUCCESSCANCEL: { + if (filesTree != nullptr) { + auto iniTweakEntry = filesTree->find("INI Tweaks", FileTreeEntry::DIRECTORY); + installResult.m_iniTweaks = + iniTweakEntry != nullptr && !iniTweakEntry->astree()->empty(); } - default: - return installResult; + installResult.m_result = IPluginInstaller::RESULT_SUCCESS; + return installResult; + } break; + case IPluginInstaller::RESULT_NOTATTEMPTED: + case IPluginInstaller::RESULT_MANUALREQUESTED: { + continue; + } + default: + return installResult; } } if (installResult.result() == IPluginInstaller::RESULT_NOTATTEMPTED) { - reportError(tr("None of the available installer plugins were able to handle that archive.\n" - "This is likely due to a corrupted or incompatible download or unrecognized archive format.")); + reportError( + tr("None of the available installer plugins were able to handle that archive.\n" + "This is likely due to a corrupted or incompatible download or unrecognized " + "archive format.")); } return installResult; @@ -835,40 +852,41 @@ InstallationResult InstallationManager::install( QString InstallationManager::getErrorString(Archive::Error errorCode) { switch (errorCode) { - case Archive::Error::ERROR_NONE: { - return tr("no error"); - } break; - case Archive::Error::ERROR_LIBRARY_NOT_FOUND: { - return tr("7z.dll not found"); - } break; - case Archive::Error::ERROR_LIBRARY_INVALID: { - return tr("7z.dll isn't valid"); - } break; - case Archive::Error::ERROR_ARCHIVE_NOT_FOUND: { - return tr("archive not found"); - } break; - case Archive::Error::ERROR_FAILED_TO_OPEN_ARCHIVE: { - return tr("failed to open archive"); - } break; - case Archive::Error::ERROR_INVALID_ARCHIVE_FORMAT: { - return tr("unsupported archive type"); - } break; - case Archive::Error::ERROR_LIBRARY_ERROR: { - return tr("internal library error"); - } break; - case Archive::Error::ERROR_ARCHIVE_INVALID: { - return tr("archive invalid"); - } break; - default: { - // this probably means the archiver.dll is newer than this - return tr("unknown archive error"); - } break; + case Archive::Error::ERROR_NONE: { + return tr("no error"); + } break; + case Archive::Error::ERROR_LIBRARY_NOT_FOUND: { + return tr("7z.dll not found"); + } break; + case Archive::Error::ERROR_LIBRARY_INVALID: { + return tr("7z.dll isn't valid"); + } break; + case Archive::Error::ERROR_ARCHIVE_NOT_FOUND: { + return tr("archive not found"); + } break; + case Archive::Error::ERROR_FAILED_TO_OPEN_ARCHIVE: { + return tr("failed to open archive"); + } break; + case Archive::Error::ERROR_INVALID_ARCHIVE_FORMAT: { + return tr("unsupported archive type"); + } break; + case Archive::Error::ERROR_LIBRARY_ERROR: { + return tr("internal library error"); + } break; + case Archive::Error::ERROR_ARCHIVE_INVALID: { + return tr("archive invalid"); + } break; + default: { + // this probably means the archiver.dll is newer than this + return tr("unknown archive error"); + } break; } } QStringList InstallationManager::getSupportedExtensions() const { - std::set supportedExtensions({ "zip", "rar", "7z", "fomod", "001" }); + std::set supportedExtensions( + {"zip", "rar", "7z", "fomod", "001"}); for (auto* installer : m_PluginContainer->plugins()) { if (m_PluginContainer->isEnabled(installer)) { if (auto* installerCustom = dynamic_cast(installer)) { @@ -880,7 +898,9 @@ QStringList InstallationManager::getSupportedExtensions() const return QStringList(supportedExtensions.begin(), supportedExtensions.end()); } -void InstallationManager::notifyInstallationStart(QString const& archive, bool reinstallation, ModInfo::Ptr currentMod) +void InstallationManager::notifyInstallationStart(QString const& archive, + bool reinstallation, + ModInfo::Ptr currentMod) { auto& installers = m_PluginContainer->plugins(); for (auto* installer : installers) { @@ -890,7 +910,8 @@ void InstallationManager::notifyInstallationStart(QString const& archive, bool r } } -void InstallationManager::notifyInstallationEnd(const InstallationResult& result, ModInfo::Ptr newMod) +void InstallationManager::notifyInstallationEnd(const InstallationResult& result, + ModInfo::Ptr newMod) { auto& installers = m_PluginContainer->plugins(); for (auto* installer : installers) { diff --git a/src/installationmanager.h b/src/installationmanager.h index a5ec11d98..e8e975152 100644 --- a/src/installationmanager.h +++ b/src/installationmanager.h @@ -20,28 +20,28 @@ along with Mod Organizer. If not, see . #ifndef INSTALLATIONMANAGER_H #define INSTALLATIONMANAGER_H +#include #include #include #include -#include #include #define WIN32_LEAN_AND_MEAN +#include #include #include -#include -#include -#include #include +#include +#include #include "modinfo.h" #include "plugincontainer.h" // contains installation result from the manager, internal class // for MO2 that is not forwarded to plugin -class InstallationResult { +class InstallationResult +{ public: - // result status of the installation // auto result() const { return m_result; } @@ -58,14 +58,17 @@ class InstallationResult { // check if the installation was a success // - explicit operator bool() const { return result() == MOBase::IPluginInstaller::EInstallResult::RESULT_SUCCESS; } + explicit operator bool() const + { + return result() == MOBase::IPluginInstaller::EInstallResult::RESULT_SUCCESS; + } private: - friend class InstallationManager; // create a failed result - InstallationResult(MOBase::IPluginInstaller::EInstallResult result = MOBase::IPluginInstaller::EInstallResult::RESULT_FAILED); + InstallationResult(MOBase::IPluginInstaller::EInstallResult result = + MOBase::IPluginInstaller::EInstallResult::RESULT_FAILED); MOBase::IPluginInstaller::EInstallResult m_result; @@ -75,16 +78,13 @@ class InstallationResult { bool m_backup; bool m_merged; bool m_replaced; - }; - class InstallationManager : public QObject, public MOBase::IInstallationManager { Q_OBJECT public: - /** * @brief constructor * @@ -94,23 +94,25 @@ class InstallationManager : public QObject, public MOBase::IInstallationManager virtual ~InstallationManager(); - void setParentWidget(QWidget *widget); + void setParentWidget(QWidget* widget); /** * @brief Notify all installer plugins that an installation is about to start. * * @param archive Path to the archive that is going to be installed. * @param reinstallation True if this is a reinstallation, false otherwise. - * @param currentMod The installed mod corresponding to the archive being installed, or a null - * if there is no such mod. + * @param currentMod The installed mod corresponding to the archive being installed, + * or a null if there is no such mod. */ - void notifyInstallationStart(QString const& archive, bool reinstallation, ModInfo::Ptr currentMod); + void notifyInstallationStart(QString const& archive, bool reinstallation, + ModInfo::Ptr currentMod); /** * @brief notify all installer plugins that an installation has ended. * * @param result The result of the installation process. - * @param currentMod The newly install mod, if result is SUCCESS, a null pointer otherwise. + * @param currentMod The newly install mod, if result is SUCCESS, a null pointer + * otherwise. */ void notifyInstallationEnd(const InstallationResult& result, ModInfo::Ptr newMod); @@ -119,7 +121,10 @@ class InstallationManager : public QObject, public MOBase::IInstallationManager * @param modsDirectory the mod directory * @note this is called a lot, probably redundantly */ - void setModsDirectory(const QString &modsDirectory) { m_ModsDirectory = modsDirectory; } + void setModsDirectory(const QString& modsDirectory) + { + m_ModsDirectory = modsDirectory; + } /** * @@ -130,17 +135,25 @@ class InstallationManager : public QObject, public MOBase::IInstallationManager * @brief update the directory where downloads are stored * @param downloadDirectory the download directory */ - void setDownloadDirectory(const QString &downloadDirectory) { m_DownloadsDirectory = downloadDirectory; } + void setDownloadDirectory(const QString& downloadDirectory) + { + m_DownloadsDirectory = downloadDirectory; + } /** * @brief install a mod from an archive * * @param fileName absolute file name of the archive to install - * @param modName suggested name of the mod. If this is empty (the default), a name will be guessed based on the filename. The user will always have a chance to rename the mod - * @return true if the archive was installed, false if installation failed or was refused - * @exception std::exception an exception may be thrown if the archive can't be opened (maybe the format is invalid or the file is damaged) + * @param modName suggested name of the mod. If this is empty (the default), a name + *will be guessed based on the filename. The user will always have a chance to rename + *the mod + * @return true if the archive was installed, false if installation failed or was + *refused + * @exception std::exception an exception may be thrown if the archive can't be opened + *(maybe the format is invalid or the file is damaged) **/ - InstallationResult install(const QString &fileName, MOBase::GuessedValue &modName, int modID = 0); + InstallationResult install(const QString& fileName, + MOBase::GuessedValue& modName, int modID = 0); /** * @return true if the installation was canceled @@ -157,7 +170,8 @@ class InstallationManager : public QObject, public MOBase::IInstallationManager * * @param errorCode an error code as returned by the archiving function * @return the error string - * @todo This function doesn't belong here, it is only public because the SelfUpdater class also uses "Archive" to get to the package.txt file + * @todo This function doesn't belong here, it is only public because the SelfUpdater + *class also uses "Archive" to get to the package.txt file **/ static QString getErrorString(Archive::Error errorCode); @@ -167,7 +181,8 @@ class InstallationManager : public QObject, public MOBase::IInstallationManager QStringList getSupportedExtensions() const override; /** - * @brief Extract the specified file from the currently opened archive to a temporary location. + * @brief Extract the specified file from the currently opened archive to a temporary + * location. * * This method cannot be used to extract directory. * @@ -181,10 +196,12 @@ class InstallationManager : public QObject, public MOBase::IInstallationManager * @note The temporary file is automatically cleaned up after the installation. * @note This call can be very slow if the archive is large and "solid". */ - QString extractFile(std::shared_ptr entry, bool silent = false) override; + QString extractFile(std::shared_ptr entry, + bool silent = false) override; /** - * @brief Extract the specified files from the currently opened archive to a temporary location. + * @brief Extract the specified files from the currently opened archive to a temporary + * location. * * This method cannot be used to extract directory. * @@ -198,20 +215,24 @@ class InstallationManager : public QObject, public MOBase::IInstallationManager * @note The temporary file is automatically cleaned up after the installation. * @note This call can be very slow if the archive is large and "solid". * - * The flatten argument is not present here while it is present in the deprecated QStringList - * version for multiple reasons: 1) it was never used, 2) it is kind of fishy because there - * is no way to know if a file is going to be overriden, 3) it is quite easy to flatten a - * IFileTree and thus to given a list of entries flattened (this was not possible with the - * QStringList version since these were based on the name of the file inside the archive). + * The flatten argument is not present here while it is present in the deprecated + * QStringList version for multiple reasons: 1) it was never used, 2) it is kind of + * fishy because there is no way to know if a file is going to be overriden, 3) it is + * quite easy to flatten a IFileTree and thus to given a list of entries flattened + * (this was not possible with the QStringList version since these were based on the + * name of the file inside the archive). */ - QStringList extractFiles(std::vector> const& entries, bool silent = false) override; + QStringList + extractFiles(std::vector> const& entries, + bool silent = false) override; /** * @brief Create a new file on the disk corresponding to the given entry. * - * This method can be used by installer that needs to create files that are not in the original - * archive. At the end of the installation, if there are entries in the final tree that were used - * to create files, the corresponding files will be moved to the mod folder. + * This method can be used by installer that needs to create files that are not in the + * original archive. At the end of the installation, if there are entries in the final + * tree that were used to create files, the corresponding files will be moved to the + * mod folder. * * @param entry The entry for which a temporary file should be created. * @@ -228,7 +249,9 @@ class InstallationManager : public QObject, public MOBase::IInstallationManager * * @return the installation result. */ - MOBase::IPluginInstaller::EInstallResult installArchive(MOBase::GuessedValue &modName, const QString &archiveName, int modId = 0) override; + MOBase::IPluginInstaller::EInstallResult + installArchive(MOBase::GuessedValue& modName, const QString& archiveName, + int modId = 0) override; /** * @param modName current possible names for the mod @@ -237,17 +260,16 @@ class InstallationManager : public QObject, public MOBase::IInstallationManager */ InstallationResult testOverwrite(MOBase::GuessedValue& modName); - QString generateBackupName(const QString &directoryName) const; + QString generateBackupName(const QString& directoryName) const; private: - // actually perform the installation (write files to the disk, etc.), returns the // installation result // - InstallationResult doInstall( - MOBase::GuessedValue &modName, QString gameName, - int modID, const QString &version, const QString &newestVersion, - int categoryID, int fileCategoryID, const QString &repository); + InstallationResult doInstall(MOBase::GuessedValue& modName, QString gameName, + int modID, const QString& version, + const QString& newestVersion, int categoryID, + int fileCategoryID, const QString& repository); /** * @brief Clean the list of created files by removing all entries that are not @@ -257,7 +279,7 @@ class InstallationManager : public QObject, public MOBase::IInstallationManager */ void cleanCreatedFiles(std::shared_ptr fileTree); - bool ensureValidModName(MOBase::GuessedValue &name) const; + bool ensureValidModName(MOBase::GuessedValue& name) const; void postInstallCleanup(); @@ -281,17 +303,17 @@ private slots: void progressUpdate(); /** - * @brief An existing mod has been replaced with a newly installed one. - */ + * @brief An existing mod has been replaced with a newly installed one. + */ void modReplaced(const QString fileName); private: - - struct CaseInsensitive { - bool operator() (const QString &LHS, const QString &RHS) const - { - return QString::compare(LHS, RHS, Qt::CaseInsensitive) < 0; - } + struct CaseInsensitive + { + bool operator()(const QString& LHS, const QString& RHS) const + { + return QString::compare(LHS, RHS, Qt::CaseInsensitive) < 0; + } }; /** @@ -305,16 +327,16 @@ private slots: * @return true if the extraction was successful, false if the extraciton was * cancelled. If an error occured, an exception is thrown. */ - bool extractFiles(QString extractPath, QString title, bool showFilenames, bool silent); + bool extractFiles(QString extractPath, QString title, bool showFilenames, + bool silent); private: - // The plugin container, mostly to check if installer are enabled or not. - const PluginContainer *m_PluginContainer; + const PluginContainer* m_PluginContainer; bool m_IsRunning; - QWidget *m_ParentWidget; + QWidget* m_ParentWidget; QString m_ModsDirectory; QString m_DownloadsDirectory; @@ -330,5 +352,4 @@ private slots: std::set m_TempFilesToDelete; }; - -#endif // INSTALLATIONMANAGER_H +#endif // INSTALLATIONMANAGER_H diff --git a/src/instancemanager.cpp b/src/instancemanager.cpp index 3a5aee4c4..df4b0078d 100644 --- a/src/instancemanager.cpp +++ b/src/instancemanager.cpp @@ -17,37 +17,35 @@ You should have received a copy of the GNU General Public License along with Mod Organizer. If not, see . */ - #include "instancemanager.h" -#include "selectiondialog.h" -#include "settings.h" -#include "plugincontainer.h" -#include "nexusinterface.h" #include "createinstancedialog.h" -#include "instancemanagerdialog.h" #include "createinstancedialogpages.h" +#include "filesystemutilities.h" +#include "instancemanagerdialog.h" +#include "nexusinterface.h" +#include "plugincontainer.h" +#include "selectiondialog.h" +#include "settings.h" #include "shared/appconfig.h" #include "shared/util.h" -#include #include -#include #include -#include "filesystemutilities.h" +#include +#include #include #include -#include #include #include +#include #include using namespace MOBase; -Instance::Instance(QString dir, bool portable, QString profileName) : - m_dir(std::move(dir)), m_portable(portable), m_plugin(nullptr), - m_profile(std::move(profileName)) -{ -} +Instance::Instance(QString dir, bool portable, QString profileName) + : m_dir(std::move(dir)), m_portable(portable), m_plugin(nullptr), + m_profile(std::move(profileName)) +{} QString Instance::displayName() const { @@ -101,8 +99,7 @@ bool Instance::isActive() const { auto& m = InstanceManager::singleton(); - if (auto i=m.currentInstance()) - { + if (auto i = m.currentInstance()) { if (m_portable) { return i->isPortable(); } else { @@ -124,17 +121,17 @@ bool Instance::readFromIni() // game name and directory are from ini unless overridden by setGame() if (m_gameName.isEmpty()) { - if (auto v=s.game().name()) + if (auto v = s.game().name()) m_gameName = *v; } if (m_gameDir.isEmpty()) { - if (auto v=s.game().directory()) + if (auto v = s.game().directory()) m_gameDir = *v; } if (m_gameVariant.isEmpty()) { - if (auto v=s.game().edition()) { + if (auto v = s.game().edition()) { m_gameVariant = *v; } } @@ -203,7 +200,7 @@ void Instance::updateIni() void Instance::setGame(const QString& name, const QString& dir) { m_gameName = name; - m_gameDir = dir; + m_gameDir = dir; } void Instance::setVariant(const QString& name) @@ -213,8 +210,7 @@ void Instance::setVariant(const QString& name) Instance::SetupResults Instance::getGamePlugin(PluginContainer& plugins) { - if (!m_gameName.isEmpty() && !m_gameDir.isEmpty()) - { + if (!m_gameName.isEmpty() && !m_gameDir.isEmpty()) { // normal case: both the name and dir are in the ini // find the plugin by name @@ -224,9 +220,8 @@ Instance::SetupResults Instance::getGamePlugin(PluginContainer& plugins) if (!game->looksValid(m_gameDir)) { // the directory from the ini is not valid anymore - log::warn( - "game plugin {} says dir {} from ini {} is not valid", - game->gameName(), m_gameDir, iniPath()); + log::warn("game plugin {} says dir {} from ini {} is not valid", + game->gameName(), m_gameDir, iniPath()); // note that some plugins return true for isInstalled() if a path // is found in the registry, but without actually checking if it's @@ -235,14 +230,14 @@ Instance::SetupResults Instance::getGamePlugin(PluginContainer& plugins) if (game->isInstalled() && game->looksValid(game->gameDirectory())) { // bad game directory but the plugin reports there's a valid one // somewhere; take it instead - log::warn( - "game plugin {} found a game at {}, taking it", - game->gameName(), game->gameDirectory().absolutePath()); + log::warn("game plugin {} found a game at {}, taking it", game->gameName(), + game->gameDirectory().absolutePath()); m_gameDir = game->gameDirectory().absolutePath(); } else { // game seems to be gone completely - log::warn("game plugin {} found no game installation at all", game->gameName()); + log::warn("game plugin {} found no game installation at all", + game->gameName()); return SetupResults::GameGone; } } @@ -254,22 +249,19 @@ Instance::SetupResults Instance::getGamePlugin(PluginContainer& plugins) log::warn("game plugin {} not found", m_gameName); return SetupResults::PluginGone; - } - else if (m_gameName.isEmpty() && !m_gameDir.isEmpty()) - { + } else if (m_gameName.isEmpty() && !m_gameDir.isEmpty()) { // the name is missing, but there's a directory; find a plugin that can // handle it - log::warn( - "game name is missing from ini {} but dir {} is available", - iniPath(), m_gameDir); + log::warn("game name is missing from ini {} but dir {} is available", iniPath(), + m_gameDir); for (IPluginGame* game : plugins.plugins()) { if (game->looksValid(m_gameDir)) { // take it log::warn("found plugin {} that can use dir {}", game->gameName(), m_gameDir); - m_plugin = game; + m_plugin = game; m_gameName = game->gameName(); return SetupResults::Okay; @@ -278,35 +270,30 @@ Instance::SetupResults Instance::getGamePlugin(PluginContainer& plugins) log::error("no plugins can use dir {}", m_gameDir); return SetupResults::GameGone; - } - else if (!m_gameName.isEmpty() && m_gameDir.isEmpty()) - { + } else if (!m_gameName.isEmpty() && m_gameDir.isEmpty()) { // dir is missing, find a plugin with the correct name and use the install // dir it detected - log::warn( - "game dir is missing from ini {} but name {} is available", - iniPath(), m_gameName); + log::warn("game dir is missing from ini {} but name {} is available", iniPath(), + m_gameName); for (IPluginGame* game : plugins.plugins()) { if (m_gameName.compare(game->gameName(), Qt::CaseInsensitive) == 0) { // plugin found, use its detected installation dir if (game->isInstalled()) { - log::warn( - "found plugin {} that matches name in ini {}, using auto detected " - "game dir {}", - game->gameName(), iniPath(), game->gameDirectory().absolutePath()); + log::warn("found plugin {} that matches name in ini {}, using auto detected " + "game dir {}", + game->gameName(), iniPath(), game->gameDirectory().absolutePath()); - m_plugin = game; + m_plugin = game; m_gameDir = game->gameDirectory().absolutePath(); return SetupResults::Okay; } else { - log::warn( - "found plugin {} that matches name in ini {}, but no game install " - "detected by plugin", - game->gameName(), iniPath()); + log::warn("found plugin {} that matches name in ini {}, but no game install " + "detected by plugin", + game->gameName(), iniPath()); return SetupResults::GameGone; } @@ -316,9 +303,7 @@ Instance::SetupResults Instance::getGamePlugin(PluginContainer& plugins) // plugin seems to be gone log::error("no plugin matches name {}", m_gameName); return SetupResults::PluginGone; - } - else - { + } else { // can't do anything with these two missing log::error("both game name and dir are missing from ini {}", iniPath()); return SetupResults::IniMissingGame; @@ -332,7 +317,7 @@ void Instance::getProfile(const Settings& s) return; } - if (auto name=s.game().selectedProfileName()) { + if (auto name = s.game().selectedProfileName()) { // use last profile m_profile = *name; return; @@ -341,9 +326,7 @@ void Instance::getProfile(const Settings& s) // profile missing from ini, use the default m_profile = QString::fromStdWString(AppConfig::defaultProfileName()); - log::warn( - "no profile found in ini {}, using default '{}'", - iniPath(), m_profile); + log::warn("no profile found in ini {}, using default '{}'", iniPath(), m_profile); } // returns a list of files and folders that must be deleted when deleting @@ -365,7 +348,6 @@ std::vector Instance::objectsForDeletion() const return QDir::toNativeSeparators(s); }; - // lowercase, native separators and ending slash auto canonicalDir = [](QString s) { s = s.toLower(); @@ -382,8 +364,6 @@ std::vector Instance::objectsForDeletion() const return QDir::toNativeSeparators(s.toLower()); }; - - // whether the given directory is contained in the root auto dirInRoot = [&](const QString& root, const QString& dir) { return canonicalDir(dir).startsWith(canonicalDir(root)); @@ -394,7 +374,6 @@ std::vector Instance::objectsForDeletion() const return canonicalFile(file).startsWith(canonicalDir(root)); }; - Settings settings(iniPath()); if (settings.iniStatus() != QSettings::NoError) { @@ -402,12 +381,9 @@ std::vector Instance::objectsForDeletion() const return {}; } - - - const QString loc = directory(); + const QString loc = directory(); const QString base = settings.paths().base(); - // directories that might contain the individual files and directories // set in the path settings std::vector roots; @@ -428,25 +404,23 @@ std::vector Instance::objectsForDeletion() const } } - // all the directories that are part of an instance; none of them are // mandatory for deletion const std::vector dirs = { - settings.paths().downloads(), - settings.paths().mods(), - settings.paths().cache(), - settings.paths().profiles(), - settings.paths().overwrite(), - QDir(m_dir).filePath(QString::fromStdWString(AppConfig::dumpsDir())), - QDir(m_dir).filePath(QString::fromStdWString(AppConfig::logPath())), + settings.paths().downloads(), + settings.paths().mods(), + settings.paths().cache(), + settings.paths().profiles(), + settings.paths().overwrite(), + QDir(m_dir).filePath(QString::fromStdWString(AppConfig::dumpsDir())), + QDir(m_dir).filePath(QString::fromStdWString(AppConfig::logPath())), }; // all the files that are part of an instance const std::vector files = { - {iniPath(), true}, // the ini file must be deleted + {iniPath(), true}, // the ini file must be deleted }; - // this will contain the root directories, plus all the individual // directories that are not inside these roots std::vector cleanDirs; @@ -471,14 +445,10 @@ std::vector Instance::objectsForDeletion() const } // prepending the roots - for (auto itor=roots.rbegin(); itor!=roots.rend(); ++itor) { - cleanDirs.insert( - cleanDirs.begin(), - {prettyDir(itor->path), itor->mandatoryDelete}); + for (auto itor = roots.rbegin(); itor != roots.rend(); ++itor) { + cleanDirs.insert(cleanDirs.begin(), {prettyDir(itor->path), itor->mandatoryDelete}); } - - // this will contain the individual files that are not inside the roots; // not that this only contains the INI file for now, so most of this is // useless @@ -503,7 +473,6 @@ std::vector Instance::objectsForDeletion() const } } - // contains all the directories and files to be deleted std::vector all; all.insert(all.end(), cleanDirs.begin(), cleanDirs.end()); @@ -515,14 +484,12 @@ std::vector Instance::objectsForDeletion() const return all; } - - InstanceManager::InstanceManager() { GlobalSettings::updateRegistryKey(); } -InstanceManager &InstanceManager::singleton() +InstanceManager& InstanceManager::singleton() { static InstanceManager s_Instance; return s_Instance; @@ -541,17 +508,15 @@ void InstanceManager::overrideProfile(const QString& profileName) void InstanceManager::clearOverrides() { m_overrideInstanceName = {}; - m_overrideProfileName = {}; + m_overrideProfileName = {}; } std::unique_ptr InstanceManager::currentInstance() const { - const QString profile = m_overrideProfileName ? - *m_overrideProfileName : ""; - - const QString name = m_overrideInstanceName ? - *m_overrideInstanceName : GlobalSettings::currentInstance(); + const QString profile = m_overrideProfileName ? *m_overrideProfileName : ""; + const QString name = m_overrideInstanceName ? *m_overrideInstanceName + : GlobalSettings::currentInstance(); if (!allowedToChangeInstance()) { // force portable instance @@ -577,7 +542,7 @@ void InstanceManager::clearCurrentInstance() m_overrideInstanceName = {}; } -void InstanceManager::setCurrentInstance(const QString &name) +void InstanceManager::setCurrentInstance(const QString& name) { GlobalSettings::setCurrentInstance(name); } @@ -590,18 +555,17 @@ QString InstanceManager::instancePath(const QString& instanceName) const QString InstanceManager::globalInstancesRootPath() const { return QDir::fromNativeSeparators( - QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation)); + QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation)); } QString InstanceManager::iniPath(const QString& instanceDir) const { - return QDir(instanceDir).filePath( - QString::fromStdWString(AppConfig::iniFileName())); + return QDir(instanceDir).filePath(QString::fromStdWString(AppConfig::iniFileName())); } std::vector InstanceManager::globalInstancePaths() const { - const std::set ignore = { "qtwebengine" }; + const std::set ignore = {"qtwebengine"}; const QDir root(globalInstancesRootPath()); const auto dirs = root.entryList(QDir::Dirs | QDir::NoDotAndDotDot); @@ -635,30 +599,30 @@ bool InstanceManager::portableInstanceExists() const bool InstanceManager::allowedToChangeInstance() const { - const auto lockFile = - qApp->applicationDirPath() + "/" + - QString::fromStdWString(AppConfig::portableLockFileName()); + const auto lockFile = qApp->applicationDirPath() + "/" + + QString::fromStdWString(AppConfig::portableLockFileName()); return !QFile::exists(lockFile); } -MOBase::IPluginGame* InstanceManager::gamePluginForDirectory( - const QString& instanceDir, PluginContainer& plugins) const +MOBase::IPluginGame* +InstanceManager::gamePluginForDirectory(const QString& instanceDir, + PluginContainer& plugins) const { - return const_cast(gamePluginForDirectory( - instanceDir, const_cast(plugins))); + return const_cast( + gamePluginForDirectory(instanceDir, const_cast(plugins))); } -const MOBase::IPluginGame* InstanceManager::gamePluginForDirectory( - const QString& instanceDir, const PluginContainer& plugins) const +const MOBase::IPluginGame* +InstanceManager::gamePluginForDirectory(const QString& instanceDir, + const PluginContainer& plugins) const { const QString ini = iniPath(instanceDir); // reading ini Settings s(ini); - if (s.iniStatus() != QSettings::NoError) - { + if (s.iniStatus() != QSettings::NoError) { log::error("failed to load settings from {}", ini); return nullptr; } @@ -666,45 +630,35 @@ const MOBase::IPluginGame* InstanceManager::gamePluginForDirectory( // using game name from ini, if available const auto instanceGameName = s.game().name(); - if (instanceGameName && !instanceGameName->isEmpty()) - { + if (instanceGameName && !instanceGameName->isEmpty()) { for (const IPluginGame* game : plugins.plugins()) { if (instanceGameName->compare(game->gameName(), Qt::CaseInsensitive) == 0) { return game; } } - log::error( - "no plugin has game name '{}' that was found in ini {}", - *instanceGameName, ini); - } - else - { + log::error("no plugin has game name '{}' that was found in ini {}", + *instanceGameName, ini); + } else { log::error("no game name found in ini {}", ini); } - // using game directory from ini, if available const auto gameDir = s.game().directory(); - if (gameDir && !gameDir->isEmpty()) - { + if (gameDir && !gameDir->isEmpty()) { for (const IPluginGame* game : plugins.plugins()) { if (game->looksValid(*gameDir)) { return game; } } - log::error( - "no plugin appears to support game directory '{}' from ini {}", - *gameDir, ini); - } - else - { + log::error("no plugin appears to support game directory '{}' from ini {}", *gameDir, + ini); + } else { log::error("no game directory found in ini {}", ini); } - // looking for a plugin that can handle the directory log::debug("falling back on looksValid check"); @@ -714,7 +668,6 @@ const MOBase::IPluginGame* InstanceManager::gamePluginForDirectory( } } - return nullptr; } @@ -724,7 +677,7 @@ QString InstanceManager::makeUniqueName(const QString& instanceName) const // trying "name (N)" QString name = sanitized; - for (int i=2; i<100; ++i) { + for (int i = 2; i < 100; ++i) { if (!instanceExists(name)) { return name; } @@ -805,9 +758,8 @@ SetupInstanceResults selectGame(Instance& instance, PluginContainer& pc) // this info will be used instead of the ini, which should fix this // particular problem - instance.setGame( - dlg.creationInfo().game->gameName(), - dlg.creationInfo().gameLocation); + instance.setGame(dlg.creationInfo().game->gameName(), + dlg.creationInfo().gameLocation); return SetupInstanceResults::TryAgain; } @@ -826,8 +778,7 @@ SetupInstanceResults selectVariant(Instance& instance, PluginContainer& pc) // the variant page uses the game page to know which game was selected, so // set it manually - dlg.getPage()->select( - instance.gamePlugin(), instance.gameDirectory()); + dlg.getPage()->select(instance.gamePlugin(), instance.gameDirectory()); // only show the variant page dlg.setSinglePage(instance.displayName()); @@ -852,84 +803,77 @@ SetupInstanceResults setupInstance(Instance& instance, PluginContainer& pc) // set up the instance const auto setupResult = instance.setup(pc); - switch (setupResult) - { - case Instance::SetupResults::Okay: - { - // all good - return SetupInstanceResults::Okay; - } + switch (setupResult) { + case Instance::SetupResults::Okay: { + // all good + return SetupInstanceResults::Okay; + } - case Instance::SetupResults::BadIni: - { - // unreadable ini, there's not much that can be done, select another - // instance + case Instance::SetupResults::BadIni: { + // unreadable ini, there's not much that can be done, select another + // instance - reportError( - QObject::tr("Cannot open instance '%1', failed to read INI file %2.") - .arg(instance.displayName()).arg(instance.iniPath())); + reportError(QObject::tr("Cannot open instance '%1', failed to read INI file %2.") + .arg(instance.displayName()) + .arg(instance.iniPath())); - return SetupInstanceResults::SelectAnother; - } + return SetupInstanceResults::SelectAnother; + } - case Instance::SetupResults::IniMissingGame: - { - // both the game name and directory are missing from the ini; although - // this shouldn't happen, setup() is able to handle when either is - // missing, but not both - // - // ask the user for the game managed by this instance + case Instance::SetupResults::IniMissingGame: { + // both the game name and directory are missing from the ini; although + // this shouldn't happen, setup() is able to handle when either is + // missing, but not both + // + // ask the user for the game managed by this instance - reportError( + reportError( QObject::tr( - "Cannot open instance '%1', the managed game was not found in the INI " - "file %2. Select the game managed by this instance.") - .arg(instance.displayName()).arg(instance.iniPath())); + "Cannot open instance '%1', the managed game was not found in the INI " + "file %2. Select the game managed by this instance.") + .arg(instance.displayName()) + .arg(instance.iniPath())); - return selectGame(instance, pc); - } + return selectGame(instance, pc); + } - case Instance::SetupResults::PluginGone: - { - // there is no plugin that can handle the game name/directory from the - // ini, so this instance is unusable + case Instance::SetupResults::PluginGone: { + // there is no plugin that can handle the game name/directory from the + // ini, so this instance is unusable - reportError( - QObject::tr( - "Cannot open instance '%1', the game plugin '%2' doesn't exist. It " - "may have been deleted by an antivirus. Select another instance.") - .arg(instance.displayName()).arg(instance.gameName())); + reportError( + QObject::tr("Cannot open instance '%1', the game plugin '%2' doesn't exist. It " + "may have been deleted by an antivirus. Select another instance.") + .arg(instance.displayName()) + .arg(instance.gameName())); - return SetupInstanceResults::SelectAnother; - } + return SetupInstanceResults::SelectAnother; + } - case Instance::SetupResults::GameGone: - { - // the game directory doesn't exist or the plugin doesn't recognize it; - // ask the user for the game managed by this instance + case Instance::SetupResults::GameGone: { + // the game directory doesn't exist or the plugin doesn't recognize it; + // ask the user for the game managed by this instance - reportError( + reportError( QObject::tr( - "Cannot open instance '%1', the game directory '%2' doesn't exist or " - "the game plugin '%3' doesn't recognize it. Select the game managed " - "by this instance.") - .arg(instance.displayName()) - .arg(instance.gameDirectory()) - .arg(instance.gameName())); - - return selectGame(instance, pc); - } + "Cannot open instance '%1', the game directory '%2' doesn't exist or " + "the game plugin '%3' doesn't recognize it. Select the game managed " + "by this instance.") + .arg(instance.displayName()) + .arg(instance.gameDirectory()) + .arg(instance.gameName())); - case Instance::SetupResults::MissingVariant: - { - // the game variant is missing from the ini, ask the user for it - return selectVariant(instance, pc); - } + return selectGame(instance, pc); + } - default: - { - // shouldn't happen - return SetupInstanceResults::Exit; - } + case Instance::SetupResults::MissingVariant: { + // the game variant is missing from the ini, ask the user for it + return selectVariant(instance, pc); + } + + default: { + // shouldn't happen + return SetupInstanceResults::Exit; + } } } diff --git a/src/instancemanager.h b/src/instancemanager.h index 7174ec3a4..176033e81 100644 --- a/src/instancemanager.h +++ b/src/instancemanager.h @@ -1,15 +1,17 @@ #ifndef MODORGANIZER_INSTANCEMANAGER_INCLUDED #define MODORGANIZER_INSTANCEMANAGER_INCLUDED -#include #include +#include -namespace MOBase { class IPluginGame; } +namespace MOBase +{ +class IPluginGame; +} class Settings; class PluginContainer; - // represents an instance, either global or portable // // if setup() is not called, the game plugin is not available and the INI is @@ -54,7 +56,6 @@ class Instance MissingVariant }; - // a file or directory owned by this instance, used by objectsForDeletion() // struct Object @@ -67,11 +68,7 @@ class Instance // base directory, etc. bool mandatoryDelete; - - Object(QString p, bool d=false) - : path(std::move(p)), mandatoryDelete(d) - { - } + Object(QString p, bool d = false) : path(std::move(p)), mandatoryDelete(d) {} // puts mandatory delete on top // @@ -87,16 +84,13 @@ class Instance } }; - - // an instance that lives in the given directory; `portable` must be `true` // if this is a portable instance // // `profileName` can be given to override what's in the INI; this typically // happens when the profile is overriden on the command line // - Instance(QString dir, bool portable, QString profileName={}); - + Instance(QString dir, bool portable, QString profileName = {}); // reads in values from the INI if they were not given yet: // - game name @@ -110,7 +104,6 @@ class Instance // bool readFromIni(); - // finds the appropriate game plugin and sets it up so MO can use it; this // calls readFromIni() first // @@ -119,7 +112,6 @@ class Instance // SetupResults setup(PluginContainer& plugins); - // overrides the game name and directory // void setGame(const QString& name, const QString& dir); @@ -128,7 +120,6 @@ class Instance // void setVariant(const QString& name); - // returns the instance name; this is the directory name or "Portable" for // portable instances // @@ -218,7 +209,6 @@ class Instance void updateIni(); }; - // manages global and portable instances // class InstanceManager @@ -253,11 +243,11 @@ class InstanceManager // // returns null if all of this fails // - const MOBase::IPluginGame* gamePluginForDirectory( - const QString& dir, const PluginContainer& plugins) const; + const MOBase::IPluginGame* + gamePluginForDirectory(const QString& dir, const PluginContainer& plugins) const; - MOBase::IPluginGame* gamePluginForDirectory( - const QString& dir, PluginContainer& plugins) const; + MOBase::IPluginGame* gamePluginForDirectory(const QString& dir, + PluginContainer& plugins) const; // clears the instance name from the registry; on restart, this will make MO // either select the portable instance if it exists, or display the instance @@ -274,7 +264,7 @@ class InstanceManager // sets the instance name in the registry so the same instance is opened next // time MO runs // - void setCurrentInstance(const QString &name); + void setCurrentInstance(const QString& name); // whether MO should allow the user to change the current instance from the // user interface @@ -335,7 +325,6 @@ class InstanceManager std::optional m_overrideProfileName; }; - // see setupInstance() // enum class SetupInstanceResults diff --git a/src/instancemanagerdialog.cpp b/src/instancemanagerdialog.cpp index 776b2496f..b5139e908 100644 --- a/src/instancemanagerdialog.cpp +++ b/src/instancemanagerdialog.cpp @@ -1,16 +1,16 @@ #include "instancemanagerdialog.h" -#include "ui_instancemanagerdialog.h" -#include "instancemanager.h" #include "createinstancedialog.h" -#include "settings.h" -#include "selectiondialog.h" +#include "filesystemutilities.h" +#include "instancemanager.h" #include "plugincontainer.h" +#include "selectiondialog.h" +#include "settings.h" #include "shared/appconfig.h" #include "shared/util.h" -#include -#include +#include "ui_instancemanagerdialog.h" #include -#include "filesystemutilities.h" +#include +#include using namespace MOBase; @@ -19,8 +19,7 @@ using namespace MOBase; // QIcon instanceIcon(PluginContainer& pc, const Instance& i) { - auto* game = InstanceManager::singleton() - .gamePluginForDirectory(i.directory(), pc); + auto* game = InstanceManager::singleton().gamePluginForDirectory(i.directory(), pc); if (!game) { QPixmap empty(32, 32); @@ -49,7 +48,6 @@ QIcon instanceIcon(PluginContainer& pc, const Instance& i) // current instance, which should _definitely_ keep pointing to the same // directory as before - // remember old game directory // // note that gameDirectory() returns a QDir, which doesn't support empty @@ -60,7 +58,9 @@ QIcon instanceIcon(PluginContainer& pc, const Instance& i) const QString old = game->isInstalled() ? game->gameDirectory().path() : ""; // revert - Guard g([&]{ game->setGamePath(old); }); + Guard g([&] { + game->setGamePath(old); + }); // set directory for this instance game->setGamePath(i.gameDirectory()); @@ -70,9 +70,8 @@ QIcon instanceIcon(PluginContainer& pc, const Instance& i) // pops up a dialog to ask for an instance name when renaming // -QString getInstanceName( - QWidget* parent, const QString& title, const QString& moreText, - const QString& label, const QString& oldName={}) +QString getInstanceName(QWidget* parent, const QString& title, const QString& moreText, + const QString& label, const QString& oldName = {}) { auto& m = InstanceManager::singleton(); @@ -81,8 +80,7 @@ QString getInstanceName( auto* ly = new QVBoxLayout(&dlg); - auto* bb = new QDialogButtonBox( - QDialogButtonBox::Cancel | QDialogButtonBox::Ok); + auto* bb = new QDialogButtonBox(QDialogButtonBox::Cancel | QDialogButtonBox::Ok); auto* text = new QLineEdit(oldName); text->selectAll(); @@ -126,9 +124,15 @@ QString getInstanceName( bb->button(QDialogButtonBox::Ok)->setEnabled(okay); }; - QObject::connect(text, &QLineEdit::textChanged, [&] { check(); }); - QObject::connect(bb, &QDialogButtonBox::accepted, [&]{ dlg.accept(); }); - QObject::connect(bb, &QDialogButtonBox::rejected, [&]{ dlg.reject(); }); + QObject::connect(text, &QLineEdit::textChanged, [&] { + check(); + }); + QObject::connect(bb, &QDialogButtonBox::accepted, [&] { + dlg.accept(); + }); + QObject::connect(bb, &QDialogButtonBox::rejected, [&] { + dlg.reject(); + }); check(); @@ -140,13 +144,11 @@ QString getInstanceName( return MOBase::sanitizeFileName(text->text()); } - InstanceManagerDialog::~InstanceManagerDialog() = default; -InstanceManagerDialog::InstanceManagerDialog( - PluginContainer& pc, QWidget *parent) : - QDialog(parent), ui(new Ui::InstanceManagerDialog), m_pc(pc), - m_model(nullptr), m_restartOnSelect(true) +InstanceManagerDialog::InstanceManagerDialog(PluginContainer& pc, QWidget* parent) + : QDialog(parent), ui(new Ui::InstanceManagerDialog), m_pc(pc), m_model(nullptr), + m_restartOnSelect(true) { ui->setupUi(this); @@ -165,23 +167,49 @@ InstanceManagerDialog::InstanceManagerDialog( updateList(); selectActiveInstance(); - connect(ui->createNew, &QPushButton::clicked, [&]{ createNew(); }); + connect(ui->createNew, &QPushButton::clicked, [&] { + createNew(); + }); - connect(ui->list->selectionModel(), &QItemSelectionModel::selectionChanged, [&]{ onSelection(); }); - connect(ui->list, &QListView::activated, [&]{ openSelectedInstance(); }); + connect(ui->list->selectionModel(), &QItemSelectionModel::selectionChanged, [&] { + onSelection(); + }); + connect(ui->list, &QListView::activated, [&] { + openSelectedInstance(); + }); - connect(ui->rename, &QPushButton::clicked, [&]{ rename(); }); - connect(ui->exploreLocation, &QPushButton::clicked, [&]{ exploreLocation(); }); - connect(ui->exploreBaseDirectory, &QPushButton::clicked, [&]{ exploreBaseDirectory(); }); - connect(ui->exploreGame, &QPushButton::clicked, [&]{ exploreGame(); }); + connect(ui->rename, &QPushButton::clicked, [&] { + rename(); + }); + connect(ui->exploreLocation, &QPushButton::clicked, [&] { + exploreLocation(); + }); + connect(ui->exploreBaseDirectory, &QPushButton::clicked, [&] { + exploreBaseDirectory(); + }); + connect(ui->exploreGame, &QPushButton::clicked, [&] { + exploreGame(); + }); - connect(ui->convertToGlobal, &QPushButton::clicked, [&]{ convertToGlobal(); }); - connect(ui->convertToPortable, &QPushButton::clicked, [&]{ convertToPortable(); }); - connect(ui->openINI, &QPushButton::clicked, [&]{ openINI(); }); - connect(ui->deleteInstance, &QPushButton::clicked, [&]{ deleteInstance(); }); + connect(ui->convertToGlobal, &QPushButton::clicked, [&] { + convertToGlobal(); + }); + connect(ui->convertToPortable, &QPushButton::clicked, [&] { + convertToPortable(); + }); + connect(ui->openINI, &QPushButton::clicked, [&] { + openINI(); + }); + connect(ui->deleteInstance, &QPushButton::clicked, [&] { + deleteInstance(); + }); - connect(ui->switchToInstance, &QPushButton::clicked, [&]{ openSelectedInstance(); }); - connect(ui->close, &QPushButton::clicked, [&]{ close(); }); + connect(ui->switchToInstance, &QPushButton::clicked, [&] { + openSelectedInstance(); + }); + connect(ui->close, &QPushButton::clicked, [&] { + close(); + }); } void InstanceManagerDialog::showEvent(QShowEvent* e) @@ -226,9 +254,8 @@ void InstanceManagerDialog::updateInstances() }); if (m.portableInstanceExists()) { - m_instances.insert( - m_instances.begin(), - std::make_unique(m.portablePath(), true)); + m_instances.insert(m_instances.begin(), + std::make_unique(m.portablePath(), true)); } // read all inis, ignore errors @@ -240,14 +267,14 @@ void InstanceManagerDialog::updateInstances() void InstanceManagerDialog::updateList() { const auto prevSelIndex = singleSelectionIndex(); - const auto* prevSel = singleSelection(); + const auto* prevSel = singleSelection(); m_model->clear(); std::size_t sel = NoSelection; // creating items for instances - for (std::size_t i=0; ilist->selectionModel()->select( - m_filter.mapFromSource(m_filter.sourceModel()->index(i, 0)), - QItemSelectionModel::ClearAndSelect); + m_filter.mapFromSource(m_filter.sourceModel()->index(i, 0)), + QItemSelectionModel::ClearAndSelect); } else { clearData(); } @@ -295,7 +322,7 @@ void InstanceManagerDialog::select(std::size_t i) void InstanceManagerDialog::select(const QString& name) { - for (std::size_t i=0; idisplayName() == name) { select(i); return; @@ -310,12 +337,11 @@ void InstanceManagerDialog::selectActiveInstance() const auto active = InstanceManager::singleton().currentInstance(); if (active) { - for (std::size_t i=0; idisplayName() == active->displayName()) { select(i); - ui->list->scrollTo( - m_filter.mapFromSource(m_filter.sourceModel()->index(i, 0))); + ui->list->scrollTo(m_filter.mapFromSource(m_filter.sourceModel()->index(i, 0))); return; } @@ -370,15 +396,14 @@ bool InstanceManagerDialog::confirmSwitch(const Instance& to) MOBase::TaskDialog dlg(this); - const auto r = dlg - .title(tr("Switching instances")) - .main(tr("Mod Organizer must restart to manage the instance '%1'.") - .arg(to.displayName())) - .content(tr("This confirmation can be disabled in the settings.")) - .icon(QMessageBox::Question) - .button({tr("Restart Mod Organizer"), QMessageBox::Ok}) - .button({tr("Cancel"), QMessageBox::Cancel}) - .exec(); + const auto r = dlg.title(tr("Switching instances")) + .main(tr("Mod Organizer must restart to manage the instance '%1'.") + .arg(to.displayName())) + .content(tr("This confirmation can be disabled in the settings.")) + .icon(QMessageBox::Question) + .button({tr("Restart Mod Organizer"), QMessageBox::Ok}) + .button({tr("Cancel"), QMessageBox::Cancel}) + .exec(); return (r == QMessageBox::Ok); } @@ -394,43 +419,41 @@ void InstanceManagerDialog::rename() auto& m = InstanceManager::singleton(); if (i->isActive()) { - QMessageBox::information(this, - tr("Rename instance"), tr("The active instance cannot be renamed.")); + QMessageBox::information(this, tr("Rename instance"), + tr("The active instance cannot be renamed.")); return; } - // getting new name - const auto newName = getInstanceName( - this, tr("Rename instance"), "", tr("Instance name"), i->displayName()); + const auto newName = getInstanceName(this, tr("Rename instance"), "", + tr("Instance name"), i->displayName()); if (newName.isEmpty()) { return; } - // renaming const QString src = i->directory(); - const QString dest = QDir::toNativeSeparators( - QFileInfo(src).dir().path() + "/" + newName); + const QString dest = + QDir::toNativeSeparators(QFileInfo(src).dir().path() + "/" + newName); log::info("renaming {} to {}", src, dest); const auto r = shell::Rename(QFileInfo(src), QFileInfo(dest), false); if (!r) { - QMessageBox::critical( - this, tr("Error"), - tr("Failed to rename \"%1\" to \"%2\": %3") - .arg(src).arg(dest).arg(r.toString())); + QMessageBox::critical(this, tr("Error"), + tr("Failed to rename \"%1\" to \"%2\": %3") + .arg(src) + .arg(dest) + .arg(r.toString())); return; } - // updating ui auto newInstance = std::make_unique(dest, false); - i = newInstance.get(); + i = newInstance.get(); m_model->item(selIndex)->setText(newName); m_instances[selIndex] = std::move(newInstance); @@ -440,28 +463,28 @@ void InstanceManagerDialog::rename() void InstanceManagerDialog::exploreLocation() { - if (const auto* i=singleSelection()) { + if (const auto* i = singleSelection()) { shell::Explore(i->directory()); } } void InstanceManagerDialog::exploreBaseDirectory() { - if (const auto* i=singleSelection()) { + if (const auto* i = singleSelection()) { shell::Explore(i->baseDirectory()); } } void InstanceManagerDialog::exploreGame() { - if (const auto* i=singleSelection()) { + if (const auto* i = singleSelection()) { shell::Explore(i->gameDirectory()); } } void InstanceManagerDialog::openINI() { - if (const auto* i=singleSelection()) { + if (const auto* i = singleSelection()) { shell::Open(i->iniPath()); } } @@ -475,37 +498,34 @@ void InstanceManagerDialog::deleteInstance() auto& m = InstanceManager::singleton(); if (i->isActive()) { - QMessageBox::information(this, - tr("Deleting instance"), tr("The active instance cannot be deleted.")); + QMessageBox::information(this, tr("Deleting instance"), + tr("The active instance cannot be deleted.")); return; } // creating dialog const auto Recycle = QMessageBox::Save; - const auto Delete = QMessageBox::Yes; - const auto Cancel = QMessageBox::Cancel; + const auto Delete = QMessageBox::Yes; + const auto Cancel = QMessageBox::Cancel; const auto files = i->objectsForDeletion(); MOBase::TaskDialog dlg(this); - dlg - .title(tr("Deleting instance")) - .main(tr("These files and folders will be deleted")) - .content(tr("All checked items will be deleted.")) - .icon(QMessageBox::Warning) - .button({tr("Move to the recycle bin"), Recycle}) - .button({tr("Delete permanently"), Delete}) - .button({tr("Cancel"), Cancel}); - + dlg.title(tr("Deleting instance")) + .main(tr("These files and folders will be deleted")) + .content(tr("All checked items will be deleted.")) + .icon(QMessageBox::Warning) + .button({tr("Move to the recycle bin"), Recycle}) + .button({tr("Delete permanently"), Delete}) + .button({tr("Cancel"), Cancel}); auto* list = new QListWidget(); list->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); list->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); list->setMaximumHeight(160); - // filling the list for (const auto& f : files) { auto* item = new QListWidgetItem(f.path); @@ -529,43 +549,37 @@ void InstanceManagerDialog::deleteInstance() dlg.addContent(list); dlg.setWidth(600); - const auto r = dlg.exec(); if (r != Recycle && r != Delete) { return; } - // gathering all the selected items QStringList selected; - for (int i=0; icount(); ++i) { + for (int i = 0; i < list->count(); ++i) { if (list->item(i)->checkState() == Qt::Checked) { selected.append(list->item(i)->text()); } } if (selected.isEmpty()) { - QMessageBox::information( - this, tr("Deleting instance"), tr("Nothing to delete.")); + QMessageBox::information(this, tr("Deleting instance"), tr("Nothing to delete.")); return; } - // deleting if (!doDelete(selected, (r == Recycle))) { return; } - // updating ui updateInstances(); updateList(); } - void InstanceManagerDialog::setRestartOnSelect(bool b) { m_restartOnSelect = b; @@ -640,8 +654,8 @@ void InstanceManagerDialog::createNew() std::size_t InstanceManagerDialog::singleSelectionIndex() const { - const auto sel = m_filter.mapSelectionToSource( - ui->list->selectionModel()->selection()); + const auto sel = + m_filter.mapSelectionToSource(ui->list->selectionModel()->selection()); if (sel.size() != 1) { return NoSelection; diff --git a/src/instancemanagerdialog.h b/src/instancemanagerdialog.h index bb03fd951..884beaa53 100644 --- a/src/instancemanagerdialog.h +++ b/src/instancemanagerdialog.h @@ -1,10 +1,13 @@ #ifndef MODORGANIZER_INSTANCEMANAGERDIALOG_INCLUDED #define MODORGANIZER_INSTANCEMANAGERDIALOG_INCLUDED -#include #include +#include -namespace Ui { class InstanceManagerDialog; }; +namespace Ui +{ +class InstanceManagerDialog; +}; class Instance; class PluginContainer; @@ -16,8 +19,7 @@ class InstanceManagerDialog : public QDialog Q_OBJECT public: - explicit InstanceManagerDialog( - PluginContainer& pc, QWidget *parent = nullptr); + explicit InstanceManagerDialog(PluginContainer& pc, QWidget* parent = nullptr); ~InstanceManagerDialog(); @@ -54,7 +56,6 @@ class InstanceManagerDialog : public QDialog // void exploreGame(); - // converts the selected, portable instance to a global one; not implemented // void convertToGlobal(); @@ -71,13 +72,11 @@ class InstanceManagerDialog : public QDialog // void deleteInstance(); - // sets whether the dialog should restart MO when selecting an instance; this // is false on startup when no instances exist // void setRestartOnSelect(bool b); - // saves geometry // void done(int r) override; @@ -113,7 +112,6 @@ class InstanceManagerDialog : public QDialog // bool confirmSwitch(const Instance& to); - // returns the index of selected instance, NoSelection if none // std::size_t singleSelectionIndex() const; @@ -144,4 +142,4 @@ class InstanceManagerDialog : public QDialog bool doDelete(const QStringList& files, bool recycle); }; -#endif // MODORGANIZER_INSTANCEMANAGERDIALOG_INCLUDED +#endif // MODORGANIZER_INSTANCEMANAGERDIALOG_INCLUDED diff --git a/src/iuserinterface.h b/src/iuserinterface.h index 277d5cf5d..7ddc545d0 100644 --- a/src/iuserinterface.h +++ b/src/iuserinterface.h @@ -1,33 +1,32 @@ -#ifndef IUSERINTERFACE_H -#define IUSERINTERFACE_H - - -#include "modinfodialogfwd.h" -#include -#include -#include -#include -#include - - -class IUserInterface -{ -public: - virtual void registerModPage(MOBase::IPluginModPage *modPage) = 0; - - virtual void installTranslator(const QString &name) = 0; - - virtual bool closeWindow() = 0; - virtual void setWindowEnabled(bool enabled) = 0; - - virtual void displayModInformation( - ModInfoPtr modInfo, unsigned int modIndex, ModInfoTabIDs tabID) = 0; - - virtual void updateBSAList(const QStringList &defaultArchives, const QStringList &activeArchives) = 0; - - virtual MOBase::DelayedFileWriterBase &archivesWriter() = 0; - - virtual QMainWindow* mainWindow() = 0; -}; - -#endif // IUSERINTERFACE_H +#ifndef IUSERINTERFACE_H +#define IUSERINTERFACE_H + +#include "modinfodialogfwd.h" +#include +#include +#include +#include +#include + +class IUserInterface +{ +public: + virtual void registerModPage(MOBase::IPluginModPage* modPage) = 0; + + virtual void installTranslator(const QString& name) = 0; + + virtual bool closeWindow() = 0; + virtual void setWindowEnabled(bool enabled) = 0; + + virtual void displayModInformation(ModInfoPtr modInfo, unsigned int modIndex, + ModInfoTabIDs tabID) = 0; + + virtual void updateBSAList(const QStringList& defaultArchives, + const QStringList& activeArchives) = 0; + + virtual MOBase::DelayedFileWriterBase& archivesWriter() = 0; + + virtual QMainWindow* mainWindow() = 0; +}; + +#endif // IUSERINTERFACE_H diff --git a/src/json.h b/src/json.h index d182f330a..3b7ae2cfd 100644 --- a/src/json.h +++ b/src/json.h @@ -1,143 +1,133 @@ #ifndef MODORGANIZER_JSON_INCLUDED #define MODORGANIZER_JSON_INCLUDED -#include +#include #include #include -#include #include +#include namespace json { -class failed {}; - +class failed +{}; namespace details { -QString typeName(const QJsonValue& v) -{ - if (v.isUndefined()) { - return "undefined"; - } else if (v.isNull()) { - return "null"; - } else if (v.isArray()) { - return "an array"; - } else if (v.isBool()) { - return "a bool"; - } else if (v.isDouble()) { - return "a double"; - } else if (v.isObject()) { - return "an object"; - } else if (v.isString()) { - return "a string"; - } else { - return "an unknown type"; + QString typeName(const QJsonValue& v) + { + if (v.isUndefined()) { + return "undefined"; + } else if (v.isNull()) { + return "null"; + } else if (v.isArray()) { + return "an array"; + } else if (v.isBool()) { + return "a bool"; + } else if (v.isDouble()) { + return "a double"; + } else if (v.isObject()) { + return "an object"; + } else if (v.isString()) { + return "a string"; + } else { + return "an unknown type"; + } } -} -QString typeName(const QJsonDocument& doc) -{ - if (doc.isEmpty()) { - return "empty"; - } else if (doc.isNull()) { - return "null"; - } else if (doc.isArray()) { - return "an array"; - } else if (doc.isObject()) { - return "an object"; - } else { - return "an unknown type"; + QString typeName(const QJsonDocument& doc) + { + if (doc.isEmpty()) { + return "empty"; + } else if (doc.isNull()) { + return "null"; + } else if (doc.isArray()) { + return "an array"; + } else if (doc.isObject()) { + return "an object"; + } else { + return "an unknown type"; + } } -} + template + T convert(const QJsonValue& v) = delete; -template -T convert(const QJsonValue& v) = delete; + template <> + bool convert(const QJsonValue& v) + { + if (!v.isBool()) { + throw failed(); + } -template <> -bool convert(const QJsonValue& v) -{ - if (!v.isBool()) { - throw failed(); + return v.toBool(); } - return v.toBool(); -} + template <> + QJsonObject convert(const QJsonValue& v) + { + if (!v.isObject()) { + throw failed(); + } -template <> -QJsonObject convert(const QJsonValue& v) -{ - if (!v.isObject()) { - throw failed(); + return v.toObject(); } - return v.toObject(); -} + template <> + QString convert(const QJsonValue& v) + { + if (!v.isString()) { + throw failed(); + } -template <> -QString convert(const QJsonValue& v) -{ - if (!v.isString()) { - throw failed(); + return v.toString(); } - return v.toString(); -} + template <> + QJsonArray convert(const QJsonValue& v) + { + if (!v.isArray()) { + throw failed(); + } -template <> -QJsonArray convert(const QJsonValue& v) -{ - if (!v.isArray()) { - throw failed(); + return v.toArray(); } - return v.toArray(); -} + template <> + qint64 convert(const QJsonValue& v) + { + if (!v.isDouble()) { + throw failed(); + } -template <> -qint64 convert(const QJsonValue& v) -{ - if (!v.isDouble()) { - throw failed(); + return static_cast(v.toDouble()); } - return static_cast(v.toDouble()); -} - -} // namespace - +} // namespace details template T convert(const QJsonValue& value, const char* what) { - try - { + try { return details::convert(value); - } - catch(failed&) - { - MOBase::log::error( - "'{}' is a {}, not a {}", - what, details::typeName(value), typeid(T).name); + } catch (failed&) { + MOBase::log::error("'{}' is a {}, not a {}", what, details::typeName(value), + typeid(T).name); throw; } } template -T convertWarn(const QJsonValue& value, const char* what, T def={}) +T convertWarn(const QJsonValue& value, const char* what, T def = {}) { - try - { + try { return details::convert(value); - } - catch(failed&) - { - MOBase::log::warn( - "'{}' is a {}, not a {}", - what, details::typeName(value), typeid(T).name()); + } catch (failed&) { + MOBase::log::warn("'{}' is a {}, not a {}", what, details::typeName(value), + typeid(T).name()); return def; } @@ -155,7 +145,7 @@ T get(const QJsonObject& o, const char* e) } template -T getWarn(const QJsonObject& o, const char* e, T def={}) +T getWarn(const QJsonObject& o, const char* e, T def = {}) { if (!o.contains(e)) { MOBase::log::warn("property '{}' is missing", e); @@ -166,7 +156,7 @@ T getWarn(const QJsonObject& o, const char* e, T def={}) } template -T getOpt(const QJsonObject& o, const char* e, T def={}) +T getOpt(const QJsonObject& o, const char* e, T def = {}) { if (!o.contains(e)) { return def; @@ -175,7 +165,6 @@ T getOpt(const QJsonObject& o, const char* e, T def={}) return convertWarn(o[e], e); } - template void requireObject(const Value& v, const char* what) { @@ -185,6 +174,6 @@ void requireObject(const Value& v, const char* what) } } -} // namespace +} // namespace json -#endif // MODORGANIZER_JSON_INCLUDED +#endif // MODORGANIZER_JSON_INCLUDED diff --git a/src/lcdnumber.cpp b/src/lcdnumber.cpp index 3191b434c..c8e176080 100644 --- a/src/lcdnumber.cpp +++ b/src/lcdnumber.cpp @@ -17,15 +17,12 @@ along with Mod Organizer. If not, see . #include "lcdnumber.h" -#include #include +#include -LCDNumber::LCDNumber(QWidget *parent) - : QLCDNumber(parent) -{ -} +LCDNumber::LCDNumber(QWidget* parent) : QLCDNumber(parent) {} -void LCDNumber::mousePressEvent(QMouseEvent *event) +void LCDNumber::mousePressEvent(QMouseEvent* event) { m_toolTipPosition = mapToGlobal(event->pos()); QTimer::singleShot(100, this, SLOT(showToolTip())); diff --git a/src/lcdnumber.h b/src/lcdnumber.h index 2f8e0454c..75f4124a4 100644 --- a/src/lcdnumber.h +++ b/src/lcdnumber.h @@ -24,8 +24,8 @@ class LCDNumber : public QLCDNumber Q_OBJECT public: - LCDNumber(QWidget *parent = nullptr); - void mousePressEvent(QMouseEvent *event); + LCDNumber(QWidget* parent = nullptr); + void mousePressEvent(QMouseEvent* event); public slots: void showToolTip(); diff --git a/src/listdialog.cpp b/src/listdialog.cpp index 9334186ac..09e37602b 100644 --- a/src/listdialog.cpp +++ b/src/listdialog.cpp @@ -16,13 +16,11 @@ along with Mod Organizer. If not, see . */ #include "listdialog.h" -#include "ui_listdialog.h" #include "settings.h" +#include "ui_listdialog.h" -ListDialog::ListDialog(QWidget *parent) - : QDialog(parent) - , ui(new Ui::ListDialog) - , m_Choices() +ListDialog::ListDialog(QWidget* parent) + : QDialog(parent), ui(new Ui::ListDialog), m_Choices() { ui->setupUi(this); ui->filterEdit->setFocus(); @@ -60,7 +58,7 @@ void ListDialog::on_filterEdit_textChanged(QString filter) { QStringList newChoices; for (auto choice : m_Choices) { - if (choice.contains(filter, Qt::CaseInsensitive)){ + if (choice.contains(filter, Qt::CaseInsensitive)) { newChoices << choice; } } @@ -68,7 +66,7 @@ void ListDialog::on_filterEdit_textChanged(QString filter) ui->choiceList->addItems(newChoices); if (newChoices.length() == 1) { - QListWidgetItem *item = ui->choiceList->item(0); + QListWidgetItem* item = ui->choiceList->item(0); item->setSelected(true); ui->choiceList->setCurrentItem(item); } @@ -79,4 +77,3 @@ void ListDialog::on_filterEdit_textChanged(QString filter) ui->choiceList->setStyleSheet(""); } } - diff --git a/src/listdialog.h b/src/listdialog.h index d0594bd76..2e33742bf 100644 --- a/src/listdialog.h +++ b/src/listdialog.h @@ -3,7 +3,8 @@ #include -namespace Ui { +namespace Ui +{ class ListDialog; } @@ -12,7 +13,7 @@ class ListDialog : public QDialog Q_OBJECT public: - explicit ListDialog(QWidget *parent = nullptr); + explicit ListDialog(QWidget* parent = nullptr); ~ListDialog(); // also saves and restores geometry @@ -26,8 +27,8 @@ public slots: void on_filterEdit_textChanged(QString filter); private: - Ui::ListDialog *ui; + Ui::ListDialog* ui; QStringList m_Choices; }; -#endif // LISTDIALOG_H +#endif // LISTDIALOG_H diff --git a/src/loghighlighter.cpp b/src/loghighlighter.cpp index cd7129acd..c230c75fb 100644 --- a/src/loghighlighter.cpp +++ b/src/loghighlighter.cpp @@ -1,51 +1,47 @@ -/* -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 "loghighlighter.h" - -LogHighlighter::LogHighlighter(QObject *parent) : - QSyntaxHighlighter(parent) -{ -} - - -void LogHighlighter::highlightBlock(const QString &text) -{ - int spacePos = text.indexOf(" "); - if (spacePos != -1) { - QString type = text.mid(0, spacePos); - if (type == "DEBUG") { - setFormat(0, text.length(), Qt::gray); - } else if (type == "INFO") { - setFormat(0, text.length(), Qt::darkGreen); - } else if (type == "ERROR") { - setFormat(0, text.length(), Qt::red); - } - } - - int markPos = text.indexOf("injecting to"); - if (markPos != -1) { - setFormat(markPos + 12, text.length(), Qt::blue); - } - - markPos = text.indexOf("using profile"); - if (markPos != -1) { - setFormat(markPos + 13, text.length(), Qt::blue); - } -} +/* +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 "loghighlighter.h" + +LogHighlighter::LogHighlighter(QObject* parent) : QSyntaxHighlighter(parent) {} + +void LogHighlighter::highlightBlock(const QString& text) +{ + int spacePos = text.indexOf(" "); + if (spacePos != -1) { + QString type = text.mid(0, spacePos); + if (type == "DEBUG") { + setFormat(0, text.length(), Qt::gray); + } else if (type == "INFO") { + setFormat(0, text.length(), Qt::darkGreen); + } else if (type == "ERROR") { + setFormat(0, text.length(), Qt::red); + } + } + + int markPos = text.indexOf("injecting to"); + if (markPos != -1) { + setFormat(markPos + 12, text.length(), Qt::blue); + } + + markPos = text.indexOf("using profile"); + if (markPos != -1) { + setFormat(markPos + 13, text.length(), Qt::blue); + } +} diff --git a/src/loghighlighter.h b/src/loghighlighter.h index 55c151423..893f036d9 100644 --- a/src/loghighlighter.h +++ b/src/loghighlighter.h @@ -1,45 +1,43 @@ -/* -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 LOGHIGHLIGHTER_H -#define LOGHIGHLIGHTER_H - -#include - -/** - * @brief Syntax highlighter to make log files from mo.dll more readable. - * @note this is currently not used! - **/ -class LogHighlighter : public QSyntaxHighlighter -{ - Q_OBJECT -public: - explicit LogHighlighter(QObject *parent = 0); - -signals: - -public slots: - -protected: - - virtual void highlightBlock(const QString &text); - -}; - -#endif // LOGHIGHLIGHTER_H +/* +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 LOGHIGHLIGHTER_H +#define LOGHIGHLIGHTER_H + +#include + +/** + * @brief Syntax highlighter to make log files from mo.dll more readable. + * @note this is currently not used! + **/ +class LogHighlighter : public QSyntaxHighlighter +{ + Q_OBJECT +public: + explicit LogHighlighter(QObject* parent = 0); + +signals: + +public slots: + +protected: + virtual void highlightBlock(const QString& text); +}; + +#endif // LOGHIGHLIGHTER_H diff --git a/src/loglist.cpp b/src/loglist.cpp index 07592edcf..345e0c6b2 100644 --- a/src/loglist.cpp +++ b/src/loglist.cpp @@ -1,397 +1,406 @@ -/* -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 "loglist.h" -#include "organizercore.h" -#include "copyeventfilter.h" -#include "env.h" - -using namespace MOBase; - -static LogModel* g_instance = nullptr; -const std::size_t MaxLines = 1000; - -static std::unique_ptr m_console; -static bool m_stdout = false; -static std::mutex m_stdoutMutex; - - -LogModel::LogModel() -{ -} - -void LogModel::create() -{ - g_instance = new LogModel; -} - -LogModel& LogModel::instance() -{ - return *g_instance; -} - -void LogModel::add(MOBase::log::Entry e) -{ - QMetaObject::invokeMethod( - this, [this, e]{ onEntryAdded(std::move(e)); }, Qt::QueuedConnection); -} - -QString LogModel::formattedMessage(const QModelIndex& index) const -{ - if (!index.isValid()) { - return ""; - } - return QString::fromStdString(m_entries[index.row()].formattedMessage); -} - -void LogModel::clear() -{ - beginResetModel(); - m_entries.clear(); - endResetModel(); -} - -const std::deque& LogModel::entries() const -{ - return m_entries; -} - -void LogModel::onEntryAdded(MOBase::log::Entry e) -{ - bool full = false; - if (m_entries.size() > MaxLines) { - m_entries.pop_front(); - full = true; - } - - const int row = static_cast(m_entries.size()); - - if (!full) { - beginInsertRows(QModelIndex(), row, row + 1); - } - - m_entries.emplace_back(std::move(e)); - - if (!full) { - endInsertRows(); - } else { - emit dataChanged( - createIndex(row, 0), - createIndex(row + 1, columnCount({}))); - } -} - -QModelIndex LogModel::index(int row, int column, const QModelIndex&) const -{ - return createIndex(row, column, row); -} - -QModelIndex LogModel::parent(const QModelIndex&) const -{ - return QModelIndex(); -} - -int LogModel::rowCount(const QModelIndex& parent) const -{ - if (parent.isValid()) - return 0; - else - return static_cast(m_entries.size()); -} - -int LogModel::columnCount(const QModelIndex&) const -{ - return 3; -} - -QVariant LogModel::data(const QModelIndex& index, int role) const -{ - using namespace std::chrono; - - const auto row = static_cast(index.row()); - if (row >= m_entries.size()) { - return {}; - } - - const auto& e = m_entries[row]; - - if (role == Qt::DisplayRole) { - if (index.column() == 0) { - const auto ms = duration_cast(e.time.time_since_epoch()); - const auto s = duration_cast(ms); - - const std::time_t tt = s.count(); - const int frac = static_cast(ms.count() % 1000); - - const auto time = QDateTime::fromSecsSinceEpoch(tt).time().addMSecs(frac); - return time.toString("hh:mm:ss.zzz"); - } else if (index.column() == 2) { - return QString::fromStdString(e.message); - } - } - - if (role == Qt::DecorationRole) { - if (index.column() == 1) { - switch (e.level) { - case log::Warning: - return QIcon(":/MO/gui/warning"); - - case log::Error: - return QIcon(":/MO/gui/problem"); - - case log::Debug: - return QIcon(":/MO/gui/debug"); - case log::Info: - return QIcon(":/MO/gui/information"); - default: - return {}; - } - } - } - - return QVariant(); -} - -QVariant LogModel::headerData(int, Qt::Orientation, int) const -{ - return {}; -} - - -LogList::LogList(QWidget* parent) - : QTreeView(parent), - m_core(nullptr), - m_copyFilter(this, [=](auto&& index) { return LogModel::instance().formattedMessage(index); }) -{ - setModel(&LogModel::instance()); - - header()->setMinimumSectionSize(0); - header()->resizeSection(1, 20); - header()->setSectionResizeMode(0, QHeaderView::ResizeToContents); - - setAutoScroll(true); - scrollToBottom(); - - connect( - this, &QWidget::customContextMenuRequested, - [&](auto&& pos){ onContextMenu(pos); }); - - connect(model(), &LogModel::rowsInserted, this, [&]{ onNewEntry(); }); - connect(model(), &LogModel::dataChanged, this, [&]{ onNewEntry(); }); - - m_timer.setSingleShot(true); - connect(&m_timer, &QTimer::timeout, [&]{ scrollToBottom(); }); - - installEventFilter(&m_copyFilter); -} - -void LogList::onNewEntry() -{ - m_timer.start(std::chrono::milliseconds(10)); -} - -void LogList::setCore(OrganizerCore& core) -{ - m_core = &core; -} - -void LogList::copyToClipboard() -{ - std::string s; - - for (const auto& e : LogModel::instance().entries()) { - s += e.formattedMessage + "\n"; - } - - if (!s.empty()) { - // last newline - s.pop_back(); - } - - QApplication::clipboard()->setText(QString::fromStdString(s)); -} - -void LogList::clear() -{ - static_cast(model())->clear(); -} - -void LogList::openLogsFolder() -{ - QString logsPath = qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::logPath()); - shell::Explore(logsPath); -} - -QMenu* LogList::createMenu(QWidget* parent) -{ - auto* menu = new QMenu(parent); - - menu->addAction(tr("Copy"), [&]{ m_copyFilter.copySelection(); }); - menu->addAction(tr("&Copy all"), [&]{ copyToClipboard(); }); - menu->addSeparator(); - menu->addAction(tr("C&lear all"), [&]{ clear(); }); - menu->addAction(tr("&Open logs folder"), [&]{ openLogsFolder(); }); - - auto* levels = new QMenu(tr("&Level")); - menu->addMenu(levels); - - auto* ag = new QActionGroup(menu); - - auto addAction = [&](auto&& text, auto&& level) { - auto* a = new QAction(text, ag); - - a->setCheckable(true); - a->setChecked(log::getDefault().level() == level); - - connect(a, &QAction::triggered, [this, level]{ - if (m_core) { - m_core->setLogLevel(level); - } - }); - - levels->addAction(a); - }; - - addAction(tr("&Debug"), log::Debug); - addAction(tr("&Info"), log::Info); - addAction(tr("&Warnings"), log::Warning); - addAction(tr("&Errors"), log::Error); - - return menu; -} - -void LogList::onContextMenu(const QPoint& pos) -{ - auto* menu = createMenu(this); - menu->popup(viewport()->mapToGlobal(pos)); -} - - -log::Levels convertQtLevel(QtMsgType t) -{ - switch (t) - { - case QtDebugMsg: - return log::Debug; - - case QtWarningMsg: - return log::Warning; - - case QtCriticalMsg: // fall-through - case QtFatalMsg: - return log::Error; - - case QtInfoMsg: // fall-through - default: - return log::Info; - } -} - -void qtLogCallback( - QtMsgType type, const QMessageLogContext& context, const QString& message) -{ - std::string_view file = ""; - - if (type != QtDebugMsg) { - if (context.file) { - file = context.file; - - const auto lastSep = file.find_last_of("/\\"); - if (lastSep != std::string_view::npos) { - file = {context.file + lastSep + 1}; - } - } - } - - if (file.empty()) { - log::log( - convertQtLevel(type), "{}", - message.toStdString()); - } else { - log::log( - convertQtLevel(type), "[{}:{}] {}", - file, context.line, message.toStdString()); - } -} - -void logToStdout(bool b) -{ - m_stdout = b; - - // logging to stdout is already set up in uibase by log::createDefault(), - // all it needs is to redirect stdout to the console, which is done by - // creating an env::Console object - - if (m_stdout) { - m_console.reset(new env::Console); - } else { - m_console.reset(); - } -} - -void initLogging() -{ - LogModel::create(); - - log::LoggerConfiguration conf; - conf.maxLevel = MOBase::log::Debug; - conf.pattern = "%^[%Y-%m-%d %H:%M:%S.%e %L] %v%$"; - conf.utc = true; - - log::createDefault(conf); - - log::getDefault().setCallback( - [](log::Entry e){ LogModel::instance().add(e); }); - - log::getDefault().addToBlacklist(std::string("\\") + getenv("USERNAME"), "\\USERNAME"); - log::getDefault().addToBlacklist(std::string("/") + getenv("USERNAME"), "/USERNAME"); - - qInstallMessageHandler(qtLogCallback); -} - -bool createAndMakeWritable(const std::wstring &subPath) { - QString const dataPath = qApp->property("dataPath").toString(); - QString fullPath = dataPath + "/" + QString::fromStdWString(subPath); - - if (!QDir(fullPath).exists() && !QDir().mkdir(fullPath)) { - QMessageBox::critical(nullptr, QObject::tr("Error"), - QObject::tr("Failed to create \"%1\". Your user " - "account probably lacks permission.") - .arg(fullPath)); - return false; - } else { - return true; - } -} - -bool setLogDirectory(const QString& dir) -{ - const auto logFile = - dir + "/" + - QString::fromStdWString(AppConfig::logPath()) + "/" + - QString::fromStdWString(AppConfig::logFileName()); - - if (!createAndMakeWritable(AppConfig::logPath())) { - return false; - } - - log::getDefault().setFile(MOBase::log::File::single(logFile.toStdWString())); - - return true; -} +/* +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 "loglist.h" +#include "copyeventfilter.h" +#include "env.h" +#include "organizercore.h" + +using namespace MOBase; + +static LogModel* g_instance = nullptr; +const std::size_t MaxLines = 1000; + +static std::unique_ptr m_console; +static bool m_stdout = false; +static std::mutex m_stdoutMutex; + +LogModel::LogModel() {} + +void LogModel::create() +{ + g_instance = new LogModel; +} + +LogModel& LogModel::instance() +{ + return *g_instance; +} + +void LogModel::add(MOBase::log::Entry e) +{ + QMetaObject::invokeMethod( + this, + [this, e] { + onEntryAdded(std::move(e)); + }, + Qt::QueuedConnection); +} + +QString LogModel::formattedMessage(const QModelIndex& index) const +{ + if (!index.isValid()) { + return ""; + } + return QString::fromStdString(m_entries[index.row()].formattedMessage); +} + +void LogModel::clear() +{ + beginResetModel(); + m_entries.clear(); + endResetModel(); +} + +const std::deque& LogModel::entries() const +{ + return m_entries; +} + +void LogModel::onEntryAdded(MOBase::log::Entry e) +{ + bool full = false; + if (m_entries.size() > MaxLines) { + m_entries.pop_front(); + full = true; + } + + const int row = static_cast(m_entries.size()); + + if (!full) { + beginInsertRows(QModelIndex(), row, row + 1); + } + + m_entries.emplace_back(std::move(e)); + + if (!full) { + endInsertRows(); + } else { + emit dataChanged(createIndex(row, 0), createIndex(row + 1, columnCount({}))); + } +} + +QModelIndex LogModel::index(int row, int column, const QModelIndex&) const +{ + return createIndex(row, column, row); +} + +QModelIndex LogModel::parent(const QModelIndex&) const +{ + return QModelIndex(); +} + +int LogModel::rowCount(const QModelIndex& parent) const +{ + if (parent.isValid()) + return 0; + else + return static_cast(m_entries.size()); +} + +int LogModel::columnCount(const QModelIndex&) const +{ + return 3; +} + +QVariant LogModel::data(const QModelIndex& index, int role) const +{ + using namespace std::chrono; + + const auto row = static_cast(index.row()); + if (row >= m_entries.size()) { + return {}; + } + + const auto& e = m_entries[row]; + + if (role == Qt::DisplayRole) { + if (index.column() == 0) { + const auto ms = duration_cast(e.time.time_since_epoch()); + const auto s = duration_cast(ms); + + const std::time_t tt = s.count(); + const int frac = static_cast(ms.count() % 1000); + + const auto time = QDateTime::fromSecsSinceEpoch(tt).time().addMSecs(frac); + return time.toString("hh:mm:ss.zzz"); + } else if (index.column() == 2) { + return QString::fromStdString(e.message); + } + } + + if (role == Qt::DecorationRole) { + if (index.column() == 1) { + switch (e.level) { + case log::Warning: + return QIcon(":/MO/gui/warning"); + + case log::Error: + return QIcon(":/MO/gui/problem"); + + case log::Debug: + return QIcon(":/MO/gui/debug"); + case log::Info: + return QIcon(":/MO/gui/information"); + default: + return {}; + } + } + } + + return QVariant(); +} + +QVariant LogModel::headerData(int, Qt::Orientation, int) const +{ + return {}; +} + +LogList::LogList(QWidget* parent) + : QTreeView(parent), m_core(nullptr), m_copyFilter(this, [=](auto&& index) { + return LogModel::instance().formattedMessage(index); + }) +{ + setModel(&LogModel::instance()); + + header()->setMinimumSectionSize(0); + header()->resizeSection(1, 20); + header()->setSectionResizeMode(0, QHeaderView::ResizeToContents); + + setAutoScroll(true); + scrollToBottom(); + + connect(this, &QWidget::customContextMenuRequested, [&](auto&& pos) { + onContextMenu(pos); + }); + + connect(model(), &LogModel::rowsInserted, this, [&] { + onNewEntry(); + }); + connect(model(), &LogModel::dataChanged, this, [&] { + onNewEntry(); + }); + + m_timer.setSingleShot(true); + connect(&m_timer, &QTimer::timeout, [&] { + scrollToBottom(); + }); + + installEventFilter(&m_copyFilter); +} + +void LogList::onNewEntry() +{ + m_timer.start(std::chrono::milliseconds(10)); +} + +void LogList::setCore(OrganizerCore& core) +{ + m_core = &core; +} + +void LogList::copyToClipboard() +{ + std::string s; + + for (const auto& e : LogModel::instance().entries()) { + s += e.formattedMessage + "\n"; + } + + if (!s.empty()) { + // last newline + s.pop_back(); + } + + QApplication::clipboard()->setText(QString::fromStdString(s)); +} + +void LogList::clear() +{ + static_cast(model())->clear(); +} + +void LogList::openLogsFolder() +{ + QString logsPath = qApp->property("dataPath").toString() + "/" + + QString::fromStdWString(AppConfig::logPath()); + shell::Explore(logsPath); +} + +QMenu* LogList::createMenu(QWidget* parent) +{ + auto* menu = new QMenu(parent); + + menu->addAction(tr("Copy"), [&] { + m_copyFilter.copySelection(); + }); + menu->addAction(tr("&Copy all"), [&] { + copyToClipboard(); + }); + menu->addSeparator(); + menu->addAction(tr("C&lear all"), [&] { + clear(); + }); + menu->addAction(tr("&Open logs folder"), [&] { + openLogsFolder(); + }); + + auto* levels = new QMenu(tr("&Level")); + menu->addMenu(levels); + + auto* ag = new QActionGroup(menu); + + auto addAction = [&](auto&& text, auto&& level) { + auto* a = new QAction(text, ag); + + a->setCheckable(true); + a->setChecked(log::getDefault().level() == level); + + connect(a, &QAction::triggered, [this, level] { + if (m_core) { + m_core->setLogLevel(level); + } + }); + + levels->addAction(a); + }; + + addAction(tr("&Debug"), log::Debug); + addAction(tr("&Info"), log::Info); + addAction(tr("&Warnings"), log::Warning); + addAction(tr("&Errors"), log::Error); + + return menu; +} + +void LogList::onContextMenu(const QPoint& pos) +{ + auto* menu = createMenu(this); + menu->popup(viewport()->mapToGlobal(pos)); +} + +log::Levels convertQtLevel(QtMsgType t) +{ + switch (t) { + case QtDebugMsg: + return log::Debug; + + case QtWarningMsg: + return log::Warning; + + case QtCriticalMsg: // fall-through + case QtFatalMsg: + return log::Error; + + case QtInfoMsg: // fall-through + default: + return log::Info; + } +} + +void qtLogCallback(QtMsgType type, const QMessageLogContext& context, + const QString& message) +{ + std::string_view file = ""; + + if (type != QtDebugMsg) { + if (context.file) { + file = context.file; + + const auto lastSep = file.find_last_of("/\\"); + if (lastSep != std::string_view::npos) { + file = {context.file + lastSep + 1}; + } + } + } + + if (file.empty()) { + log::log(convertQtLevel(type), "{}", message.toStdString()); + } else { + log::log(convertQtLevel(type), "[{}:{}] {}", file, context.line, + message.toStdString()); + } +} + +void logToStdout(bool b) +{ + m_stdout = b; + + // logging to stdout is already set up in uibase by log::createDefault(), + // all it needs is to redirect stdout to the console, which is done by + // creating an env::Console object + + if (m_stdout) { + m_console.reset(new env::Console); + } else { + m_console.reset(); + } +} + +void initLogging() +{ + LogModel::create(); + + log::LoggerConfiguration conf; + conf.maxLevel = MOBase::log::Debug; + conf.pattern = "%^[%Y-%m-%d %H:%M:%S.%e %L] %v%$"; + conf.utc = true; + + log::createDefault(conf); + + log::getDefault().setCallback([](log::Entry e) { + LogModel::instance().add(e); + }); + + log::getDefault().addToBlacklist(std::string("\\") + getenv("USERNAME"), + "\\USERNAME"); + log::getDefault().addToBlacklist(std::string("/") + getenv("USERNAME"), "/USERNAME"); + + qInstallMessageHandler(qtLogCallback); +} + +bool createAndMakeWritable(const std::wstring& subPath) +{ + QString const dataPath = qApp->property("dataPath").toString(); + QString fullPath = dataPath + "/" + QString::fromStdWString(subPath); + + if (!QDir(fullPath).exists() && !QDir().mkdir(fullPath)) { + QMessageBox::critical(nullptr, QObject::tr("Error"), + QObject::tr("Failed to create \"%1\". Your user " + "account probably lacks permission.") + .arg(fullPath)); + return false; + } else { + return true; + } +} + +bool setLogDirectory(const QString& dir) +{ + const auto logFile = dir + "/" + QString::fromStdWString(AppConfig::logPath()) + "/" + + QString::fromStdWString(AppConfig::logFileName()); + + if (!createAndMakeWritable(AppConfig::logPath())) { + return false; + } + + log::getDefault().setFile(MOBase::log::File::single(logFile.toStdWString())); + + return true; +} diff --git a/src/loglist.h b/src/loglist.h index df7c14679..58f4d1c71 100644 --- a/src/loglist.h +++ b/src/loglist.h @@ -1,94 +1,91 @@ -/* -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 LOGBUFFER_H -#define LOGBUFFER_H - - -#include "shared/appconfig.h" -#include "copyeventfilter.h" -#include -#include -#include - -class OrganizerCore; - -class LogModel : public QAbstractItemModel -{ - Q_OBJECT - -public: - static void create(); - static LogModel& instance(); - - void add(MOBase::log::Entry e); - void clear(); - - const std::deque& entries() const; - - QString formattedMessage(const QModelIndex& index) const; - -protected: - QModelIndex index(int row, int column, const QModelIndex& parent) const override; - QModelIndex parent(const QModelIndex &child) const override; - int rowCount(const QModelIndex &parent) const override; - int columnCount(const QModelIndex &parent) const override; - QVariant data(const QModelIndex &index, int role) const override; - - QVariant headerData( - int section, Qt::Orientation ori, int role=Qt::DisplayRole) const override; - -private: - std::deque m_entries; - - LogModel(); - void onEntryAdded(MOBase::log::Entry e); -}; - - -class LogList : public QTreeView -{ - Q_OBJECT; - -public: - LogList(QWidget* parent=nullptr); - - void setCore(OrganizerCore& core); - - void copyToClipboard(); - void clear(); - void openLogsFolder(); - - QMenu* createMenu(QWidget* parent=nullptr); - -private: - OrganizerCore* m_core; - QTimer m_timer; - CopyEventFilter m_copyFilter; - - void onContextMenu(const QPoint& pos); - void onNewEntry(); -}; - - -void logToStdout(bool b); -void initLogging(); -bool setLogDirectory(const QString& dir); - -#endif // LOGBUFFER_H +/* +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 LOGBUFFER_H +#define LOGBUFFER_H + +#include "copyeventfilter.h" +#include "shared/appconfig.h" +#include +#include +#include + +class OrganizerCore; + +class LogModel : public QAbstractItemModel +{ + Q_OBJECT + +public: + static void create(); + static LogModel& instance(); + + void add(MOBase::log::Entry e); + void clear(); + + const std::deque& entries() const; + + QString formattedMessage(const QModelIndex& index) const; + +protected: + QModelIndex index(int row, int column, const QModelIndex& parent) const override; + QModelIndex parent(const QModelIndex& child) const override; + int rowCount(const QModelIndex& parent) const override; + int columnCount(const QModelIndex& parent) const override; + QVariant data(const QModelIndex& index, int role) const override; + + QVariant headerData(int section, Qt::Orientation ori, + int role = Qt::DisplayRole) const override; + +private: + std::deque m_entries; + + LogModel(); + void onEntryAdded(MOBase::log::Entry e); +}; + +class LogList : public QTreeView +{ + Q_OBJECT; + +public: + LogList(QWidget* parent = nullptr); + + void setCore(OrganizerCore& core); + + void copyToClipboard(); + void clear(); + void openLogsFolder(); + + QMenu* createMenu(QWidget* parent = nullptr); + +private: + OrganizerCore* m_core; + QTimer m_timer; + CopyEventFilter m_copyFilter; + + void onContextMenu(const QPoint& pos); + void onNewEntry(); +}; + +void logToStdout(bool b); +void initLogging(); +bool setLogDirectory(const QString& dir); + +#endif // LOGBUFFER_H diff --git a/src/loot.cpp b/src/loot.cpp index 9c9ab2aec..5ff569874 100644 --- a/src/loot.cpp +++ b/src/loot.cpp @@ -1,23 +1,21 @@ #include "loot.h" +#include "json.h" #include "lootdialog.h" -#include "spawn.h" #include "organizercore.h" -#include "json.h" +#include "spawn.h" #include #include using namespace MOBase; using namespace json; -static QString LootReportPath = QDir::temp().absoluteFilePath("lootreport.json"); +static QString LootReportPath = QDir::temp().absoluteFilePath("lootreport.json"); static const DWORD PipeTimeout = 500; - class AsyncPipe { public: - AsyncPipe() - : m_ioPending(false) + AsyncPipe() : m_ioPending(false) { std::fill(std::begin(m_buffer), std::end(m_buffer), 0); std::memset(&m_ov, 0, sizeof(m_ov)); @@ -68,17 +66,17 @@ class AsyncPipe static const wchar_t* PipeName = L"\\\\.\\pipe\\lootcli_pipe"; SECURITY_ATTRIBUTES sa = {}; - sa.nLength = sizeof(SECURITY_ATTRIBUTES); - sa.bInheritHandle = TRUE; + sa.nLength = sizeof(SECURITY_ATTRIBUTES); + sa.bInheritHandle = TRUE; env::HandlePtr pipe; // creating pipe { - HANDLE pipeHandle = ::CreateNamedPipe( - PipeName, PIPE_ACCESS_DUPLEX|FILE_FLAG_OVERLAPPED, - PIPE_TYPE_BYTE|PIPE_READMODE_BYTE|PIPE_WAIT, - 1, 50'000, 50'000, PipeTimeout, &sa); + HANDLE pipeHandle = + ::CreateNamedPipe(PipeName, PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, + PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, 1, 50'000, + 50'000, PipeTimeout, &sa); if (pipeHandle == INVALID_HANDLE_VALUE) { const auto e = GetLastError(); @@ -93,9 +91,9 @@ class AsyncPipe // duplicating the handle to read from it HANDLE outputRead = INVALID_HANDLE_VALUE; - const auto r = DuplicateHandle( - GetCurrentProcess(), pipe.get(), GetCurrentProcess(), &outputRead, - 0, TRUE, DUPLICATE_SAME_ACCESS); + const auto r = + DuplicateHandle(GetCurrentProcess(), pipe.get(), GetCurrentProcess(), + &outputRead, 0, TRUE, DUPLICATE_SAME_ACCESS); if (!r) { const auto e = GetLastError(); @@ -106,11 +104,9 @@ class AsyncPipe m_stdout.reset(outputRead); } - // creating handle to pipe which is passed to CreateProcess() - HANDLE outputWrite = ::CreateFileW( - PipeName, FILE_WRITE_DATA|SYNCHRONIZE, 0, - &sa, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); + HANDLE outputWrite = ::CreateFileW(PipeName, FILE_WRITE_DATA | SYNCHRONIZE, 0, &sa, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); if (outputWrite == INVALID_HANDLE_VALUE) { const auto e = GetLastError(); @@ -128,25 +124,21 @@ class AsyncPipe if (!::ReadFile(m_stdout.get(), m_buffer, bufferSize, &bytesRead, &m_ov)) { const auto e = GetLastError(); - switch (e) - { - case ERROR_IO_PENDING: - { - m_ioPending = true; - break; - } - - case ERROR_BROKEN_PIPE: - { - // broken pipe probably means lootcli is finished - break; - } - - default: - { - log::error("{}", formatSystemMessage(e)); - break; - } + switch (e) { + case ERROR_IO_PENDING: { + m_ioPending = true; + break; + } + + case ERROR_BROKEN_PIPE: { + // broken pipe probably means lootcli is finished + break; + } + + default: { + log::error("{}", formatSystemMessage(e)); + break; + } } return {}; @@ -170,29 +162,24 @@ class AsyncPipe if (!::GetOverlappedResult(m_stdout.get(), &m_ov, &bytesRead, FALSE)) { const auto e = GetLastError(); - switch (e) - { - case ERROR_IO_INCOMPLETE: - { - break; - } - - case WAIT_TIMEOUT: - { - break; - } - - case ERROR_BROKEN_PIPE: - { - // broken pipe probably means lootcli is finished - break; - } - - default: - { - log::error("GetOverlappedResult failed, {}", formatSystemMessage(e)); - break; - } + switch (e) { + case ERROR_IO_INCOMPLETE: { + break; + } + + case WAIT_TIMEOUT: { + break; + } + + case ERROR_BROKEN_PIPE: { + // broken pipe probably means lootcli is finished + break; + } + + default: { + log::error("GetOverlappedResult failed, {}", formatSystemMessage(e)); + break; + } } return {}; @@ -205,29 +192,26 @@ class AsyncPipe } }; - - log::Levels levelFromLoot(lootcli::LogLevels level) { using LC = lootcli::LogLevels; - switch (level) - { - case LC::Trace: // fall-through - case LC::Debug: - return log::Debug; + switch (level) { + case LC::Trace: // fall-through + case LC::Debug: + return log::Debug; - case LC::Info: - return log::Info; + case LC::Info: + return log::Info; - case LC::Warning: - return log::Warning; + case LC::Warning: + return log::Warning; - case LC::Error: - return log::Error; + case LC::Error: + return log::Error; - default: - return log::Info; + default: + return log::Info; } } @@ -318,9 +302,9 @@ QString Loot::Report::errorsMarkdown() const QString Loot::Stats::toMarkdown() const { return QString("`stats: %1s, lootcli %2, loot %3`") - .arg(QString::number(time / 1000.0, 'f', 2)) - .arg(lootcliVersion) - .arg(lootVersion); + .arg(QString::number(time / 1000.0, 'f', 2)) + .arg(lootcliVersion) + .arg(lootVersion); } QString Loot::Plugin::toMarkdown() const @@ -376,7 +360,7 @@ QString Loot::Dirty::toString(bool isClean) const { if (isClean) { return QObject::tr("Verified clean by %1") - .arg(cleaningUtility.isEmpty() ? "?" : cleaningUtility); + .arg(cleaningUtility.isEmpty() ? "?" : cleaningUtility); } QString s = cleaningString(); @@ -395,35 +379,32 @@ QString Loot::Dirty::toMarkdown(bool isClean) const QString Loot::Dirty::cleaningString() const { - return QObject::tr("%1 found %2 ITM record(s), %3 deleted reference(s) and %4 deleted navmesh(es).") - .arg(cleaningUtility.isEmpty() ? "?" : cleaningUtility) - .arg(itm) - .arg(deletedReferences) - .arg(deletedNavmesh); + return QObject::tr("%1 found %2 ITM record(s), %3 deleted reference(s) and %4 " + "deleted navmesh(es).") + .arg(cleaningUtility.isEmpty() ? "?" : cleaningUtility) + .arg(itm) + .arg(deletedReferences) + .arg(deletedNavmesh); } QString Loot::Message::toMarkdown() const { QString s; - switch (type) - { - case log::Error: - { - s += "**" + QObject::tr("Error") + "**: "; - break; - } + switch (type) { + case log::Error: { + s += "**" + QObject::tr("Error") + "**: "; + break; + } - case log::Warning: - { - s += "**" + QObject::tr("Warning") + "**: "; - break; - } + case log::Warning: { + s += "**" + QObject::tr("Warning") + "**: "; + break; + } - default: - { - break; - } + default: { + break; + } } s += text; @@ -431,11 +412,9 @@ QString Loot::Message::toMarkdown() const return s; } - Loot::Loot(OrganizerCore& core) - : m_core(core), m_thread(nullptr), m_cancel(false), m_result(false) -{ -} + : m_core(core), m_thread(nullptr), m_cancel(false), m_result(false) +{} Loot::~Loot() { @@ -469,43 +448,42 @@ bool Loot::start(QWidget* parent, bool didUpdateMasterList) // starting thread log::debug("starting loot thread"); - m_thread.reset(QThread::create([&]{ lootThread(); })); + m_thread.reset(QThread::create([&] { + lootThread(); + })); m_thread->start(); return true; } -bool Loot::spawnLootcli( - QWidget* parent, bool didUpdateMasterList, env::HandlePtr stdoutHandle) +bool Loot::spawnLootcli(QWidget* parent, bool didUpdateMasterList, + env::HandlePtr stdoutHandle) { const auto logLevel = m_core.settings().diagnostics().lootLogLevel(); QStringList parameters; - parameters - << "--game" - << m_core.managedGame()->gameShortName() + parameters << "--game" << m_core.managedGame()->gameShortName() - << "--gamePath" - << QString("\"%1\"").arg(m_core.managedGame()->gameDirectory().absolutePath()) + << "--gamePath" + << QString("\"%1\"").arg( + m_core.managedGame()->gameDirectory().absolutePath()) - << "--pluginListPath" - << QString("\"%1/loadorder.txt\"").arg(m_core.profilePath()) + << "--pluginListPath" + << QString("\"%1/loadorder.txt\"").arg(m_core.profilePath()) - << "--logLevel" - << QString::fromStdString(lootcli::logLevelToString(logLevel)) + << "--logLevel" + << QString::fromStdString(lootcli::logLevelToString(logLevel)) - << "--out" - << QString("\"%1\"").arg(LootReportPath) + << "--out" << QString("\"%1\"").arg(LootReportPath) - << "--language" - << m_core.settings().interface().language(); + << "--language" << m_core.settings().interface().language(); if (didUpdateMasterList) { parameters << "--skipUpdateMasterlist"; } spawn::SpawnParameters sp; - sp.binary = QFileInfo(qApp->applicationDirPath() + "/loot/lootcli.exe"); + sp.binary = QFileInfo(qApp->applicationDirPath() + "/loot/lootcli.exe"); sp.arguments = parameters.join(" "); sp.currentDirectory.setPath(qApp->applicationDirPath() + "/loot"); sp.hooked = true; @@ -558,8 +536,7 @@ const std::vector& Loot::warnings() const void Loot::lootThread() { - try - { + try { m_result = false; if (waitForCompletion()) { @@ -567,9 +544,7 @@ void Loot::lootThread() } m_report = createReport(); - } - catch(...) - { + } catch (...) { log::error("unhandled exception in loot thread"); } @@ -577,7 +552,6 @@ void Loot::lootThread() emit finished(); } - bool Loot::waitForCompletion() { bool terminating = false; @@ -629,14 +603,15 @@ bool Loot::waitForCompletion() } if (exitCode != 0UL) { - emit log(log::Levels::Error, tr("Loot failed. Exit code was: 0x%1").arg(exitCode, 0, 16)); + emit log(log::Levels::Error, + tr("Loot failed. Exit code was: 0x%1").arg(exitCode, 0, 16)); return false; } return true; } -void Loot::processStdout(const std::string &lootOut) +void Loot::processStdout(const std::string& lootOut) { emit output(QString::fromStdString(lootOut)); @@ -671,27 +646,24 @@ void Loot::processStdout(const std::string &lootOut) void Loot::processMessage(const lootcli::Message& m) { - switch (m.type) - { - case lootcli::MessageType::Log: - { - const auto level = levelFromLoot(m.logLevel); + switch (m.type) { + case lootcli::MessageType::Log: { + const auto level = levelFromLoot(m.logLevel); - if (level == log::Error) { - m_errors.push_back(QString::fromStdString(m.log)); - } else if (level == log::Warning) { - m_warnings.push_back(QString::fromStdString(m.log)); - } - - emit log(level, QString::fromStdString(m.log)); - break; + if (level == log::Error) { + m_errors.push_back(QString::fromStdString(m.log)); + } else if (level == log::Warning) { + m_warnings.push_back(QString::fromStdString(m.log)); } - case lootcli::MessageType::Progress: - { - emit progress(m.progress); - break; - } + emit log(level, QString::fromStdString(m.log)); + break; + } + + case lootcli::MessageType::Progress: { + emit progress(m.progress); + break; + } } } @@ -699,8 +671,8 @@ Loot::Report Loot::createReport() const { Report r; - r.okay = m_result; - r.errors = m_errors; + r.okay = m_result; + r.errors = m_errors; r.warnings = m_warnings; if (m_result) { @@ -717,9 +689,8 @@ void Loot::deleteReportFile() const auto r = shell::Delete(QFileInfo(LootReportPath)); if (!r) { - log::error( - "failed to remove temporary loot json report '{}': {}", - LootReportPath, r.toString()); + log::error("failed to remove temporary loot json report '{}': {}", LootReportPath, + r.toString()); } } } @@ -730,10 +701,9 @@ void Loot::processOutputFile(Report& r) const QFile outFile(LootReportPath); if (!outFile.open(QIODevice::ReadOnly)) { - emit log( - MOBase::log::Error, - QString("failed to open file, %1 (error %2)") - .arg(outFile.errorString()).arg(outFile.error())); + emit log(MOBase::log::Error, QString("failed to open file, %1 (error %2)") + .arg(outFile.errorString()) + .arg(outFile.error())); return; } @@ -741,10 +711,8 @@ void Loot::processOutputFile(Report& r) const QJsonParseError e; const QJsonDocument doc = QJsonDocument::fromJson(outFile.readAll(), &e); if (doc.isNull()) { - emit log( - MOBase::log::Error, - QString("invalid json, %1 (error %2)") - .arg(e.errorString()).arg(e.error)); + emit log(MOBase::log::Error, + QString("invalid json, %1 (error %2)").arg(e.errorString()).arg(e.error)); return; } @@ -754,8 +722,8 @@ void Loot::processOutputFile(Report& r) const const QJsonObject object = doc.object(); r.messages = reportMessages(getOpt(object, "messages")); - r.plugins = reportPlugins(getOpt(object, "plugins")); - r.stats = reportStats(getWarn(object, "stats")); + r.plugins = reportPlugins(getOpt(object, "plugins")); + r.stats = reportStats(getWarn(object, "stats")); } std::vector Loot::reportPlugins(const QJsonArray& plugins) const @@ -812,8 +780,8 @@ Loot::Plugin Loot::reportPlugin(const QJsonObject& plugin) const p.missingMasters = reportStringArray(getOpt(plugin, "missingMasters")); } - p.loadsArchive = getOpt(plugin, "loadsArchive", false); - p.isMaster = getOpt(plugin, "isMaster", false); + p.loadsArchive = getOpt(plugin, "loadsArchive", false); + p.isMaster = getOpt(plugin, "isMaster", false); p.isLightMaster = getOpt(plugin, "isLightMaster", false); return p; @@ -823,9 +791,9 @@ Loot::Stats Loot::reportStats(const QJsonObject& stats) const { Stats s; - s.time = getWarn(stats, "time"); + s.time = getWarn(stats, "time"); s.lootcliVersion = getWarn(stats, "lootcliVersion"); - s.lootVersion = getWarn(stats, "lootVersion"); + s.lootVersion = getWarn(stats, "lootVersion"); return s; } @@ -877,7 +845,7 @@ std::vector Loot::reportFiles(const QJsonArray& array) const File f; - f.name = getWarn(o, "name"); + f.name = getWarn(o, "name"); f.displayName = getOpt(o, "displayName"); if (!f.name.isEmpty()) { @@ -897,12 +865,12 @@ std::vector Loot::reportDirty(const QJsonArray& array) const Dirty d; - d.crc = getWarn(o, "crc"); - d.itm = getOpt(o, "itm"); + d.crc = getWarn(o, "crc"); + d.itm = getOpt(o, "itm"); d.deletedReferences = getOpt(o, "deletedReferences"); - d.deletedNavmesh = getOpt(o, "deletedNavmesh"); - d.cleaningUtility = getOpt(o, "cleaningUtility"); - d.info = getOpt(o, "info"); + d.deletedNavmesh = getOpt(o, "deletedNavmesh"); + d.cleaningUtility = getOpt(o, "cleaningUtility"); + d.info = getOpt(o, "info"); v.emplace_back(std::move(d)); } @@ -926,7 +894,6 @@ std::vector Loot::reportStringArray(const QJsonArray& array) const return v; } - bool runLoot(QWidget* parent, OrganizerCore& core, bool didUpdateMasterList) { core.savePluginList(); @@ -942,10 +909,10 @@ bool runLoot(QWidget* parent, OrganizerCore& core, bool didUpdateMasterList) dialog.exec(); return dialog.result(); - } catch (const UsvfsConnectorException &e) { + } catch (const UsvfsConnectorException& e) { log::debug("{}", e.what()); return false; - } catch (const std::exception &e) { + } catch (const std::exception& e) { reportError(QObject::tr("failed to run loot: %1").arg(e.what())); return false; } diff --git a/src/loot.h b/src/loot.h index f9943626c..5442a7229 100644 --- a/src/loot.h +++ b/src/loot.h @@ -2,10 +2,10 @@ #define MODORGANIZER_LOOT_H #include "envmodule.h" +#include #include #include #include -#include Q_DECLARE_METATYPE(lootcli::Progress); Q_DECLARE_METATYPE(MOBase::log::Levels); @@ -34,10 +34,10 @@ class Loot : public QObject struct Dirty { - qint64 crc=0; - qint64 itm=0; - qint64 deletedReferences=0; - qint64 deletedNavmesh=0; + qint64 crc = 0; + qint64 itm = 0; + qint64 deletedReferences = 0; + qint64 deletedNavmesh = 0; QString cleaningUtility; QString info; @@ -53,8 +53,8 @@ class Loot : public QObject std::vector messages; std::vector dirty, clean; std::vector missingMasters; - bool loadsArchive = false; - bool isMaster = false; + bool loadsArchive = false; + bool isMaster = false; bool isLightMaster = false; QString toMarkdown() const; @@ -84,7 +84,6 @@ class Loot : public QObject QString errorsMarkdown() const; }; - Loot(OrganizerCore& core); ~Loot(); @@ -114,13 +113,13 @@ class Loot : public QObject std::vector m_errors, m_warnings; Report m_report; - bool spawnLootcli( - QWidget* parent, bool didUpdateMasterList, env::HandlePtr stdoutHandle); + bool spawnLootcli(QWidget* parent, bool didUpdateMasterList, + env::HandlePtr stdoutHandle); void lootThread(); bool waitForCompletion(); - void processStdout(const std::string &lootOut); + void processStdout(const std::string& lootOut); void processMessage(const lootcli::Message& m); Report createReport() const; @@ -138,7 +137,6 @@ class Loot : public QObject std::vector reportStringArray(const QJsonArray& array) const; }; - bool runLoot(QWidget* parent, OrganizerCore& core, bool didUpdateMasterList); -#endif // MODORGANIZER_LOOT_H +#endif // MODORGANIZER_LOOT_H diff --git a/src/lootdialog.cpp b/src/lootdialog.cpp index 9f7b16f0e..6e31b8a68 100644 --- a/src/lootdialog.cpp +++ b/src/lootdialog.cpp @@ -1,9 +1,9 @@ #include "lootdialog.h" -#include "ui_lootdialog.h" #include "loot.h" #include "organizercore.h" -#include +#include "ui_lootdialog.h" #include +#include using namespace MOBase; @@ -12,15 +12,14 @@ QString progressToString(lootcli::Progress p) using P = lootcli::Progress; static const std::map map = { - {P::CheckingMasterlistExistence, QObject::tr("Checking masterlist existence")}, - {P::UpdatingMasterlist, QObject::tr("Updating masterlist")}, - {P::LoadingLists, QObject::tr("Loading lists")}, - {P::ReadingPlugins, QObject::tr("Reading plugins")}, - {P::SortingPlugins, QObject::tr("Sorting plugins")}, - {P::WritingLoadorder, QObject::tr("Writing loadorder.txt")}, - {P::ParsingLootMessages, QObject::tr("Parsing loot messages")}, - {P::Done, QObject::tr("Done")} - }; + {P::CheckingMasterlistExistence, QObject::tr("Checking masterlist existence")}, + {P::UpdatingMasterlist, QObject::tr("Updating masterlist")}, + {P::LoadingLists, QObject::tr("Loading lists")}, + {P::ReadingPlugins, QObject::tr("Reading plugins")}, + {P::SortingPlugins, QObject::tr("Sorting plugins")}, + {P::WritingLoadorder, QObject::tr("Writing loadorder.txt")}, + {P::ParsingLootMessages, QObject::tr("Parsing loot messages")}, + {P::Done, QObject::tr("Done")}}; auto itor = map.find(p); if (itor == map.end()) { @@ -30,11 +29,7 @@ QString progressToString(lootcli::Progress p) } } - -MarkdownDocument::MarkdownDocument(QObject* parent) - : QObject(parent) -{ -} +MarkdownDocument::MarkdownDocument(QObject* parent) : QObject(parent) {} void MarkdownDocument::setText(const QString& text) { @@ -45,13 +40,9 @@ void MarkdownDocument::setText(const QString& text) emit textChanged(m_text); } +MarkdownPage::MarkdownPage(QObject* parent) : QWebEnginePage(parent) {} -MarkdownPage::MarkdownPage(QObject* parent) - : QWebEnginePage(parent) -{ -} - -bool MarkdownPage::acceptNavigationRequest(const QUrl &url, NavigationType, bool) +bool MarkdownPage::acceptNavigationRequest(const QUrl& url, NavigationType, bool) { static const QStringList allowed = {"qrc", "data"}; @@ -63,28 +54,39 @@ bool MarkdownPage::acceptNavigationRequest(const QUrl &url, NavigationType, bool return true; } - -LootDialog::LootDialog(QWidget* parent, OrganizerCore& core, Loot& loot) : - QDialog(parent, Qt::WindowMaximizeButtonHint), ui(new Ui::LootDialog), m_core(core), m_loot(loot), - m_finished(false), m_cancelling(false) +LootDialog::LootDialog(QWidget* parent, OrganizerCore& core, Loot& loot) + : QDialog(parent, Qt::WindowMaximizeButtonHint), ui(new Ui::LootDialog), + m_core(core), m_loot(loot), m_finished(false), m_cancelling(false) { createUI(); QObject::connect( - &m_loot, &Loot::output, this, - [&](auto&& s){ addOutput(s); }, Qt::QueuedConnection); + &m_loot, &Loot::output, this, + [&](auto&& s) { + addOutput(s); + }, + Qt::QueuedConnection); QObject::connect( - &m_loot, &Loot::progress, - this, [&](auto&& p){ setProgress(p); }, Qt::QueuedConnection); + &m_loot, &Loot::progress, this, + [&](auto&& p) { + setProgress(p); + }, + Qt::QueuedConnection); QObject::connect( - &m_loot, &Loot::log, this, - [&](auto&& lv, auto&& s){ log(lv, s); }, Qt::QueuedConnection); + &m_loot, &Loot::log, this, + [&](auto&& lv, auto&& s) { + log(lv, s); + }, + Qt::QueuedConnection); QObject::connect( - &m_loot, &Loot::finished, this, - [&]{ onFinished(); }, Qt::QueuedConnection); + &m_loot, &Loot::finished, this, + [&] { + onFinished(); + }, + Qt::QueuedConnection); } LootDialog::~LootDialog() = default; @@ -208,7 +210,9 @@ void LootDialog::createUI() m_expander.set(ui->details, ui->detailsPanel); ui->openJsonReport->setEnabled(false); - connect(ui->openJsonReport, &QPushButton::clicked, [&]{ openReport(); }); + connect(ui->openJsonReport, &QPushButton::clicked, [&] { + openReport(); + }); ui->buttons->setStandardButtons(QDialogButtonBox::Cancel); diff --git a/src/lootdialog.h b/src/lootdialog.h index 3cec15c68..f632b9c10 100644 --- a/src/lootdialog.h +++ b/src/lootdialog.h @@ -1,48 +1,49 @@ #ifndef MODORGANIZER_LOOTDIALOG_H #define MODORGANIZER_LOOTDIALOG_H -#include -#include #include +#include +#include -namespace Ui { class LootDialog; } +namespace Ui +{ +class LootDialog; +} class OrganizerCore; class Loot; - class MarkdownDocument : public QObject { Q_OBJECT; Q_PROPERTY(QString text MEMBER m_text NOTIFY textChanged FINAL); public: - explicit MarkdownDocument(QObject* parent=nullptr); + explicit MarkdownDocument(QObject* parent = nullptr); void setText(const QString& text); signals: - void textChanged(const QString &text); + void textChanged(const QString& text); private: QString m_text; }; - class MarkdownPage : public QWebEnginePage { Q_OBJECT; public: - explicit MarkdownPage(QObject* parent=nullptr); + explicit MarkdownPage(QObject* parent = nullptr); protected: - bool acceptNavigationRequest(const QUrl &url, NavigationType, bool) override; + bool acceptNavigationRequest(const QUrl& url, NavigationType, bool) override; }; - class LootDialog : public QDialog { Q_OBJECT; + public: LootDialog(QWidget* parent, OrganizerCore& core, Loot& loot); ~LootDialog(); @@ -76,4 +77,4 @@ class LootDialog : public QDialog void showReport(); }; -#endif // MODORGANIZER_LOOTDIALOG_H +#endif // MODORGANIZER_LOOTDIALOG_H diff --git a/src/main.cpp b/src/main.cpp index 84db88cb8..de1578593 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,36 +1,36 @@ -#include "multiprocess.h" -#include "loglist.h" -#include "moapplication.h" -#include "organizercore.h" #include "commandline.h" #include "env.h" #include "instancemanager.h" -#include "thread_utils.h" +#include "loglist.h" +#include "moapplication.h" +#include "multiprocess.h" +#include "organizercore.h" #include "shared/util.h" -#include +#include "thread_utils.h" #include +#include using namespace MOBase; thread_local LPTOP_LEVEL_EXCEPTION_FILTER g_prevExceptionFilter = nullptr; -thread_local std::terminate_handler g_prevTerminateHandler = nullptr; +thread_local std::terminate_handler g_prevTerminateHandler = nullptr; -int run(int argc, char *argv[]); +int run(int argc, char* argv[]); -int main(int argc, char *argv[]) +int main(int argc, char* argv[]) { const int r = run(argc, argv); std::cout << "mod organizer done\n"; return r; } -int run(int argc, char *argv[]) +int run(int argc, char* argv[]) { MOShared::SetThisThreadName("main"); setExceptionHandlers(); cl::CommandLine cl; - if (auto r=cl.process(GetCommandLineW())) { + if (auto r = cl.process(GetCommandLineW())) { return *r; } @@ -42,13 +42,11 @@ int run(int argc, char *argv[]) QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); MOApplication app(argc, argv); - // check if the command line wants to run something right now - if (auto r=cl.runPostApplication(app)) { + if (auto r = cl.runPostApplication(app)) { return *r; } - // check if there's another process running MOMultiProcess multiProcess(cl.multiple()); @@ -62,21 +60,19 @@ int run(int argc, char *argv[]) } QMessageBox::information( - nullptr, QObject::tr("Mod Organizer"), - QObject::tr("An instance of Mod Organizer is already running")); + nullptr, QObject::tr("Mod Organizer"), + QObject::tr("An instance of Mod Organizer is already running")); return 1; } - // check if the command line wants to run something right now - if (auto r=cl.runPostMultiProcess(multiProcess)) { + if (auto r = cl.runPostMultiProcess(multiProcess)) { return *r; } tt.stop(); - // stuff that's done only once, even if MO restarts in the loop below app.firstTimeSetup(multiProcess); @@ -86,10 +82,8 @@ int run(int argc, char *argv[]) // MO runs in a loop because it can be restarted in several ways, such as // when switching instances or changing some settings - for (;;) - { - try - { + for (;;) { + try { auto& m = InstanceManager::singleton(); if (cl.instance()) { @@ -100,11 +94,10 @@ int run(int argc, char *argv[]) m.overrideProfile(*cl.profile()); } - // set up plugins, OrganizerCore, etc. { const auto r = app.setup(multiProcess, pick); - pick = false; + pick = false; if (r == RestartExitCode || r == ReselectExitCode) { // resets things when MO is "restarted" @@ -124,17 +117,14 @@ int run(int argc, char *argv[]) } } - // check if the command line wants to run something right now - if (auto r=cl.runPostOrganizer(app.core())) { + if (auto r = cl.runPostOrganizer(app.core())) { return *r; } - // run the main window const auto r = app.run(multiProcess); - if (r == RestartExitCode) { // resets things when MO is "restarted" app.resetForRestart(); @@ -146,9 +136,7 @@ int run(int argc, char *argv[]) } return r; - } - catch (const std::exception &e) - { + } catch (const std::exception& e) { reportError(e.what()); return 1; } @@ -178,16 +166,11 @@ LONG WINAPI onUnhandledException(_EXCEPTION_POINTERS* ptrs) void onTerminate() noexcept { - __try - { + __try { // force an exception to get a valid stack trace for this thread *(int*)0 = 42; - } - __except - ( - onUnhandledException(GetExceptionInformation()), EXCEPTION_EXECUTE_HANDLER - ) - { + } __except (onUnhandledException(GetExceptionInformation()), + EXCEPTION_EXECUTE_HANDLER) { } if (g_prevTerminateHandler) { @@ -204,6 +187,6 @@ void setExceptionHandlers() return; } - g_prevExceptionFilter = SetUnhandledExceptionFilter(onUnhandledException); + g_prevExceptionFilter = SetUnhandledExceptionFilter(onUnhandledException); g_prevTerminateHandler = std::set_terminate(onTerminate); } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 3e785177d..146bfc68a 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -20,65 +20,64 @@ along with Mod Organizer. If not, see . #include "mainwindow.h" #include "ui_mainwindow.h" +#include "aboutdialog.h" +#include "browserdialog.h" +#include "categories.h" +#include "categoriesdialog.h" +#include "datatab.h" +#include "downloadlist.h" +#include "downloadstab.h" +#include "editexecutablesdialog.h" +#include "envshortcut.h" +#include "eventfilter.h" #include "executableinfo.h" #include "executableslist.h" +#include "filedialogmemory.h" +#include "filterlist.h" #include "guessedvalue.h" #include "imodinterface.h" -#include "iplugingame.h" +#include "installationmanager.h" +#include "instancemanager.h" +#include "instancemanagerdialog.h" #include "iplugindiagnose.h" +#include "iplugingame.h" #include "isavegame.h" #include "isavegameinfowidget.h" +#include "listdialog.h" +#include "localsavegames.h" +#include "messagedialog.h" +#include "modlist.h" +#include "modlistcontextmenu.h" +#include "modlistviewactions.h" +#include "motddialog.h" #include "nexusinterface.h" +#include "nxmaccessmanager.h" #include "organizercore.h" +#include "overwriteinfodialog.h" +#include "pluginlist.h" +#include "previewdialog.h" #include "previewgenerator.h" -#include "serverinfo.h" -#include "savegameinfo.h" -#include "spawn.h" -#include "versioninfo.h" -#include "instancemanager.h" -#include "report.h" -#include "modlist.h" +#include "problemsdialog.h" #include "profile.h" -#include "pluginlist.h" #include "profilesdialog.h" -#include "editexecutablesdialog.h" -#include "categories.h" -#include "categoriesdialog.h" -#include "overwriteinfodialog.h" -#include "downloadlist.h" -#include "messagedialog.h" -#include "installationmanager.h" -#include "motddialog.h" -#include "filedialogmemory.h" -#include "tutorialmanager.h" +#include "report.h" +#include "savegameinfo.h" +#include "savestab.h" #include "selectiondialog.h" -#include "problemsdialog.h" -#include "previewdialog.h" -#include "browserdialog.h" -#include "aboutdialog.h" +#include "serverinfo.h" #include "settingsdialog.h" -#include -#include "nxmaccessmanager.h" #include "shared/appconfig.h" -#include "eventfilter.h" +#include "spawn.h" #include "statusbar.h" -#include "filterlist.h" -#include "datatab.h" -#include "downloadstab.h" -#include "savestab.h" -#include "instancemanagerdialog.h" -#include -#include +#include "tutorialmanager.h" +#include "versioninfo.h" #include -#include +#include +#include #include +#include #include -#include "localsavegames.h" -#include "listdialog.h" -#include "envshortcut.h" -#include "browserdialog.h" -#include "modlistviewactions.h" -#include "modlistcontextmenu.h" +#include #include "directoryrefresher.h" #include "shared/directoryentry.h" @@ -88,22 +87,25 @@ along with Mod Organizer. If not, see . #include #include #include -#include #include +#include #include #include #include +#include +#include #include #include #include #include #include +#include #include #include #include #include -#include #include +#include #include #include #include @@ -128,14 +130,14 @@ along with Mod Organizer. If not, see . #include #include #include -#include #include #include #include #include #include -#include +#include #include +#include #include #include #include @@ -146,42 +148,39 @@ along with Mod Organizer. If not, see . #include #include #include +#include #include #include -#include -#include -#include -#include #include #include #ifndef Q_MOC_RUN -#include #include -#include #include +#include #include +#include #endif #include -#include #include #include +#include #include #include -#include #include +#include #include #include "gameplugins.h" #ifdef TEST_MODELS #include "modeltest.h" -#endif // TEST_MODELS +#endif // TEST_MODELS -#pragma warning( disable : 4428 ) +#pragma warning(disable : 4428) using namespace MOBase; using namespace MOShared; @@ -223,7 +222,6 @@ void setFilterShortcuts(QWidget* widget, QLineEdit* edit) QObject::connect(s, &QShortcut::activated, reset); }; - hookActivate(widget); hookReset(widget); @@ -231,27 +229,16 @@ void setFilterShortcuts(QWidget* widget, QLineEdit* edit) hookReset(edit); } -MainWindow::MainWindow(Settings &settings - , OrganizerCore &organizerCore - , PluginContainer &pluginContainer - , QWidget *parent) - : QMainWindow(parent) - , ui(new Ui::MainWindow) - , m_WasVisible(false) - , m_FirstPaint(true) - , m_linksSeparator(nullptr) - , m_Tutorial(this, "MainWindow") - , m_OldProfileIndex(-1) - , m_OldExecutableIndex(-1) - , m_CategoryFactory(CategoryFactory::instance()) - , m_OrganizerCore(organizerCore) - , m_PluginContainer(pluginContainer) - , m_ArchiveListWriter(std::bind(&MainWindow::saveArchiveList, this)) - , m_LinkToolbar(nullptr) - , m_LinkDesktop(nullptr) - , m_LinkStartMenu(nullptr) - , m_NumberOfProblems(0) - , m_ProblemsCheckRequired(false) +MainWindow::MainWindow(Settings& settings, OrganizerCore& organizerCore, + PluginContainer& pluginContainer, QWidget* parent) + : QMainWindow(parent), ui(new Ui::MainWindow), m_WasVisible(false), + m_FirstPaint(true), m_linksSeparator(nullptr), m_Tutorial(this, "MainWindow"), + m_OldProfileIndex(-1), m_OldExecutableIndex(-1), + m_CategoryFactory(CategoryFactory::instance()), m_OrganizerCore(organizerCore), + m_PluginContainer(pluginContainer), + m_ArchiveListWriter(std::bind(&MainWindow::saveArchiveList, this)), + m_LinkToolbar(nullptr), m_LinkDesktop(nullptr), m_LinkStartMenu(nullptr), + m_NumberOfProblems(0), m_ProblemsCheckRequired(false) { // disables incredibly slow menu fade in effect that looks and feels like crap. // this was only happening to users with the windows @@ -263,10 +250,12 @@ MainWindow::MainWindow(Settings &settings QApplication::setEffectEnabled(Qt::UI_AnimateTooltip, false); QApplication::setEffectEnabled(Qt::UI_FadeTooltip, false); - QWebEngineProfile::defaultProfile()->setPersistentCookiesPolicy(QWebEngineProfile::NoPersistentCookies); + QWebEngineProfile::defaultProfile()->setPersistentCookiesPolicy( + QWebEngineProfile::NoPersistentCookies); QWebEngineProfile::defaultProfile()->setHttpCacheMaximumSize(52428800); QWebEngineProfile::defaultProfile()->setCachePath(settings.paths().cache()); - QWebEngineProfile::defaultProfile()->setPersistentStoragePath(settings.paths().cache()); + QWebEngineProfile::defaultProfile()->setPersistentStoragePath( + settings.paths().cache()); // qt resets the thread name somewhere within the QWebEngineProfile calls // above @@ -305,7 +294,6 @@ MainWindow::MainWindow(Settings &settings ui->logList->setCore(m_OrganizerCore); - setupToolbar(); toggleMO2EndorseState(); toggleUpdateAction(); @@ -318,23 +306,24 @@ MainWindow::MainWindow(Settings &settings ui->bsaList->setHeaderHidden(true); const bool pluginListAdjusted = - settings.geometry().restoreState(ui->espList->header()); - + settings.geometry().restoreState(ui->espList->header()); // data tab m_DataTab.reset(new DataTab(m_OrganizerCore, m_PluginContainer, this, ui)); m_DataTab->restoreState(settings); - connect(m_DataTab.get(), &DataTab::executablesChanged, [&]{ refreshExecutablesList(); }); - - connect( - m_DataTab.get(), &DataTab::originModified, - [&](int id){ originModified(id); }); + connect(m_DataTab.get(), &DataTab::executablesChanged, [&] { + refreshExecutablesList(); + }); - connect( - m_DataTab.get(), &DataTab::displayModInformation, - [&](auto&& m, auto&& i, auto&& tab){ displayModInformation(m, i, tab); }); + connect(m_DataTab.get(), &DataTab::originModified, [&](int id) { + originModified(id); + }); + connect(m_DataTab.get(), &DataTab::displayModInformation, + [&](auto&& m, auto&& i, auto&& tab) { + displayModInformation(m, i, tab); + }); // downloads tab m_DownloadsTab.reset(new DownloadsTab(m_OrganizerCore, ui)); @@ -359,82 +348,112 @@ MainWindow::MainWindow(Settings &settings resizeLists(pluginListAdjusted); - QMenu *linkMenu = new QMenu(this); - m_LinkToolbar = linkMenu->addAction(QIcon(":/MO/gui/link"), tr("Toolbar and Menu"), this, SLOT(linkToolbar())); - m_LinkDesktop = linkMenu->addAction(QIcon(":/MO/gui/link"), tr("Desktop"), this, SLOT(linkDesktop())); - m_LinkStartMenu = linkMenu->addAction(QIcon(":/MO/gui/link"), tr("Start Menu"), this, SLOT(linkMenu())); + QMenu* linkMenu = new QMenu(this); + m_LinkToolbar = linkMenu->addAction(QIcon(":/MO/gui/link"), tr("Toolbar and Menu"), + this, SLOT(linkToolbar())); + m_LinkDesktop = linkMenu->addAction(QIcon(":/MO/gui/link"), tr("Desktop"), this, + SLOT(linkDesktop())); + m_LinkStartMenu = linkMenu->addAction(QIcon(":/MO/gui/link"), tr("Start Menu"), this, + SLOT(linkMenu())); ui->linkButton->setMenu(linkMenu); - ui->listOptionsBtn->setMenu(new ModListGlobalContextMenu(m_OrganizerCore, ui->modList, this)); + ui->listOptionsBtn->setMenu( + new ModListGlobalContextMenu(m_OrganizerCore, ui->modList, this)); ui->openFolderMenu->setMenu(openFolderMenu()); // don't allow mouse wheel to switch grouping, too many people accidentally // turn on grouping and then don't understand what happened - EventFilter *noWheel - = new EventFilter(this, [](QObject *, QEvent *event) -> bool { - return event->type() == QEvent::Wheel; - }); + EventFilter* noWheel = new EventFilter(this, [](QObject*, QEvent* event) -> bool { + return event->type() == QEvent::Wheel; + }); ui->groupCombo->installEventFilter(noWheel); ui->profileBox->installEventFilter(noWheel); - if (organizerCore.managedGame()->sortMechanism() == MOBase::IPluginGame::SortMechanism::NONE) { + if (organizerCore.managedGame()->sortMechanism() == + MOBase::IPluginGame::SortMechanism::NONE) { ui->bossButton->setDisabled(true); - ui->bossButton->setToolTip(tr("There is no supported sort mechanism for this game. You will probably have to use a third-party tool.")); + ui->bossButton->setToolTip(tr("There is no supported sort mechanism for this game. " + "You will probably have to use a third-party tool.")); } - connect(&m_PluginContainer, SIGNAL(diagnosisUpdate()), this, SLOT(scheduleCheckForProblems())); + connect(&m_PluginContainer, SIGNAL(diagnosisUpdate()), this, + SLOT(scheduleCheckForProblems())); - connect(&m_OrganizerCore, &OrganizerCore::directoryStructureReady, - this, &MainWindow::onDirectoryStructureChanged); - connect(m_OrganizerCore.directoryRefresher(), SIGNAL(progress(const DirectoryRefreshProgress*)), - this, SLOT(refresherProgress(const DirectoryRefreshProgress*))); - connect(m_OrganizerCore.directoryRefresher(), SIGNAL(error(QString)), this, SLOT(showError(QString))); + connect(&m_OrganizerCore, &OrganizerCore::directoryStructureReady, this, + &MainWindow::onDirectoryStructureChanged); + connect(m_OrganizerCore.directoryRefresher(), + SIGNAL(progress(const DirectoryRefreshProgress*)), this, + SLOT(refresherProgress(const DirectoryRefreshProgress*))); + connect(m_OrganizerCore.directoryRefresher(), SIGNAL(error(QString)), this, + SLOT(showError(QString))); - connect(&m_OrganizerCore.settings(), SIGNAL(languageChanged(QString)), this, SLOT(languageChange(QString))); - connect(&m_OrganizerCore.settings(), SIGNAL(styleChanged(QString)), this, SIGNAL(styleChanged(QString))); + connect(&m_OrganizerCore.settings(), SIGNAL(languageChanged(QString)), this, + SLOT(languageChange(QString))); + connect(&m_OrganizerCore.settings(), SIGNAL(styleChanged(QString)), this, + SIGNAL(styleChanged(QString))); connect(m_OrganizerCore.updater(), SIGNAL(restart()), this, SLOT(close())); - connect(m_OrganizerCore.updater(), SIGNAL(updateAvailable()), this, SLOT(updateAvailable())); - connect(m_OrganizerCore.updater(), SIGNAL(motdAvailable(QString)), this, SLOT(motdReceived(QString))); - - connect(&NexusInterface::instance(), SIGNAL(requestNXMDownload(QString)), &m_OrganizerCore, SLOT(downloadRequestedNXM(QString))); - connect(&NexusInterface::instance(), SIGNAL(nxmDownloadURLsAvailable(QString,int,int,QVariant,QVariant,int)), this, SLOT(nxmDownloadURLs(QString,int,int,QVariant,QVariant,int))); - connect(&NexusInterface::instance(), SIGNAL(needLogin()), &m_OrganizerCore, SLOT(nexusApi())); - - connect( - NexusInterface::instance().getAccessManager(), - SIGNAL(credentialsReceived(const APIUserAccount&)), - this, - SLOT(updateWindowTitle(const APIUserAccount&))); - - connect( - NexusInterface::instance().getAccessManager(), - SIGNAL(credentialsReceived(const APIUserAccount&)), - &NexusInterface::instance(), - SLOT(setUserAccount(const APIUserAccount&))); + connect(m_OrganizerCore.updater(), SIGNAL(updateAvailable()), this, + SLOT(updateAvailable())); + connect(m_OrganizerCore.updater(), SIGNAL(motdAvailable(QString)), this, + SLOT(motdReceived(QString))); + + connect(&NexusInterface::instance(), SIGNAL(requestNXMDownload(QString)), + &m_OrganizerCore, SLOT(downloadRequestedNXM(QString))); + connect(&NexusInterface::instance(), + SIGNAL(nxmDownloadURLsAvailable(QString, int, int, QVariant, QVariant, int)), + this, SLOT(nxmDownloadURLs(QString, int, int, QVariant, QVariant, int))); + connect(&NexusInterface::instance(), SIGNAL(needLogin()), &m_OrganizerCore, + SLOT(nexusApi())); + + connect(NexusInterface::instance().getAccessManager(), + SIGNAL(credentialsReceived(const APIUserAccount&)), this, + SLOT(updateWindowTitle(const APIUserAccount&))); + + connect(NexusInterface::instance().getAccessManager(), + SIGNAL(credentialsReceived(const APIUserAccount&)), + &NexusInterface::instance(), SLOT(setUserAccount(const APIUserAccount&))); + + connect(&NexusInterface::instance(), + SIGNAL(requestsChanged(const APIStats&, const APIUserAccount&)), this, + SLOT(onRequestsChanged(const APIStats&, const APIUserAccount&))); + + connect(&TutorialManager::instance(), SIGNAL(windowTutorialFinished(QString)), this, + SLOT(windowTutorialFinished(QString))); + connect(ui->tabWidget, SIGNAL(currentChanged(int)), &TutorialManager::instance(), + SIGNAL(tabChanged(int))); + connect(ui->toolBar, SIGNAL(customContextMenuRequested(QPoint)), this, + SLOT(toolBar_customContextMenuRequested(QPoint))); + connect(ui->menuToolbars, &QMenu::aboutToShow, [&] { + updateToolbarMenu(); + }); + connect(ui->menuView, &QMenu::aboutToShow, [&] { + updateViewMenu(); + }); + connect(ui->actionTool->menu(), &QMenu::aboutToShow, [&] { + updateToolMenu(); + }); + connect(&m_PluginContainer, &PluginContainer::pluginEnabled, this, + [this](IPlugin* plugin) { + if (m_PluginContainer.implementInterface(plugin)) { + updateModPageMenu(); + } + }); + connect(&m_PluginContainer, &PluginContainer::pluginDisabled, this, + [this](IPlugin* plugin) { + if (m_PluginContainer.implementInterface(plugin)) { + updateModPageMenu(); + } + }); + connect(&m_PluginContainer, &PluginContainer::pluginRegistered, this, + &MainWindow::onPluginRegistrationChanged); + connect(&m_PluginContainer, &PluginContainer::pluginUnregistered, this, + &MainWindow::onPluginRegistrationChanged); - connect( - &NexusInterface::instance(), - SIGNAL(requestsChanged(const APIStats&, const APIUserAccount&)), - this, - SLOT(onRequestsChanged(const APIStats&, const APIUserAccount&))); - - connect(&TutorialManager::instance(), SIGNAL(windowTutorialFinished(QString)), this, SLOT(windowTutorialFinished(QString))); - connect(ui->tabWidget, SIGNAL(currentChanged(int)), &TutorialManager::instance(), SIGNAL(tabChanged(int))); - connect(ui->toolBar, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(toolBar_customContextMenuRequested(QPoint))); - connect(ui->menuToolbars, &QMenu::aboutToShow, [&]{ updateToolbarMenu(); }); - connect(ui->menuView, &QMenu::aboutToShow, [&]{ updateViewMenu(); }); - connect(ui->actionTool->menu(), &QMenu::aboutToShow, [&] { updateToolMenu(); }); - connect(&m_PluginContainer, &PluginContainer::pluginEnabled, this, [this](IPlugin* plugin) { - if (m_PluginContainer.implementInterface(plugin)) { updateModPageMenu(); } }); - connect(&m_PluginContainer, &PluginContainer::pluginDisabled, this, [this](IPlugin* plugin) { - if (m_PluginContainer.implementInterface(plugin)) { updateModPageMenu(); } }); - connect(&m_PluginContainer, &PluginContainer::pluginRegistered, this, &MainWindow::onPluginRegistrationChanged); - connect(&m_PluginContainer, &PluginContainer::pluginUnregistered, this, &MainWindow::onPluginRegistrationChanged); - - connect(&m_OrganizerCore, &OrganizerCore::modInstalled, this, &MainWindow::modInstalled); + connect(&m_OrganizerCore, &OrganizerCore::modInstalled, this, + &MainWindow::modInstalled); m_CheckBSATimer.setSingleShot(true); connect(&m_CheckBSATimer, SIGNAL(timeout()), this, SLOT(checkBSAList())); @@ -444,8 +463,10 @@ MainWindow::MainWindow(Settings &settings setFilterShortcuts(ui->downloadView, ui->downloadFilterEdit); m_UpdateProblemsTimer.setSingleShot(true); - connect(&m_UpdateProblemsTimer, &QTimer::timeout, this, &MainWindow::checkForProblemsAsync); - connect(this, &MainWindow::checkForProblemsDone, this, &MainWindow::updateProblemsButton, Qt::ConnectionType::QueuedConnection); + connect(&m_UpdateProblemsTimer, &QTimer::timeout, this, + &MainWindow::checkForProblemsAsync); + connect(this, &MainWindow::checkForProblemsDone, this, + &MainWindow::updateProblemsButton, Qt::ConnectionType::QueuedConnection); m_SaveMetaTimer.setSingleShot(false); connect(&m_SaveMetaTimer, SIGNAL(timeout()), this, SLOT(saveModMetas())); @@ -461,19 +482,28 @@ MainWindow::MainWindow(Settings &settings m_Tutorial.expose("espList", m_OrganizerCore.pluginList()); m_OrganizerCore.setUserInterface(this); - connect(m_OrganizerCore.modList(), &ModList::showMessage, - [=](auto&& message) { showMessage(message); }); + connect(m_OrganizerCore.modList(), &ModList::showMessage, [=](auto&& message) { + showMessage(message); + }); connect(m_OrganizerCore.modList(), &ModList::modRenamed, - [=](auto&& oldName, auto&& newName) { modRenamed(oldName, newName); }); - connect(m_OrganizerCore.modList(), &ModList::modUninstalled, - [=](auto&& name) { modRemoved(name); }); - connect(m_OrganizerCore.modList(), &ModList::fileMoved, - [=](auto&& ...args) { fileMoved(args...); }); + [=](auto&& oldName, auto&& newName) { + modRenamed(oldName, newName); + }); + connect(m_OrganizerCore.modList(), &ModList::modUninstalled, [=](auto&& name) { + modRemoved(name); + }); + connect(m_OrganizerCore.modList(), &ModList::fileMoved, [=](auto&&... args) { + fileMoved(args...); + }); connect(m_OrganizerCore.installationManager(), &InstallationManager::modReplaced, - [=](auto&& name) { modRemoved(name); }); + [=](auto&& name) { + modRemoved(name); + }); connect(m_OrganizerCore.downloadManager(), &DownloadManager::showMessage, - [=](auto&& message) { showMessage(message); }); - for (const QString &fileName : m_PluginContainer.pluginFileNames()) { + [=](auto&& message) { + showMessage(message); + }); + for (const QString& fileName : m_PluginContainer.pluginFileNames()) { installTranslator(QFileInfo(fileName).baseName()); } @@ -484,13 +514,10 @@ MainWindow::MainWindow(Settings &settings ui->profileBox->setCurrentText(m_OrganizerCore.currentProfile()->name()); - if (settings.archiveParsing()) - { + if (settings.archiveParsing()) { ui->dataTabShowFromArchives->setCheckState(Qt::Checked); ui->dataTabShowFromArchives->setEnabled(true); - } - else - { + } else { ui->dataTabShowFromArchives->setCheckState(Qt::Unchecked); ui->dataTabShowFromArchives->setEnabled(false); } @@ -512,11 +539,17 @@ void MainWindow::setupModList() { ui->modList->setup(m_OrganizerCore, *m_CategoryFactory, this, ui); - connect(&ui->modList->actions(), &ModListViewActions::overwriteCleared, [=]() { scheduleCheckForProblems(); }); - connect(&ui->modList->actions(), &ModListViewActions::originModified, this, &MainWindow::originModified); - connect(&ui->modList->actions(), &ModListViewActions::modInfoDisplayed, this, &MainWindow::modInfoDisplayed); + connect(&ui->modList->actions(), &ModListViewActions::overwriteCleared, [=]() { + scheduleCheckForProblems(); + }); + connect(&ui->modList->actions(), &ModListViewActions::originModified, this, + &MainWindow::originModified); + connect(&ui->modList->actions(), &ModListViewActions::modInfoDisplayed, this, + &MainWindow::modInfoDisplayed); - connect(m_OrganizerCore.modList(), &ModList::modPrioritiesChanged, [&]() { m_ArchiveListWriter.write(); }); + connect(m_OrganizerCore.modList(), &ModList::modPrioritiesChanged, [&]() { + m_ArchiveListWriter.write(); + }); } void MainWindow::resetActionIcons() @@ -568,7 +601,7 @@ void MainWindow::resetActionIcons() // button associated with the action on the toolbar QWidget* actionWidget = ui->toolBar->widgetForAction(action); - if (auto* actionButton=dynamic_cast(actionWidget)) { + if (auto* actionButton = dynamic_cast(actionWidget)) { actionButton->setIcon(icon); } @@ -599,19 +632,21 @@ MainWindow::~MainWindow() } delete ui; - } catch (std::exception &e) { - QMessageBox::critical(nullptr, tr("Crash on exit"), - tr("MO crashed while exiting. Some settings may not be saved.\n\nError: %1").arg(e.what()), - QMessageBox::Ok); + } catch (std::exception& e) { + QMessageBox::critical( + nullptr, tr("Crash on exit"), + tr("MO crashed while exiting. Some settings may not be saved.\n\nError: %1") + .arg(e.what()), + QMessageBox::Ok); } } void MainWindow::updateWindowTitle(const APIUserAccount& user) { //"\xe2\x80\x93" is an "em dash", a longer "-" - QString title = QString("%1 \xe2\x80\x93 Mod Organizer v%2").arg( - m_OrganizerCore.managedGame()->gameName(), - m_OrganizerCore.getVersion().displayString(3)); + QString title = QString("%1 \xe2\x80\x93 Mod Organizer v%2") + .arg(m_OrganizerCore.managedGame()->gameName(), + m_OrganizerCore.getVersion().displayString(3)); if (!user.name().isEmpty()) { const QString premium = (user.type() == APIUserAccountTypes::Premium ? "*" : ""); @@ -664,7 +699,7 @@ void MainWindow::updateStyle(const QString&) resetActionIcons(); } -void MainWindow::resizeEvent(QResizeEvent *event) +void MainWindow::resizeEvent(QResizeEvent* event) { m_Tutorial.resize(event->size()); QMainWindow::resizeEvent(event); @@ -708,7 +743,7 @@ void MainWindow::setupActionMenu(QAction* a) a->setMenu(new QMenu(this)); auto* w = ui->toolBar->widgetForAction(a); - if (auto* tb=dynamic_cast(w)) + if (auto* tb = dynamic_cast(w)) tb->setPopupMode(QToolButton::InstantPopup); } @@ -729,8 +764,8 @@ void MainWindow::updatePinnedExecutables() if (!exe.hide() && exe.isShownOnToolbar()) { hasLinks = true; - QAction *exeAction = new QAction( - iconForExecutable(exe.binaryInfo().filePath()), exe.title()); + QAction* exeAction = + new QAction(iconForExecutable(exe.binaryInfo().filePath()), exe.title()); exeAction->setObjectName(QString("custom__") + exe.title()); exeAction->setStatusTip(exe.binaryInfo().filePath()); @@ -761,12 +796,16 @@ void MainWindow::updateToolbarMenu() ui->actionStatusBarToggle->setChecked(ui->statusBar->isVisible()); ui->actionToolBarSmallIcons->setChecked(ui->toolBar->iconSize() == SmallToolbarSize); - ui->actionToolBarMediumIcons->setChecked(ui->toolBar->iconSize() == MediumToolbarSize); + ui->actionToolBarMediumIcons->setChecked(ui->toolBar->iconSize() == + MediumToolbarSize); ui->actionToolBarLargeIcons->setChecked(ui->toolBar->iconSize() == LargeToolbarSize); - ui->actionToolBarIconsOnly->setChecked(ui->toolBar->toolButtonStyle() == Qt::ToolButtonIconOnly); - ui->actionToolBarTextOnly->setChecked(ui->toolBar->toolButtonStyle() == Qt::ToolButtonTextOnly); - ui->actionToolBarIconsAndText->setChecked(ui->toolBar->toolButtonStyle() == Qt::ToolButtonTextUnderIcon); + ui->actionToolBarIconsOnly->setChecked(ui->toolBar->toolButtonStyle() == + Qt::ToolButtonIconOnly); + ui->actionToolBarTextOnly->setChecked(ui->toolBar->toolButtonStyle() == + Qt::ToolButtonTextOnly); + ui->actionToolBarIconsAndText->setChecked(ui->toolBar->toolButtonStyle() == + Qt::ToolButtonTextUnderIcon); } void MainWindow::updateViewMenu() @@ -859,7 +898,7 @@ void MainWindow::setToolbarButtonStyle(Qt::ToolButtonStyle s) } } -void MainWindow::on_centralWidget_customContextMenuRequested(const QPoint &pos) +void MainWindow::on_centralWidget_customContextMenuRequested(const QPoint& pos) { // this allows for getting the context menu even if both the menubar and all // the toolbars are hidden; an alternative is the Alt key handled in @@ -894,8 +933,9 @@ void MainWindow::updateProblemsButton() const std::size_t numProblems = m_NumberOfProblems; // original icon without a count painted on it - const QIcon original = m_originalNotificationIcon.isNull() ? - QIcon(DefaultIconName) : m_originalNotificationIcon; + const QIcon original = m_originalNotificationIcon.isNull() + ? QIcon(DefaultIconName) + : m_originalNotificationIcon; // final icon QIcon final; @@ -912,8 +952,9 @@ void MainWindow::updateProblemsButton() QPainter painter(&merged); const std::string badgeName = - std::string(":/MO/gui/badge_") + - (numProblems < 10 ? std::to_string(static_cast(numProblems)) : "more"); + std::string(":/MO/gui/badge_") + + (numProblems < 10 ? std::to_string(static_cast(numProblems)) + : "more"); painter.drawPixmap(32, 32, 32, 32, QPixmap(badgeName.c_str())); } @@ -932,8 +973,8 @@ void MainWindow::updateProblemsButton() ui->actionNotifications->setIcon(final); // setting the icon on the toolbar button - if (auto* actionWidget=ui->toolBar->widgetForAction(ui->actionNotifications)) { - if (auto* button=dynamic_cast(actionWidget)) { + if (auto* actionWidget = ui->toolBar->widgetForAction(ui->actionNotifications)) { + if (auto* button = dynamic_cast(actionWidget)) { button->setIcon(final); } } @@ -944,11 +985,13 @@ void MainWindow::updateProblemsButton() } } -bool MainWindow::errorReported(QString &logFile) +bool MainWindow::errorReported(QString& logFile) { - QDir dir(qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::logPath())); - QFileInfoList files = dir.entryInfoList(QStringList("ModOrganizer_??_??_??_??_??.log"), - QDir::Files, QDir::Name | QDir::Reversed); + QDir dir(qApp->property("dataPath").toString() + "/" + + QString::fromStdWString(AppConfig::logPath())); + QFileInfoList files = + dir.entryInfoList(QStringList("ModOrganizer_??_??_??_??_??.log"), QDir::Files, + QDir::Name | QDir::Reversed); if (files.count() > 0) { logFile = files.at(0).absoluteFilePath(); @@ -973,10 +1016,11 @@ bool MainWindow::errorReported(QString &logFile) return false; } -QFuture MainWindow::checkForProblemsAsync() { +QFuture MainWindow::checkForProblemsAsync() +{ return QtConcurrent::run([this]() { checkForProblemsImpl(); - }); + }); } void MainWindow::checkForProblemsImpl() @@ -990,10 +1034,10 @@ void MainWindow::checkForProblemsImpl() m_ProblemsCheckRequired = false; TimeThis tt("MainWindow::checkForProblemsImpl()"); size_t numProblems = 0; - for (QObject *pluginObj : m_PluginContainer.plugins()) { - IPlugin *plugin = qobject_cast(pluginObj); + for (QObject* pluginObj : m_PluginContainer.plugins()) { + IPlugin* plugin = qobject_cast(pluginObj); if (plugin == nullptr || m_PluginContainer.isEnabled(plugin)) { - IPluginDiagnose *diagnose = qobject_cast(pluginObj); + IPluginDiagnose* diagnose = qobject_cast(pluginObj); if (diagnose != nullptr) numProblems += diagnose->activeProblems().size(); } @@ -1018,11 +1062,11 @@ void MainWindow::createEndorseMenu() menu->clear(); - QAction *endorseAction = new QAction(tr("Endorse"), menu); + QAction* endorseAction = new QAction(tr("Endorse"), menu); connect(endorseAction, SIGNAL(triggered()), this, SLOT(actionEndorseMO())); menu->addAction(endorseAction); - QAction *wontEndorseAction = new QAction(tr("Won't Endorse"), menu); + QAction* wontEndorseAction = new QAction(tr("Won't Endorse"), menu); connect(wontEndorseAction, SIGNAL(triggered()), this, SLOT(actionWontEndorseMO())); menu->addAction(wontEndorseAction); } @@ -1038,35 +1082,36 @@ void MainWindow::createHelpMenu() menu->clear(); - QAction *helpAction = new QAction(tr("Help on UI"), menu); + QAction* helpAction = new QAction(tr("Help on UI"), menu); connect(helpAction, SIGNAL(triggered()), this, SLOT(helpTriggered())); menu->addAction(helpAction); - QAction *wikiAction = new QAction(tr("Documentation"), menu); + QAction* wikiAction = new QAction(tr("Documentation"), menu); connect(wikiAction, SIGNAL(triggered()), this, SLOT(wikiTriggered())); menu->addAction(wikiAction); - if(!m_OrganizerCore.managedGame()->getSupportURL().isEmpty()) { - QAction *gameSupportAction = new QAction(tr("Game Support Wiki"), menu); + if (!m_OrganizerCore.managedGame()->getSupportURL().isEmpty()) { + QAction* gameSupportAction = new QAction(tr("Game Support Wiki"), menu); connect(gameSupportAction, SIGNAL(triggered()), this, SLOT(gameSupportTriggered())); menu->addAction(gameSupportAction); } - QAction *discordAction = new QAction(tr("Chat on Discord"), menu); + QAction* discordAction = new QAction(tr("Chat on Discord"), menu); connect(discordAction, SIGNAL(triggered()), this, SLOT(discordTriggered())); menu->addAction(discordAction); - QAction *issueAction = new QAction(tr("Report Issue"), menu); + QAction* issueAction = new QAction(tr("Report Issue"), menu); connect(issueAction, SIGNAL(triggered()), this, SLOT(issueTriggered())); menu->addAction(issueAction); - QMenu *tutorialMenu = new QMenu(tr("Tutorials"), menu); + QMenu* tutorialMenu = new QMenu(tr("Tutorials"), menu); - typedef std::vector > ActionList; + typedef std::vector> ActionList; ActionList tutorials; - QDirIterator dirIter(QApplication::applicationDirPath() + "/tutorials", QStringList("*.js"), QDir::Files); + QDirIterator dirIter(QApplication::applicationDirPath() + "/tutorials", + QStringList("*.js"), QDir::Files); while (dirIter.hasNext()) { dirIter.next(); QString fileName = dirIter.fileName(); @@ -1080,18 +1125,20 @@ void MainWindow::createHelpMenu() if (firstLine.startsWith("//TL")) { QStringList params = firstLine.mid(4).trimmed().split('#'); if (params.size() != 2) { - log::error("invalid header line for tutorial {}, expected 2 parameters", fileName); + log::error("invalid header line for tutorial {}, expected 2 parameters", + fileName); continue; } - QAction *tutAction = new QAction(params.at(0), tutorialMenu); + QAction* tutAction = new QAction(params.at(0), tutorialMenu); tutAction->setData(fileName); tutorials.push_back(std::make_pair(params.at(1).toInt(), tutAction)); } } std::sort(tutorials.begin(), tutorials.end(), - [] (const ActionList::value_type &LHS, const ActionList::value_type &RHS) { - return LHS.first < RHS.first; } ); + [](const ActionList::value_type& LHS, const ActionList::value_type& RHS) { + return LHS.first < RHS.first; + }); for (auto iter = tutorials.begin(); iter != tutorials.end(); ++iter) { connect(iter->second, SIGNAL(triggered()), this, SLOT(tutorialTriggered())); @@ -1105,8 +1152,8 @@ void MainWindow::createHelpMenu() bool MainWindow::addProfile() { - QComboBox *profileBox = findChild("profileBox"); - bool okClicked = false; + QComboBox* profileBox = findChild("profileBox"); + bool okClicked = false; QString name = QInputDialog::getText(this, tr("Name"), tr("Please enter a name for the new profile"), @@ -1127,7 +1174,8 @@ bool MainWindow::addProfile() void MainWindow::hookUpWindowTutorials() { - QDirIterator dirIter(QApplication::applicationDirPath() + "/tutorials", QStringList("*.js"), QDir::Files); + QDirIterator dirIter(QApplication::applicationDirPath() + "/tutorials", + QStringList("*.js"), QDir::Files); while (dirIter.hasNext()) { dirIter.next(); QString fileName = dirIter.fileName(); @@ -1153,11 +1201,11 @@ bool MainWindow::shouldStartTutorial() const } QMessageBox dlg( - QMessageBox::Question, tr("Show tutorial?"), - tr("You are starting Mod Organizer for the first time. " - "Do you want to show a tutorial of its basic features? If you choose " - "no you can always start the tutorial from the \"Help\" menu."), - QMessageBox::Yes | QMessageBox::No); + QMessageBox::Question, tr("Show tutorial?"), + tr("You are starting Mod Organizer for the first time. " + "Do you want to show a tutorial of its basic features? If you choose " + "no you can always start the tutorial from the \"Help\" menu."), + QMessageBox::Yes | QMessageBox::No); dlg.setCheckBox(new QCheckBox(tr("Never ask to show tutorials"))); @@ -1170,7 +1218,7 @@ bool MainWindow::shouldStartTutorial() const return (r == QMessageBox::Yes); } -void MainWindow::showEvent(QShowEvent *event) +void MainWindow::showEvent(QShowEvent* event) { QMainWindow::showEvent(event); @@ -1197,7 +1245,8 @@ void MainWindow::showEvent(QShowEvent *event) QString firstStepsTutorial = ToQString(AppConfig::firstStepsTutorial()); if (TutorialManager::instance().hasTutorial(firstStepsTutorial)) { if (shouldStartTutorial()) { - TutorialManager::instance().activateTutorial("MainWindow", firstStepsTutorial); + TutorialManager::instance().activateTutorial("MainWindow", + firstStepsTutorial); } } else { log::error("{} missing", firstStepsTutorial); @@ -1205,13 +1254,17 @@ void MainWindow::showEvent(QShowEvent *event) pos.rx() += ui->toolBar->width() / 2; pos.ry() += ui->toolBar->height(); QWhatsThis::showText(pos, - QObject::tr("Please use \"Help\" from the toolbar to get usage instructions to all elements")); + QObject::tr("Please use \"Help\" from the toolbar to get " + "usage instructions to all elements")); } if (!m_OrganizerCore.managedGame()->getSupportURL().isEmpty()) { QMessageBox::information(this, tr("Game Support Wiki"), - tr("Do you know how to mod this game? Do you need to learn? There's a game support wiki available! " - "Click OK to open the wiki. In the future, you can access this link from the \"Help\" menu."), QMessageBox::Ok); + tr("Do you know how to mod this game? Do you need to " + "learn? There's a game support wiki available! " + "Click OK to open the wiki. In the future, you can " + "access this link from the \"Help\" menu."), + QMessageBox::Ok); gameSupportTriggered(); } @@ -1375,9 +1428,10 @@ void MainWindow::closeEvent(QCloseEvent* event) bool MainWindow::canExit() { if (m_OrganizerCore.downloadManager()->downloadsInProgressNoPause()) { - if (QMessageBox::question(this, tr("Downloads in progress"), - tr("There are still downloads in progress, do you really want to quit?"), - QMessageBox::Yes | QMessageBox::Cancel) == QMessageBox::Cancel) { + if (QMessageBox::question( + this, tr("Downloads in progress"), + tr("There are still downloads in progress, do you really want to quit?"), + QMessageBox::Yes | QMessageBox::Cancel) == QMessageBox::Cancel) { return false; } else { m_OrganizerCore.downloadManager()->pauseAll(); @@ -1406,7 +1460,7 @@ void MainWindow::cleanup() m_MetaSave.waitForFinished(); } -bool MainWindow::eventFilter(QObject *object, QEvent *event) +bool MainWindow::eventFilter(QObject* object, QEvent* event) { if (event->type() == QEvent::StatusTip && object != this) { QMainWindow::event(event); @@ -1416,7 +1470,7 @@ bool MainWindow::eventFilter(QObject *object, QEvent *event) return false; } -void MainWindow::registerPluginTool(IPluginTool *tool, QString name, QMenu *menu) +void MainWindow::registerPluginTool(IPluginTool* tool, QString name, QMenu* menu) { if (!menu) { menu = ui->actionTool->menu(); @@ -1425,20 +1479,22 @@ void MainWindow::registerPluginTool(IPluginTool *tool, QString name, QMenu *menu if (name.isEmpty()) name = tool->displayName(); - QAction *action = new QAction(tool->icon(), name, menu); + QAction* action = new QAction(tool->icon(), name, menu); action->setToolTip(tool->tooltip()); tool->setParentWidget(this); - connect(action, &QAction::triggered, this, [this, tool]() { - try { - tool->display(); - } - catch (const std::exception& e) { - reportError(tr("Plugin \"%1\" failed: %2").arg(tool->localizedName()).arg(e.what())); - } - catch (...) { - reportError(tr("Plugin \"%1\" failed").arg(tool->localizedName())); - } - }, Qt::QueuedConnection); + connect( + action, &QAction::triggered, this, + [this, tool]() { + try { + tool->display(); + } catch (const std::exception& e) { + reportError( + tr("Plugin \"%1\" failed: %2").arg(tool->localizedName()).arg(e.what())); + } catch (...) { + reportError(tr("Plugin \"%1\" failed").arg(tool->localizedName())); + } + }, + Qt::QueuedConnection); menu->addAction(action); } @@ -1452,63 +1508,64 @@ void MainWindow::updateToolMenu() // Sort the plugins by display name std::sort(std::begin(toolPlugins), std::end(toolPlugins), - [](IPluginTool *left, IPluginTool *right) { - return left->displayName().toLower() < right->displayName().toLower(); - } - ); + [](IPluginTool* left, IPluginTool* right) { + return left->displayName().toLower() < right->displayName().toLower(); + }); // Remove disabled plugins: - toolPlugins.erase( - std::remove_if(std::begin(toolPlugins), std::end(toolPlugins), [&](auto* tool) { - return !m_PluginContainer.isEnabled(tool); - }), - toolPlugins.end()); + toolPlugins.erase(std::remove_if(std::begin(toolPlugins), std::end(toolPlugins), + [&](auto* tool) { + return !m_PluginContainer.isEnabled(tool); + }), + toolPlugins.end()); // Group the plugins into submenus - QMap>> submenuMap; + QMap>> submenuMap; for (auto toolPlugin : toolPlugins) { QStringList toolName = toolPlugin->displayName().split("/"); - QString submenu = toolName[0]; + QString submenu = toolName[0]; toolName.pop_front(); - submenuMap[submenu].append(QPair(toolName.join("/"), toolPlugin)); + submenuMap[submenu].append( + QPair(toolName.join("/"), toolPlugin)); } // Start registering plugins for (auto submenuKey : submenuMap.keys()) { if (submenuMap[submenuKey].length() > 1) { - QMenu *submenu = new QMenu(submenuKey, this); + QMenu* submenu = new QMenu(submenuKey, this); for (auto info : submenuMap[submenuKey]) { registerPluginTool(info.second, info.first, submenu); } ui->actionTool->menu()->addMenu(submenu); - } - else { + } else { registerPluginTool(submenuMap[submenuKey].front().second); } } } -void MainWindow::registerModPage(IPluginModPage *modPage) +void MainWindow::registerModPage(IPluginModPage* modPage) { - QAction *action = new QAction(modPage->icon(), modPage->displayName(), this); - connect(action, &QAction::triggered, this, [this, modPage]() { - if (modPage->useIntegratedBrowser()) { + QAction* action = new QAction(modPage->icon(), modPage->displayName(), this); + connect( + action, &QAction::triggered, this, + [this, modPage]() { + if (modPage->useIntegratedBrowser()) { - if (!m_IntegratedBrowser) { - m_IntegratedBrowser.reset(new BrowserDialog); + if (!m_IntegratedBrowser) { + m_IntegratedBrowser.reset(new BrowserDialog); - connect( - m_IntegratedBrowser.get(), SIGNAL(requestDownload(QUrl, QNetworkReply*)), - &m_OrganizerCore, SLOT(requestDownload(QUrl, QNetworkReply*))); - } + connect(m_IntegratedBrowser.get(), + SIGNAL(requestDownload(QUrl, QNetworkReply*)), &m_OrganizerCore, + SLOT(requestDownload(QUrl, QNetworkReply*))); + } - m_IntegratedBrowser->setWindowTitle(modPage->displayName()); - m_IntegratedBrowser->openUrl(modPage->pageURL()); - } - else { - shell::Open(QUrl(modPage->pageURL())); - } - }, Qt::QueuedConnection); + m_IntegratedBrowser->setWindowTitle(modPage->displayName()); + m_IntegratedBrowser->openUrl(modPage->pageURL()); + } else { + shell::Open(QUrl(modPage->pageURL())); + } + }, + Qt::QueuedConnection); ui->actionModPage->menu()->addAction(action); } @@ -1526,15 +1583,16 @@ bool MainWindow::registerNexusPage(const QString& gameName) return false; // Create an action - QAction* action = new QAction( - plugin->gameIcon(), - QObject::tr("Visit %1 on Nexus").arg(gameName), - this); + QAction* action = new QAction(plugin->gameIcon(), + QObject::tr("Visit %1 on Nexus").arg(gameName), this); // Bind the action - connect(action, &QAction::triggered, this, [this, gameURL]() { - shell::Open(QUrl(gameURL)); - }, Qt::QueuedConnection); + connect( + action, &QAction::triggered, this, + [this, gameURL]() { + shell::Open(QUrl(gameURL)); + }, + Qt::QueuedConnection); // Add the action ui->actionModPage->menu()->addAction(action); @@ -1548,21 +1606,22 @@ void MainWindow::updateModPageMenu() ui->actionModPage->menu()->clear(); // Determine the loaded mod page plugins - std::vector modPagePlugins = m_PluginContainer.plugins(); + std::vector modPagePlugins = + m_PluginContainer.plugins(); // Sort the plugins by display name std::sort(std::begin(modPagePlugins), std::end(modPagePlugins), - [](IPluginModPage* left, IPluginModPage* right) { - return left->displayName().toLower() < right->displayName().toLower(); - } - ); + [](IPluginModPage* left, IPluginModPage* right) { + return left->displayName().toLower() < right->displayName().toLower(); + }); // Remove disabled plugins - modPagePlugins.erase( - std::remove_if(std::begin(modPagePlugins), std::end(modPagePlugins), [&](auto* tool) { - return !m_PluginContainer.isEnabled(tool); - }), - modPagePlugins.end()); + modPagePlugins.erase(std::remove_if(std::begin(modPagePlugins), + std::end(modPagePlugins), + [&](auto* tool) { + return !m_PluginContainer.isEnabled(tool); + }), + modPagePlugins.end()); for (auto* modPagePlugin : modPagePlugins) { registerModPage(modPagePlugin); @@ -1576,8 +1635,7 @@ void MainWindow::updateModPageMenu() registeredSources << gameShortName; // Add the primary sources - for (auto gameName : m_OrganizerCore.managedGame()->primarySources()) - { + for (auto gameName : m_OrganizerCore.managedGame()->primarySources()) { if (!registeredSources.contains(gameName) && registerNexusPage(gameName)) registeredSources << gameName; } @@ -1589,18 +1647,17 @@ void MainWindow::updateModPageMenu() // Add the secondary games (sorted) QStringList secondaryGames = m_OrganizerCore.managedGame()->validShortNames(); secondaryGames.sort(Qt::CaseInsensitive); - for (auto gameName : secondaryGames) - { + for (auto gameName : secondaryGames) { if (!registeredSources.contains(gameName) && registerNexusPage(gameName)) registeredSources << gameName; } // No mod page plugin and the menu was visible - bool keepOriginalAction = modPagePlugins.size() == 0 && registeredSources.length() <= 1; + bool keepOriginalAction = + modPagePlugins.size() == 0 && registeredSources.length() <= 1; if (keepOriginalAction) { ui->toolBar->insertAction(ui->actionAdd_Profile, ui->actionNexus); - } - else { + } else { ui->toolBar->removeAction(ui->actionNexus); } ui->actionModPage->setVisible(!keepOriginalAction); @@ -1608,7 +1665,7 @@ void MainWindow::updateModPageMenu() void MainWindow::startExeAction() { - QAction *action = qobject_cast(sender()); + QAction* action = qobject_cast(sender()); if (action == nullptr) { log::error("not an action?"); @@ -1618,7 +1675,7 @@ void MainWindow::startExeAction() const auto& list = *m_OrganizerCore.executablesList(); const auto title = action->text(); - auto itor = list.find(title); + auto itor = list.find(title); if (itor == list.end()) { log::warn("startExeAction(): executable '{}' not found", title); @@ -1626,12 +1683,14 @@ void MainWindow::startExeAction() } action->setEnabled(false); - Guard g([&]{ action->setEnabled(true); }); + Guard g([&] { + action->setEnabled(true); + }); m_OrganizerCore.processRunner() - .setFromExecutable(*itor) - .setWaitForCompletion(ProcessRunner::TriggerRefresh) - .run(); + .setFromExecutable(*itor) + .setWaitForCompletion(ProcessRunner::TriggerRefresh) + .run(); } void MainWindow::activateSelectedProfile() @@ -1655,8 +1714,7 @@ void MainWindow::on_profileBox_currentIndexChanged(int index) m_OldProfileIndex = index; // select has changed, save stuff - if ((previousIndex != -1) && - (m_OrganizerCore.currentProfile() != nullptr) && + if ((previousIndex != -1) && (m_OrganizerCore.currentProfile() != nullptr) && m_OrganizerCore.currentProfile()->exists()) { m_OrganizerCore.saveCurrentLists(); } @@ -1664,10 +1722,9 @@ void MainWindow::on_profileBox_currentIndexChanged(int index) // Avoid doing any refresh if currentProfile is already set but previous // index was -1 as it means that this is happening during initialization so // everything has already been set. - if (previousIndex == -1 - && m_OrganizerCore.currentProfile() != nullptr - && m_OrganizerCore.currentProfile()->exists() - && ui->profileBox->currentText() == m_OrganizerCore.currentProfile()->name()){ + if (previousIndex == -1 && m_OrganizerCore.currentProfile() != nullptr && + m_OrganizerCore.currentProfile()->exists() && + ui->profileBox->currentText() == m_OrganizerCore.currentProfile()->name()) { return; } @@ -1677,7 +1734,6 @@ void MainWindow::on_profileBox_currentIndexChanged(int index) ui->profileBox->setCurrentIndex(ui->profileBox->count() - 1); } - // handle item if (ui->profileBox->currentIndex() == 0) { // remember the profile name that was selected before, previousIndex can't @@ -1718,17 +1774,17 @@ void MainWindow::on_profileBox_currentIndexChanged(int index) return; } - activateSelectedProfile(); - LocalSavegames *saveGames = m_OrganizerCore.managedGame()->feature(); + LocalSavegames* saveGames = m_OrganizerCore.managedGame()->feature(); if (saveGames != nullptr) { if (saveGames->prepareProfile(m_OrganizerCore.currentProfile())) { m_SavesTab->refreshSaveList(); } } - BSAInvalidation *invalidation = m_OrganizerCore.managedGame()->feature(); + BSAInvalidation* invalidation = + m_OrganizerCore.managedGame()->feature(); if (invalidation != nullptr) { if (invalidation->prepareProfile(m_OrganizerCore.currentProfile())) { QTimer::singleShot(5, &m_OrganizerCore, SLOT(profileRefresh())); @@ -1756,7 +1812,9 @@ bool MainWindow::refreshProfiles(bool selectProfile, QString newProfile) try { profileBox->addItem(profileIter.fileName()); } catch (const std::runtime_error& error) { - reportError(QObject::tr("failed to parse profile %1: %2").arg(profileIter.fileName()).arg(error.what())); + reportError(QObject::tr("failed to parse profile %1: %2") + .arg(profileIter.fileName()) + .arg(error.what())); } } @@ -1767,8 +1825,7 @@ bool MainWindow::refreshProfiles(bool selectProfile, QString newProfile) if (profileBox->count() > 1) { if (newProfile.isEmpty()) { profileBox->setCurrentText(currentProfileName); - } - else { + } else { profileBox->setCurrentText(newProfile); } if (profileBox->currentIndex() == 0) { @@ -1781,7 +1838,7 @@ bool MainWindow::refreshProfiles(bool selectProfile, QString newProfile) void MainWindow::refreshExecutablesList() { - QAbstractItemModel *model = ui->executablesListBox->model(); + QAbstractItemModel* model = ui->executablesListBox->model(); auto add = [&](const QString& title, const QFileInfo& binary) { QIcon icon; @@ -1793,13 +1850,11 @@ void MainWindow::refreshExecutablesList() const auto i = ui->executablesListBox->count() - 1; - model->setData( - model->index(i, 0), - QSize(0, ui->executablesListBox->iconSize().height() + 4), - Qt::SizeHintRole); + model->setData(model->index(i, 0), + QSize(0, ui->executablesListBox->iconSize().height() + 4), + Qt::SizeHintRole); }; - ui->executablesListBox->setEnabled(false); ui->executablesListBox->clear(); @@ -1821,7 +1876,8 @@ void MainWindow::refreshExecutablesList() ui->executablesListBox->setEnabled(true); } -static bool BySortValue(const std::pair &LHS, const std::pair &RHS) +static bool BySortValue(const std::pair& LHS, + const std::pair& RHS) { return LHS.first < RHS.first; } @@ -1836,27 +1892,32 @@ static QStringList toStringList(InputIterator current, InputIterator end) return result; } -void MainWindow::updateBSAList(const QStringList &defaultArchives, const QStringList &activeArchives) +void MainWindow::updateBSAList(const QStringList& defaultArchives, + const QStringList& activeArchives) { m_DefaultArchives = defaultArchives; ui->bsaList->clear(); ui->bsaList->header()->setSectionResizeMode(QHeaderView::ResizeToContents); std::vector> items; - BSAInvalidation * invalidation = m_OrganizerCore.managedGame()->feature(); + BSAInvalidation* invalidation = + m_OrganizerCore.managedGame()->feature(); std::vector files = m_OrganizerCore.directoryStructure()->getFiles(); - QStringList plugins = m_OrganizerCore.findFiles("", [](const QString &fileName) -> bool { - return fileName.endsWith(".esp", Qt::CaseInsensitive) - || fileName.endsWith(".esm", Qt::CaseInsensitive) - || fileName.endsWith(".esl", Qt::CaseInsensitive); - }); + QStringList plugins = + m_OrganizerCore.findFiles("", [](const QString& fileName) -> bool { + return fileName.endsWith(".esp", Qt::CaseInsensitive) || + fileName.endsWith(".esm", Qt::CaseInsensitive) || + fileName.endsWith(".esl", Qt::CaseInsensitive); + }); - auto hasAssociatedPlugin = [&](const QString &bsaName) -> bool { - for (const QString &pluginName : plugins) { + auto hasAssociatedPlugin = [&](const QString& bsaName) -> bool { + for (const QString& pluginName : plugins) { QFileInfo pluginInfo(pluginName); - if (bsaName.startsWith(QFileInfo(pluginName).completeBaseName(), Qt::CaseInsensitive) - && (m_OrganizerCore.pluginList()->state(pluginInfo.fileName()) == IPluginList::STATE_ACTIVE)) { + if (bsaName.startsWith(QFileInfo(pluginName).completeBaseName(), + Qt::CaseInsensitive) && + (m_OrganizerCore.pluginList()->state(pluginInfo.fileName()) == + IPluginList::STATE_ACTIVE)) { return true; } } @@ -1870,28 +1931,29 @@ void MainWindow::updateBSAList(const QStringList &defaultArchives, const QString int index = activeArchives.indexOf(fileInfo.fileName()); if (index == -1) { index = 0xFFFF; - } - else { + } else { index += 2; } - if ((invalidation != nullptr) && invalidation->isInvalidationBSA(fileInfo.fileName())) { + if ((invalidation != nullptr) && + invalidation->isInvalidationBSA(fileInfo.fileName())) { index = 1; } int originId = current->getOrigin(); - FilesOrigin & origin = m_OrganizerCore.directoryStructure()->getOriginByID(originId); + FilesOrigin& origin = + m_OrganizerCore.directoryStructure()->getOriginByID(originId); - QTreeWidgetItem * newItem = new QTreeWidgetItem(QStringList() - << fileInfo.fileName() - << ToQString(origin.getName())); + QTreeWidgetItem* newItem = new QTreeWidgetItem( + QStringList() << fileInfo.fileName() << ToQString(origin.getName())); newItem->setData(0, Qt::UserRole, index); newItem->setData(1, Qt::UserRole, originId); - newItem->setFlags(newItem->flags() & ~(Qt::ItemIsDropEnabled | Qt::ItemIsUserCheckable)); + newItem->setFlags(newItem->flags() & + ~(Qt::ItemIsDropEnabled | Qt::ItemIsUserCheckable)); newItem->setCheckState(0, (index != -1) ? Qt::Checked : Qt::Unchecked); newItem->setData(0, Qt::UserRole, false); - if (m_OrganizerCore.settings().game().forceEnableCoreFiles() - && defaultArchives.contains(fileInfo.fileName())) { + if (m_OrganizerCore.settings().game().forceEnableCoreFiles() && + defaultArchives.contains(fileInfo.fileName())) { newItem->setCheckState(0, Qt::Checked); newItem->setDisabled(true); newItem->setData(0, Qt::UserRole, true); @@ -1905,7 +1967,8 @@ void MainWindow::updateBSAList(const QStringList &defaultArchives, const QString newItem->setCheckState(0, Qt::Unchecked); newItem->setDisabled(true); } - if (index < 0) index = 0; + if (index < 0) + index = 0; UINT32 sortValue = ((origin.getPriority() & 0xFFFF) << 16) | (index & 0xFFFF); items.push_back(std::make_pair(sortValue, newItem)); @@ -1916,7 +1979,8 @@ void MainWindow::updateBSAList(const QStringList &defaultArchives, const QString for (auto iter = items.begin(); iter != items.end(); ++iter) { int originID = iter->second->data(1, Qt::UserRole).toInt(); - const FilesOrigin& origin = m_OrganizerCore.directoryStructure()->getOriginByID(originID); + const FilesOrigin& origin = + m_OrganizerCore.directoryStructure()->getOriginByID(originID); QString modName; const unsigned int modIndex = ModInfo::getIndex(ToQString(origin.getName())); @@ -1925,15 +1989,15 @@ void MainWindow::updateBSAList(const QStringList &defaultArchives, const QString modName = UnmanagedModName(); } else { ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); - modName = modInfo->name(); + modName = modInfo->name(); } - QList items = ui->bsaList->findItems(modName, Qt::MatchFixedString); - QTreeWidgetItem * subItem = nullptr; + QList items = + ui->bsaList->findItems(modName, Qt::MatchFixedString); + QTreeWidgetItem* subItem = nullptr; if (items.length() > 0) { subItem = items.at(0); - } - else { + } else { subItem = new QTreeWidgetItem(QStringList(modName)); subItem->setFlags(subItem->flags() & ~Qt::ItemIsDragEnabled); ui->bsaList->addTopLevelItem(subItem); @@ -1946,29 +2010,32 @@ void MainWindow::updateBSAList(const QStringList &defaultArchives, const QString void MainWindow::checkBSAList() { - DataArchives * archives = m_OrganizerCore.managedGame()->feature(); + DataArchives* archives = m_OrganizerCore.managedGame()->feature(); if (archives != nullptr) { ui->bsaList->blockSignals(true); - ON_BLOCK_EXIT([&]() { ui->bsaList->blockSignals(false); }); + ON_BLOCK_EXIT([&]() { + ui->bsaList->blockSignals(false); + }); QStringList defaultArchives = archives->archives(m_OrganizerCore.currentProfile()); bool warning = false; for (int i = 0; i < ui->bsaList->topLevelItemCount(); ++i) { - bool modWarning = false; - QTreeWidgetItem * tlItem = ui->bsaList->topLevelItem(i); + bool modWarning = false; + QTreeWidgetItem* tlItem = ui->bsaList->topLevelItem(i); for (int j = 0; j < tlItem->childCount(); ++j) { - QTreeWidgetItem * item = tlItem->child(j); - QString filename = item->text(0); + QTreeWidgetItem* item = tlItem->child(j); + QString filename = item->text(0); item->setIcon(0, QIcon()); item->setToolTip(0, QString()); if (item->checkState(0) == Qt::Unchecked) { if (defaultArchives.contains(filename)) { item->setIcon(0, QIcon(":/MO/gui/warning")); - item->setToolTip(0, tr("This bsa is enabled in the ini file so it may be required!")); + item->setToolTip( + 0, tr("This bsa is enabled in the ini file so it may be required!")); modWarning = true; } } @@ -2001,7 +2068,7 @@ void MainWindow::saveModMetas() void MainWindow::fixCategories() { for (unsigned int i = 0; i < ModInfo::getNumMods(); ++i) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(i); + ModInfo::Ptr modInfo = ModInfo::getByIndex(i); std::set categories = modInfo->getCategories(); for (std::set::iterator iter = categories.begin(); iter != categories.end(); ++iter) { @@ -2019,20 +2086,19 @@ void MainWindow::setupNetworkProxy(bool activate) void MainWindow::activateProxy(bool activate) { - QProgressDialog busyDialog(tr("Activating Network Proxy"), QString(), 0, 0, parentWidget()); - busyDialog.setWindowFlags(busyDialog.windowFlags() & ~Qt::WindowContextHelpButtonHint); + QProgressDialog busyDialog(tr("Activating Network Proxy"), QString(), 0, 0, + parentWidget()); + busyDialog.setWindowFlags(busyDialog.windowFlags() & + ~Qt::WindowContextHelpButtonHint); busyDialog.setWindowModality(Qt::WindowModal); busyDialog.show(); QFutureWatcher futureWatcher; QEventLoop loop; - connect(&futureWatcher, &QFutureWatcher::finished, - &loop, &QEventLoop::quit, + connect(&futureWatcher, &QFutureWatcher::finished, &loop, &QEventLoop::quit, Qt::QueuedConnection); - futureWatcher.setFuture( - QtConcurrent::run(MainWindow::setupNetworkProxy, activate) - ); + futureWatcher.setFuture(QtConcurrent::run(MainWindow::setupNetworkProxy, activate)); // wait for setupNetworkProxy while keeping ui responsive loop.exec(); @@ -2084,11 +2150,12 @@ void MainWindow::readSettings() } } -void MainWindow::processUpdates() { - auto& settings = m_OrganizerCore.settings(); +void MainWindow::processUpdates() +{ + auto& settings = m_OrganizerCore.settings(); const auto earliest = QVersionNumber::fromString("2.1.2").normalized(); - const auto lastVersion = settings.version().value_or(earliest); + const auto lastVersion = settings.version().value_or(earliest); const auto currentVersion = m_OrganizerCore.getVersion().asQVersionNumber(); m_LastVersion = lastVersion; @@ -2102,24 +2169,25 @@ void MainWindow::processUpdates() { if (lastVersion < QVersionNumber(2, 2, 1)) { // hide new columns by default - for (int i=DownloadList::COL_MODNAME; idownloadView->header()->hideSection(i); } } if (lastVersion < QVersionNumber(2, 3)) { - for (int i=1; idataTree->header()->count(); ++i) + for (int i = 1; i < ui->dataTree->header()->count(); ++i) ui->dataTree->setColumnWidth(i, 150); } } if (currentVersion < lastVersion) { - const auto text = tr( - "Notice: Your current MO version (%1) is lower than the previously used one (%2). " - "The GUI may not downgrade gracefully, so you may experience oddities. " - "However, there should be no serious issues.") - .arg(currentVersion.toString()) - .arg(lastVersion.toString()); + const auto text = + tr("Notice: Your current MO version (%1) is lower than the previously used one " + "(%2). " + "The GUI may not downgrade gracefully, so you may experience oddities. " + "However, there should be no serious issues.") + .arg(currentVersion.toString()) + .arg(lastVersion.toString()); log::warn("{}", text); } @@ -2181,12 +2249,14 @@ void MainWindow::on_startButton_clicked() } ui->startButton->setEnabled(false); - Guard g([&]{ ui->startButton->setEnabled(true); }); + Guard g([&] { + ui->startButton->setEnabled(true); + }); m_OrganizerCore.processRunner() - .setFromExecutable(*selectedExecutable) - .setWaitForCompletion(ProcessRunner::TriggerRefresh) - .run(); + .setFromExecutable(*selectedExecutable) + .setWaitForCompletion(ProcessRunner::TriggerRefresh) + .run(); } bool MainWindow::modifyExecutablesDialog(int selection) @@ -2200,7 +2270,7 @@ bool MainWindow::modifyExecutablesDialog(int selection) refreshExecutablesList(); updatePinnedExecutables(); - } catch (const std::exception &e) { + } catch (const std::exception& e) { reportError(e.what()); } @@ -2213,8 +2283,7 @@ void MainWindow::on_executablesListBox_currentIndexChanged(int index) return; } - const int previousIndex = - (m_OldExecutableIndex > 0 ? m_OldExecutableIndex : 1); + const int previousIndex = (m_OldExecutableIndex > 0 ? m_OldExecutableIndex : 1); m_OldExecutableIndex = index; @@ -2257,12 +2326,15 @@ void MainWindow::issueTriggered() void MainWindow::tutorialTriggered() { - QAction *tutorialAction = qobject_cast(sender()); + QAction* tutorialAction = qobject_cast(sender()); if (tutorialAction != nullptr) { if (QMessageBox::question(this, tr("Start Tutorial?"), - tr("You're about to start a tutorial. For technical reasons it's not possible to end " - "the tutorial early. Continue?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - TutorialManager::instance().activateTutorial("MainWindow", tutorialAction->data().toString()); + tr("You're about to start a tutorial. For technical " + "reasons it's not possible to end " + "the tutorial early. Continue?"), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + TutorialManager::instance().activateTutorial("MainWindow", + tutorialAction->data().toString()); } } } @@ -2281,28 +2353,30 @@ void MainWindow::on_actionAdd_Profile_triggered() { for (;;) { ProfilesDialog profilesDialog(m_OrganizerCore.currentProfile()->name(), - m_OrganizerCore, - this); + m_OrganizerCore, this); - // workaround: need to disable monitoring of the saves directory, otherwise the active - // profile directory is locked + // workaround: need to disable monitoring of the saves directory, otherwise the + // active profile directory is locked m_SavesTab->stopMonitorSaves(); profilesDialog.exec(); - m_SavesTab->refreshSaveList(); // since the save list may now be outdated we have to refresh it completely + m_SavesTab->refreshSaveList(); // since the save list may now be outdated we have + // to refresh it completely - if (refreshProfiles(true, profilesDialog.selectedProfile().value_or("")) && !profilesDialog.failed()) { + if (refreshProfiles(true, profilesDialog.selectedProfile().value_or("")) && + !profilesDialog.failed()) { break; } } - LocalSavegames *saveGames = m_OrganizerCore.managedGame()->feature(); + LocalSavegames* saveGames = m_OrganizerCore.managedGame()->feature(); if (saveGames != nullptr) { if (saveGames->prepareProfile(m_OrganizerCore.currentProfile())) { m_SavesTab->refreshSaveList(); } } - BSAInvalidation *invalidation = m_OrganizerCore.managedGame()->feature(); + BSAInvalidation* invalidation = + m_OrganizerCore.managedGame()->feature(); if (invalidation != nullptr) { if (invalidation->prepareProfile(m_OrganizerCore.currentProfile())) { QTimer::singleShot(5, &m_OrganizerCore, SLOT(profileRefresh())); @@ -2312,7 +2386,7 @@ void MainWindow::on_actionAdd_Profile_triggered() void MainWindow::on_actionModify_Executables_triggered() { - const auto sel = (m_OldExecutableIndex > 0 ? m_OldExecutableIndex - 1 : 0); + const auto sel = (m_OldExecutableIndex > 0 ? m_OldExecutableIndex - 1 : 0); if (modifyExecutablesDialog(sel)) { const auto newCount = ui->executablesListBox->count(); @@ -2337,13 +2411,13 @@ void MainWindow::refresherProgress(const DirectoryRefreshProgress* p) void MainWindow::onDirectoryStructureChanged() { - // some problem-reports may rely on the virtual directory tree so they need to be updated - // now + // some problem-reports may rely on the virtual directory tree so they need to be + // updated now scheduleCheckForProblems(); m_DataTab->updateTree(); } -void MainWindow::modInstalled(const QString &modName) +void MainWindow::modInstalled(const QString& modName) { if (!m_OrganizerCore.settings().interface().checkUpdateAfterInstallation()) { return; @@ -2356,7 +2430,8 @@ void MainWindow::modInstalled(const QString &modName) } // force an update to happen - ui->modList->actions().checkModsForUpdates({ m_OrganizerCore.modList()->index(index, 0) }); + ui->modList->actions().checkModsForUpdates( + {m_OrganizerCore.modList()->index(index, 0)}); } void MainWindow::importCategories(bool) @@ -2366,17 +2441,17 @@ void MainWindow::importCategories(bool) nexus.requestGameInfo(Settings::instance().game().plugin()->gameShortName(), this, QVariant(), QString()); } -void MainWindow::showMessage(const QString &message) +void MainWindow::showMessage(const QString& message) { MessageDialog::showMessage(message, this); } -void MainWindow::showError(const QString &message) +void MainWindow::showError(const QString& message) { reportError(message); } -void MainWindow::modRenamed(const QString &oldName, const QString &newName) +void MainWindow::modRenamed(const QString& oldName, const QString& newName) { Profile::renameModInAllProfiles(oldName, newName); @@ -2386,23 +2461,27 @@ void MainWindow::modRenamed(const QString &oldName, const QString &newName) // also fix the directory structure try { if (m_OrganizerCore.directoryStructure()->originExists(ToWString(oldName))) { - FilesOrigin &origin = m_OrganizerCore.directoryStructure()->getOriginByName(ToWString(oldName)); + FilesOrigin& origin = + m_OrganizerCore.directoryStructure()->getOriginByName(ToWString(oldName)); origin.setName(ToWString(newName)); } else { - } - } catch (const std::exception &e) { + } catch (const std::exception& e) { reportError(tr("failed to change origin name: %1").arg(e.what())); } } -void MainWindow::fileMoved(const QString &filePath, const QString &oldOriginName, const QString &newOriginName) +void MainWindow::fileMoved(const QString& filePath, const QString& oldOriginName, + const QString& newOriginName) { - const FileEntryPtr filePtr = m_OrganizerCore.directoryStructure()->findFile(ToWString(filePath)); + const FileEntryPtr filePtr = + m_OrganizerCore.directoryStructure()->findFile(ToWString(filePath)); if (filePtr.get() != nullptr) { try { - if (m_OrganizerCore.directoryStructure()->originExists(ToWString(newOriginName))) { - FilesOrigin &newOrigin = m_OrganizerCore.directoryStructure()->getOriginByName(ToWString(newOriginName)); + if (m_OrganizerCore.directoryStructure()->originExists( + ToWString(newOriginName))) { + FilesOrigin& newOrigin = m_OrganizerCore.directoryStructure()->getOriginByName( + ToWString(newOriginName)); QString fullNewPath = ToQString(newOrigin.getPath()) + "\\" + filePath; WIN32_FIND_DATAW findData; @@ -2411,31 +2490,38 @@ void MainWindow::fileMoved(const QString &filePath, const QString &oldOriginName filePtr->addOrigin(newOrigin.getID(), findData.ftCreationTime, L"", -1); FindClose(hFind); } - if (m_OrganizerCore.directoryStructure()->originExists(ToWString(oldOriginName))) { - FilesOrigin &oldOrigin = m_OrganizerCore.directoryStructure()->getOriginByName(ToWString(oldOriginName)); + if (m_OrganizerCore.directoryStructure()->originExists( + ToWString(oldOriginName))) { + FilesOrigin& oldOrigin = m_OrganizerCore.directoryStructure()->getOriginByName( + ToWString(oldOriginName)); 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())); + } 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 { // this is probably not an error, the specified path is likely a directory } } -void MainWindow::modRemoved(const QString &fileName) +void MainWindow::modRemoved(const QString& fileName) { if (!fileName.isEmpty() && !QFileInfo(fileName).isAbsolute()) { m_OrganizerCore.downloadManager()->markUninstalled(fileName); } } -void MainWindow::windowTutorialFinished(const QString &windowName) +void MainWindow::windowTutorialFinished(const QString& windowName) { m_OrganizerCore.settings().interface().setTutorialCompleted(windowName); } -void MainWindow::displayModInformation(ModInfo::Ptr modInfo, unsigned int modIndex, ModInfoTabIDs tabID) +void MainWindow::displayModInformation(ModInfo::Ptr modInfo, unsigned int modIndex, + ModInfoTabIDs tabID) { ui->modList->actions().displayModInformation(modInfo, modIndex, tabID); } @@ -2452,7 +2538,7 @@ void MainWindow::setWindowEnabled(bool enabled) void MainWindow::refreshProfile_activated() { - m_OrganizerCore.profileRefresh(); + m_OrganizerCore.profileRefresh(); } void MainWindow::saveArchiveList() @@ -2460,9 +2546,9 @@ void MainWindow::saveArchiveList() if (m_OrganizerCore.isArchivesInit()) { SafeWriteFile archiveFile(m_OrganizerCore.currentProfile()->getArchivesFileName()); for (int i = 0; i < ui->bsaList->topLevelItemCount(); ++i) { - QTreeWidgetItem * tlItem = ui->bsaList->topLevelItem(i); + QTreeWidgetItem* tlItem = ui->bsaList->topLevelItem(i); for (int j = 0; j < tlItem->childCount(); ++j) { - QTreeWidgetItem * item = tlItem->child(j); + QTreeWidgetItem* item = tlItem->child(j); if (item->checkState(0) == Qt::Checked) { archiveFile->write(item->text(0).toUtf8().append("\r\n")); } @@ -2487,13 +2573,15 @@ void MainWindow::openInstallFolder() void MainWindow::openPluginsFolder() { - QString pluginsPath = QCoreApplication::applicationDirPath() + "/" + ToQString(AppConfig::pluginPath()); + QString pluginsPath = + QCoreApplication::applicationDirPath() + "/" + ToQString(AppConfig::pluginPath()); shell::Explore(pluginsPath); } void MainWindow::openStylesheetsFolder() { - QString ssPath = QCoreApplication::applicationDirPath() + "/" + ToQString(AppConfig::stylesheetsPath()); + QString ssPath = QCoreApplication::applicationDirPath() + "/" + + ToQString(AppConfig::stylesheetsPath()); shell::Explore(ssPath); } @@ -2504,11 +2592,9 @@ void MainWindow::openProfileFolder() void MainWindow::openIniFolder() { - if (m_OrganizerCore.currentProfile()->localSettingsEnabled()) - { + if (m_OrganizerCore.currentProfile()->localSettingsEnabled()) { shell::Explore(m_OrganizerCore.currentProfile()->absolutePath()); - } - else { + } else { shell::Explore(m_OrganizerCore.managedGame()->documentsDirectory()); } } @@ -2533,33 +2619,36 @@ void MainWindow::openMyGamesFolder() shell::Explore(m_OrganizerCore.managedGame()->documentsDirectory()); } -QMenu *MainWindow::openFolderMenu() +QMenu* MainWindow::openFolderMenu() { - QMenu *FolderMenu = new QMenu(this); + QMenu* FolderMenu = new QMenu(this); // game folders that are not necessarily MO-specific - FolderMenu->addAction(tr("Open Game folder"), this, SLOT(openGameFolder())); - FolderMenu->addAction(tr("Open MyGames folder"), this, SLOT(openMyGamesFolder())); + FolderMenu->addAction(tr("Open Game folder"), this, SLOT(openGameFolder())); + FolderMenu->addAction(tr("Open MyGames folder"), this, SLOT(openMyGamesFolder())); FolderMenu->addAction(tr("Open INIs folder"), this, SLOT(openIniFolder())); - FolderMenu->addSeparator(); + FolderMenu->addSeparator(); // MO-specific folders that are related to modding the game - FolderMenu->addAction(tr("Open Instance folder"), this, SLOT(openInstanceFolder())); + FolderMenu->addAction(tr("Open Instance folder"), this, SLOT(openInstanceFolder())); FolderMenu->addAction(tr("Open Mods folder"), this, SLOT(openModsFolder())); - FolderMenu->addAction(tr("Open Profile folder"), this, SLOT(openProfileFolder())); - FolderMenu->addAction(tr("Open Downloads folder"), this, SLOT(openDownloadsFolder())); + FolderMenu->addAction(tr("Open Profile folder"), this, SLOT(openProfileFolder())); + FolderMenu->addAction(tr("Open Downloads folder"), this, SLOT(openDownloadsFolder())); - FolderMenu->addSeparator(); + FolderMenu->addSeparator(); // MO-specific folders that are not directly related to modding and are either // in the installation folder or the instance - FolderMenu->addAction(tr("Open MO2 Install folder"), this, SLOT(openInstallFolder())); - FolderMenu->addAction(tr("Open MO2 Plugins folder"), this, SLOT(openPluginsFolder())); - FolderMenu->addAction(tr("Open MO2 Stylesheets folder"), this, SLOT(openStylesheetsFolder())); - FolderMenu->addAction(tr("Open MO2 Logs folder"), [=] { ui->logList->openLogsFolder(); }); + FolderMenu->addAction(tr("Open MO2 Install folder"), this, SLOT(openInstallFolder())); + FolderMenu->addAction(tr("Open MO2 Plugins folder"), this, SLOT(openPluginsFolder())); + FolderMenu->addAction(tr("Open MO2 Stylesheets folder"), this, + SLOT(openStylesheetsFolder())); + FolderMenu->addAction(tr("Open MO2 Logs folder"), [=] { + ui->logList->openLogsFolder(); + }); - return FolderMenu; + return FolderMenu; } void MainWindow::linkToolbar() @@ -2575,14 +2664,14 @@ void MainWindow::linkToolbar() void MainWindow::linkDesktop() { - if (auto* exe=getSelectedExecutable()) { + if (auto* exe = getSelectedExecutable()) { env::Shortcut(*exe).toggle(env::Shortcut::Desktop); } } void MainWindow::linkMenu() { - if (auto* exe=getSelectedExecutable()) { + if (auto* exe = getSelectedExecutable()) { env::Shortcut(*exe).toggle(env::Shortcut::StartMenu); } } @@ -2599,19 +2688,18 @@ void MainWindow::on_linkButton_pressed() env::Shortcut shortcut(*exe); - m_LinkToolbar->setIcon( - exe->isShownOnToolbar() ? removeIcon : addIcon); + m_LinkToolbar->setIcon(exe->isShownOnToolbar() ? removeIcon : addIcon); - m_LinkDesktop->setIcon( - shortcut.exists(env::Shortcut::Desktop) ? removeIcon : addIcon); + m_LinkDesktop->setIcon(shortcut.exists(env::Shortcut::Desktop) ? removeIcon + : addIcon); - m_LinkStartMenu->setIcon( - shortcut.exists(env::Shortcut::StartMenu) ? removeIcon : addIcon); + m_LinkStartMenu->setIcon(shortcut.exists(env::Shortcut::StartMenu) ? removeIcon + : addIcon); } void MainWindow::on_actionSettings_triggered() { - Settings &settings = m_OrganizerCore.settings(); + Settings& settings = m_OrganizerCore.settings(); QString oldModDirectory(settings.paths().mods()); QString oldCacheDirectory(settings.paths().cache()); @@ -2619,11 +2707,10 @@ void MainWindow::on_actionSettings_triggered() QString oldManagedGameDirectory(settings.game().directory().value_or("")); bool oldDisplayForeign(settings.interface().displayForeign()); bool oldArchiveParsing(settings.archiveParsing()); - bool proxy = settings.network().useProxy(); - DownloadManager *dlManager = m_OrganizerCore.downloadManager(); + bool proxy = settings.network().useProxy(); + DownloadManager* dlManager = m_OrganizerCore.downloadManager(); const bool oldCheckForUpdates = settings.checkForUpdates(); - const int oldMaxDumps = settings.diagnostics().maxCoreDumps(); - + const int oldMaxDumps = settings.diagnostics().maxCoreDumps(); SettingsDialog dialog(&m_PluginContainer, settings, this); dialog.exec(); @@ -2635,21 +2722,23 @@ void MainWindow::on_actionSettings_triggered() } if (e.testFlag(Exit::Restart)) { - const auto r = MOBase::TaskDialog(this) - .title(tr("Restart Mod Organizer")) - .main("Restart Mod Organizer") - .content(tr("Mod Organizer must restart to finish configuration changes")) - .icon(QMessageBox::Question) - .button({tr("Restart"), QMessageBox::Yes}) - .button({tr("Continue"), tr("Some things might be weird."), QMessageBox::No}) - .exec(); + const auto r = + MOBase::TaskDialog(this) + .title(tr("Restart Mod Organizer")) + .main("Restart Mod Organizer") + .content(tr("Mod Organizer must restart to finish configuration changes")) + .icon(QMessageBox::Question) + .button({tr("Restart"), QMessageBox::Yes}) + .button( + {tr("Continue"), tr("Some things might be weird."), QMessageBox::No}) + .exec(); if (r == QMessageBox::Yes) { ExitModOrganizer(e); } } - InstallationManager *instManager = m_OrganizerCore.installationManager(); + InstallationManager* instManager = m_OrganizerCore.installationManager(); instManager->setModsDirectory(settings.paths().mods()); instManager->setDownloadDirectory(settings.paths().downloads()); @@ -2674,21 +2763,17 @@ void MainWindow::on_actionSettings_triggered() } } - if ((settings.paths().mods() != oldModDirectory) - || (settings.interface().displayForeign() != oldDisplayForeign)) { + if ((settings.paths().mods() != oldModDirectory) || + (settings.interface().displayForeign() != oldDisplayForeign)) { m_OrganizerCore.profileRefresh(); } const auto state = settings.archiveParsing(); - if (state != oldArchiveParsing) - { - if (!state) - { + if (state != oldArchiveParsing) { + if (!state) { ui->dataTabShowFromArchives->setCheckState(Qt::Unchecked); ui->dataTabShowFromArchives->setEnabled(false); - } - else - { + } else { ui->dataTabShowFromArchives->setCheckState(Qt::Checked); ui->dataTabShowFromArchives->setEnabled(true); } @@ -2696,8 +2781,7 @@ void MainWindow::on_actionSettings_triggered() } if (settings.paths().cache() != oldCacheDirectory) { - NexusInterface::instance().setCacheDirectory( - settings.paths().cache()); + NexusInterface::instance().setCacheDirectory(settings.paths().cache()); } if (proxy != settings.network().useProxy()) { @@ -2733,30 +2817,31 @@ void MainWindow::onPluginRegistrationChanged() void MainWindow::on_actionNexus_triggered() { - const IPluginGame *game = m_OrganizerCore.managedGame(); - QString gameName = game->gameShortName(); + const IPluginGame* game = m_OrganizerCore.managedGame(); + QString gameName = game->gameShortName(); if (game->gameNexusName().isEmpty() && game->primarySources().count()) gameName = game->primarySources()[0]; shell::Open(QUrl(NexusInterface::instance().getGameURL(gameName))); } -void MainWindow::installTranslator(const QString &name) +void MainWindow::installTranslator(const QString& name) { - QTranslator *translator = new QTranslator(this); - QString fileName = name + "_" + m_CurrentLanguage; + QTranslator* translator = new QTranslator(this); + QString fileName = name + "_" + m_CurrentLanguage; if (!translator->load(fileName, qApp->applicationDirPath() + "/translations")) { if (m_CurrentLanguage.contains(QRegularExpression("^.*_(EN|en)(-.*)?$"))) { log::debug("localization file %s not found", fileName); - } // we don't actually expect localization files for English (en, en-us, en-uk, and any variation thereof) + } // we don't actually expect localization files for English (en, en-us, en-uk, and + // any variation thereof) } qApp->installTranslator(translator); m_Translators.push_back(translator); } -void MainWindow::languageChange(const QString &newLanguage) +void MainWindow::languageChange(const QString& newLanguage) { - for (QTranslator *trans : m_Translators) { + for (QTranslator* trans : m_Translators) { qApp->removeTranslator(trans); } m_Translators.clear(); @@ -2766,7 +2851,7 @@ void MainWindow::languageChange(const QString &newLanguage) installTranslator("qt"); installTranslator("qtbase"); installTranslator(ToQString(AppConfig::translationPrefix())); - for (const QString &fileName : m_PluginContainer.pluginFileNames()) { + for (const QString& fileName : m_PluginContainer.pluginFileNames()) { installTranslator(QFileInfo(fileName).baseName()); } ui->retranslateUi(this); @@ -2780,18 +2865,19 @@ void MainWindow::languageChange(const QString &newLanguage) m_DownloadsTab->update(); } - ui->listOptionsBtn->setMenu(new ModListGlobalContextMenu(m_OrganizerCore, ui->modList, this)); + ui->listOptionsBtn->setMenu( + new ModListGlobalContextMenu(m_OrganizerCore, ui->modList, this)); ui->openFolderMenu->setMenu(openFolderMenu()); } void MainWindow::originModified(int originID) { - FilesOrigin &origin = m_OrganizerCore.directoryStructure()->getOriginByID(originID); + FilesOrigin& origin = m_OrganizerCore.directoryStructure()->getOriginByID(originID); origin.enable(false); DirectoryStats dummy; m_OrganizerCore.directoryStructure()->addFromOrigin( - origin.getName(), origin.getPath(), origin.getPriority(), dummy); + origin.getName(), origin.getPath(), origin.getPriority(), dummy); DirectoryRefresher::cleanStructure(m_OrganizerCore.directoryStructure()); } @@ -2803,7 +2889,7 @@ void MainWindow::updateAvailable() ui->statusBar->setUpdateAvailable(true); } -void MainWindow::motdReceived(const QString &motd) +void MainWindow::motdReceived(const QString& motd) { // don't show motd after 5 seconds, may be annoying. Hopefully the user's // internet connection is faster next time @@ -2829,32 +2915,43 @@ void MainWindow::on_actionExit_triggered() void MainWindow::actionEndorseMO() { - // Normally this would be the managed game but MO2 is only uploaded to the Skyrim SE site right now - IPluginGame * game = m_OrganizerCore.getGame("skyrimse"); - if (!game) return; + // Normally this would be the managed game but MO2 is only uploaded to the Skyrim SE + // site right now + IPluginGame* game = m_OrganizerCore.getGame("skyrimse"); + if (!game) + return; - if (QMessageBox::question(this, tr("Endorse Mod Organizer"), - tr("Do you want to endorse Mod Organizer on %1 now?").arg( - NexusInterface::instance().getGameURL(game->gameShortName())), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + if (QMessageBox::question( + this, tr("Endorse Mod Organizer"), + tr("Do you want to endorse Mod Organizer on %1 now?") + .arg(NexusInterface::instance().getGameURL(game->gameShortName())), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { NexusInterface::instance().requestToggleEndorsement( - game->gameShortName(), game->nexusModOrganizerID(), m_OrganizerCore.getVersion().canonicalString(), true, this, QVariant(), QString()); + game->gameShortName(), game->nexusModOrganizerID(), + m_OrganizerCore.getVersion().canonicalString(), true, this, QVariant(), + QString()); } } void MainWindow::actionWontEndorseMO() { - // Normally this would be the managed game but MO2 is only uploaded to the Skyrim SE site right now - IPluginGame * game = m_OrganizerCore.getGame("skyrimse"); - if (!game) return; + // Normally this would be the managed game but MO2 is only uploaded to the Skyrim SE + // site right now + IPluginGame* game = m_OrganizerCore.getGame("skyrimse"); + if (!game) + return; - if (QMessageBox::question(this, tr("Abstain from Endorsing Mod Organizer"), - tr("Are you sure you want to abstain from endorsing Mod Organizer 2?\n" - "You will have to visit the mod page on the %1 Nexus site to change your mind.").arg( - NexusInterface::instance().getGameURL(game->gameShortName())), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + if (QMessageBox::question( + this, tr("Abstain from Endorsing Mod Organizer"), + tr("Are you sure you want to abstain from endorsing Mod Organizer 2?\n" + "You will have to visit the mod page on the %1 Nexus site to change your " + "mind.") + .arg(NexusInterface::instance().getGameURL(game->gameShortName())), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { NexusInterface::instance().requestToggleEndorsement( - game->gameShortName(), game->nexusModOrganizerID(), m_OrganizerCore.getVersion().canonicalString(), false, this, QVariant(), QString()); + game->gameShortName(), game->nexusModOrganizerID(), + m_OrganizerCore.getVersion().canonicalString(), false, this, QVariant(), + QString()); } } @@ -2872,25 +2969,21 @@ void MainWindow::toggleMO2EndorseState() bool enabled = false; QString text; - switch (s.nexus().endorsementState()) - { - case EndorsementState::Accepted: - { - text = tr("Thank you for endorsing MO2! :)"); - break; - } + switch (s.nexus().endorsementState()) { + case EndorsementState::Accepted: { + text = tr("Thank you for endorsing MO2! :)"); + break; + } - case EndorsementState::Refused: - { - text = tr("Please reconsider endorsing MO2 on Nexus!"); - break; - } + case EndorsementState::Refused: { + text = tr("Please reconsider endorsing MO2 on Nexus!"); + break; + } - case EndorsementState::NoDecision: - { - enabled = true; - break; - } + case EndorsementState::NoDecision: { + enabled = true; + break; + } } ui->actionEndorseMO->menu()->setEnabled(enabled); @@ -2912,17 +3005,21 @@ void MainWindow::nxmEndorsementsAvailable(QVariant userData, QVariant resultData games += m_OrganizerCore.managedGame()->gameShortName(); bool searchedMO2NexusGame = false; for (auto endorsementData : data) { - QVariantMap endorsement = endorsementData.toMap(); - std::pair data = std::make_pair(endorsement["mod_id"].toInt(), endorsement["status"].toString()); - sorted.insert(std::pair>(endorsement["domain_name"].toString(), data)); + QVariantMap endorsement = endorsementData.toMap(); + std::pair data = std::make_pair( + endorsement["mod_id"].toInt(), endorsement["status"].toString()); + sorted.insert(std::pair>( + endorsement["domain_name"].toString(), data)); } for (auto game : games) { - IPluginGame *gamePlugin = m_OrganizerCore.getGame(game); - if (gamePlugin != nullptr && gamePlugin->gameShortName().compare("SkyrimSE", Qt::CaseInsensitive) == 0) + IPluginGame* gamePlugin = m_OrganizerCore.getGame(game); + if (gamePlugin != nullptr && + gamePlugin->gameShortName().compare("SkyrimSE", Qt::CaseInsensitive) == 0) searchedMO2NexusGame = true; auto iter = sorted.equal_range(gamePlugin->gameNexusName()); for (auto result = iter.first; result != iter.second; ++result) { - std::vector modsList = ModInfo::getByModID(result->first, result->second.first); + std::vector modsList = + ModInfo::getByModID(result->first, result->second.first); for (auto mod : modsList) { if (result->second.second == "Endorsed") @@ -2934,9 +3031,10 @@ void MainWindow::nxmEndorsementsAvailable(QVariant userData, QVariant resultData } if (Settings::instance().nexus().endorsementIntegration()) { - if (result->first == "skyrimspecialedition" && result->second.first == gamePlugin->nexusModOrganizerID()) { + if (result->first == "skyrimspecialedition" && + result->second.first == gamePlugin->nexusModOrganizerID()) { m_OrganizerCore.settings().nexus().setEndorsementState( - endorsementStateFromString(result->second.second)); + endorsementStateFromString(result->second.second)); toggleMO2EndorseState(); } @@ -2951,7 +3049,7 @@ void MainWindow::nxmEndorsementsAvailable(QVariant userData, QVariant resultData for (auto result = iter.first; result != iter.second; ++result) { if (result->second.first == gamePlugin->nexusModOrganizerID()) { m_OrganizerCore.settings().nexus().setEndorsementState( - endorsementStateFromString(result->second.second)); + endorsementStateFromString(result->second.second)); toggleMO2EndorseState(); break; @@ -2961,10 +3059,11 @@ void MainWindow::nxmEndorsementsAvailable(QVariant userData, QVariant resultData } } -void MainWindow::nxmUpdateInfoAvailable(QString gameName, QVariant userData, QVariant resultData, int) +void MainWindow::nxmUpdateInfoAvailable(QString gameName, QVariant userData, + QVariant resultData, int) { QString gameNameReal; - for (IPluginGame *game : m_PluginContainer.plugins()) { + for (IPluginGame* game : m_PluginContainer.plugins()) { if (game->gameNexusName() == gameName) { gameNameReal = game->gameShortName(); break; @@ -2973,12 +3072,15 @@ void MainWindow::nxmUpdateInfoAvailable(QString gameName, QVariant userData, QVa QVariantList resultList = resultData.toList(); auto* watcher = new QFutureWatcher(); - QObject::connect(watcher, &QFutureWatcher::finished, [this, watcher]() { - finishUpdateInfo(watcher->result()); - watcher->deleteLater(); - }); + QObject::connect(watcher, &QFutureWatcher::finished, + [this, watcher]() { + finishUpdateInfo(watcher->result()); + watcher->deleteLater(); + }); auto future = QtConcurrent::run([=]() { - return NxmUpdateInfoData{ gameNameReal, ModInfo::filteredMods(gameNameReal, resultList, userData.toBool(), true) }; + return NxmUpdateInfoData{ + gameNameReal, + ModInfo::filteredMods(gameNameReal, resultList, userData.toBool(), true)}; }); watcher->setFuture(future); ui->modList->invalidateFilter(); @@ -2987,32 +3089,37 @@ void MainWindow::nxmUpdateInfoAvailable(QString gameName, QVariant userData, QVa void MainWindow::finishUpdateInfo(const NxmUpdateInfoData& data) { if (data.finalMods.empty()) { - log::info("{}", tr("None of your %1 mods appear to have had recent file updates.").arg(data.game)); + log::info("{}", tr("None of your %1 mods appear to have had recent file updates.") + .arg(data.game)); } std::set> organizedGames; for (auto& mod : data.finalMods) { if (mod->canBeUpdated()) { - organizedGames.insert(std::make_pair(mod->gameName().toLower(), mod->nexusId())); + organizedGames.insert( + std::make_pair(mod->gameName().toLower(), mod->nexusId())); } } if (!data.finalMods.empty() && organizedGames.empty()) - log::warn("{}", tr("All of your mods have been checked recently. We restrict update checks to help preserve your available API requests.")); + log::warn("{}", tr("All of your mods have been checked recently. We restrict " + "update checks to help preserve your available API requests.")); for (const auto& game : organizedGames) { - NexusInterface::instance().requestUpdates(game.second, this, QVariant(), game.first, QString()); + NexusInterface::instance().requestUpdates(game.second, this, QVariant(), game.first, + QString()); } } -void MainWindow::nxmUpdatesAvailable(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID) +void MainWindow::nxmUpdatesAvailable(QString gameName, int modID, QVariant userData, + QVariant resultData, int requestID) { QVariantMap resultInfo = resultData.toMap(); - QList files = resultInfo["files"].toList(); - QList fileUpdates = resultInfo["file_updates"].toList(); + QList files = resultInfo["files"].toList(); + QList fileUpdates = resultInfo["file_updates"].toList(); QString gameNameReal; - for (IPluginGame *game : m_PluginContainer.plugins()) { + for (IPluginGame* game : m_PluginContainer.plugins()) { if (game->gameNexusName() == gameName) { gameNameReal = game->gameShortName(); break; @@ -3025,25 +3132,27 @@ void MainWindow::nxmUpdatesAvailable(QString gameName, int modID, QVariant userD for (auto mod : modsList) { QString validNewVersion; - int newModStatus = -1; + int newModStatus = -1; QString installedFile = QFileInfo(mod->installationFile()).fileName(); if (!installedFile.isEmpty()) { QVariantMap foundFileData; // update the file status - for (auto &file : files) { + for (auto& file : files) { QVariantMap fileData = file.toMap(); - if (fileData["file_name"].toString().compare(installedFile, Qt::CaseInsensitive) == 0) { + if (fileData["file_name"].toString().compare(installedFile, + Qt::CaseInsensitive) == 0) { foundFileData = fileData; - newModStatus = foundFileData["category_id"].toInt(); + newModStatus = foundFileData["category_id"].toInt(); if (newModStatus != NexusInterface::FileStatus::OLD_VERSION && newModStatus != NexusInterface::FileStatus::REMOVED && newModStatus != NexusInterface::FileStatus::ARCHIVED) { - // since the file is still active if there are no updates for it, use this as current version + // since the file is still active if there are no updates for it, use this + // as current version validNewVersion = foundFileData["version"].toString(); } break; @@ -3051,18 +3160,21 @@ void MainWindow::nxmUpdatesAvailable(QString gameName, int modID, QVariant userD } if (foundFileData.isEmpty()) { - // The file was not listed, the file is likely archived and archived files are being hidden on the mod + // The file was not listed, the file is likely archived and archived files are + // being hidden on the mod newModStatus = NexusInterface::FileStatus::ARCHIVED_HIDDEN; } // look for updates of the file int currentUpdateId = -1; - // find installed file ID from the updates list since old filenames are not guaranteed to be unique + // find installed file ID from the updates list since old filenames are not + // guaranteed to be unique for (auto& updateEntry : fileUpdates) { const QVariantMap& updateData = updateEntry.toMap(); - if (installedFile.compare(updateData["old_file_name"].toString(), Qt::CaseInsensitive) == 0) { + if (installedFile.compare(updateData["old_file_name"].toString(), + Qt::CaseInsensitive) == 0) { currentUpdateId = updateData["old_file_id"].toInt(); break; } @@ -3096,7 +3208,7 @@ void MainWindow::nxmUpdatesAvailable(QString gameName, int modID, QVariant userD updateStatus != NexusInterface::FileStatus::ARCHIVED) { // new version is active, so record it - validNewVersion = fileData["version"].toString(); + validNewVersion = fileData["version"].toString(); foundActiveUpdate = true; } break; @@ -3118,8 +3230,7 @@ void MainWindow::nxmUpdatesAvailable(QString gameName, int modID, QVariant userD requiresInfo = true; } } - } - else { + } else { // No installedFile means we don't know what to look at for a version so // just get the global mod version requiresInfo = true; @@ -3139,17 +3250,19 @@ void MainWindow::nxmUpdatesAvailable(QString gameName, int modID, QVariant userD ui->modList->invalidateFilter(); if (requiresInfo) { - NexusInterface::instance().requestModInfo(gameNameReal, modID, this, QVariant(), QString()); + NexusInterface::instance().requestModInfo(gameNameReal, modID, this, QVariant(), + QString()); } } -void MainWindow::nxmModInfoAvailable(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID) +void MainWindow::nxmModInfoAvailable(QString gameName, int modID, QVariant userData, + QVariant resultData, int requestID) { QVariantMap result = resultData.toMap(); QString gameNameReal; bool foundUpdate = false; - for (IPluginGame *game : m_PluginContainer.plugins()) { + for (IPluginGame* game : m_PluginContainer.plugins()) { if (game->gameNexusName() == gameName) { gameNameReal = game->gameShortName(); break; @@ -3159,11 +3272,11 @@ void MainWindow::nxmModInfoAvailable(QString gameName, int modID, QVariant userD std::vector modsList = ModInfo::getByModID(gameNameReal, modID); for (auto mod : modsList) { - QDateTime now = QDateTime::currentDateTimeUtc(); + QDateTime now = QDateTime::currentDateTimeUtc(); QDateTime updateTarget = mod->getExpires(); - // if file is still listed as optional or miscellaneous don't update the version as often optional files are left - // with an older version than the main mod version. + // if file is still listed as optional or miscellaneous don't update the version as + // often optional files are left with an older version than the main mod version. if (!result["version"].toString().isEmpty() && mod->getNexusFileStatus() != NexusInterface::FileStatus::OPTIONAL_FILE && mod->getNexusFileStatus() != NexusInterface::FileStatus::MISCELLANEOUS) { @@ -3177,8 +3290,9 @@ void MainWindow::nxmModInfoAvailable(QString gameName, int modID, QVariant userD mod->setNexusDescription(result["description"].toString()); - if ((mod->endorsedState() != EndorsedState::ENDORSED_NEVER) && (result.contains("endorsement"))) { - QVariantMap endorsement = result["endorsement"].toMap(); + if ((mod->endorsedState() != EndorsedState::ENDORSED_NEVER) && + (result.contains("endorsement"))) { + QVariantMap endorsement = result["endorsement"].toMap(); QString endorsementStatus = endorsement["endorse_status"].toString(); if (endorsementStatus.compare("Endorsed") == 00) @@ -3190,7 +3304,8 @@ void MainWindow::nxmModInfoAvailable(QString gameName, int modID, QVariant userD } mod->setLastNexusQuery(QDateTime::currentDateTimeUtc()); - mod->setNexusLastModified(QDateTime::fromSecsSinceEpoch(result["updated_timestamp"].toInt(), Qt::UTC)); + mod->setNexusLastModified( + QDateTime::fromSecsSinceEpoch(result["updated_timestamp"].toInt(), Qt::UTC)); m_OrganizerCore.modList()->notifyChange(ModInfo::getIndex(mod->name())); } @@ -3213,33 +3328,33 @@ void MainWindow::nxmEndorsementToggled(QString, int, QVariant, QVariant resultDa const auto s = endorsementStateFromString(itor->toString()); - switch (s) - { - case EndorsementState::Accepted: - { - QMessageBox::information(this, tr("Thank you!"), tr("Thank you for your endorsement!")); - break; - } + switch (s) { + case EndorsementState::Accepted: { + QMessageBox::information(this, tr("Thank you!"), + tr("Thank you for your endorsement!")); + break; + } - case EndorsementState::Refused: - { - // don't spam message boxes if the user doesn't want to endorse - log::info("Mod Organizer will not be endorsed and will no longer ask you to endorse."); - break; - } + case EndorsementState::Refused: { + // don't spam message boxes if the user doesn't want to endorse + log::info( + "Mod Organizer will not be endorsed and will no longer ask you to endorse."); + break; + } - case EndorsementState::NoDecision: - { - log::error("bad status '{}' in endorsement response", itor->toString()); - return; - } + case EndorsementState::NoDecision: { + log::error("bad status '{}' in endorsement response", itor->toString()); + return; + } } m_OrganizerCore.settings().nexus().setEndorsementState(s); toggleMO2EndorseState(); - if (!disconnect(sender(), SIGNAL(nxmEndorsementToggled(QString, int, QVariant, QVariant, int)), - this, SLOT(nxmEndorsementToggled(QString, int, QVariant, QVariant, int)))) { + if (!disconnect(sender(), + SIGNAL(nxmEndorsementToggled(QString, int, QVariant, QVariant, int)), + this, + SLOT(nxmEndorsementToggled(QString, int, QVariant, QVariant, int)))) { log::error("failed to disconnect endorsement slot"); } } @@ -3256,11 +3371,12 @@ void MainWindow::nxmTrackedModsAvailable(QVariant userData, QVariant resultData, if (modInfo->nexusId() <= 0) continue; - bool found = false; + bool found = false; auto resultsList = resultData.toList(); for (auto item : resultsList) { auto results = item.toMap(); - if ((gameNames[results["domain_name"].toString()].compare(modInfo->gameName(), Qt::CaseInsensitive) == 0) && + if ((gameNames[results["domain_name"].toString()].compare( + modInfo->gameName(), Qt::CaseInsensitive) == 0) && (results["mod_id"].toInt() == modInfo->nexusId())) { found = true; break; @@ -3276,12 +3392,14 @@ void MainWindow::nxmDownloadURLs(QString, int, int, QVariant, QVariant resultDat { auto servers = m_OrganizerCore.settings().network().servers(); - for (const QVariant &var : resultData.toList()) { + for (const QVariant& var : resultData.toList()) { const QVariantMap map = var.toMap(); const auto name = map["short_name"].toString(); - const auto isPremium = map["name"].toString().contains("Premium", Qt::CaseInsensitive); - const auto isCDN = map["short_name"].toString().contains("CDN", Qt::CaseInsensitive); + const auto isPremium = + map["name"].toString().contains("Premium", Qt::CaseInsensitive); + const auto isCDN = + map["short_name"].toString().contains("CDN", Qt::CaseInsensitive); bool found = false; @@ -3328,12 +3446,16 @@ void MainWindow::nxmGameInfoAvailable(QString gameName, QVariant, QVariant resul } } -void MainWindow::nxmRequestFailed(QString gameName, int modID, int, QVariant, int, int errorCode, const QString &errorString) +void MainWindow::nxmRequestFailed(QString gameName, int modID, int, QVariant, int, + int errorCode, const QString& errorString) { - if (errorCode == QNetworkReply::ContentAccessDenied || errorCode == QNetworkReply::ContentNotFoundError) { - log::debug("{}", tr("Mod ID %1 no longer seems to be available on Nexus.").arg(modID)); + if (errorCode == QNetworkReply::ContentAccessDenied || + errorCode == QNetworkReply::ContentNotFoundError) { + log::debug("{}", + tr("Mod ID %1 no longer seems to be available on Nexus.").arg(modID)); - // update last checked timestamp on orphaned mods as well to avoid repeating requests + // update last checked timestamp on orphaned mods as well to avoid repeating + // requests QString gameNameReal; for (IPluginGame* game : m_PluginContainer.plugins()) { if (game->gameNexusName() == gameName) { @@ -3347,12 +3469,15 @@ void MainWindow::nxmRequestFailed(QString gameName, int modID, int, QVariant, in mod->setLastNexusQuery(QDateTime::currentDateTimeUtc()); } } else { - MessageDialog::showMessage(tr("Error %1: Request to Nexus failed: %2").arg(errorCode).arg(errorString), this); + MessageDialog::showMessage( + tr("Error %1: Request to Nexus failed: %2").arg(errorCode).arg(errorString), + this); } } -BSA::EErrorCode MainWindow::extractBSA(BSA::Archive &archive, BSA::Folder::Ptr folder, const QString &destination, - QProgressDialog &progress) +BSA::EErrorCode MainWindow::extractBSA(BSA::Archive& archive, BSA::Folder::Ptr folder, + const QString& destination, + QProgressDialog& progress) { QDir().mkdir(destination); BSA::EErrorCode result = BSA::ERROR_NONE; @@ -3374,16 +3499,19 @@ BSA::EErrorCode MainWindow::extractBSA(BSA::Archive &archive, BSA::Folder::Ptr f } if (result != BSA::ERROR_NONE) { - if (QMessageBox::critical(this, tr("Error"), tr("failed to extract %1 (errorcode %2)").arg(errorFile).arg(result), - QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Cancel) { + if (QMessageBox::critical( + this, tr("Error"), + tr("failed to extract %1 (errorcode %2)").arg(errorFile).arg(result), + QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Cancel) { return result; } } for (unsigned int i = 0; i < folder->getNumSubFolders(); ++i) { BSA::Folder::Ptr subFolder = folder->getSubFolder(i); - BSA::EErrorCode res = extractBSA(archive, subFolder, - destination.mid(0).append("/").append(subFolder->getName().c_str()), progress); + BSA::EErrorCode res = extractBSA( + archive, subFolder, + destination.mid(0).append("/").append(subFolder->getName().c_str()), progress); if (res != BSA::ERROR_NONE) { return res; } @@ -3391,8 +3519,8 @@ BSA::EErrorCode MainWindow::extractBSA(BSA::Archive &archive, BSA::Folder::Ptr f return BSA::ERROR_NONE; } - -bool MainWindow::extractProgress(QProgressDialog &progress, int percentage, std::string fileName) +bool MainWindow::extractProgress(QProgressDialog& progress, int percentage, + std::string fileName) { progress.setLabelText(fileName.c_str()); progress.setValue(percentage); @@ -3400,30 +3528,37 @@ bool MainWindow::extractProgress(QProgressDialog &progress, int percentage, std: return !progress.wasCanceled(); } - void MainWindow::extractBSATriggered(QTreeWidgetItem* item) { using namespace boost::placeholders; QString origin; - QString targetFolder = FileDialogMemory::getExistingDirectory("extractBSA", this, tr("Extract BSA")); + QString targetFolder = + FileDialogMemory::getExistingDirectory("extractBSA", this, tr("Extract BSA")); QStringList archives = {}; if (!targetFolder.isEmpty()) { if (!item->parent()) { for (int i = 0; i < item->childCount(); ++i) { archives.append(item->child(i)->text(0)); } - origin = QDir::fromNativeSeparators(ToQString(m_OrganizerCore.directoryStructure()->getOriginByName(ToWString(item->text(0))).getPath())); + origin = QDir::fromNativeSeparators( + ToQString(m_OrganizerCore.directoryStructure() + ->getOriginByName(ToWString(item->text(0))) + .getPath())); } else { - origin = QDir::fromNativeSeparators(ToQString(m_OrganizerCore.directoryStructure()->getOriginByName(ToWString(item->text(1))).getPath())); - archives = QStringList({ item->text(0) }); + origin = QDir::fromNativeSeparators( + ToQString(m_OrganizerCore.directoryStructure() + ->getOriginByName(ToWString(item->text(1))) + .getPath())); + archives = QStringList({item->text(0)}); } for (auto archiveName : archives) { BSA::Archive archive; QString archivePath = QString("%1\\%2").arg(origin).arg(archiveName); - BSA::EErrorCode result = archive.read(archivePath.toLocal8Bit().constData(), true); + BSA::EErrorCode result = + archive.read(archivePath.toLocal8Bit().constData(), true); if ((result != BSA::ERROR_NONE) && (result != BSA::ERROR_INVALIDHASHES)) { reportError(tr("failed to read %1: %2").arg(archivePath).arg(result)); return; @@ -3433,10 +3568,13 @@ void MainWindow::extractBSATriggered(QTreeWidgetItem* item) progress.setMaximum(100); progress.setValue(0); progress.show(); - archive.extractAll(QDir::toNativeSeparators(targetFolder).toLocal8Bit().constData(), - boost::bind(&MainWindow::extractProgress, this, boost::ref(progress), _1, _2)); + archive.extractAll( + QDir::toNativeSeparators(targetFolder).toLocal8Bit().constData(), + boost::bind(&MainWindow::extractProgress, this, boost::ref(progress), _1, + _2)); if (result == BSA::ERROR_INVALIDHASHES) { - reportError(tr("This archive contains invalid hashes. Some files may be broken.")); + reportError( + tr("This archive contains invalid hashes. Some files may be broken.")); } archive.close(); } @@ -3446,8 +3584,9 @@ void MainWindow::extractBSATriggered(QTreeWidgetItem* item) void MainWindow::on_bsaList_customContextMenuRequested(const QPoint& pos) { QMenu menu; - menu.addAction(tr("Extract..."), - [=, item = ui->bsaList->itemAt(pos)]() { extractBSATriggered(item); }); + menu.addAction(tr("Extract..."), [=, item = ui->bsaList->itemAt(pos)]() { + extractBSATriggered(item); + }); menu.exec(ui->bsaList->viewport()->mapToGlobal(pos)); } @@ -3495,7 +3634,7 @@ void MainWindow::on_displayCategoriesBtn_toggled(bool checked) void MainWindow::removeFromToolbar(QAction* action) { const auto& title = action->text(); - auto& list = *m_OrganizerCore.executablesList(); + auto& list = *m_OrganizerCore.executablesList(); auto itor = list.find(title); if (itor == list.end()) { @@ -3507,17 +3646,17 @@ void MainWindow::removeFromToolbar(QAction* action) updatePinnedExecutables(); } - -void MainWindow::toolBar_customContextMenuRequested(const QPoint &point) +void MainWindow::toolBar_customContextMenuRequested(const QPoint& point) { - QAction *action = ui->toolBar->actionAt(point); + QAction* action = ui->toolBar->actionAt(point); if (action != nullptr) { if (action->objectName().startsWith("custom_")) { QMenu menu; - menu.addAction( - tr("Remove '%1' from the toolbar").arg(action->text()), - [&, action]() { removeFromToolbar(action); }); + menu.addAction(tr("Remove '%1' from the toolbar").arg(action->text()), + [&, action]() { + removeFromToolbar(action); + }); menu.exec(ui->toolBar->mapToGlobal(point)); return; } @@ -3530,15 +3669,12 @@ void MainWindow::toolBar_customContextMenuRequested(const QPoint &point) Executable* MainWindow::getSelectedExecutable() { - const QString name = ui->executablesListBox->itemText( - ui->executablesListBox->currentIndex()); + const QString name = + ui->executablesListBox->itemText(ui->executablesListBox->currentIndex()); - try - { + try { return &m_OrganizerCore.executablesList()->get(name); - } - catch(std::runtime_error&) - { + } catch (std::runtime_error&) { return nullptr; } } @@ -3548,16 +3684,18 @@ void MainWindow::on_showHiddenBox_toggled(bool checked) m_OrganizerCore.downloadManager()->setShowHidden(checked); } -const char *MainWindow::PATTERN_BACKUP_GLOB = ".????_??_??_??_??_??"; -const char *MainWindow::PATTERN_BACKUP_REGEX = "\\.(\\d\\d\\d\\d_\\d\\d_\\d\\d_\\d\\d_\\d\\d_\\d\\d)"; -const char *MainWindow::PATTERN_BACKUP_DATE = "yyyy_MM_dd_hh_mm_ss"; +const char* MainWindow::PATTERN_BACKUP_GLOB = ".????_??_??_??_??_??"; +const char* MainWindow::PATTERN_BACKUP_REGEX = + "\\.(\\d\\d\\d\\d_\\d\\d_\\d\\d_\\d\\d_\\d\\d_\\d\\d)"; +const char* MainWindow::PATTERN_BACKUP_DATE = "yyyy_MM_dd_hh_mm_ss"; -bool MainWindow::createBackup(const QString &filePath, const QDateTime &time) +bool MainWindow::createBackup(const QString& filePath, const QDateTime& time) { QString outPath = filePath + "." + time.toString(PATTERN_BACKUP_DATE); if (shellCopy(QStringList(filePath), QStringList(outPath), this)) { QFileInfo fileInfo(filePath); - removeOldFiles(fileInfo.absolutePath(), fileInfo.fileName() + PATTERN_BACKUP_GLOB, 10, QDir::Name); + removeOldFiles(fileInfo.absolutePath(), fileInfo.fileName() + PATTERN_BACKUP_GLOB, + 10, QDir::Name); return true; } else { return false; @@ -3568,24 +3706,27 @@ void MainWindow::on_saveButton_clicked() { m_OrganizerCore.savePluginList(); QDateTime now = QDateTime::currentDateTime(); - if (createBackup(m_OrganizerCore.currentProfile()->getPluginsFileName(), now) - && createBackup(m_OrganizerCore.currentProfile()->getLoadOrderFileName(), now) - && createBackup(m_OrganizerCore.currentProfile()->getLockedOrderFileName(), now)) { + if (createBackup(m_OrganizerCore.currentProfile()->getPluginsFileName(), now) && + createBackup(m_OrganizerCore.currentProfile()->getLoadOrderFileName(), now) && + createBackup(m_OrganizerCore.currentProfile()->getLockedOrderFileName(), now)) { MessageDialog::showMessage(tr("Backup of load order created"), this); } } -QString MainWindow::queryRestore(const QString &filePath) +QString MainWindow::queryRestore(const QString& filePath) { QFileInfo pluginFileInfo(filePath); - QString pattern = pluginFileInfo.fileName() + ".*"; - QFileInfoList files = pluginFileInfo.absoluteDir().entryInfoList(QStringList(pattern), QDir::Files, QDir::Name); + QString pattern = pluginFileInfo.fileName() + ".*"; + QFileInfoList files = pluginFileInfo.absoluteDir().entryInfoList( + QStringList(pattern), QDir::Files, QDir::Name); SelectionDialog dialog(tr("Choose backup to restore"), this); - QRegularExpression exp(QRegularExpression::anchoredPattern(pluginFileInfo.fileName() + PATTERN_BACKUP_REGEX)); - QRegularExpression exp2(QRegularExpression::anchoredPattern(pluginFileInfo.fileName() + "\\.(.*)")); - for(const QFileInfo &info : boost::adaptors::reverse(files)) { - auto match = exp.match(info.fileName()); + QRegularExpression exp(QRegularExpression::anchoredPattern(pluginFileInfo.fileName() + + PATTERN_BACKUP_REGEX)); + QRegularExpression exp2( + QRegularExpression::anchoredPattern(pluginFileInfo.fileName() + "\\.(.*)")); + for (const QFileInfo& info : boost::adaptors::reverse(files)) { + auto match = exp.match(info.fileName()); auto match2 = exp2.match(info.fileName()); if (match.hasMatch()) { QDateTime time = QDateTime::fromString(match.captured(1), PATTERN_BACKUP_DATE); @@ -3596,7 +3737,8 @@ QString MainWindow::queryRestore(const QString &filePath) } if (dialog.numChoices() == 0) { - QMessageBox::information(this, tr("No Backups"), tr("There are no backups to restore")); + QMessageBox::information(this, tr("No Backups"), + tr("There are no backups to restore")); return QString(); } @@ -3610,20 +3752,19 @@ QString MainWindow::queryRestore(const QString &filePath) void MainWindow::on_restoreButton_clicked() { QString pluginName = m_OrganizerCore.currentProfile()->getPluginsFileName(); - QString choice = queryRestore(pluginName); + QString choice = queryRestore(pluginName); if (!choice.isEmpty()) { QString loadOrderName = m_OrganizerCore.currentProfile()->getLoadOrderFileName(); - QString lockedName = m_OrganizerCore.currentProfile()->getLockedOrderFileName(); - if (!shellCopy(pluginName + "." + choice, pluginName, true, this) || + QString lockedName = m_OrganizerCore.currentProfile()->getLockedOrderFileName(); + if (!shellCopy(pluginName + "." + choice, pluginName, true, this) || !shellCopy(loadOrderName + "." + choice, loadOrderName, true, this) || - !shellCopy(lockedName + "." + choice, lockedName, true, this)) { + !shellCopy(lockedName + "." + choice, lockedName, true, this)) { const auto e = GetLastError(); - QMessageBox::critical( - this, tr("Restore failed"), - tr("Failed to restore the backup. Errorcode: %1") - .arg(QString::fromStdWString(formatSystemMessage(e)))); + QMessageBox::critical(this, tr("Restore failed"), + tr("Failed to restore the backup. Errorcode: %1") + .arg(QString::fromStdWString(formatSystemMessage(e)))); } m_OrganizerCore.refreshESPList(true); } @@ -3641,14 +3782,13 @@ void MainWindow::on_saveModsButton_clicked() void MainWindow::on_restoreModsButton_clicked() { QString modlistName = m_OrganizerCore.currentProfile()->getModlistFileName(); - QString choice = queryRestore(modlistName); + QString choice = queryRestore(modlistName); if (!choice.isEmpty()) { if (!shellCopy(modlistName + "." + choice, modlistName, true, this)) { const auto e = GetLastError(); - QMessageBox::critical( - this, tr("Restore failed"), - tr("Failed to restore the backup. Errorcode: %1") - .arg(formatSystemMessage(e))); + QMessageBox::critical(this, tr("Restore failed"), + tr("Failed to restore the backup. Errorcode: %1") + .arg(formatSystemMessage(e))); } m_OrganizerCore.refresh(false); } @@ -3656,44 +3796,43 @@ void MainWindow::on_restoreModsButton_clicked() void MainWindow::on_managedArchiveLabel_linkHovered(const QString&) { - QToolTip::showText(QCursor::pos(), - ui->managedArchiveLabel->toolTip()); + QToolTip::showText(QCursor::pos(), ui->managedArchiveLabel->toolTip()); } -void MainWindow::dragEnterEvent(QDragEnterEvent *event) +void MainWindow::dragEnterEvent(QDragEnterEvent* event) { - //Accept copy or move drags to the download window. Link drags are not - //meaningful (Well, they are - we could drop a link in the download folder, - //but you need privileges to do that). + // Accept copy or move drags to the download window. Link drags are not + // meaningful (Well, they are - we could drop a link in the download folder, + // but you need privileges to do that). if (ui->downloadTab->isVisible() && (event->proposedAction() == Qt::CopyAction || event->proposedAction() == Qt::MoveAction) && event->answerRect().intersects(ui->downloadTab->rect())) { - //If I read the documentation right, this won't work under a motif windows - //manager and the check needs to be done at the drop. However, that means - //the user might be allowed to drop things which we can't sanely process - QMimeData const *data = event->mimeData(); + // If I read the documentation right, this won't work under a motif windows + // manager and the check needs to be done at the drop. However, that means + // the user might be allowed to drop things which we can't sanely process + QMimeData const* data = event->mimeData(); if (data->hasUrls()) { QStringList extensions = - m_OrganizerCore.installationManager()->getSupportedExtensions(); + m_OrganizerCore.installationManager()->getSupportedExtensions(); - //This is probably OK - scan to see if these are moderately sane archive - //types + // This is probably OK - scan to see if these are moderately sane archive + // types QList urls = data->urls(); - bool ok = true; - for (const QUrl &url : urls) { + bool ok = true; + for (const QUrl& url : urls) { if (url.isLocalFile()) { QString local = url.toLocalFile(); - bool fok = false; + bool fok = false; for (auto ext : extensions) { if (local.endsWith(ext, Qt::CaseInsensitive)) { fok = true; break; } } - if (! fok) { + if (!fok) { ok = false; break; } @@ -3706,7 +3845,7 @@ void MainWindow::dragEnterEvent(QDragEnterEvent *event) } } -void MainWindow::dropLocalFile(const QUrl &url, const QString &outputDir, bool move) +void MainWindow::dropLocalFile(const QUrl& url, const QString& outputDir, bool move) { QFileInfo file(url.toLocalFile()); if (!file.exists()) { @@ -3715,8 +3854,7 @@ void MainWindow::dropLocalFile(const QUrl &url, const QString &outputDir, bool m } QString target = outputDir + "/" + file.fileName(); if (QFile::exists(target)) { - QMessageBox box(QMessageBox::Question, - file.fileName(), + QMessageBox box(QMessageBox::Question, file.fileName(), tr("A file with the same name has already been downloaded. " "What would you like to do?")); box.addButton(tr("Overwrite"), QMessageBox::ActionRole); @@ -3725,14 +3863,14 @@ void MainWindow::dropLocalFile(const QUrl &url, const QString &outputDir, bool m box.exec(); switch (box.buttonRole(box.clickedButton())) { - case QMessageBox::RejectRole: - return; - case QMessageBox::ActionRole: - break; - default: - case QMessageBox::YesRole: - target = m_OrganizerCore.downloadManager()->getDownloadFileName(file.fileName()); - break; + case QMessageBox::RejectRole: + return; + case QMessageBox::ActionRole: + break; + default: + case QMessageBox::YesRole: + target = m_OrganizerCore.downloadManager()->getDownloadFileName(file.fileName()); + break; } } @@ -3748,15 +3886,15 @@ void MainWindow::dropLocalFile(const QUrl &url, const QString &outputDir, bool m } } -void MainWindow::dropEvent(QDropEvent *event) +void MainWindow::dropEvent(QDropEvent* event) { Qt::DropAction action = event->proposedAction(); - QString outputDir = m_OrganizerCore.downloadManager()->getOutputDirectory(); + QString outputDir = m_OrganizerCore.downloadManager()->getOutputDirectory(); if (action == Qt::MoveAction) { - //Tell windows I'm taking control and will delete the source of a move. + // Tell windows I'm taking control and will delete the source of a move. event->setDropAction(Qt::TargetMoveAction); } - for (const QUrl &url : event->mimeData()->urls()) { + for (const QUrl& url : event->mimeData()->urls()) { if (url.isLocalFile()) { dropLocalFile(url, outputDir, action == Qt::MoveAction); } else { @@ -3766,7 +3904,7 @@ void MainWindow::dropEvent(QDropEvent *event) event->accept(); } -void MainWindow::keyReleaseEvent(QKeyEvent *event) +void MainWindow::keyReleaseEvent(QKeyEvent* event) { // if the ui is locked, ignore the ALT key event // alt-tabbing out of a game triggers this diff --git a/src/mainwindow.h b/src/mainwindow.h index 2e7b8042f..aad4e4cec 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -24,14 +24,14 @@ along with Mod Organizer. If not, see . #include "delayedfilewriter.h" #include "errorcodes.h" #include "imoinfo.h" +#include "iplugingame.h" //namespace MOBase { class IPluginGame; } #include "iuserinterface.h" #include "modinfo.h" #include "modlistbypriorityproxy.h" #include "modlistsortproxy.h" -#include "tutorialcontrol.h" -#include "plugincontainer.h" //class PluginContainer; -#include "iplugingame.h" //namespace MOBase { class IPluginGame; } +#include "plugincontainer.h" //class PluginContainer; #include "shared/fileregisterfwd.h" +#include "tutorialcontrol.h" #include class Executable; @@ -44,17 +44,31 @@ class SavesTab; class BrowserDialog; class PluginListSortProxy; -namespace BSA { class Archive; } +namespace BSA +{ +class Archive; +} -namespace MOBase { class IPluginModPage; } -namespace MOBase { class IPluginTool; } +namespace MOBase +{ +class IPluginModPage; +} +namespace MOBase +{ +class IPluginTool; +} -namespace MOShared { class DirectoryEntry; } +namespace MOShared +{ +class DirectoryEntry; +} #include #include #include #include +#include +#include #include #include #include @@ -64,11 +78,9 @@ namespace MOShared { class DirectoryEntry; } #include #include #include -#include #include #include #include -#include class QAction; class QAbstractItemModel; @@ -89,7 +101,7 @@ class QWidget; #include #endif -//Sigh - just for HANDLE +// Sigh - just for HANDLE #define WIN32_LEAN_AND_MEAN #include @@ -98,13 +110,13 @@ class QWidget; #include #include -namespace Ui { - class MainWindow; +namespace Ui +{ +class MainWindow; } class Settings; - class MainWindow : public QMainWindow, public IUserInterface { Q_OBJECT @@ -112,9 +124,8 @@ class MainWindow : public QMainWindow, public IUserInterface friend class OrganizerProxy; public: - explicit MainWindow(Settings &settings, - OrganizerCore &organizerCore, PluginContainer &pluginContainer, - QWidget *parent = 0); + explicit MainWindow(Settings& settings, OrganizerCore& organizerCore, + PluginContainer& pluginContainer, QWidget* parent = 0); ~MainWindow(); void processUpdates(); @@ -122,14 +133,15 @@ class MainWindow : public QMainWindow, public IUserInterface QMainWindow* mainWindow() override; bool addProfile(); - void updateBSAList(const QStringList &defaultArchives, const QStringList &activeArchives); + void updateBSAList(const QStringList& defaultArchives, + const QStringList& activeArchives); void saveArchiveList(); - void installTranslator(const QString &name); + void installTranslator(const QString& name); - void displayModInformation( - ModInfo::Ptr modInfo, unsigned int modIndex, ModInfoTabIDs tabID) override; + void displayModInformation(ModInfo::Ptr modInfo, unsigned int modIndex, + ModInfoTabIDs tabID) override; bool canExit(); void onBeforeClose(); @@ -137,7 +149,10 @@ class MainWindow : public QMainWindow, public IUserInterface virtual bool closeWindow(); virtual void setWindowEnabled(bool enabled); - virtual MOBase::DelayedFileWriterBase &archivesWriter() override { return m_ArchiveListWriter; } + virtual MOBase::DelayedFileWriterBase& archivesWriter() override + { + return m_ArchiveListWriter; + } public slots: void refresherProgress(const DirectoryRefreshProgress* p); @@ -150,26 +165,24 @@ public slots: /** * @brief emitted when the selected style changes */ - void styleChanged(const QString &styleFile); + void styleChanged(const QString& styleFile); void checkForProblemsDone(); protected: - - void showEvent(QShowEvent *event) override; + void showEvent(QShowEvent* event) override; void paintEvent(QPaintEvent* event) override; - void closeEvent(QCloseEvent *event) override; - bool eventFilter(QObject *obj, QEvent *event) override; - void resizeEvent(QResizeEvent *event) override; - void dragEnterEvent(QDragEnterEvent *event) override; - void dropEvent(QDropEvent *event) override; - void keyReleaseEvent(QKeyEvent *event) override; + void closeEvent(QCloseEvent* event) override; + bool eventFilter(QObject* obj, QEvent* event) override; + void resizeEvent(QResizeEvent* event) override; + void dragEnterEvent(QDragEnterEvent* event) override; + void dropEvent(QDropEvent* event) override; + void keyReleaseEvent(QKeyEvent* event) override; private slots: void on_actionChange_Game_triggered(); private: - // update data tab and schedule a problem check after a directory // structure update // @@ -188,7 +201,8 @@ private slots: void registerModPage(MOBase::IPluginModPage* modPage); bool registerNexusPage(const QString& gameName); - void registerPluginTool(MOBase::IPluginTool* tool, QString name = QString(), QMenu* menu = nullptr); + void registerPluginTool(MOBase::IPluginTool* tool, QString name = QString(), + QMenu* menu = nullptr); void updateToolbarMenu(); void updateToolMenu(); @@ -206,44 +220,44 @@ private slots: // remove invalid category-references from mods void fixCategories(); - bool extractProgress(QProgressDialog &extractProgress, int percentage, std::string fileName); + bool extractProgress(QProgressDialog& extractProgress, int percentage, + std::string fileName); // Performs checks, sets the m_NumberOfProblems and signals checkForProblemsDone(). void checkForProblemsImpl(); void setCategoryListVisible(bool visible); - bool errorReported(QString &logFile); + bool errorReported(QString& logFile); static void setupNetworkProxy(bool activate); void activateProxy(bool activate); - bool createBackup(const QString &filePath, const QDateTime &time); - QString queryRestore(const QString &filePath); + bool createBackup(const QString& filePath, const QDateTime& time); + QString queryRestore(const QString& filePath); - QMenu *openFolderMenu(); + QMenu* openFolderMenu(); - void dropLocalFile(const QUrl &url, const QString &outputDir, bool move); + void dropLocalFile(const QUrl& url, const QString& outputDir, bool move); void toggleMO2EndorseState(); void toggleUpdateAction(); // update info - struct NxmUpdateInfoData { + struct NxmUpdateInfoData + { QString game; std::set finalMods; }; void finishUpdateInfo(const NxmUpdateInfoData& data); private: - - static const char *PATTERN_BACKUP_GLOB; - static const char *PATTERN_BACKUP_REGEX; - static const char *PATTERN_BACKUP_DATE; + static const char* PATTERN_BACKUP_GLOB; + static const char* PATTERN_BACKUP_REGEX; + static const char* PATTERN_BACKUP_DATE; private: - - Ui::MainWindow *ui; + Ui::MainWindow* ui; bool m_WasVisible; bool m_FirstPaint; @@ -260,15 +274,16 @@ private slots: int m_OldProfileIndex; - std::vector m_ModNameList; // the mod-list to go with the directory structure + std::vector + m_ModNameList; // the mod-list to go with the directory structure QStringList m_DefaultArchives; int m_OldExecutableIndex; - QAction *m_ContextAction; + QAction* m_ContextAction; - CategoryFactory *m_CategoryFactory; + CategoryFactory* m_CategoryFactory; QTimer m_CheckBSATimer; QTimer m_SaveMetaTimer; @@ -278,8 +293,8 @@ private slots: QTime m_StartTime; - OrganizerCore &m_OrganizerCore; - PluginContainer &m_PluginContainer; + OrganizerCore& m_OrganizerCore; + PluginContainer& m_PluginContainer; QString m_CurrentLanguage; std::vector m_Translators; @@ -308,9 +323,8 @@ private slots: private slots: void updateWindowTitle(const APIUserAccount& user); - void showMessage(const QString &message); - void showError(const QString &message); - + void showMessage(const QString& message); + void showError(const QString& message); // main window actions void helpTriggered(); @@ -327,11 +341,13 @@ private slots: void linkDesktop(); void linkMenu(); - void languageChange(const QString &newLanguage); + void languageChange(const QString& newLanguage); - void windowTutorialFinished(const QString &windowName); + void windowTutorialFinished(const QString& windowName); - BSA::EErrorCode extractBSA(BSA::Archive &archive, BSA::Folder::Ptr folder, const QString &destination, QProgressDialog &extractProgress); + BSA::EErrorCode extractBSA(BSA::Archive& archive, BSA::Folder::Ptr folder, + const QString& destination, + QProgressDialog& extractProgress); // nexus related void updateAvailable(); @@ -339,37 +355,42 @@ private slots: void actionEndorseMO(); void actionWontEndorseMO(); - void motdReceived(const QString &motd); + void motdReceived(const QString& motd); void originModified(int originID); - void modInstalled(const QString &modName); + void modInstalled(const QString& modName); void importCategories(bool); // update info - void nxmUpdateInfoAvailable(QString gameName, QVariant userData, QVariant resultData, int requestID); + void nxmUpdateInfoAvailable(QString gameName, QVariant userData, QVariant resultData, + int requestID); void nxmEndorsementsAvailable(QVariant userData, QVariant resultData, int); - void nxmUpdatesAvailable(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID); - void nxmModInfoAvailable(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID); + void nxmUpdatesAvailable(QString gameName, int modID, QVariant userData, + QVariant resultData, int requestID); + void nxmModInfoAvailable(QString gameName, int modID, QVariant userData, + QVariant resultData, int requestID); void nxmEndorsementToggled(QString, int, QVariant, QVariant resultData, int); void nxmTrackedModsAvailable(QVariant userData, QVariant resultData, int); - void nxmDownloadURLs(QString, int modID, int fileID, QVariant userData, QVariant resultData, int requestID); + void nxmDownloadURLs(QString, int modID, int fileID, QVariant userData, + QVariant resultData, int requestID); void nxmGameInfoAvailable(QString gameName, QVariant, QVariant resultData, int); - void nxmRequestFailed(QString gameName, int modID, int fileID, QVariant userData, int requestID, int errorCode, const QString &errorString); + void nxmRequestFailed(QString gameName, int modID, int fileID, QVariant userData, + int requestID, int errorCode, const QString& errorString); void onRequestsChanged(const APIStats& stats, const APIUserAccount& user); - void modRenamed(const QString &oldName, const QString &newName); - void modRemoved(const QString &fileName); + void modRenamed(const QString& oldName, const QString& newName); + void modRemoved(const QString& fileName); void hookUpWindowTutorials(); bool shouldStartTutorial() const; void openInstanceFolder(); void openInstallFolder(); - void openPluginsFolder(); + void openPluginsFolder(); void openStylesheetsFolder(); void openDownloadsFolder(); void openModsFolder(); @@ -384,7 +405,8 @@ private slots: // Only visually update the problems icon. void updateProblemsButton(); - // Queue a problem check to allow collapsing of multiple requests in short amount of time. + // Queue a problem check to allow collapsing of multiple requests in short amount of + // time. void scheduleCheckForProblems(); // Perform the actual problem check in another thread. @@ -392,25 +414,26 @@ private slots: void saveModMetas(); - void updateStyle(const QString &style); + void updateStyle(const QString& style); void resizeLists(bool pluginListCustom); - void fileMoved(const QString& filePath, const QString& oldOriginName, const QString& newOriginName); + void fileMoved(const QString& filePath, const QString& oldOriginName, + const QString& newOriginName); /** * @brief allow columns in mod list and plugin list to be resized */ void allowListResize(); - void toolBar_customContextMenuRequested(const QPoint &point); + void toolBar_customContextMenuRequested(const QPoint& point); void removeFromToolbar(QAction* action); void about(); void resetActionIcons(); -private slots: // ui slots +private slots: // ui slots // actions void on_actionAdd_Profile_triggered(); void on_actionInstallMod_triggered(); @@ -432,8 +455,8 @@ private slots: // ui slots void on_actionToolBarIconsAndText_triggered(); void on_actionViewLog_triggered(); - void on_centralWidget_customContextMenuRequested(const QPoint &pos); - void on_bsaList_customContextMenuRequested(const QPoint &pos); + void on_centralWidget_customContextMenuRequested(const QPoint& pos); + void on_bsaList_customContextMenuRequested(const QPoint& pos); void on_executablesListBox_currentIndexChanged(int index); void on_profileBox_currentIndexChanged(int index); void on_startButton_clicked(); @@ -442,13 +465,13 @@ private slots: // ui slots void on_displayCategoriesBtn_toggled(bool checked); void on_linkButton_pressed(); void on_showHiddenBox_toggled(bool checked); - void on_bsaList_itemChanged(QTreeWidgetItem *item, int column); + void on_bsaList_itemChanged(QTreeWidgetItem* item, int column); void on_saveButton_clicked(); void on_restoreButton_clicked(); void on_restoreModsButton_clicked(); void on_saveModsButton_clicked(); - void on_managedArchiveLabel_linkHovered(const QString &link); + void on_managedArchiveLabel_linkHovered(const QString& link); void onPluginRegistrationChanged(); @@ -458,4 +481,4 @@ private slots: // ui slots void setupModList(); }; -#endif // MAINWINDOW_H +#endif // MAINWINDOW_H diff --git a/src/messagedialog.cpp b/src/messagedialog.cpp index 1244e8c6e..54b234e5c 100644 --- a/src/messagedialog.cpp +++ b/src/messagedialog.cpp @@ -1,108 +1,110 @@ -/* -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 "messagedialog.h" -#include "ui_messagedialog.h" -#include -#include -#include -#include - -using namespace MOBase; - -MessageDialog::MessageDialog(const QString &text, QWidget *reference) : - QDialog(reference), - ui(new Ui::MessageDialog) -{ - ui->setupUi(this); - - // very crude way to ensure no single word in the test is wider than the message window. ellide in the center if necessary - QFontMetrics metrics(ui->message->font()); - QString restrictedText; - QStringList lines = text.split("\n"); - foreach (const QString &line, lines) { - QString newLine; - QStringList words = line.split(" "); - foreach (const QString &word, words) { - if (word.length() > 10) { - newLine += "" + metrics.elidedText(word, Qt::ElideMiddle, ui->message->maximumWidth()) + ""; - } else { - newLine += word; - } - newLine += " "; - } - restrictedText += newLine + "\n"; - } - - ui->message->setText(restrictedText); - this->setWindowFlags(Qt::Tool | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint); - this->setFocusPolicy(Qt::NoFocus); - this->setAttribute(Qt::WA_ShowWithoutActivating); - QTimer::singleShot(1000 + (text.length() * 40), this, SLOT(hide())); - if (reference != nullptr) { - QPoint position = reference->mapToGlobal(QPoint(reference->width() / 2, reference->height())); - position.rx() -= this->width() / 2; - position.ry() -= this->height() + 5; - move(position); - } -} - - -MessageDialog::~MessageDialog() -{ - delete ui; -} - - -void MessageDialog::resizeEvent(QResizeEvent *event) -{ - QWidget *par = parentWidget(); - if (par != nullptr) { - QPoint position = par->mapToGlobal(QPoint(par->width() / 2, par->height())); - position.rx() -= event->size().width() / 2; - position.ry() -= event->size().height() + 5; - move(position); - } -} - - -void MessageDialog::showMessage(const QString &text, QWidget *reference, bool bringToFront) -{ - log::debug("{}", text); - - if (!reference) { - for (QWidget* w : qApp->topLevelWidgets()) { - if (dynamic_cast(w)) { - reference = w; - break; - } - } - } - - if (!reference) { - return; - } - - MessageDialog *dialog = new MessageDialog(text, reference); - dialog->show(); - - if (bringToFront) { - reference->activateWindow(); - } -} +/* +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 "messagedialog.h" +#include "ui_messagedialog.h" +#include +#include +#include +#include + +using namespace MOBase; + +MessageDialog::MessageDialog(const QString& text, QWidget* reference) + : QDialog(reference), ui(new Ui::MessageDialog) +{ + ui->setupUi(this); + + // very crude way to ensure no single word in the test is wider than the message + // window. ellide in the center if necessary + QFontMetrics metrics(ui->message->font()); + QString restrictedText; + QStringList lines = text.split("\n"); + foreach (const QString& line, lines) { + QString newLine; + QStringList words = line.split(" "); + foreach (const QString& word, words) { + if (word.length() > 10) { + newLine += + "" + + metrics.elidedText(word, Qt::ElideMiddle, ui->message->maximumWidth()) + + ""; + } else { + newLine += word; + } + newLine += " "; + } + restrictedText += newLine + "\n"; + } + + ui->message->setText(restrictedText); + this->setWindowFlags(Qt::Tool | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint); + this->setFocusPolicy(Qt::NoFocus); + this->setAttribute(Qt::WA_ShowWithoutActivating); + QTimer::singleShot(1000 + (text.length() * 40), this, SLOT(hide())); + if (reference != nullptr) { + QPoint position = + reference->mapToGlobal(QPoint(reference->width() / 2, reference->height())); + position.rx() -= this->width() / 2; + position.ry() -= this->height() + 5; + move(position); + } +} + +MessageDialog::~MessageDialog() +{ + delete ui; +} + +void MessageDialog::resizeEvent(QResizeEvent* event) +{ + QWidget* par = parentWidget(); + if (par != nullptr) { + QPoint position = par->mapToGlobal(QPoint(par->width() / 2, par->height())); + position.rx() -= event->size().width() / 2; + position.ry() -= event->size().height() + 5; + move(position); + } +} + +void MessageDialog::showMessage(const QString& text, QWidget* reference, + bool bringToFront) +{ + log::debug("{}", text); + + if (!reference) { + for (QWidget* w : qApp->topLevelWidgets()) { + if (dynamic_cast(w)) { + reference = w; + break; + } + } + } + + if (!reference) { + return; + } + + MessageDialog* dialog = new MessageDialog(text, reference); + dialog->show(); + + if (bringToFront) { + reference->activateWindow(); + } +} diff --git a/src/messagedialog.h b/src/messagedialog.h index 7e9b9c3ba..d5bb0c57a 100644 --- a/src/messagedialog.h +++ b/src/messagedialog.h @@ -1,67 +1,73 @@ -/* -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 MESSAGEDIALOG_H -#define MESSAGEDIALOG_H - -#include - -namespace Ui { - class MessageDialog; -} - -/** - * borderless dialog used to display short messages that will automatically - * vanish after a moment - **/ -class MessageDialog : public QDialog -{ - Q_OBJECT - -public: - /** - * @brief constructor - * - * @param text the message to display - * @param reference parent widget. This will also be used to position the message at the bottom center of the dialog - **/ - - explicit MessageDialog(const QString &text, QWidget *reference); - - ~MessageDialog(); - - /** - * factory function for message dialogs. This can be used as a fire-and-forget. The message - * will automatically positioned to the reference dialog and get a reasonable view time - * - * @param text the text to display. The length of this text is used to determine how long the dialog is to be shown - * @param reference the reference widget on top of which the message should be displayed - * @param true if the message should bring MO to front to ensure this message is visible - **/ - static void showMessage(const QString &text, QWidget *reference, bool bringToFront = true); - -protected: - - virtual void resizeEvent(QResizeEvent *event); - -private: - Ui::MessageDialog *ui; -}; - -#endif // MESSAGEDIALOG_H +/* +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 MESSAGEDIALOG_H +#define MESSAGEDIALOG_H + +#include + +namespace Ui +{ +class MessageDialog; +} + +/** + * borderless dialog used to display short messages that will automatically + * vanish after a moment + **/ +class MessageDialog : public QDialog +{ + Q_OBJECT + +public: + /** + * @brief constructor + * + * @param text the message to display + * @param reference parent widget. This will also be used to position the message at + *the bottom center of the dialog + **/ + + explicit MessageDialog(const QString& text, QWidget* reference); + + ~MessageDialog(); + + /** + * factory function for message dialogs. This can be used as a fire-and-forget. The + *message will automatically positioned to the reference dialog and get a reasonable + *view time + * + * @param text the text to display. The length of this text is used to determine how + *long the dialog is to be shown + * @param reference the reference widget on top of which the message should be + *displayed + * @param true if the message should bring MO to front to ensure this message is + *visible + **/ + static void showMessage(const QString& text, QWidget* reference, + bool bringToFront = true); + +protected: + virtual void resizeEvent(QResizeEvent* event); + +private: + Ui::MessageDialog* ui; +}; + +#endif // MESSAGEDIALOG_H diff --git a/src/moapplication.cpp b/src/moapplication.cpp index 7c125664a..7e95217ed 100644 --- a/src/moapplication.cpp +++ b/src/moapplication.cpp @@ -1,663 +1,642 @@ -/* -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 "moapplication.h" -#include "settings.h" -#include "commandline.h" -#include "instancemanager.h" -#include "organizercore.h" -#include "thread_utils.h" -#include "loglist.h" -#include "multiprocess.h" -#include "nexusinterface.h" -#include "nxmaccessmanager.h" -#include "tutorialmanager.h" -#include "sanitychecks.h" -#include "mainwindow.h" -#include "messagedialog.h" -#include "shared/util.h" -#include -#include -#include -#include -#include "shared/appconfig.h" -#include -#include -#include -#include -#include -#include -#include -#include - -// see addDllsToPath() below -#pragma comment(linker, "/manifestDependency:\"" \ - "name='dlls' " \ - "processorArchitecture='x86' " \ - "version='1.0.0.0' " \ - "type='win32' \"") - -using namespace MOBase; -using namespace MOShared; - -// style proxy that changes the appearance of drop indicators -// -class ProxyStyle : public QProxyStyle { -public: - ProxyStyle(QStyle* baseStyle = 0) - : QProxyStyle(baseStyle) - { - } - - void drawPrimitive( - PrimitiveElement element, const QStyleOption* option, - QPainter* painter, const QWidget* widget) const override - { - if (element == QStyle::PE_IndicatorItemViewItemDrop) { - - // 0. Fix a bug that made the drop indicator sometimes appear on top - // of the mod list when selecting a mod. - if (option->rect.height() == 0 - && option->rect.bottomRight() == QPoint(-1, -1)) { - return; - } - - // 1. full-width drop indicator - QRect rect(option->rect); - if (auto* view = qobject_cast(widget)) { - rect.setLeft(view->indentation()); - rect.setRight(widget->width()); - } - - // 2. stylish drop indicator - painter->setRenderHint(QPainter::Antialiasing, true); - - QColor col(option->palette.windowText().color()); - QPen pen(col); - pen.setWidth(2); - col.setAlpha(50); - - painter->setPen(pen); - painter->setBrush(QBrush(col)); - if (rect.height() == 0) { - QPoint tri[3] = { - rect.topLeft(), - rect.topLeft() + QPoint(-5, 5), - rect.topLeft() + QPoint(-5, -5) - }; - painter->drawPolygon(tri, 3); - painter->drawLine(rect.topLeft(), rect.topRight()); - } - else { - painter->drawRoundedRect(rect, 5, 5); - } - } - else { - QProxyStyle::drawPrimitive(element, option, painter, widget); - } - } - -}; - - -// This adds the `dlls` directory to the path so the dlls can be found. How -// MO is able to find dlls in there is a bit convoluted: -// -// Dependencies on DLLs can be baked into an executable by passing a -// `manifestdependency` option to the linker. This can be done on the command -// line or with a pragma. Typically, the dependency will not be a hardcoded -// filename, but an assembly name, such as Microsoft.Windows.Common-Controls. -// -// When Windows loads the exe, it will look for this assembly in a variety of -// places, such as in the WinSxS folder, but also in the program's folder. It -// will look for `assemblyname.dll` or `assemblyname/assemblyname.dll` and try -// to load that. -// -// If these files don't exist, then the loader gets creative and looks for -// `assemblyname.manifest` and `assemblyname/assemblyname.manifest`. A manifest -// file is just an XML file that can contain a list of DLLs to load for this -// assembly. -// -// In MO's case, there's a `pragma` at the beginning of this file which adds -// `dlls` as an "assembly" dependency. This is a bit of a hack to just force -// the loader to eventually find `dlls/dlls.manifest`, which contains the list -// of all the DLLs MO requires to load. -// -// This file was handwritten in `modorganizer/src/dlls.manifest.qt5` and -// is copied and renamed in CMakeLists.txt into `bin/dlls/dlls.manifest`. Note -// that the useless and incorrect .qt5 extension is removed. -// -void addDllsToPath() -{ - const auto dllsPath = QDir::toNativeSeparators( - QCoreApplication::applicationDirPath() + "/dlls"); - - QCoreApplication::setLibraryPaths( - QStringList(dllsPath) + QCoreApplication::libraryPaths()); - - env::prependToPath(dllsPath); -} - - -MOApplication::MOApplication(int& argc, char** argv) - : QApplication(argc, argv) -{ - TimeThis tt("MOApplication()"); - - qputenv("QML_DISABLE_DISK_CACHE", "true"); - - connect(&m_styleWatcher, &QFileSystemWatcher::fileChanged, [&](auto&& file){ - log::debug("style file '{}' changed, reloading", file); - updateStyle(file); - }); - - m_defaultStyle = style()->objectName(); - setStyle(new ProxyStyle(style())); - addDllsToPath(); -} - -OrganizerCore& MOApplication::core() -{ - return *m_core; -} - -void MOApplication::firstTimeSetup(MOMultiProcess& multiProcess) -{ - connect( - &multiProcess, &MOMultiProcess::messageSent, this, - [this](auto&& s){ externalMessage(s); }, - Qt::QueuedConnection); -} - -int MOApplication::setup(MOMultiProcess& multiProcess, bool forceSelect) -{ - TimeThis tt("MOApplication setup()"); - - // makes plugin data path available to plugins, see - // IOrganizer::getPluginDataPath() - MOBase::details::setPluginDataPath(OrganizerCore::pluginDataPath()); - - // figuring out the current instance - m_instance = getCurrentInstance(forceSelect); - if (!m_instance) { - return 1; - } - - // first time the data path is available, set the global property and log - // directory, then log a bunch of debug stuff - const QString dataPath = m_instance->directory(); - setProperty("dataPath", dataPath); - - if (!setLogDirectory(dataPath)) { - reportError(tr("Failed to create log folder.")); - InstanceManager::singleton().clearCurrentInstance(); - return 1; - } - - log::debug("command line: '{}'", QString::fromWCharArray(GetCommandLineW())); - - log::info( - "starting Mod Organizer version {} revision {} in {}, usvfs: {}", - createVersionInfo().displayString(3), GITID, - QCoreApplication::applicationDirPath(), MOShared::getUsvfsVersionString()); - - if (multiProcess.secondary()) { - log::debug("another instance of MO is running but --multiple was given"); - } - - log::info("data path: {}", m_instance->directory()); - log::info("working directory: {}", QDir::currentPath()); - - - tt.start("MOApplication::doOneRun() settings"); - - // deleting old files, only for the main instance - if (!multiProcess.secondary()) { - purgeOldFiles(); - } - - // loading settings - m_settings.reset(new Settings(m_instance->iniPath(), true)); - log::getDefault().setLevel(m_settings->diagnostics().logLevel()); - log::debug("using ini at '{}'", m_settings->filename()); - - OrganizerCore::setGlobalCoreDumpType(m_settings->diagnostics().coreDumpType()); - - - tt.start("MOApplication::doOneRun() log and checks"); - - // logging and checking - env::Environment env; - env.dump(*m_settings); - m_settings->dump(); - sanity::checkEnvironment(env); - - m_modules = std::move(env.onModuleLoaded(qApp, [](auto&& m) { - if (m.interesting()) { - log::debug("loaded module {}", m.toString()); - } - - sanity::checkIncompatibleModule(m); - })); - - auto sslBuildVersion = QSslSocket::sslLibraryBuildVersionString(); - auto sslVersion = QSslSocket::sslLibraryVersionString(); - log::debug("SSL Build Version: {}, SSL Runtime Version {}", sslBuildVersion, sslVersion); - - // nexus interface - tt.start("MOApplication::doOneRun() NexusInterface"); - log::debug("initializing nexus interface"); - m_nexus.reset(new NexusInterface(m_settings.get())); - - // organizer core - tt.start("MOApplication::doOneRun() OrganizerCore"); - log::debug("initializing core"); - - m_core.reset(new OrganizerCore(*m_settings)); - if (!m_core->bootstrap()) { - reportError(tr("Failed to set up data paths.")); - InstanceManager::singleton().clearCurrentInstance(); - return 1; - } - - // plugins - tt.start("MOApplication::doOneRun() plugins"); - log::debug("initializing plugins"); - - m_plugins = std::make_unique(m_core.get()); - m_plugins->loadPlugins(); - - // instance - if (auto r=setupInstanceLoop(*m_instance, *m_plugins)) { - return *r; - } - - if (m_instance->isPortable()) { - log::debug("this is a portable instance"); - } - - tt.start("MOApplication::doOneRun() OrganizerCore setup"); - - sanity::checkPaths(*m_instance->gamePlugin(), *m_settings); - - // setting up organizer core - m_core->setManagedGame(m_instance->gamePlugin()); - m_core->createDefaultProfile(); - - log::info( - "using game plugin '{}' ('{}', variant {}, steam id '{}') at {}", - m_instance->gamePlugin()->gameName(), - m_instance->gamePlugin()->gameShortName(), - (m_settings->game().edition().value_or("").isEmpty() ? - "(none)" : *m_settings->game().edition()), - m_instance->gamePlugin()->steamAPPId(), - m_instance->gamePlugin()->gameDirectory().absolutePath()); - - CategoryFactory::instance()->loadCategories(); - m_core->updateExecutablesList(); - m_core->updateModInfoFromDisc(); - m_core->setCurrentProfile(m_instance->profileName()); - - return 0; -} - -int MOApplication::run(MOMultiProcess& multiProcess) -{ - // checking command line - TimeThis tt("MOApplication::run()"); - - // show splash - tt.start("MOApplication::doOneRun() splash"); - - MOSplash splash(*m_settings, m_instance->directory(), m_instance->gamePlugin()); - - tt.start("MOApplication::doOneRun() finishing"); - - // start an api check - QString apiKey; - if (GlobalSettings::nexusApiKey(apiKey)) { - m_nexus->getAccessManager()->apiCheck(apiKey); - } - - // tutorials - log::debug("initializing tutorials"); - TutorialManager::init( - qApp->applicationDirPath() + "/" - + QString::fromStdWString(AppConfig::tutorialsPath()) + "/", - m_core.get()); - - // styling - if (!setStyleFile(m_settings->interface().styleName().value_or(""))) { - // disable invalid stylesheet - m_settings->interface().setStyleName(""); - } - - - int res = 1; - - { - tt.start("MOApplication::doOneRun() MainWindow setup"); - MainWindow mainWindow(*m_settings, *m_core, *m_plugins); - - // the nexus interface can show dialogs, make sure they're parented to the - // main window - m_nexus->getAccessManager()->setTopLevelWidget(&mainWindow); - - connect( - &mainWindow, &MainWindow::styleChanged, this, - [this](auto&& file){ setStyleFile(file); }, - Qt::QueuedConnection); - - - log::debug("displaying main window"); - mainWindow.show(); - mainWindow.activateWindow(); - splash.close(); - - tt.stop(); - - res = exec(); - mainWindow.close(); - - // main window is about to be destroyed - m_nexus->getAccessManager()->setTopLevelWidget(nullptr); - } - - // reset geometry if the flag was set from the settings dialog - m_settings->geometry().resetIfNeeded(); - - return res; -} - -void MOApplication::externalMessage(const QString& message) -{ - log::debug("received external message '{}'", message); - - MOShortcut moshortcut(message); - - if (moshortcut.isValid()) { - if(moshortcut.hasExecutable()) { - try { - m_core->processRunner() - .setFromShortcut(moshortcut) - .setWaitForCompletion(ProcessRunner::TriggerRefresh) - .run(); - } catch(std::exception&) { - // user was already warned - } - } - } else if (isNxmLink(message)) { - MessageDialog::showMessage(tr("Download started"), qApp->activeWindow(), false); - m_core->downloadRequestedNXM(message); - } else { - cl::CommandLine cl; - - if (auto r=cl.process(message.toStdWString())) { - log::debug( - "while processing external message, command line wants to " - "exit; ignoring"); - - return; - } - - if (auto i=cl.instance()) { - const auto ci = InstanceManager::singleton().currentInstance(); - - if (*i != ci->displayName()) { - reportError(tr( - "This shortcut or command line is for instance '%1', but the current " - "instance is '%2'.") - .arg(*i).arg(ci->displayName())); - - return; - } - } - - if (auto p=cl.profile()) { - if (*p != m_core->profileName()) { - reportError(tr( - "This shortcut or command line is for profile '%1', but the current " - "profile is '%2'.") - .arg(*p).arg(m_core->profileName())); - - return; - } - } - - cl.runPostOrganizer(*m_core); - } -} - -std::unique_ptr MOApplication::getCurrentInstance(bool forceSelect) -{ - auto& m = InstanceManager::singleton(); - auto currentInstance = m.currentInstance(); - - if (forceSelect || !currentInstance) - { - // clear any overrides that might have been given on the command line - m.clearOverrides(); - currentInstance = selectInstance(); - } - else - { - if (!QDir(currentInstance->directory()).exists()) { - // the previously used instance doesn't exist anymore - - // clear any overrides that might have been given on the command line - m.clearOverrides(); - - if (m.hasAnyInstances()) { - reportError(QObject::tr( - "Instance at '%1' not found. Select another instance.") - .arg(currentInstance->directory())); - } else { - reportError(QObject::tr( - "Instance at '%1' not found. You must create a new instance") - .arg(currentInstance->directory())); - } - - currentInstance = selectInstance(); - } - } - - return currentInstance; -} - -std::optional MOApplication::setupInstanceLoop( - Instance& currentInstance, PluginContainer& pc) -{ - for (;;) - { - const auto setupResult = setupInstance(currentInstance, pc); - - if (setupResult == SetupInstanceResults::Okay) { - return {}; - } else if (setupResult == SetupInstanceResults::TryAgain) { - continue; - } else if (setupResult == SetupInstanceResults::SelectAnother) { - InstanceManager::singleton().clearCurrentInstance(); - return ReselectExitCode; - } else { - return 1; - } - } -} - -void MOApplication::purgeOldFiles() -{ - // remove the temporary backup directory in case we're restarting after an - // update - QString backupDirectory = qApp->applicationDirPath() + "/update_backup"; - if (QDir(backupDirectory).exists()) { - shellDelete(QStringList(backupDirectory)); - } - - // cycle log file - removeOldFiles( - qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::logPath()), - "usvfs*.log", 5, QDir::Name); -} - -void MOApplication::resetForRestart() -{ - LogModel::instance().clear(); - ResetExitFlag(); - - // make sure the log file isn't locked in case MO was restarted and - // the previous instance gets deleted - log::getDefault().setFile({}); - - // clear instance and profile overrides - InstanceManager::singleton().clearOverrides(); - - m_core = {}; - m_plugins = {}; - m_nexus = {}; - m_settings = {}; - m_instance = {}; -} - -bool MOApplication::setStyleFile(const QString& styleName) -{ - // remove all files from watch - QStringList currentWatch = m_styleWatcher.files(); - if (currentWatch.count() != 0) { - m_styleWatcher.removePaths(currentWatch); - } - // set new stylesheet or clear it - if (styleName.length() != 0) { - QString styleSheetName = applicationDirPath() + "/" + MOBase::ToQString(AppConfig::stylesheetsPath()) + "/" + styleName; - if (QFile::exists(styleSheetName)) { - m_styleWatcher.addPath(styleSheetName); - updateStyle(styleSheetName); - } else { - updateStyle(styleName); - } - } else { - setStyle(new ProxyStyle(QStyleFactory::create(m_defaultStyle))); - setStyleSheet(""); - } - return true; -} - -bool MOApplication::notify(QObject* receiver, QEvent* event) -{ - try { - return QApplication::notify(receiver, event); - } catch (const std::exception &e) { - log::error( - "uncaught exception in handler (object {}, eventtype {}): {}", - receiver->objectName(), event->type(), e.what()); - reportError(tr("an error occurred: %1").arg(e.what())); - return false; - } catch (...) { - log::error( - "uncaught non-std exception in handler (object {}, eventtype {})", - receiver->objectName(), event->type()); - reportError(tr("an error occurred")); - return false; - } -} - -void MOApplication::updateStyle(const QString& fileName) -{ - if (QStyleFactory::keys().contains(fileName)) { - setStyleSheet(""); - setStyle(new ProxyStyle(QStyleFactory::create(fileName))); - } else { - setStyle(new ProxyStyle(QStyleFactory::create(m_defaultStyle))); - if (QFile::exists(fileName)) { - setStyleSheet(QString("file:///%1").arg(fileName)); - } else { - log::warn("invalid stylesheet: {}", fileName); - } - } -} - - -MOSplash::MOSplash( - const Settings& settings, const QString& dataPath, - const MOBase::IPluginGame* game) -{ - const auto splashPath = getSplashPath(settings, dataPath, game); - if (splashPath.isEmpty()) { - return; - } - - QPixmap image(splashPath); - if (image.isNull()) { - log::error("failed to load splash from {}", splashPath); - return; - } - - ss_.reset(new QSplashScreen(image)); - settings.geometry().centerOnMainWindowMonitor(ss_.get()); - - ss_->show(); - ss_->activateWindow(); -} - -void MOSplash::close() -{ - if (ss_) { - // don't pass mainwindow as it just waits half a second for it - // instead of proceding - ss_->finish(nullptr); - } -} - -QString MOSplash::getSplashPath( - const Settings& settings, const QString& dataPath, - const MOBase::IPluginGame* game) const -{ - if (!settings.useSplash()) { - return {}; - } - - // try splash from instance directory - const QString splashPath = dataPath + "/splash.png"; - if (QFile::exists(dataPath + "/splash.png")) { - QImage image(splashPath); - if (!image.isNull()) { - return splashPath; - } - } - - // try splash from plugin - QString pluginSplash = QString(":/%1/splash").arg(game->gameShortName()); - if (QFile::exists(pluginSplash)) { - QImage image(pluginSplash); - if (!image.isNull()) { - image.save(splashPath); - return pluginSplash; - } - } - - // try default splash from resource - QString defaultSplash = ":/MO/gui/splash"; - if (QFile::exists(defaultSplash)) { - QImage image(defaultSplash); - if (!image.isNull()) { - return defaultSplash; - } - } - - return splashPath; -} +/* +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 "moapplication.h" +#include "commandline.h" +#include "instancemanager.h" +#include "loglist.h" +#include "mainwindow.h" +#include "messagedialog.h" +#include "multiprocess.h" +#include "nexusinterface.h" +#include "nxmaccessmanager.h" +#include "organizercore.h" +#include "sanitychecks.h" +#include "settings.h" +#include "shared/appconfig.h" +#include "shared/util.h" +#include "thread_utils.h" +#include "tutorialmanager.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// see addDllsToPath() below +#pragma comment(linker, "/manifestDependency:\"" \ + "name='dlls' " \ + "processorArchitecture='x86' " \ + "version='1.0.0.0' " \ + "type='win32' \"") + +using namespace MOBase; +using namespace MOShared; + +// style proxy that changes the appearance of drop indicators +// +class ProxyStyle : public QProxyStyle +{ +public: + ProxyStyle(QStyle* baseStyle = 0) : QProxyStyle(baseStyle) {} + + void drawPrimitive(PrimitiveElement element, const QStyleOption* option, + QPainter* painter, const QWidget* widget) const override + { + if (element == QStyle::PE_IndicatorItemViewItemDrop) { + + // 0. Fix a bug that made the drop indicator sometimes appear on top + // of the mod list when selecting a mod. + if (option->rect.height() == 0 && option->rect.bottomRight() == QPoint(-1, -1)) { + return; + } + + // 1. full-width drop indicator + QRect rect(option->rect); + if (auto* view = qobject_cast(widget)) { + rect.setLeft(view->indentation()); + rect.setRight(widget->width()); + } + + // 2. stylish drop indicator + painter->setRenderHint(QPainter::Antialiasing, true); + + QColor col(option->palette.windowText().color()); + QPen pen(col); + pen.setWidth(2); + col.setAlpha(50); + + painter->setPen(pen); + painter->setBrush(QBrush(col)); + if (rect.height() == 0) { + QPoint tri[3] = {rect.topLeft(), rect.topLeft() + QPoint(-5, 5), + rect.topLeft() + QPoint(-5, -5)}; + painter->drawPolygon(tri, 3); + painter->drawLine(rect.topLeft(), rect.topRight()); + } else { + painter->drawRoundedRect(rect, 5, 5); + } + } else { + QProxyStyle::drawPrimitive(element, option, painter, widget); + } + } +}; + +// This adds the `dlls` directory to the path so the dlls can be found. How +// MO is able to find dlls in there is a bit convoluted: +// +// Dependencies on DLLs can be baked into an executable by passing a +// `manifestdependency` option to the linker. This can be done on the command +// line or with a pragma. Typically, the dependency will not be a hardcoded +// filename, but an assembly name, such as Microsoft.Windows.Common-Controls. +// +// When Windows loads the exe, it will look for this assembly in a variety of +// places, such as in the WinSxS folder, but also in the program's folder. It +// will look for `assemblyname.dll` or `assemblyname/assemblyname.dll` and try +// to load that. +// +// If these files don't exist, then the loader gets creative and looks for +// `assemblyname.manifest` and `assemblyname/assemblyname.manifest`. A manifest +// file is just an XML file that can contain a list of DLLs to load for this +// assembly. +// +// In MO's case, there's a `pragma` at the beginning of this file which adds +// `dlls` as an "assembly" dependency. This is a bit of a hack to just force +// the loader to eventually find `dlls/dlls.manifest`, which contains the list +// of all the DLLs MO requires to load. +// +// This file was handwritten in `modorganizer/src/dlls.manifest.qt5` and +// is copied and renamed in CMakeLists.txt into `bin/dlls/dlls.manifest`. Note +// that the useless and incorrect .qt5 extension is removed. +// +void addDllsToPath() +{ + const auto dllsPath = + QDir::toNativeSeparators(QCoreApplication::applicationDirPath() + "/dlls"); + + QCoreApplication::setLibraryPaths(QStringList(dllsPath) + + QCoreApplication::libraryPaths()); + + env::prependToPath(dllsPath); +} + +MOApplication::MOApplication(int& argc, char** argv) : QApplication(argc, argv) +{ + TimeThis tt("MOApplication()"); + + qputenv("QML_DISABLE_DISK_CACHE", "true"); + + connect(&m_styleWatcher, &QFileSystemWatcher::fileChanged, [&](auto&& file) { + log::debug("style file '{}' changed, reloading", file); + updateStyle(file); + }); + + m_defaultStyle = style()->objectName(); + setStyle(new ProxyStyle(style())); + addDllsToPath(); +} + +OrganizerCore& MOApplication::core() +{ + return *m_core; +} + +void MOApplication::firstTimeSetup(MOMultiProcess& multiProcess) +{ + connect( + &multiProcess, &MOMultiProcess::messageSent, this, + [this](auto&& s) { + externalMessage(s); + }, + Qt::QueuedConnection); +} + +int MOApplication::setup(MOMultiProcess& multiProcess, bool forceSelect) +{ + TimeThis tt("MOApplication setup()"); + + // makes plugin data path available to plugins, see + // IOrganizer::getPluginDataPath() + MOBase::details::setPluginDataPath(OrganizerCore::pluginDataPath()); + + // figuring out the current instance + m_instance = getCurrentInstance(forceSelect); + if (!m_instance) { + return 1; + } + + // first time the data path is available, set the global property and log + // directory, then log a bunch of debug stuff + const QString dataPath = m_instance->directory(); + setProperty("dataPath", dataPath); + + if (!setLogDirectory(dataPath)) { + reportError(tr("Failed to create log folder.")); + InstanceManager::singleton().clearCurrentInstance(); + return 1; + } + + log::debug("command line: '{}'", QString::fromWCharArray(GetCommandLineW())); + + log::info("starting Mod Organizer version {} revision {} in {}, usvfs: {}", + createVersionInfo().displayString(3), GITID, + QCoreApplication::applicationDirPath(), MOShared::getUsvfsVersionString()); + + if (multiProcess.secondary()) { + log::debug("another instance of MO is running but --multiple was given"); + } + + log::info("data path: {}", m_instance->directory()); + log::info("working directory: {}", QDir::currentPath()); + + tt.start("MOApplication::doOneRun() settings"); + + // deleting old files, only for the main instance + if (!multiProcess.secondary()) { + purgeOldFiles(); + } + + // loading settings + m_settings.reset(new Settings(m_instance->iniPath(), true)); + log::getDefault().setLevel(m_settings->diagnostics().logLevel()); + log::debug("using ini at '{}'", m_settings->filename()); + + OrganizerCore::setGlobalCoreDumpType(m_settings->diagnostics().coreDumpType()); + + tt.start("MOApplication::doOneRun() log and checks"); + + // logging and checking + env::Environment env; + env.dump(*m_settings); + m_settings->dump(); + sanity::checkEnvironment(env); + + m_modules = std::move(env.onModuleLoaded(qApp, [](auto&& m) { + if (m.interesting()) { + log::debug("loaded module {}", m.toString()); + } + + sanity::checkIncompatibleModule(m); + })); + + auto sslBuildVersion = QSslSocket::sslLibraryBuildVersionString(); + auto sslVersion = QSslSocket::sslLibraryVersionString(); + log::debug("SSL Build Version: {}, SSL Runtime Version {}", sslBuildVersion, + sslVersion); + + // nexus interface + tt.start("MOApplication::doOneRun() NexusInterface"); + log::debug("initializing nexus interface"); + m_nexus.reset(new NexusInterface(m_settings.get())); + + // organizer core + tt.start("MOApplication::doOneRun() OrganizerCore"); + log::debug("initializing core"); + + m_core.reset(new OrganizerCore(*m_settings)); + if (!m_core->bootstrap()) { + reportError(tr("Failed to set up data paths.")); + InstanceManager::singleton().clearCurrentInstance(); + return 1; + } + + // plugins + tt.start("MOApplication::doOneRun() plugins"); + log::debug("initializing plugins"); + + m_plugins = std::make_unique(m_core.get()); + m_plugins->loadPlugins(); + + // instance + if (auto r = setupInstanceLoop(*m_instance, *m_plugins)) { + return *r; + } + + if (m_instance->isPortable()) { + log::debug("this is a portable instance"); + } + + tt.start("MOApplication::doOneRun() OrganizerCore setup"); + + sanity::checkPaths(*m_instance->gamePlugin(), *m_settings); + + // setting up organizer core + m_core->setManagedGame(m_instance->gamePlugin()); + m_core->createDefaultProfile(); + + log::info("using game plugin '{}' ('{}', variant {}, steam id '{}') at {}", + m_instance->gamePlugin()->gameName(), + m_instance->gamePlugin()->gameShortName(), + (m_settings->game().edition().value_or("").isEmpty() + ? "(none)" + : *m_settings->game().edition()), + m_instance->gamePlugin()->steamAPPId(), + m_instance->gamePlugin()->gameDirectory().absolutePath()); + + CategoryFactory::instance()->loadCategories(); + m_core->updateExecutablesList(); + m_core->updateModInfoFromDisc(); + m_core->setCurrentProfile(m_instance->profileName()); + + return 0; +} + +int MOApplication::run(MOMultiProcess& multiProcess) +{ + // checking command line + TimeThis tt("MOApplication::run()"); + + // show splash + tt.start("MOApplication::doOneRun() splash"); + + MOSplash splash(*m_settings, m_instance->directory(), m_instance->gamePlugin()); + + tt.start("MOApplication::doOneRun() finishing"); + + // start an api check + QString apiKey; + if (GlobalSettings::nexusApiKey(apiKey)) { + m_nexus->getAccessManager()->apiCheck(apiKey); + } + + // tutorials + log::debug("initializing tutorials"); + TutorialManager::init(qApp->applicationDirPath() + "/" + + QString::fromStdWString(AppConfig::tutorialsPath()) + "/", + m_core.get()); + + // styling + if (!setStyleFile(m_settings->interface().styleName().value_or(""))) { + // disable invalid stylesheet + m_settings->interface().setStyleName(""); + } + + int res = 1; + + { + tt.start("MOApplication::doOneRun() MainWindow setup"); + MainWindow mainWindow(*m_settings, *m_core, *m_plugins); + + // the nexus interface can show dialogs, make sure they're parented to the + // main window + m_nexus->getAccessManager()->setTopLevelWidget(&mainWindow); + + connect( + &mainWindow, &MainWindow::styleChanged, this, + [this](auto&& file) { + setStyleFile(file); + }, + Qt::QueuedConnection); + + log::debug("displaying main window"); + mainWindow.show(); + mainWindow.activateWindow(); + splash.close(); + + tt.stop(); + + res = exec(); + mainWindow.close(); + + // main window is about to be destroyed + m_nexus->getAccessManager()->setTopLevelWidget(nullptr); + } + + // reset geometry if the flag was set from the settings dialog + m_settings->geometry().resetIfNeeded(); + + return res; +} + +void MOApplication::externalMessage(const QString& message) +{ + log::debug("received external message '{}'", message); + + MOShortcut moshortcut(message); + + if (moshortcut.isValid()) { + if (moshortcut.hasExecutable()) { + try { + m_core->processRunner() + .setFromShortcut(moshortcut) + .setWaitForCompletion(ProcessRunner::TriggerRefresh) + .run(); + } catch (std::exception&) { + // user was already warned + } + } + } else if (isNxmLink(message)) { + MessageDialog::showMessage(tr("Download started"), qApp->activeWindow(), false); + m_core->downloadRequestedNXM(message); + } else { + cl::CommandLine cl; + + if (auto r = cl.process(message.toStdWString())) { + log::debug("while processing external message, command line wants to " + "exit; ignoring"); + + return; + } + + if (auto i = cl.instance()) { + const auto ci = InstanceManager::singleton().currentInstance(); + + if (*i != ci->displayName()) { + reportError( + tr("This shortcut or command line is for instance '%1', but the current " + "instance is '%2'.") + .arg(*i) + .arg(ci->displayName())); + + return; + } + } + + if (auto p = cl.profile()) { + if (*p != m_core->profileName()) { + reportError( + tr("This shortcut or command line is for profile '%1', but the current " + "profile is '%2'.") + .arg(*p) + .arg(m_core->profileName())); + + return; + } + } + + cl.runPostOrganizer(*m_core); + } +} + +std::unique_ptr MOApplication::getCurrentInstance(bool forceSelect) +{ + auto& m = InstanceManager::singleton(); + auto currentInstance = m.currentInstance(); + + if (forceSelect || !currentInstance) { + // clear any overrides that might have been given on the command line + m.clearOverrides(); + currentInstance = selectInstance(); + } else { + if (!QDir(currentInstance->directory()).exists()) { + // the previously used instance doesn't exist anymore + + // clear any overrides that might have been given on the command line + m.clearOverrides(); + + if (m.hasAnyInstances()) { + reportError(QObject::tr("Instance at '%1' not found. Select another instance.") + .arg(currentInstance->directory())); + } else { + reportError( + QObject::tr("Instance at '%1' not found. You must create a new instance") + .arg(currentInstance->directory())); + } + + currentInstance = selectInstance(); + } + } + + return currentInstance; +} + +std::optional MOApplication::setupInstanceLoop(Instance& currentInstance, + PluginContainer& pc) +{ + for (;;) { + const auto setupResult = setupInstance(currentInstance, pc); + + if (setupResult == SetupInstanceResults::Okay) { + return {}; + } else if (setupResult == SetupInstanceResults::TryAgain) { + continue; + } else if (setupResult == SetupInstanceResults::SelectAnother) { + InstanceManager::singleton().clearCurrentInstance(); + return ReselectExitCode; + } else { + return 1; + } + } +} + +void MOApplication::purgeOldFiles() +{ + // remove the temporary backup directory in case we're restarting after an + // update + QString backupDirectory = qApp->applicationDirPath() + "/update_backup"; + if (QDir(backupDirectory).exists()) { + shellDelete(QStringList(backupDirectory)); + } + + // cycle log file + removeOldFiles(qApp->property("dataPath").toString() + "/" + + QString::fromStdWString(AppConfig::logPath()), + "usvfs*.log", 5, QDir::Name); +} + +void MOApplication::resetForRestart() +{ + LogModel::instance().clear(); + ResetExitFlag(); + + // make sure the log file isn't locked in case MO was restarted and + // the previous instance gets deleted + log::getDefault().setFile({}); + + // clear instance and profile overrides + InstanceManager::singleton().clearOverrides(); + + m_core = {}; + m_plugins = {}; + m_nexus = {}; + m_settings = {}; + m_instance = {}; +} + +bool MOApplication::setStyleFile(const QString& styleName) +{ + // remove all files from watch + QStringList currentWatch = m_styleWatcher.files(); + if (currentWatch.count() != 0) { + m_styleWatcher.removePaths(currentWatch); + } + // set new stylesheet or clear it + if (styleName.length() != 0) { + QString styleSheetName = applicationDirPath() + "/" + + MOBase::ToQString(AppConfig::stylesheetsPath()) + "/" + + styleName; + if (QFile::exists(styleSheetName)) { + m_styleWatcher.addPath(styleSheetName); + updateStyle(styleSheetName); + } else { + updateStyle(styleName); + } + } else { + setStyle(new ProxyStyle(QStyleFactory::create(m_defaultStyle))); + setStyleSheet(""); + } + return true; +} + +bool MOApplication::notify(QObject* receiver, QEvent* event) +{ + try { + return QApplication::notify(receiver, event); + } catch (const std::exception& e) { + log::error("uncaught exception in handler (object {}, eventtype {}): {}", + receiver->objectName(), event->type(), e.what()); + reportError(tr("an error occurred: %1").arg(e.what())); + return false; + } catch (...) { + log::error("uncaught non-std exception in handler (object {}, eventtype {})", + receiver->objectName(), event->type()); + reportError(tr("an error occurred")); + return false; + } +} + +void MOApplication::updateStyle(const QString& fileName) +{ + if (QStyleFactory::keys().contains(fileName)) { + setStyleSheet(""); + setStyle(new ProxyStyle(QStyleFactory::create(fileName))); + } else { + setStyle(new ProxyStyle(QStyleFactory::create(m_defaultStyle))); + if (QFile::exists(fileName)) { + setStyleSheet(QString("file:///%1").arg(fileName)); + } else { + log::warn("invalid stylesheet: {}", fileName); + } + } +} + +MOSplash::MOSplash(const Settings& settings, const QString& dataPath, + const MOBase::IPluginGame* game) +{ + const auto splashPath = getSplashPath(settings, dataPath, game); + if (splashPath.isEmpty()) { + return; + } + + QPixmap image(splashPath); + if (image.isNull()) { + log::error("failed to load splash from {}", splashPath); + return; + } + + ss_.reset(new QSplashScreen(image)); + settings.geometry().centerOnMainWindowMonitor(ss_.get()); + + ss_->show(); + ss_->activateWindow(); +} + +void MOSplash::close() +{ + if (ss_) { + // don't pass mainwindow as it just waits half a second for it + // instead of proceding + ss_->finish(nullptr); + } +} + +QString MOSplash::getSplashPath(const Settings& settings, const QString& dataPath, + const MOBase::IPluginGame* game) const +{ + if (!settings.useSplash()) { + return {}; + } + + // try splash from instance directory + const QString splashPath = dataPath + "/splash.png"; + if (QFile::exists(dataPath + "/splash.png")) { + QImage image(splashPath); + if (!image.isNull()) { + return splashPath; + } + } + + // try splash from plugin + QString pluginSplash = QString(":/%1/splash").arg(game->gameShortName()); + if (QFile::exists(pluginSplash)) { + QImage image(pluginSplash); + if (!image.isNull()) { + image.save(splashPath); + return pluginSplash; + } + } + + // try default splash from resource + QString defaultSplash = ":/MO/gui/splash"; + if (QFile::exists(defaultSplash)) { + QImage image(defaultSplash); + if (!image.isNull()) { + return defaultSplash; + } + } + + return splashPath; +} diff --git a/src/moapplication.h b/src/moapplication.h index 7e8a8c6f4..498242f3e 100644 --- a/src/moapplication.h +++ b/src/moapplication.h @@ -1,111 +1,111 @@ -/* -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 MOAPPLICATION_H -#define MOAPPLICATION_H - -#include -#include -#include "env.h" - -class Settings; -class MOMultiProcess; -class Instance; -class PluginContainer; -class OrganizerCore; -class NexusInterface; - -namespace MOBase { class IPluginGame; } - -class MOApplication : public QApplication -{ - Q_OBJECT - -public: - MOApplication(int& argc, char** argv); - - // called from main() only once for stuff that persists across "restarts" - // - void firstTimeSetup(MOMultiProcess& multiProcess); - - // called from main() each time MO "restarts", loads settings, plugins, - // OrganizerCore and the current instance - // - int setup(MOMultiProcess& multiProcess, bool forceSelect); - - // shows splash, starts an api check, shows the main window and blocks until - // MO exits - // - int run(MOMultiProcess& multiProcess); - - // called from main() when MO "restarts", must clean up everything so setup() - // starts fresh - // - void resetForRestart(); - - // undefined if setup() wasn't called - // - OrganizerCore& core(); - - // wraps QApplication::notify() in a catch, reports errors and ignores them - // - bool notify(QObject* receiver, QEvent* event) override; - -public slots: - bool setStyleFile(const QString& style); - -private slots: - void updateStyle(const QString& fileName); - -private: - QFileSystemWatcher m_styleWatcher; - QString m_defaultStyle; - std::unique_ptr m_modules; - - std::unique_ptr m_instance; - std::unique_ptr m_settings; - std::unique_ptr m_nexus; - std::unique_ptr m_plugins; - std::unique_ptr m_core; - - void externalMessage(const QString& message); - std::unique_ptr getCurrentInstance(bool forceSelect); - std::optional setupInstanceLoop(Instance& currentInstance, PluginContainer& pc); - void purgeOldFiles(); -}; - - -class MOSplash -{ -public: - MOSplash( - const Settings& settings, const QString& dataPath, - const MOBase::IPluginGame* game); - - void close(); - -private: - std::unique_ptr ss_; - - QString getSplashPath( - const Settings& settings, const QString& dataPath, - const MOBase::IPluginGame* game) const; -}; - -#endif // MOAPPLICATION_H +/* +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 MOAPPLICATION_H +#define MOAPPLICATION_H + +#include "env.h" +#include +#include + +class Settings; +class MOMultiProcess; +class Instance; +class PluginContainer; +class OrganizerCore; +class NexusInterface; + +namespace MOBase +{ +class IPluginGame; +} + +class MOApplication : public QApplication +{ + Q_OBJECT + +public: + MOApplication(int& argc, char** argv); + + // called from main() only once for stuff that persists across "restarts" + // + void firstTimeSetup(MOMultiProcess& multiProcess); + + // called from main() each time MO "restarts", loads settings, plugins, + // OrganizerCore and the current instance + // + int setup(MOMultiProcess& multiProcess, bool forceSelect); + + // shows splash, starts an api check, shows the main window and blocks until + // MO exits + // + int run(MOMultiProcess& multiProcess); + + // called from main() when MO "restarts", must clean up everything so setup() + // starts fresh + // + void resetForRestart(); + + // undefined if setup() wasn't called + // + OrganizerCore& core(); + + // wraps QApplication::notify() in a catch, reports errors and ignores them + // + bool notify(QObject* receiver, QEvent* event) override; + +public slots: + bool setStyleFile(const QString& style); + +private slots: + void updateStyle(const QString& fileName); + +private: + QFileSystemWatcher m_styleWatcher; + QString m_defaultStyle; + std::unique_ptr m_modules; + + std::unique_ptr m_instance; + std::unique_ptr m_settings; + std::unique_ptr m_nexus; + std::unique_ptr m_plugins; + std::unique_ptr m_core; + + void externalMessage(const QString& message); + std::unique_ptr getCurrentInstance(bool forceSelect); + std::optional setupInstanceLoop(Instance& currentInstance, PluginContainer& pc); + void purgeOldFiles(); +}; + +class MOSplash +{ +public: + MOSplash(const Settings& settings, const QString& dataPath, + const MOBase::IPluginGame* game); + + void close(); + +private: + std::unique_ptr ss_; + + QString getSplashPath(const Settings& settings, const QString& dataPath, + const MOBase::IPluginGame* game) const; +}; + +#endif // MOAPPLICATION_H diff --git a/src/modconflicticondelegate.cpp b/src/modconflicticondelegate.cpp index fc0ee48a6..c0b4d46d8 100644 --- a/src/modconflicticondelegate.cpp +++ b/src/modconflicticondelegate.cpp @@ -1,29 +1,31 @@ #include "modconflicticondelegate.h" #include "modlist.h" #include "modlistview.h" -#include #include +#include using namespace MOBase; -ModConflictIconDelegate::ModConflictIconDelegate(ModListView* view, int logicalIndex, int compactSize) - : IconDelegate(view, logicalIndex, compactSize), m_view(view) -{ -} +ModConflictIconDelegate::ModConflictIconDelegate(ModListView* view, int logicalIndex, + int compactSize) + : IconDelegate(view, logicalIndex, compactSize), m_view(view) +{} -QList ModConflictIconDelegate::getIconsForFlags( - std::vector flags, bool compact) +QList +ModConflictIconDelegate::getIconsForFlags(std::vector flags, + bool compact) { QList result; // Don't do flags for overwrite - if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE_CONFLICT) != flags.end()) + if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE_CONFLICT) != + flags.end()) return result; // insert conflict icons to provide nicer alignment - { // insert loose file conflicts first - auto iter = std::find_first_of(flags.begin(), flags.end(), - s_ConflictFlags.begin(), s_ConflictFlags.end()); + { // insert loose file conflicts first + auto iter = std::find_first_of(flags.begin(), flags.end(), s_ConflictFlags.begin(), + s_ConflictFlags.end()); if (iter != flags.end()) { result.append(getFlagIcon(*iter)); flags.erase(iter); @@ -32,9 +34,9 @@ QList ModConflictIconDelegate::getIconsForFlags( } } - { // insert loose vs archive overwrite second + { // insert loose vs archive overwrite second auto iter = std::find(flags.begin(), flags.end(), - ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITE); + ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITE); if (iter != flags.end()) { result.append(getFlagIcon(*iter)); flags.erase(iter); @@ -43,9 +45,9 @@ QList ModConflictIconDelegate::getIconsForFlags( } } - { // insert loose vs archive overwritten third + { // insert loose vs archive overwritten third auto iter = std::find(flags.begin(), flags.end(), - ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITTEN); + ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITTEN); if (iter != flags.end()) { result.append(getFlagIcon(*iter)); flags.erase(iter); @@ -54,9 +56,10 @@ QList ModConflictIconDelegate::getIconsForFlags( } } - { // insert archive conflicts last - auto iter = std::find_first_of(flags.begin(), flags.end(), - s_ArchiveConflictFlags.begin(), s_ArchiveConflictFlags.end()); + { // insert archive conflicts last + auto iter = + std::find_first_of(flags.begin(), flags.end(), s_ArchiveConflictFlags.begin(), + s_ArchiveConflictFlags.end()); if (iter != flags.end()) { result.append(getFlagIcon(*iter)); flags.erase(iter); @@ -74,7 +77,7 @@ QList ModConflictIconDelegate::getIconsForFlags( return result; } -QList ModConflictIconDelegate::getIcons(const QModelIndex &index) const +QList ModConflictIconDelegate::getIcons(const QModelIndex& index) const { QVariant modIndex = index.data(ModList::IndexRole); @@ -90,23 +93,33 @@ QList ModConflictIconDelegate::getIcons(const QModelIndex &index) const QString ModConflictIconDelegate::getFlagIcon(ModInfo::EConflictFlag flag) { switch (flag) { - case ModInfo::FLAG_CONFLICT_MIXED: return QStringLiteral(":/MO/gui/emblem_conflict_mixed"); - case ModInfo::FLAG_CONFLICT_OVERWRITE: return QStringLiteral(":/MO/gui/emblem_conflict_overwrite"); - case ModInfo::FLAG_CONFLICT_OVERWRITTEN: return QStringLiteral(":/MO/gui/emblem_conflict_overwritten"); - case ModInfo::FLAG_CONFLICT_REDUNDANT: return QStringLiteral(":/MO/gui/emblem_conflict_redundant"); - case ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITE: return QStringLiteral(":/MO/gui/archive_loose_conflict_overwrite"); - case ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITTEN: return QStringLiteral(":/MO/gui/archive_loose_conflict_overwritten"); - case ModInfo::FLAG_ARCHIVE_CONFLICT_MIXED: return QStringLiteral(":/MO/gui/archive_conflict_mixed"); - case ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITE: return QStringLiteral(":/MO/gui/archive_conflict_winner"); - case ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITTEN: return QStringLiteral(":/MO/gui/archive_conflict_loser"); - case ModInfo::FLAG_OVERWRITE_CONFLICT: return QString(); - default: - log::warn("ModInfo flag {} has no defined icon", flag); - return QString(); + case ModInfo::FLAG_CONFLICT_MIXED: + return QStringLiteral(":/MO/gui/emblem_conflict_mixed"); + case ModInfo::FLAG_CONFLICT_OVERWRITE: + return QStringLiteral(":/MO/gui/emblem_conflict_overwrite"); + case ModInfo::FLAG_CONFLICT_OVERWRITTEN: + return QStringLiteral(":/MO/gui/emblem_conflict_overwritten"); + case ModInfo::FLAG_CONFLICT_REDUNDANT: + return QStringLiteral(":/MO/gui/emblem_conflict_redundant"); + case ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITE: + return QStringLiteral(":/MO/gui/archive_loose_conflict_overwrite"); + case ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITTEN: + return QStringLiteral(":/MO/gui/archive_loose_conflict_overwritten"); + case ModInfo::FLAG_ARCHIVE_CONFLICT_MIXED: + return QStringLiteral(":/MO/gui/archive_conflict_mixed"); + case ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITE: + return QStringLiteral(":/MO/gui/archive_conflict_winner"); + case ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITTEN: + return QStringLiteral(":/MO/gui/archive_conflict_loser"); + case ModInfo::FLAG_OVERWRITE_CONFLICT: + return QString(); + default: + log::warn("ModInfo flag {} has no defined icon", flag); + return QString(); } } -size_t ModConflictIconDelegate::getNumIcons(const QModelIndex &index) const +size_t ModConflictIconDelegate::getNumIcons(const QModelIndex& index) const { QVariant modIndex = index.data(ModList::IndexRole); @@ -117,9 +130,10 @@ size_t ModConflictIconDelegate::getNumIcons(const QModelIndex &index) const return m_view->conflictFlags(index, nullptr).size(); } -QSize ModConflictIconDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &modelIndex) const +QSize ModConflictIconDelegate::sizeHint(const QStyleOptionViewItem& option, + const QModelIndex& modelIndex) const { - size_t count = getNumIcons(modelIndex); + size_t count = getNumIcons(modelIndex); unsigned int index = modelIndex.data(ModList::IndexRole).toInt(); QSize result; if (index < ModInfo::getNumMods()) { diff --git a/src/modconflicticondelegate.h b/src/modconflicticondelegate.h index fd524444d..81e97179d 100644 --- a/src/modconflicticondelegate.h +++ b/src/modconflicticondelegate.h @@ -13,39 +13,35 @@ class ModConflictIconDelegate : public IconDelegate Q_OBJECT; public: - explicit ModConflictIconDelegate(ModListView* view, int logicalIndex = -1, int compactSize = 80); - QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex &index) const override; + explicit ModConflictIconDelegate(ModListView* view, int logicalIndex = -1, + int compactSize = 80); + QSize sizeHint(const QStyleOptionViewItem& option, + const QModelIndex& index) const override; protected: - - static QList getIconsForFlags(std::vector flags, bool compact); + static QList getIconsForFlags(std::vector flags, + bool compact); static QString getFlagIcon(ModInfo::EConflictFlag flag); - QList getIcons(const QModelIndex &index) const override; - size_t getNumIcons(const QModelIndex &index) const override; + QList getIcons(const QModelIndex& index) const override; + size_t getNumIcons(const QModelIndex& index) const override; // constructor for color table // - ModConflictIconDelegate() : ModConflictIconDelegate(nullptr) { } + ModConflictIconDelegate() : ModConflictIconDelegate(nullptr) {} private: static constexpr std::array s_ConflictFlags{ - ModInfo::FLAG_CONFLICT_MIXED, - ModInfo::FLAG_CONFLICT_OVERWRITE, - ModInfo::FLAG_CONFLICT_OVERWRITTEN, - ModInfo::FLAG_CONFLICT_REDUNDANT - }; + ModInfo::FLAG_CONFLICT_MIXED, ModInfo::FLAG_CONFLICT_OVERWRITE, + ModInfo::FLAG_CONFLICT_OVERWRITTEN, ModInfo::FLAG_CONFLICT_REDUNDANT}; static constexpr std::array s_ArchiveLooseConflictFlags{ - ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITE, - ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITTEN - }; + ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITE, + ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITTEN}; static constexpr std::array s_ArchiveConflictFlags{ - ModInfo::FLAG_ARCHIVE_CONFLICT_MIXED, - ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITE, - ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITTEN - }; + ModInfo::FLAG_ARCHIVE_CONFLICT_MIXED, ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITE, + ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITTEN}; ModListView* m_view; }; -#endif // MODCONFLICTICONDELEGATE_H +#endif // MODCONFLICTICONDELEGATE_H diff --git a/src/modcontenticondelegate.cpp b/src/modcontenticondelegate.cpp index 57a4dc6d8..c2c88933a 100644 --- a/src/modcontenticondelegate.cpp +++ b/src/modcontenticondelegate.cpp @@ -2,10 +2,10 @@ #include "modlistview.h" -ModContentIconDelegate::ModContentIconDelegate(ModListView* view, int column, int compactSize) - : IconDelegate(view, column, compactSize), m_view(view) -{ -} +ModContentIconDelegate::ModContentIconDelegate(ModListView* view, int column, + int compactSize) + : IconDelegate(view, column, compactSize), m_view(view) +{} QList ModContentIconDelegate::getIcons(const QModelIndex& index) const { @@ -30,8 +30,9 @@ size_t ModContentIconDelegate::getNumIcons(const QModelIndex& index) const return getIcons(index).size(); } -bool ModContentIconDelegate::helpEvent( - QHelpEvent* event, QAbstractItemView* view, const QStyleOptionViewItem& option, const QModelIndex& index) +bool ModContentIconDelegate::helpEvent(QHelpEvent* event, QAbstractItemView* view, + const QStyleOptionViewItem& option, + const QModelIndex& index) { if (!event || !view) { return false; @@ -39,19 +40,21 @@ bool ModContentIconDelegate::helpEvent( if (event->type() == QEvent::ToolTip) { // this code is from QAbstractItemDelegate::helpEvent, only the the way // text is retrieved has been changed - QHelpEvent* he = static_cast(event); - const int precision = inherits("QItemDelegate") ? 10 : 6; // keep in sync with DBL_DIG in qitemdelegate.cpp - const QString tooltip = index.isValid() ? m_view->contentsTooltip(index) : QString(); + QHelpEvent* he = static_cast(event); + const int precision = inherits("QItemDelegate") + ? 10 + : 6; // keep in sync with DBL_DIG in qitemdelegate.cpp + const QString tooltip = + index.isValid() ? m_view->contentsTooltip(index) : QString(); QRect rect; if (index.isValid()) { const QRect r = view->visualRect(index); - rect = QRect(view->mapToGlobal(r.topLeft()), r.size()); + rect = QRect(view->mapToGlobal(r.topLeft()), r.size()); } QToolTip::showText(he->globalPos(), tooltip, view, rect); event->setAccepted(!tooltip.isEmpty()); return true; - } - else { + } else { return IconDelegate::helpEvent(event, view, option, index); } } diff --git a/src/modcontenticondelegate.h b/src/modcontenticondelegate.h index c195df184..736244f6c 100644 --- a/src/modcontenticondelegate.h +++ b/src/modcontenticondelegate.h @@ -9,11 +9,11 @@ class ModContentIconDelegate : public IconDelegate { Q_OBJECT public: - - explicit ModContentIconDelegate(ModListView* view, int column = -1, int compactSize = 150); + explicit ModContentIconDelegate(ModListView* view, int column = -1, + int compactSize = 150); bool helpEvent(QHelpEvent* event, QAbstractItemView* view, - const QStyleOptionViewItem& option, const QModelIndex& index) override; + const QStyleOptionViewItem& option, const QModelIndex& index) override; protected: QList getIcons(const QModelIndex& index) const override; @@ -21,10 +21,10 @@ class ModContentIconDelegate : public IconDelegate // constructor for color table // - ModContentIconDelegate() : ModContentIconDelegate(nullptr) { } + ModContentIconDelegate() : ModContentIconDelegate(nullptr) {} private: ModListView* m_view; }; -#endif // GENERICICONDELEGATE_H +#endif // GENERICICONDELEGATE_H diff --git a/src/modelutils.cpp b/src/modelutils.cpp index 83054806a..1cdf96579 100644 --- a/src/modelutils.cpp +++ b/src/modelutils.cpp @@ -2,10 +2,11 @@ #include -namespace MOShared { +namespace MOShared +{ -QModelIndexList flatIndex( - const QAbstractItemModel* model, int column, const QModelIndex& parent) +QModelIndexList flatIndex(const QAbstractItemModel* model, int column, + const QModelIndex& parent) { QModelIndexList index; for (std::size_t i = 0; i < model->rowCount(parent); ++i) { @@ -15,7 +16,8 @@ QModelIndexList flatIndex( return index; } -static QModelIndexList visibleIndexImpl(QTreeView* view, int column, const QModelIndex& parent) +static QModelIndexList visibleIndexImpl(QTreeView* view, int column, + const QModelIndex& parent) { if (parent.isValid() && !view->isExpanded(parent)) { return {}; @@ -59,7 +61,8 @@ QModelIndex indexModelToView(const QModelIndex& index, const QAbstractItemView* return qindex; } -QModelIndexList indexModelToView(const QModelIndexList& index, const QAbstractItemView* view) +QModelIndexList indexModelToView(const QModelIndexList& index, + const QAbstractItemView* view) { QModelIndexList result; for (auto& idx : index) { @@ -72,16 +75,15 @@ QModelIndex indexViewToModel(const QModelIndex& index, const QAbstractItemModel* { if (index.model() == model) { return index; - } - else if (auto* proxy = qobject_cast(index.model())) { + } else if (auto* proxy = qobject_cast(index.model())) { return indexViewToModel(proxy->mapToSource(index), model); - } - else { + } else { return QModelIndex(); } } -QModelIndexList indexViewToModel(const QModelIndexList& index, const QAbstractItemModel* model) +QModelIndexList indexViewToModel(const QModelIndexList& index, + const QAbstractItemModel* model) { QModelIndexList result; for (auto& idx : index) { @@ -90,4 +92,4 @@ QModelIndexList indexViewToModel(const QModelIndexList& index, const QAbstractIt return result; } -} +} // namespace MOShared diff --git a/src/modelutils.h b/src/modelutils.h index e65109414..f2faeff77 100644 --- a/src/modelutils.h +++ b/src/modelutils.h @@ -1,25 +1,28 @@ #ifndef MODELUTILS_H #define MODELUTILS_H -#include #include +#include #include -namespace MOShared { +namespace MOShared +{ // retrieve all the row index under the given parent -QModelIndexList flatIndex( - const QAbstractItemModel* model, int column = 0, const QModelIndex& parent = QModelIndex()); +QModelIndexList flatIndex(const QAbstractItemModel* model, int column = 0, + const QModelIndex& parent = QModelIndex()); // retrieve all the visible index in the given view QModelIndexList visibleIndex(QTreeView* view, int column = 0); // convert back-and-forth through model proxies QModelIndex indexModelToView(const QModelIndex& index, const QAbstractItemView* view); -QModelIndexList indexModelToView(const QModelIndexList& index, const QAbstractItemView* view); +QModelIndexList indexModelToView(const QModelIndexList& index, + const QAbstractItemView* view); QModelIndex indexViewToModel(const QModelIndex& index, const QAbstractItemModel* model); -QModelIndexList indexViewToModel(const QModelIndexList& index, const QAbstractItemModel* model); +QModelIndexList indexViewToModel(const QModelIndexList& index, + const QAbstractItemModel* model); -} +} // namespace MOShared #endif diff --git a/src/modflagicondelegate.cpp b/src/modflagicondelegate.cpp index 30a3c376d..e656e3905 100644 --- a/src/modflagicondelegate.cpp +++ b/src/modflagicondelegate.cpp @@ -1,93 +1,102 @@ -#include "modflagicondelegate.h" -#include "modlist.h" -#include "modlistview.h" -#include -#include - -using namespace MOBase; - -ModFlagIconDelegate::ModFlagIconDelegate(ModListView* view, int column, int compactSize) - : IconDelegate(view, column, compactSize), m_view(view) -{ -} - -QList ModFlagIconDelegate::getIconsForFlags( - std::vector flags, bool compact) -{ - QList result; - - // Don't do flags for overwrite - if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end()) - return result; - - for (auto iter = flags.begin(); iter != flags.end(); ++iter) { - auto iconPath = getFlagIcon(*iter); - if (!iconPath.isEmpty()) - result.append(iconPath); - } - - return result; -} - -QList ModFlagIconDelegate::getIcons(const QModelIndex &index) const -{ - QVariant modid = index.data(ModList::IndexRole); - - if (modid.isValid()) { - bool compact; - auto flags = m_view->modFlags(index, &compact); - return getIconsForFlags(flags, compact || this->compact()); - } - - return {}; -} - -QString ModFlagIconDelegate::getFlagIcon(ModInfo::EFlag flag) -{ - switch (flag) { - case ModInfo::FLAG_BACKUP: return QStringLiteral(":/MO/gui/emblem_backup"); - case ModInfo::FLAG_INVALID: return QStringLiteral(":/MO/gui/problem"); - case ModInfo::FLAG_NOTENDORSED: return QStringLiteral(":/MO/gui/emblem_notendorsed"); - case ModInfo::FLAG_NOTES: return QStringLiteral(":/MO/gui/emblem_notes"); - case ModInfo::FLAG_HIDDEN_FILES: return QStringLiteral(":/MO/gui/emblem_hidden_files"); - case ModInfo::FLAG_ALTERNATE_GAME: return QStringLiteral(":/MO/gui/alternate_game"); - case ModInfo::FLAG_FOREIGN: return QString(); - case ModInfo::FLAG_SEPARATOR: return QString(); - case ModInfo::FLAG_OVERWRITE: return QString(); - case ModInfo::FLAG_PLUGIN_SELECTED: return QString(); - case ModInfo::FLAG_TRACKED: return QStringLiteral(":/MO/gui/tracked"); - default: - log::warn("ModInfo flag {} has no defined icon", flag); - return QString(); - } -} - -size_t ModFlagIconDelegate::getNumIcons(const QModelIndex &index) const -{ - unsigned int modIdx = index.data(ModList::IndexRole).toInt(); - if (modIdx < ModInfo::getNumMods()) { - ModInfo::Ptr info = ModInfo::getByIndex(modIdx); - std::vector flags = info->getFlags(); - return flags.size(); - } else { - return 0; - } -} - - -QSize ModFlagIconDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &modelIndex) const -{ - size_t count = getNumIcons(modelIndex); - unsigned int index = modelIndex.data(ModList::IndexRole).toInt(); - QSize result; - if (index < ModInfo::getNumMods()) { - result = QSize(static_cast(count) * 40, 20); - } else { - result = QSize(1, 20); - } - if (option.rect.width() > 0) { - result.setWidth(std::min(option.rect.width(), result.width())); - } - return result; -} - +#include "modflagicondelegate.h" +#include "modlist.h" +#include "modlistview.h" +#include +#include + +using namespace MOBase; + +ModFlagIconDelegate::ModFlagIconDelegate(ModListView* view, int column, int compactSize) + : IconDelegate(view, column, compactSize), m_view(view) +{} + +QList ModFlagIconDelegate::getIconsForFlags(std::vector flags, + bool compact) +{ + QList result; + + // Don't do flags for overwrite + if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end()) + return result; + + for (auto iter = flags.begin(); iter != flags.end(); ++iter) { + auto iconPath = getFlagIcon(*iter); + if (!iconPath.isEmpty()) + result.append(iconPath); + } + + return result; +} + +QList ModFlagIconDelegate::getIcons(const QModelIndex& index) const +{ + QVariant modid = index.data(ModList::IndexRole); + + if (modid.isValid()) { + bool compact; + auto flags = m_view->modFlags(index, &compact); + return getIconsForFlags(flags, compact || this->compact()); + } + + return {}; +} + +QString ModFlagIconDelegate::getFlagIcon(ModInfo::EFlag flag) +{ + switch (flag) { + case ModInfo::FLAG_BACKUP: + return QStringLiteral(":/MO/gui/emblem_backup"); + case ModInfo::FLAG_INVALID: + return QStringLiteral(":/MO/gui/problem"); + case ModInfo::FLAG_NOTENDORSED: + return QStringLiteral(":/MO/gui/emblem_notendorsed"); + case ModInfo::FLAG_NOTES: + return QStringLiteral(":/MO/gui/emblem_notes"); + case ModInfo::FLAG_HIDDEN_FILES: + return QStringLiteral(":/MO/gui/emblem_hidden_files"); + case ModInfo::FLAG_ALTERNATE_GAME: + return QStringLiteral(":/MO/gui/alternate_game"); + case ModInfo::FLAG_FOREIGN: + return QString(); + case ModInfo::FLAG_SEPARATOR: + return QString(); + case ModInfo::FLAG_OVERWRITE: + return QString(); + case ModInfo::FLAG_PLUGIN_SELECTED: + return QString(); + case ModInfo::FLAG_TRACKED: + return QStringLiteral(":/MO/gui/tracked"); + default: + log::warn("ModInfo flag {} has no defined icon", flag); + return QString(); + } +} + +size_t ModFlagIconDelegate::getNumIcons(const QModelIndex& index) const +{ + unsigned int modIdx = index.data(ModList::IndexRole).toInt(); + if (modIdx < ModInfo::getNumMods()) { + ModInfo::Ptr info = ModInfo::getByIndex(modIdx); + std::vector flags = info->getFlags(); + return flags.size(); + } else { + return 0; + } +} + +QSize ModFlagIconDelegate::sizeHint(const QStyleOptionViewItem& option, + const QModelIndex& modelIndex) const +{ + size_t count = getNumIcons(modelIndex); + unsigned int index = modelIndex.data(ModList::IndexRole).toInt(); + QSize result; + if (index < ModInfo::getNumMods()) { + result = QSize(static_cast(count) * 40, 20); + } else { + result = QSize(1, 20); + } + if (option.rect.width() > 0) { + result.setWidth(std::min(option.rect.width(), result.width())); + } + return result; +} diff --git a/src/modflagicondelegate.h b/src/modflagicondelegate.h index 24dd4a0e0..e3571b2f0 100644 --- a/src/modflagicondelegate.h +++ b/src/modflagicondelegate.h @@ -1,35 +1,37 @@ -#ifndef MODFLAGICONDELEGATE_H -#define MODFLAGICONDELEGATE_H - -#include - -#include "icondelegate.h" -#include "modinfo.h" - -class ModListView; - -class ModFlagIconDelegate : public IconDelegate -{ - Q_OBJECT; - -public: - explicit ModFlagIconDelegate(ModListView* view, int column = -1, int compactSize = 120); - QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; - -protected: - static QList getIconsForFlags(std::vector flags, bool compact); - static QString getFlagIcon(ModInfo::EFlag flag); - - QList getIcons(const QModelIndex &index) const override; - size_t getNumIcons(const QModelIndex &index) const override; - - // constructor for color table - // - ModFlagIconDelegate() : ModFlagIconDelegate(nullptr) { } - -private: - ModListView* m_view; - -}; - -#endif // MODFLAGICONDELEGATE_H +#ifndef MODFLAGICONDELEGATE_H +#define MODFLAGICONDELEGATE_H + +#include + +#include "icondelegate.h" +#include "modinfo.h" + +class ModListView; + +class ModFlagIconDelegate : public IconDelegate +{ + Q_OBJECT; + +public: + explicit ModFlagIconDelegate(ModListView* view, int column = -1, + int compactSize = 120); + QSize sizeHint(const QStyleOptionViewItem& option, + const QModelIndex& index) const override; + +protected: + static QList getIconsForFlags(std::vector flags, + bool compact); + static QString getFlagIcon(ModInfo::EFlag flag); + + QList getIcons(const QModelIndex& index) const override; + size_t getNumIcons(const QModelIndex& index) const override; + + // constructor for color table + // + ModFlagIconDelegate() : ModFlagIconDelegate(nullptr) {} + +private: + ModListView* m_view; +}; + +#endif // MODFLAGICONDELEGATE_H diff --git a/src/modidlineedit.cpp b/src/modidlineedit.cpp index 7d1f0c38d..1920f3778 100644 --- a/src/modidlineedit.cpp +++ b/src/modidlineedit.cpp @@ -4,16 +4,17 @@ #include #include -ModIDLineEdit::ModIDLineEdit(QWidget *parent) : QLineEdit(parent) {} -ModIDLineEdit::ModIDLineEdit(const QString &text, QWidget *parent) : QLineEdit(text, parent) {} +ModIDLineEdit::ModIDLineEdit(QWidget* parent) : QLineEdit(parent) {} +ModIDLineEdit::ModIDLineEdit(const QString& text, QWidget* parent) + : QLineEdit(text, parent) +{} -bool ModIDLineEdit::event(QEvent *event) +bool ModIDLineEdit::event(QEvent* event) { - if (event->type() == QEvent::WhatsThisClicked) - { - QWhatsThisClickedEvent *wtcEvent = static_cast(event); + if (event->type() == QEvent::WhatsThisClicked) { + QWhatsThisClickedEvent* wtcEvent = static_cast(event); emit linkClicked(wtcEvent->href().trimmed()); } return QLineEdit::event(event); -} \ No newline at end of file +} diff --git a/src/modidlineedit.h b/src/modidlineedit.h index 2a3db36cb..1572f07dc 100644 --- a/src/modidlineedit.h +++ b/src/modidlineedit.h @@ -8,15 +8,14 @@ class ModIDLineEdit : public QLineEdit Q_OBJECT public: - explicit ModIDLineEdit(QWidget *parent = 0); - explicit ModIDLineEdit(const QString &text, QWidget *parent = 0); + explicit ModIDLineEdit(QWidget* parent = 0); + explicit ModIDLineEdit(const QString& text, QWidget* parent = 0); public: virtual bool event(QEvent* event) override; signals: void linkClicked(QString); - }; -#endif //MODIDLINEEDIT_H \ No newline at end of file +#endif // MODIDLINEEDIT_H diff --git a/src/modinfo.cpp b/src/modinfo.cpp index 839a415f0..5ef36ef0d 100644 --- a/src/modinfo.cpp +++ b/src/modinfo.cpp @@ -20,26 +20,26 @@ along with Mod Organizer. If not, see . #include "modinfo.h" #include "modinfobackup.h" -#include "modinforegular.h" #include "modinfoforeign.h" #include "modinfooverwrite.h" +#include "modinforegular.h" #include "modinfoseparator.h" #include "categories.h" #include "modinfodialog.h" -#include "organizercore.h" #include "modlist.h" +#include "organizercore.h" #include "overwriteinfodialog.h" -#include "versioninfo.h" #include "thread_utils.h" +#include "versioninfo.h" -#include -#include #include "shared/appconfig.h" -#include -#include +#include #include #include +#include +#include +#include #include #include @@ -48,7 +48,6 @@ along with Mod Organizer. If not, see . using namespace MOBase; using namespace MOShared; - const std::set ModInfo::s_EmptySet; std::vector ModInfo::s_Collection; ModInfo::Ptr ModInfo::s_Overwrite; @@ -59,21 +58,22 @@ QRecursiveMutex ModInfo::s_Mutex; QString ModInfo::s_HiddenExt(".mohidden"); - -bool ModInfo::ByName(const ModInfo::Ptr &LHS, const ModInfo::Ptr &RHS) +bool ModInfo::ByName(const ModInfo::Ptr& LHS, const ModInfo::Ptr& RHS) { return QString::compare(LHS->name(), RHS->name(), Qt::CaseInsensitive) < 0; } bool ModInfo::isSeparatorName(const QString& name) { - static QRegularExpression separatorExp(QRegularExpression::anchoredPattern(".*_separator")); + static QRegularExpression separatorExp( + QRegularExpression::anchoredPattern(".*_separator")); return separatorExp.match(name).hasMatch(); } bool ModInfo::isBackupName(const QString& name) { - static QRegularExpression backupExp(QRegularExpression::anchoredPattern(".*backup[0-9]*")); + static QRegularExpression backupExp( + QRegularExpression::anchoredPattern(".*backup[0-9]*")); return backupExp.match(name).hasMatch(); } @@ -82,8 +82,7 @@ bool ModInfo::isRegularName(const QString& name) return !isSeparatorName(name) && !isBackupName(name); } - -ModInfo::Ptr ModInfo::createFrom(const QDir &dir, OrganizerCore& core) +ModInfo::Ptr ModInfo::createFrom(const QDir& dir, OrganizerCore& core) { QMutexLocker locker(&s_Mutex); ModInfo::Ptr result; @@ -100,13 +99,13 @@ ModInfo::Ptr ModInfo::createFrom(const QDir &dir, OrganizerCore& core) return result; } -ModInfo::Ptr ModInfo::createFromPlugin(const QString &modName, - const QString &espName, - const QStringList &bsaNames, - ModInfo::EModType modType, - OrganizerCore& core) { +ModInfo::Ptr ModInfo::createFromPlugin(const QString& modName, const QString& espName, + const QStringList& bsaNames, + ModInfo::EModType modType, OrganizerCore& core) +{ QMutexLocker locker(&s_Mutex); - ModInfo::Ptr result = ModInfo::Ptr(new ModInfoForeign(modName, espName, bsaNames, modType, core)); + ModInfo::Ptr result = + ModInfo::Ptr(new ModInfoForeign(modName, espName, bsaNames, modType, core)); result->m_Index = s_Collection.size(); s_Collection.push_back(result); return result; @@ -116,7 +115,7 @@ ModInfo::Ptr ModInfo::createFromOverwrite(OrganizerCore& core) { QMutexLocker locker(&s_Mutex); ModInfo::Ptr overwrite = ModInfo::Ptr(new ModInfoOverwrite(core)); - overwrite->m_Index = s_Collection.size(); + overwrite->m_Index = s_Collection.size(); s_Collection.push_back(overwrite); return overwrite; } @@ -127,7 +126,6 @@ unsigned int ModInfo::getNumMods() return static_cast(s_Collection.size()); } - ModInfo::Ptr ModInfo::getByIndex(unsigned int index) { QMutexLocker locker(&s_Mutex); @@ -135,11 +133,11 @@ ModInfo::Ptr ModInfo::getByIndex(unsigned int index) if (index >= s_Collection.size() && index != ULONG_MAX) { throw MyException(tr("invalid mod index: %1").arg(index)); } - if (index == ULONG_MAX) return s_Collection[ModInfo::getIndex("Overwrite")]; + if (index == ULONG_MAX) + return s_Collection[ModInfo::getIndex("Overwrite")]; return s_Collection[index]; } - std::vector ModInfo::getByModID(QString game, int modID) { QMutexLocker locker(&s_Mutex); @@ -164,15 +162,13 @@ std::vector ModInfo::getByModID(QString game, int modID) return result; } - -ModInfo::Ptr ModInfo::getByName(const QString &name) +ModInfo::Ptr ModInfo::getByName(const QString& name) { QMutexLocker locker(&s_Mutex); return s_Collection[ModInfo::getIndex(name)]; } - bool ModInfo::removeMod(unsigned int index) { QMutexLocker locker(&s_Mutex); @@ -186,7 +182,8 @@ bool ModInfo::removeMod(unsigned int index) // remove the actual mod (this is the most likely to fail so we do this first) if (modInfo->isRegular()) { if (!shellDelete(QStringList(modInfo->absolutePath()), true)) { - reportError(tr("remove: failed to delete mod '%1' directory").arg(modInfo->name())); + reportError( + tr("remove: failed to delete mod '%1' directory").arg(modInfo->name())); return false; } } @@ -194,11 +191,13 @@ bool ModInfo::removeMod(unsigned int index) // update the indices s_ModsByName.erase(s_ModsByName.find(modInfo->name())); - auto iter = s_ModsByModID.find(std::pair(modInfo->gameName(), modInfo->nexusId())); + auto iter = s_ModsByModID.find( + std::pair(modInfo->gameName(), modInfo->nexusId())); if (iter != s_ModsByModID.end()) { std::vector indices = iter->second; indices.erase(std::remove(indices.begin(), indices.end(), index), indices.end()); - s_ModsByModID[std::pair(modInfo->gameName(), modInfo->nexusId())] = indices; + s_ModsByModID[std::pair(modInfo->gameName(), modInfo->nexusId())] = + indices; } // finally, remove the mod from the collection @@ -209,8 +208,7 @@ bool ModInfo::removeMod(unsigned int index) return true; } - -unsigned int ModInfo::getIndex(const QString &name) +unsigned int ModInfo::getIndex(const QString& name) { QMutexLocker locker(&s_Mutex); @@ -222,7 +220,7 @@ unsigned int ModInfo::getIndex(const QString &name) return iter->second; } -unsigned int ModInfo::findMod(const boost::function &filter) +unsigned int ModInfo::findMod(const boost::function& filter) { for (unsigned int i = 0U; i < s_Collection.size(); ++i) { if (filter(s_Collection[i])) { @@ -232,19 +230,17 @@ unsigned int ModInfo::findMod(const boost::function &filter return UINT_MAX; } - -void ModInfo::updateFromDisc( - const QString& modsDirectory, OrganizerCore& core, - bool displayForeign, std::size_t refreshThreadCount) +void ModInfo::updateFromDisc(const QString& modsDirectory, OrganizerCore& core, + bool displayForeign, std::size_t refreshThreadCount) { TimeThis tt("ModInfo::updateFromDisc()"); QMutexLocker lock(&s_Mutex); s_Collection.clear(); - s_NextID = 0; + s_NextID = 0; s_Overwrite = nullptr; - { // list all directories in the mod directory and make a mod out of each + { // list all directories in the mod directory and make a mod out of each QDir mods(QDir::fromNativeSeparators(modsDirectory)); mods.setFilter(QDir::Dirs | QDir::NoDotAndDotDot); QDirIterator modIter(mods); @@ -253,17 +249,22 @@ void ModInfo::updateFromDisc( } } - auto* game = core.managedGame(); - UnmanagedMods *unmanaged = game->feature(); + auto* game = core.managedGame(); + UnmanagedMods* unmanaged = game->feature(); if (unmanaged != nullptr) { - for (const QString &modName : unmanaged->mods(!displayForeign)) { - ModInfo::EModType modType = game->DLCPlugins().contains(unmanaged->referenceFile(modName).fileName(), Qt::CaseInsensitive) ? ModInfo::EModType::MOD_DLC : - (game->CCPlugins().contains(unmanaged->referenceFile(modName).fileName(), Qt::CaseInsensitive) ? ModInfo::EModType::MOD_CC : ModInfo::EModType::MOD_DEFAULT); + for (const QString& modName : unmanaged->mods(!displayForeign)) { + ModInfo::EModType modType = + game->DLCPlugins().contains(unmanaged->referenceFile(modName).fileName(), + Qt::CaseInsensitive) + ? ModInfo::EModType::MOD_DLC + : (game->CCPlugins().contains( + unmanaged->referenceFile(modName).fileName(), Qt::CaseInsensitive) + ? ModInfo::EModType::MOD_CC + : ModInfo::EModType::MOD_DEFAULT); createFromPlugin(unmanaged->displayName(modName), unmanaged->referenceFile(modName).absoluteFilePath(), - unmanaged->secondaryFiles(modName), - modType, core); + unmanaged->secondaryFiles(modName), modType, core); } } @@ -271,41 +272,35 @@ void ModInfo::updateFromDisc( std::sort(s_Collection.begin(), s_Collection.end(), ModInfo::ByName); - parallelMap(std::begin(s_Collection), std::end(s_Collection), &ModInfo::prefetch, refreshThreadCount); + parallelMap(std::begin(s_Collection), std::end(s_Collection), &ModInfo::prefetch, + refreshThreadCount); updateIndices(); - } - void ModInfo::updateIndices() { s_ModsByName.clear(); s_ModsByModID.clear(); for (unsigned int i = 0; i < s_Collection.size(); ++i) { - QString modName = s_Collection[i]->internalName(); - QString game = s_Collection[i]->gameName(); - int modID = s_Collection[i]->nexusId(); + QString modName = s_Collection[i]->internalName(); + QString game = s_Collection[i]->gameName(); + int modID = s_Collection[i]->nexusId(); s_Collection[i]->m_Index = i; - s_ModsByName[modName] = i; + s_ModsByName[modName] = i; s_ModsByModID[std::pair(game, modID)].push_back(i); } } +ModInfo::ModInfo(OrganizerCore& core) : m_PrimaryCategory(-1), m_Core(core) {} -ModInfo::ModInfo(OrganizerCore& core) - : m_PrimaryCategory(-1), m_Core(core) -{ -} - - -bool ModInfo::checkAllForUpdate(PluginContainer *pluginContainer, QObject *receiver) +bool ModInfo::checkAllForUpdate(PluginContainer* pluginContainer, QObject* receiver) { bool updatesAvailable = true; QDateTime earliest = QDateTime::currentDateTimeUtc(); - QDateTime latest = QDateTime::fromMSecsSinceEpoch(0); + QDateTime latest = QDateTime::fromMSecsSinceEpoch(0); std::set games; for (auto mod : s_Collection) { if (mod->canBeUpdated()) { @@ -319,16 +314,19 @@ bool ModInfo::checkAllForUpdate(PluginContainer *pluginContainer, QObject *recei // Detect invalid source games for (auto itr = games.begin(); itr != games.end();) { - auto gamePlugins = pluginContainer->plugins(); + auto gamePlugins = pluginContainer->plugins(); IPluginGame* gamePlugin = qApp->property("managed_game").value(); for (auto plugin : gamePlugins) { - if (plugin != nullptr && plugin->gameShortName().compare(*itr, Qt::CaseInsensitive) == 0) { + if (plugin != nullptr && + plugin->gameShortName().compare(*itr, Qt::CaseInsensitive) == 0) { gamePlugin = plugin; break; } } if (gamePlugin != nullptr && gamePlugin->gameNexusName().isEmpty()) { - log::warn("{}", tr("The update check has found a mod with a Nexus ID and source game of %1, but this game is not a valid Nexus source.").arg(gamePlugin->gameName())); + log::warn("{}", tr("The update check has found a mod with a Nexus ID and source " + "game of %1, but this game is not a valid Nexus source.") + .arg(gamePlugin->gameName())); itr = games.erase(itr); } else { ++itr; @@ -338,66 +336,95 @@ bool ModInfo::checkAllForUpdate(PluginContainer *pluginContainer, QObject *recei if (latest < QDateTime::currentDateTimeUtc().addMonths(-1)) { std::set> organizedGames; for (auto mod : s_Collection) { - if (mod->canBeUpdated() && mod->getLastNexusUpdate() < QDateTime::currentDateTimeUtc().addMonths(-1)) { - organizedGames.insert(std::make_pair(mod->gameName().toLower(), mod->nexusId())); + if (mod->canBeUpdated() && + mod->getLastNexusUpdate() < QDateTime::currentDateTimeUtc().addMonths(-1)) { + organizedGames.insert( + std::make_pair(mod->gameName().toLower(), mod->nexusId())); } } if (organizedGames.empty()) { - log::warn("{}", tr("All of your mods have been checked recently. We restrict update checks to help preserve your available API requests.")); + log::warn("{}", + tr("All of your mods have been checked recently. We restrict update " + "checks to help preserve your available API requests.")); updatesAvailable = false; } else { - log::info("{}", tr( - "You have mods that haven't been checked within the last month using the new API. These mods must be checked before we can use the bulk update API. " - "This will consume significantly more API requests than usual. You will need to rerun the update check once complete in order to parse the remaining mods.")); + log::info("{}", tr("You have mods that haven't been checked within the last " + "month using the new API. These mods must be checked before " + "we can use the bulk update API. " + "This will consume significantly more API requests than " + "usual. You will need to rerun the update check once complete " + "in order to parse the remaining mods.")); } for (auto game : organizedGames) - NexusInterface::instance().requestUpdates(game.second, receiver, QVariant(), game.first, QString()); + NexusInterface::instance().requestUpdates(game.second, receiver, QVariant(), + game.first, QString()); } else if (earliest < QDateTime::currentDateTimeUtc().addMonths(-1)) { for (auto gameName : games) - NexusInterface::instance().requestUpdateInfo(gameName, NexusInterface::UpdatePeriod::MONTH, receiver, QVariant(true), QString()); + NexusInterface::instance().requestUpdateInfo(gameName, + NexusInterface::UpdatePeriod::MONTH, + receiver, QVariant(true), QString()); } else if (earliest < QDateTime::currentDateTimeUtc().addDays(-7)) { for (auto gameName : games) - NexusInterface::instance().requestUpdateInfo(gameName, NexusInterface::UpdatePeriod::MONTH, receiver, QVariant(false), QString()); + NexusInterface::instance().requestUpdateInfo( + gameName, NexusInterface::UpdatePeriod::MONTH, receiver, QVariant(false), + QString()); } else if (earliest < QDateTime::currentDateTimeUtc().addDays(-1)) { for (auto gameName : games) - NexusInterface::instance().requestUpdateInfo(gameName, NexusInterface::UpdatePeriod::WEEK, receiver, QVariant(false), QString()); + NexusInterface::instance().requestUpdateInfo( + gameName, NexusInterface::UpdatePeriod::WEEK, receiver, QVariant(false), + QString()); } else { for (auto gameName : games) - NexusInterface::instance().requestUpdateInfo(gameName, NexusInterface::UpdatePeriod::DAY, receiver, QVariant(false), QString()); + NexusInterface::instance().requestUpdateInfo( + gameName, NexusInterface::UpdatePeriod::DAY, receiver, QVariant(false), + QString()); } return updatesAvailable; } -std::set> ModInfo::filteredMods(QString gameName, QVariantList updateData, bool addOldMods, bool markUpdated) +std::set> ModInfo::filteredMods(QString gameName, + QVariantList updateData, + bool addOldMods, + bool markUpdated) { std::set> finalMods; for (QVariant result : updateData) { QVariantMap update = result.toMap(); - std::copy_if(s_Collection.begin(), s_Collection.end(), std::inserter(finalMods, finalMods.end()), [=](QSharedPointer info) -> bool { - if (info->nexusId() == update["mod_id"].toInt() && info->gameName().compare(gameName, Qt::CaseInsensitive) == 0) - if (info->getLastNexusUpdate().addSecs(-3600) < QDateTime::fromSecsSinceEpoch(update["latest_file_update"].toInt(), Qt::UTC)) - return true; - return false; - }); + std::copy_if(s_Collection.begin(), s_Collection.end(), + std::inserter(finalMods, finalMods.end()), + [=](QSharedPointer info) -> bool { + if (info->nexusId() == update["mod_id"].toInt() && + info->gameName().compare(gameName, Qt::CaseInsensitive) == 0) + if (info->getLastNexusUpdate().addSecs(-3600) < + QDateTime::fromSecsSinceEpoch( + update["latest_file_update"].toInt(), Qt::UTC)) + return true; + return false; + }); } if (addOldMods) for (auto mod : s_Collection) - if (mod->getLastNexusUpdate() < QDateTime::currentDateTimeUtc().addMonths(-1) && mod->gameName().compare(gameName, Qt::CaseInsensitive) == 0) + if (mod->getLastNexusUpdate() < QDateTime::currentDateTimeUtc().addMonths(-1) && + mod->gameName().compare(gameName, Qt::CaseInsensitive) == 0) finalMods.insert(mod); if (markUpdated) { std::set> updates; - std::copy_if(s_Collection.begin(), s_Collection.end(), std::inserter(updates, updates.end()), [=](QSharedPointer info) -> bool { - if (info->gameName().compare(gameName, Qt::CaseInsensitive) == 0 && info->canBeUpdated()) - return true; - return false; - }); + std::copy_if(s_Collection.begin(), s_Collection.end(), + std::inserter(updates, updates.end()), + [=](QSharedPointer info) -> bool { + if (info->gameName().compare(gameName, Qt::CaseInsensitive) == 0 && + info->canBeUpdated()) + return true; + return false; + }); std::set> diff; - std::set_difference(updates.begin(), updates.end(), finalMods.begin(), finalMods.end(), std::inserter(diff, diff.end())); + std::set_difference(updates.begin(), updates.end(), finalMods.begin(), + finalMods.end(), std::inserter(diff, diff.end())); for (auto skipped : diff) { skipped->setLastNexusUpdate(QDateTime::currentDateTimeUtc()); } @@ -405,7 +432,7 @@ std::set> ModInfo::filteredMods(QString gameName, QVaria return finalMods; } -void ModInfo::manualUpdateCheck(QObject *receiver, std::multimap IDs) +void ModInfo::manualUpdateCheck(QObject* receiver, std::multimap IDs) { std::vector> mods; std::set> organizedGames; @@ -423,45 +450,48 @@ void ModInfo::manualUpdateCheck(QObject *receiver, std::multimap I mods.push_back(matchedMod); } } - mods.erase( - std::remove_if(mods.begin(), mods.end(), [](ModInfo::Ptr mod) -> bool { return mod->nexusId() <= 0; }), - mods.end() - ); + mods.erase(std::remove_if(mods.begin(), mods.end(), + [](ModInfo::Ptr mod) -> bool { + return mod->nexusId() <= 0; + }), + mods.end()); for (auto mod : mods) { mod->setLastNexusUpdate(QDateTime()); } - std::sort(mods.begin(), mods.end(), [](QSharedPointer a, QSharedPointer b) -> bool { - return a->getLastNexusUpdate() < b->getLastNexusUpdate(); - }); + std::sort(mods.begin(), mods.end(), + [](QSharedPointer a, QSharedPointer b) -> bool { + return a->getLastNexusUpdate() < b->getLastNexusUpdate(); + }); if (mods.size()) { log::info("Checking updates for {} mods...", mods.size()); for (auto mod : mods) { - organizedGames.insert(std::make_pair(mod->gameName().toLower(), mod->nexusId())); + organizedGames.insert( + std::make_pair(mod->gameName().toLower(), mod->nexusId())); } for (auto game : organizedGames) { - NexusInterface::instance().requestUpdates(game.second, receiver, QVariant(), game.first, QString()); + NexusInterface::instance().requestUpdates(game.second, receiver, QVariant(), + game.first, QString()); } } else { log::info("None of the selected mods can be updated."); } } - -void ModInfo::setVersion(const VersionInfo &version) +void ModInfo::setVersion(const VersionInfo& version) { m_Version = version; } -void ModInfo::setPluginSelected(const bool &isSelected) +void ModInfo::setPluginSelected(const bool& isSelected) { m_PluginSelected = isSelected; } -void ModInfo::addCategory(const QString &categoryName) +void ModInfo::addCategory(const QString& categoryName) { int id = CategoryFactory::instance()->getCategoryID(categoryName); if (id == -1) { @@ -470,7 +500,7 @@ void ModInfo::addCategory(const QString &categoryName) setCategory(id, true); } -bool ModInfo::removeCategory(const QString &categoryName) +bool ModInfo::removeCategory(const QString& categoryName) { int id = CategoryFactory::instance()->getCategoryID(categoryName); if (id == -1) { @@ -487,7 +517,7 @@ QStringList ModInfo::categories() const { QStringList result; - CategoryFactory *catFac = CategoryFactory::instance(); + CategoryFactory* catFac = CategoryFactory::instance(); for (int id : m_Categories) { result.append(catFac->getCategoryName(catFac->getCategoryIndex(id))); } @@ -504,12 +534,9 @@ bool ModInfo::hasFlag(ModInfo::EFlag flag) const bool ModInfo::hasAnyOfTheseFlags(std::vector flags) const { std::vector modFlags = getFlags(); - for (auto modFlag : modFlags) - { - for (auto flag : flags) - { - if (modFlag == flag) - { + for (auto modFlag : modFlags) { + for (auto flag : flags) { + if (modFlag == flag) { return true; } } @@ -519,7 +546,8 @@ bool ModInfo::hasAnyOfTheseFlags(std::vector flags) const bool ModInfo::categorySet(int categoryID) const { - for (std::set::const_iterator iter = m_Categories.begin(); iter != m_Categories.end(); ++iter) { + for (std::set::const_iterator iter = m_Categories.begin(); + iter != m_Categories.end(); ++iter) { if ((*iter == categoryID) || (CategoryFactory::instance()->isDescendantOf(*iter, categoryID))) { return true; diff --git a/src/modinfo.h b/src/modinfo.h index 8f82a4533..9895d44c6 100644 --- a/src/modinfo.h +++ b/src/modinfo.h @@ -20,8 +20,8 @@ along with Mod Organizer. If not, see . #ifndef MODINFO_H #define MODINFO_H -#include "imodinterface.h" #include "ifiletree.h" +#include "imodinterface.h" #include "versioninfo.h" class OrganizerCore; @@ -29,11 +29,11 @@ class PluginContainer; class QDir; class QDateTime; +#include #include #include #include #include -#include #include @@ -41,8 +41,14 @@ class QDateTime; #include #include -namespace MOBase { class IPluginGame; } -namespace MOShared { class DirectoryEntry; } +namespace MOBase +{ +class IPluginGame; +} +namespace MOShared +{ +class DirectoryEntry; +} /** * @brief Represents meta information about a single mod. @@ -56,13 +62,13 @@ class ModInfo : public QObject, public MOBase::IModInterface Q_OBJECT -public: // Type definitions: - +public: // Type definitions: typedef QSharedPointer Ptr; static QString s_HiddenExt; - enum EConflictFlag { + enum EConflictFlag + { FLAG_CONFLICT_OVERWRITE, FLAG_CONFLICT_OVERWRITTEN, FLAG_CONFLICT_MIXED, @@ -75,7 +81,8 @@ class ModInfo : public QObject, public MOBase::IModInterface FLAG_OVERWRITE_CONFLICT, }; - enum EFlag { + enum EFlag + { FLAG_INVALID, FLAG_BACKUP, FLAG_SEPARATOR, @@ -89,31 +96,35 @@ class ModInfo : public QObject, public MOBase::IModInterface FLAG_TRACKED, }; - enum EHighlight { - HIGHLIGHT_NONE = 0, - HIGHLIGHT_INVALID = 1, - HIGHLIGHT_CENTER = 2, + enum EHighlight + { + HIGHLIGHT_NONE = 0, + HIGHLIGHT_INVALID = 1, + HIGHLIGHT_CENTER = 2, HIGHLIGHT_IMPORTANT = 4, - HIGHLIGHT_PLUGIN = 8 + HIGHLIGHT_PLUGIN = 8 }; - enum EModType { + enum EModType + { MOD_DEFAULT, MOD_DLC, MOD_CC }; - -public: // Static functions: - +public: // Static functions: /** * @brief Read the mod directory and Mod ModInfo objects for all subdirectories. */ - static void updateFromDisc( - const QString &modDirectory, OrganizerCore& core, - bool displayForeign, std::size_t refreshThreadCount); + static void updateFromDisc(const QString& modDirectory, OrganizerCore& core, + bool displayForeign, std::size_t refreshThreadCount); - static void clear() { s_Collection.clear(); s_ModsByName.clear(); s_ModsByModID.clear(); } + static void clear() + { + s_Collection.clear(); + s_ModsByName.clear(); + s_ModsByModID.clear(); + } /** * @brief Retrieve the number of mods. @@ -129,8 +140,8 @@ class ModInfo : public QObject, public MOBase::IModInterface * * @return a reference counting pointer to the mod info. * - * @note since the pointer is reference counting, the pointer remains valid even if the - * collection is refreshed in a different thread. + * @note since the pointer is reference counting, the pointer remains valid even if + * the collection is refreshed in a different thread. */ static ModInfo::Ptr getByIndex(unsigned int index); @@ -139,7 +150,8 @@ class ModInfo : public QObject, public MOBase::IModInterface * * @param modID The mod id to look up. * - * @return a vector of reference counting pointer to the mod info objects with the given mod ID. + * @return a vector of reference counting pointer to the mod info objects with the + * given mod ID. */ static std::vector getByModID(QString game, int modID); @@ -153,13 +165,13 @@ class ModInfo : public QObject, public MOBase::IModInterface * @note Since the pointer is reference counter, the pointer remains valid even if the * collection is refreshed in a different thread. */ - static ModInfo::Ptr getByName(const QString &name); + static ModInfo::Ptr getByName(const QString& name); /** * @brief Remove a mod by index. * - * This physically deletes the specified mod from the disc and updates the ModInfo collection - * but not other structures that reference mods. + * This physically deletes the specified mod from the disc and updates the ModInfo + * collection but not other structures that reference mods. * * @param index Index of the mod to delete. * @@ -174,7 +186,7 @@ class ModInfo : public QObject, public MOBase::IModInterface * * @return The index of the mod. If the mod doesn't exist, UINT_MAX is returned. */ - static unsigned int getIndex(const QString &name); + static unsigned int getIndex(const QString& name); /** * @brief Retrieve the overwrite mod. @@ -182,32 +194,35 @@ class ModInfo : public QObject, public MOBase::IModInterface static ModInfo::Ptr getOverwrite() { return s_Overwrite; } /** - * @brief Find the first mod that fulfills the filter function (after no particular order). + * @brief Find the first mod that fulfills the filter function (after no particular + * order). * * @param filter A function to filter mods by. Should return true for a match. * * @return index of the matching mod or UINT_MAX if there was no match. */ - static unsigned int findMod(const boost::function &filter); + static unsigned int findMod(const boost::function& filter); /** * @brief Run a limited batch of mod update checks for "newest version" information. * */ - static void manualUpdateCheck(QObject *receiver, std::multimap IDs); + static void manualUpdateCheck(QObject* receiver, std::multimap IDs); /** - * @brief Query nexus information for every mod and update the "newest version" information. + * @brief Query nexus information for every mod and update the "newest version" + * information. * * @return true if any mods are checked for update. */ - static bool checkAllForUpdate(PluginContainer *pluginContainer, QObject *receiver); + static bool checkAllForUpdate(PluginContainer* pluginContainer, QObject* receiver); /** * */ - static std::set filteredMods( - QString gameName, QVariantList updateData, bool addOldMods = false, bool markUpdated = false); + static std::set filteredMods(QString gameName, QVariantList updateData, + bool addOldMods = false, + bool markUpdated = false); /** * @brief Check wheter a name corresponds to a separator or not, @@ -230,11 +245,10 @@ class ModInfo : public QObject, public MOBase::IModInterface */ static bool isRegularName(const QString& name); -public: // IModInterface implementations / Re-declaration - - // Note: This section contains default-implementation for some of the virtual methods from - // IModInterface, but also redeclaration of all the pure-virtual methods to centralize all - // of them in a single place. +public: // IModInterface implementations / Re-declaration + // Note: This section contains default-implementation for some of the virtual methods + // from IModInterface, but also redeclaration of all the pure-virtual methods to + // centralize all of them in a single place. /** * @return the name of the mod. @@ -257,8 +271,8 @@ class ModInfo : public QObject, public MOBase::IModInterface virtual QString notes() const = 0; /** - * @brief Retrieve the short name of the game associated with this mod. This may differ - * from the current game plugin (e.g. you can install a Skyrim LE game in a SSE + * @brief Retrieve the short name of the game associated with this mod. This may + * differ from the current game plugin (e.g. you can install a Skyrim LE game in a SSE * installation). * * @return the name of the game associated with this mod. @@ -281,14 +295,14 @@ class ModInfo : public QObject, public MOBase::IModInterface virtual MOBase::VersionInfo version() const override { return m_Version; } /** - * @return the newest version of thid mod (as known by MO2). If this matches version(), - * then the mod is up-to-date. + * @return the newest version of thid mod (as known by MO2). If this matches + * version(), then the mod is up-to-date. */ virtual MOBase::VersionInfo newestVersion() const = 0; /** - * @return the ignored version of this mod (for update), or an invalid version if the user - * did not ignore version for this mod. + * @return the ignored version of this mod (for update), or an invalid version if the + * user did not ignore version for this mod. */ virtual MOBase::VersionInfo ignoredVersion() const = 0; @@ -302,17 +316,17 @@ class ModInfo : public QObject, public MOBase::IModInterface /** * @return true if this mod was marked as converted by the user. * - * @note When a mod is for a different game, a flag is shown to users to warn them, but - * they can mark mods as converted to remove this flag. + * @note When a mod is for a different game, a flag is shown to users to warn them, + * but they can mark mods as converted to remove this flag. */ virtual bool converted() const = 0; /** * @return true if th is mod was marked as containing valid game data. * - * @note MO2 uses ModDataChecker to check the content of mods, but sometimes these fail, in - * which case mods are incorrectly marked as 'not containing valid games data'. Users can - * choose to mark these mods as valid to hide the warning / flag. + * @note MO2 uses ModDataChecker to check the content of mods, but sometimes these + * fail, in which case mods are incorrectly marked as 'not containing valid games + * data'. Users can choose to mark these mods as valid to hide the warning / flag. */ virtual bool validated() const = 0; @@ -340,12 +354,18 @@ class ModInfo : public QObject, public MOBase::IModInterface /** * @return the tracked state of this mod. */ - virtual MOBase::TrackedState trackedState() const override { return MOBase::TrackedState::TRACKED_FALSE; } + virtual MOBase::TrackedState trackedState() const override + { + return MOBase::TrackedState::TRACKED_FALSE; + } /** * @return the endorsement state of this mod. */ - virtual MOBase::EndorsedState endorsedState() const override { return MOBase::EndorsedState::ENDORSED_NEVER; } + virtual MOBase::EndorsedState endorsedState() const override + { + return MOBase::EndorsedState::ENDORSED_NEVER; + } /** * @brief Retrieve a file tree corresponding to the underlying disk content @@ -383,8 +403,7 @@ class ModInfo : public QObject, public MOBase::IModInterface */ virtual bool isForeign() const { return false; } -public: // Mutable operations: - +public: // Mutable operations: /** * @brief Sets or changes the version of this mod. * @@ -431,7 +450,8 @@ class ModInfo : public QObject, public MOBase::IModInterface virtual void addNexusCategory(int categoryID) = 0; /** - * @brief Assigns a category to the mod. If the named category does not exist it is created. + * @brief Assigns a category to the mod. If the named category does not exist it is + * created. * * @param categoryName Name of the new category. */ @@ -466,8 +486,7 @@ class ModInfo : public QObject, public MOBase::IModInterface */ virtual bool setName(const QString& name) = 0; -public: // Methods after this do not come from IModInterface: - +public: // Methods after this do not come from IModInterface: /** * @return true if this mod is empty, false otherwise. */ @@ -490,7 +509,8 @@ class ModInfo : public QObject, public MOBase::IModInterface virtual bool updateIgnored() const = 0; /** - * @brief Check if the "newest" version of the mod is older than the installed version. + * @brief Check if the "newest" version of the mod is older than the installed + * version. * * Check 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 @@ -504,9 +524,9 @@ class ModInfo : public QObject, public MOBase::IModInterface * @brief Request an update of nexus description for this mod. * * This requests mod information from the nexus. This is an asynchronous request, - * so there is no immediate effect of this call. Right now, Mod Organizer interprets the - * "newest version" and "description" from the response, though the description is only - * stored in memory. + * so there is no immediate effect of this call. Right now, Mod Organizer interprets + * the "newest version" and "description" from the response, though the description is + * only stored in memory. * */ virtual bool updateNXMInfo() = 0; @@ -519,39 +539,41 @@ class ModInfo : public QObject, public MOBase::IModInterface * @param categoryID ID of the category to set. * @param active Determines whether the category is assigned or unassigned. * - * @note This function does not test whether categoryID actually identifies a valid category. + * @note This function does not test whether categoryID actually identifies a valid + * category. */ virtual void setCategory(int categoryID, bool active) = 0; /** - * @brief Changes the comments (manually set information displayed in the mod list) for this mod. + * @brief Changes the comments (manually set information displayed in the mod list) + * for this mod. * * @param comments The new comments. */ - virtual void setComments(const QString &comments) = 0; + virtual void setComments(const QString& comments) = 0; /** * @brief Change the notes (manually set information) for this mod. * * @param notes The new notes. */ - virtual void setNotes(const QString ¬es) = 0; + virtual void setNotes(const QString& notes) = 0; /** - * @brief Controls if mod should be highlighted based on plugin selection. - * - * @param isSelected Whether or not the plugin has a selected mod. - */ - virtual void setPluginSelected(const bool &isSelected); + * @brief Controls if mod should be highlighted based on plugin selection. + * + * @param isSelected Whether or not the plugin has a selected mod. + */ + virtual void setPluginSelected(const bool& isSelected); /** * @brief Sets the repository that was used to download the mod. */ - virtual void setRepository(const QString &) {} + virtual void setRepository(const QString&) {} /** - * @brief Set the mod to "I do not intend to endorse.". The mod will not show as unendorsed - * but can still be endorsed. + * @brief Set the mod to "I do not intend to endorse.". The mod will not show as + * unendorsed but can still be endorsed. */ virtual void setNeverEndorse() = 0; @@ -589,9 +611,9 @@ class ModInfo : public QObject, public MOBase::IModInterface virtual void clearCaches() {} /** - * @brief Retrieve the internal name of the mod. This is usually the same as the regular name, - * but with special mod types it might be used to distinguish between mods that have the same - * visible name. + * @brief Retrieve the internal name of the mod. This is usually the same as the + * regular name, but with special mod types it might be used to distinguish between + * mods that have the same visible name. * * @return the internal mod name. */ @@ -640,8 +662,8 @@ class ModInfo : public QObject, public MOBase::IModInterface /** * @return a list of content types contained in a mod. * - * @note The IDs of the content are game-dependent. See the ModDataContent game feature - * for more details on this. + * @note The IDs of the content are game-dependent. See the ModDataContent game + * feature for more details on this. */ virtual const std::set& getContents() const = 0; @@ -693,8 +715,8 @@ class ModInfo : public QObject, public MOBase::IModInterface virtual QDateTime creationTime() const = 0; /** - * @return the list of files that, if they exist in the data directory are treated as files in - * THIS mod. + * @return the list of files that, if they exist in the data directory are treated as + * files in THIS mod. */ virtual QStringList stealFiles() const { return QStringList(); } @@ -710,7 +732,7 @@ class ModInfo : public QObject, public MOBase::IModInterface * * @note Currently, this changes the color of the cell under the "Notes" column. */ - virtual void setColor(QColor color) { } + virtual void setColor(QColor color) {} /** * @brief Adds the information that a file has been installed into this mod. @@ -736,7 +758,7 @@ class ModInfo : public QObject, public MOBase::IModInterface * * @return the IDs of categories this mod belongs to. */ - const std::set &getCategories() const { return m_Categories; } + const std::set& getCategories() const { return m_Categories; } /** * @brief Sets the new primary category of the mod. @@ -746,20 +768,21 @@ class ModInfo : public QObject, public MOBase::IModInterface virtual void setPrimaryCategory(int categoryID) { m_PrimaryCategory = categoryID; } /** - * @return true if this mod is considered "valid", that is it contains data used by the game. + * @return true if this mod is considered "valid", that is it contains data used by + * the game. */ virtual bool isValid() const = 0; /** - * @brief Updates the mod to flag it as converted in order to ignore the alternate game - * warning. + * @brief Updates the mod to flag it as converted in order to ignore the alternate + * game warning. */ virtual void markConverted(bool) {} /** - * @brief Updates the mod to flag it as valid in order to ignore the invalid game data - * flag. - */ + * @brief Updates the mod to flag it as valid in order to ignore the invalid game data + * flag. + */ virtual void markValidated(bool) {} /** @@ -797,7 +820,8 @@ class ModInfo : public QObject, public MOBase::IModInterface * * @param url The new URL. */ - void setUrl(QString const& url) override { + void setUrl(QString const& url) override + { setHasCustomURL(true); setCustomURL(url); } @@ -809,8 +833,7 @@ class ModInfo : public QObject, public MOBase::IModInterface */ QUrl parseCustomURL() const; -public: // Nexus stuff - +public: // Nexus stuff /** * @brief Changes the nexus description text. * @@ -865,8 +888,7 @@ class ModInfo : public QObject, public MOBase::IModInterface */ virtual void setNexusLastModified(QDateTime time) = 0; -public: // Conflicts - +public: // Conflicts // retrieve the list of mods (as mod index) that are overwritten by this one. // Updates may be delayed. // @@ -880,22 +902,34 @@ class ModInfo : public QObject, public MOBase::IModInterface // retrieve the list of mods (as mod index) with archives that are overwritten by // this one. Updates may be delayed // - virtual const std::set& getModArchiveOverwrite() const { return s_EmptySet; } + virtual const std::set& getModArchiveOverwrite() const + { + return s_EmptySet; + } - // retrieve the list of mods (as mod index) with archives that overwrite this one. Updates - // may be delayed. + // retrieve the list of mods (as mod index) with archives that overwrite this one. + // Updates may be delayed. // - virtual const std::set& getModArchiveOverwritten() const { return s_EmptySet; } + virtual const std::set& getModArchiveOverwritten() const + { + return s_EmptySet; + } - // retrieve the list of mods (as mod index) with archives that are overwritten by loose - // files of this mod. Updates may be delayed. + // retrieve the list of mods (as mod index) with archives that are overwritten by + // loose files of this mod. Updates may be delayed. // - virtual const std::set& getModArchiveLooseOverwrite() const { return s_EmptySet; } + virtual const std::set& getModArchiveLooseOverwrite() const + { + return s_EmptySet; + } // retrieve the list of mods (as mod index) with loose files that overwrite this one's // archive files. Updates may be delayed. // - virtual const std::set& getModArchiveLooseOverwritten() const { return s_EmptySet; } + virtual const std::set& getModArchiveLooseOverwritten() const + { + return s_EmptySet; + } public slots: @@ -914,7 +948,6 @@ public slots: void modDetailsUpdated(bool success); protected: - /** * */ @@ -928,10 +961,9 @@ public slots: * using multiple threads for all the mods. */ virtual void prefetch() = 0; - static bool ByName(const ModInfo::Ptr &LHS, const ModInfo::Ptr &RHS); + static bool ByName(const ModInfo::Ptr& LHS, const ModInfo::Ptr& RHS); protected: - // the mod list OrganizerCore& m_Core; @@ -948,7 +980,6 @@ public slots: static const std::set s_EmptySet; protected: - friend class OrganizerCore; /** @@ -968,9 +999,9 @@ public slots: * * @return a new mod. */ - static ModInfo::Ptr createFromPlugin( - const QString& modName, const QString& espName, const QStringList& bsaNames, - ModInfo::EModType modType, OrganizerCore& core); + static ModInfo::Ptr createFromPlugin(const QString& modName, const QString& espName, + const QStringList& bsaNames, + ModInfo::EModType modType, OrganizerCore& core); static ModInfo::Ptr createFromOverwrite(OrganizerCore& core); @@ -979,15 +1010,12 @@ public slots: static void updateIndices(); protected: - static QRecursiveMutex s_Mutex; static std::vector s_Collection; static ModInfo::Ptr s_Overwrite; static std::map s_ModsByName; static std::map, std::vector> s_ModsByModID; static int s_NextID; - }; - -#endif // MODINFO_H +#endif // MODINFO_H diff --git a/src/modinfobackup.cpp b/src/modinfobackup.cpp index 603e74f63..4b6af8383 100644 --- a/src/modinfobackup.cpp +++ b/src/modinfobackup.cpp @@ -1,6 +1,5 @@ #include "modinfobackup.h" - std::vector ModInfoBackup::getFlags() const { std::vector result = ModInfoRegular::getFlags(); @@ -8,14 +7,11 @@ std::vector ModInfoBackup::getFlags() const return result; } - QString ModInfoBackup::getDescription() const { return tr("This is the backup of a mod"); } - ModInfoBackup::ModInfoBackup(const QDir& path, OrganizerCore& core) - : ModInfoRegular(path, core) -{ -} + : ModInfoRegular(path, core) +{} diff --git a/src/modinfobackup.h b/src/modinfobackup.h index 853a1211a..01abf1845 100644 --- a/src/modinfobackup.h +++ b/src/modinfobackup.h @@ -11,7 +11,6 @@ class ModInfoBackup : public ModInfoRegular friend class ModInfo; public: - virtual bool updateAvailable() const override { return false; } virtual bool updateIgnored() const override { return false; } virtual bool downgradeAvailable() const override { return false; } @@ -24,7 +23,10 @@ class ModInfoBackup : public ModInfoRegular virtual bool canBeUpdated() const override { return false; } virtual QDateTime getExpires() const override { return QDateTime(); } virtual bool canBeEnabled() const override { return false; } - virtual std::vector getIniTweaks() const override { return std::vector(); } + virtual std::vector getIniTweaks() const override + { + return std::vector(); + } virtual std::vector getFlags() const override; virtual QString getDescription() const override; virtual int getNexusFileStatus() const override { return 0; } @@ -41,10 +43,7 @@ class ModInfoBackup : public ModInfoRegular virtual void addInstalledFile(int, int) override {} private: - ModInfoBackup(const QDir& path, OrganizerCore& core); - }; - -#endif // MODINFOBACKUP_H +#endif // MODINFOBACKUP_H diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index fc83bf11c..d0e8d1ad2 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -18,19 +18,19 @@ along with Mod Organizer. If not, see . */ #include "modinfodialog.h" -#include "ui_modinfodialog.h" -#include "plugincontainer.h" -#include "organizercore.h" -#include "modlistview.h" -#include "modinfodialogtextfiles.h" -#include "modinfodialogimages.h" -#include "modinfodialogesps.h" -#include "modinfodialogconflicts.h" #include "modinfodialogcategories.h" -#include "modinfodialognexus.h" +#include "modinfodialogconflicts.h" +#include "modinfodialogesps.h" #include "modinfodialogfiletree.h" +#include "modinfodialogimages.h" +#include "modinfodialognexus.h" +#include "modinfodialogtextfiles.h" +#include "modlistview.h" +#include "organizercore.h" +#include "plugincontainer.h" #include "shared/directoryentry.h" #include "shared/filesorigin.h" +#include "ui_modinfodialog.h" #include using namespace MOBase; @@ -39,10 +39,8 @@ namespace fs = std::filesystem; const int max_scan_for_context_menu = 50; - -bool canPreviewFile( - const PluginContainer& pluginContainer, - bool isArchive, const QString& filename) +bool canPreviewFile(const PluginContainer& pluginContainer, bool isArchive, + const QString& filename) { if (isArchive) { return false; @@ -54,9 +52,7 @@ bool canPreviewFile( bool isExecutableFilename(const QString& filename) { - static const std::set exeExtensions = { - "exe", "cmd", "bat" - }; + static const std::set exeExtensions = {"exe", "cmd", "bat"}; const auto ext = QFileInfo(filename).suffix().toLower(); @@ -111,26 +107,26 @@ bool canUnhideFile(bool isArchive, const QString& filename) return true; } -FileRenamer::RenameResults hideFile(FileRenamer& renamer, const QString &oldName) +FileRenamer::RenameResults hideFile(FileRenamer& renamer, const QString& oldName) { const QString newName = oldName + ModInfo::s_HiddenExt; return renamer.rename(oldName, newName); } -FileRenamer::RenameResults unhideFile(FileRenamer& renamer, const QString &oldName) +FileRenamer::RenameResults unhideFile(FileRenamer& renamer, const QString& oldName) { QString newName = oldName.left(oldName.length() - ModInfo::s_HiddenExt.length()); return renamer.rename(oldName, newName); } - -FileRenamer::RenameResults restoreHiddenFilesRecursive(FileRenamer& renamer, const QString& targetDir) +FileRenamer::RenameResults restoreHiddenFilesRecursive(FileRenamer& renamer, + const QString& targetDir) { FileRenamer::RenameResults results = FileRenamer::RESULT_OK; - QDir currentDir = targetDir; - for (QString hiddenFile : currentDir.entryList( - (QStringList() << "*" + ModInfo::s_HiddenExt), - QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot)) { + QDir currentDir = targetDir; + for (QString hiddenFile : + currentDir.entryList((QStringList() << "*" + ModInfo::s_HiddenExt), + QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot)) { QString oldName = currentDir.absoluteFilePath(hiddenFile); QString newName = oldName.left(oldName.length() - ModInfo::s_HiddenExt.length()); @@ -163,51 +159,61 @@ FileRenamer::RenameResults restoreHiddenFilesRecursive(FileRenamer& renamer, con return results; } - ModInfoDialog::TabInfo::TabInfo(std::unique_ptr tab) - : tab(std::move(tab)), realPos(-1), widget(nullptr) -{ -} + : tab(std::move(tab)), realPos(-1), widget(nullptr) +{} bool ModInfoDialog::TabInfo::isVisible() const { return (realPos != -1); } -ModInfoDialog::ModInfoDialog( - OrganizerCore& core, PluginContainer& plugin, - ModInfo::Ptr mod, ModListView* modListView, QWidget *parent) : - TutorableDialog("ModInfoDialog", parent), - ui(new Ui::ModInfoDialog), - m_core(core), - m_plugin(plugin), - m_modListView(modListView), - m_initialTab(ModInfoTabIDs::None), - m_arrangingTabs(false) +ModInfoDialog::ModInfoDialog(OrganizerCore& core, PluginContainer& plugin, + ModInfo::Ptr mod, ModListView* modListView, + QWidget* parent) + : TutorableDialog("ModInfoDialog", parent), ui(new Ui::ModInfoDialog), m_core(core), + m_plugin(plugin), m_modListView(modListView), m_initialTab(ModInfoTabIDs::None), + m_arrangingTabs(false) { ui->setupUi(this); { auto* sc = new QShortcut(QKeySequence::Delete, this); - connect(sc, &QShortcut::activated, [&] { onDeleteShortcut(); }); + connect(sc, &QShortcut::activated, [&] { + onDeleteShortcut(); + }); } { auto* sc = new QShortcut(QKeySequence::MoveToNextPage, this); - connect(sc, &QShortcut::activated, [&] { onNextMod(); }); + connect(sc, &QShortcut::activated, [&] { + onNextMod(); + }); } { auto* sc = new QShortcut(QKeySequence::MoveToPreviousPage, this); - connect(sc, &QShortcut::activated, [&] { onPreviousMod(); }); + connect(sc, &QShortcut::activated, [&] { + onPreviousMod(); + }); } setMod(mod); createTabs(); - connect(ui->tabWidget, &QTabWidget::currentChanged, [&]{ onTabSelectionChanged(); }); - connect(ui->tabWidget->tabBar(), &QTabBar::tabMoved, [&]{ onTabMoved(); }); - connect(ui->close, &QPushButton::clicked, [&]{ onCloseButton(); }); - connect(ui->previousMod, &QPushButton::clicked, [&]{ onPreviousMod(); }); - connect(ui->nextMod, &QPushButton::clicked, [&]{ onNextMod(); }); + connect(ui->tabWidget, &QTabWidget::currentChanged, [&] { + onTabSelectionChanged(); + }); + connect(ui->tabWidget->tabBar(), &QTabBar::tabMoved, [&] { + onTabMoved(); + }); + connect(ui->close, &QPushButton::clicked, [&] { + onCloseButton(); + }); + connect(ui->previousMod, &QPushButton::clicked, [&] { + onPreviousMod(); + }); + connect(ui->nextMod, &QPushButton::clicked, [&] { + onNextMod(); + }); } ModInfoDialog::~ModInfoDialog() = default; @@ -216,7 +222,7 @@ template std::unique_ptr createTab(ModInfoDialog& d, ModInfoTabIDs id) { return std::make_unique(ModInfoDialogTabContext( - d.m_core, d.m_plugin, &d, d.ui.get(), id, d.m_mod, d.getOrigin())); + d.m_core, d.m_plugin, &d, d.ui.get(), id, d.m_mod, d.getOrigin())); } void ModInfoDialog::createTabs() @@ -242,29 +248,31 @@ void ModInfoDialog::createTabs() // for each tab in the widget; connects the widgets with the tab objects // - for (int i=0; i(i)]; // remembering tab information so tabs can be removed and re-added - tabInfo.widget = ui->tabWidget->widget(i); + tabInfo.widget = ui->tabWidget->widget(i); tabInfo.caption = ui->tabWidget->tabText(i); - tabInfo.icon = ui->tabWidget->tabIcon(i); + tabInfo.icon = ui->tabWidget->tabIcon(i); - connect( - tabInfo.tab.get(), &ModInfoDialogTab::originModified, - [this](int originID){ onOriginModified(originID); }); + connect(tabInfo.tab.get(), &ModInfoDialogTab::originModified, [this](int originID) { + onOriginModified(originID); + }); - connect( - tabInfo.tab.get(), &ModInfoDialogTab::modOpen, - [&](const QString& name){ setMod(name); update(); }); + connect(tabInfo.tab.get(), &ModInfoDialogTab::modOpen, [&](const QString& name) { + setMod(name); + update(); + }); - connect( - tabInfo.tab.get(), &ModInfoDialogTab::hasDataChanged, - [&]{ setTabsColors(); }); + connect(tabInfo.tab.get(), &ModInfoDialogTab::hasDataChanged, [&] { + setTabsColors(); + }); - connect( - tabInfo.tab.get(), &ModInfoDialogTab::wantsFocus, - [&, id=tabInfo.tab->tabID()]{ switchToTab(id); }); + connect(tabInfo.tab.get(), &ModInfoDialogTab::wantsFocus, + [&, id = tabInfo.tab->tabID()] { + switchToTab(id); + }); } } @@ -276,7 +284,7 @@ int ModInfoDialog::exec() // whether to select the first tab; if the main window requested a specific // tab, it is selected when encountered in update() const auto noCustomTabRequested = (m_initialTab == ModInfoTabIDs::None); - const auto requestedTab = m_initialTab; + const auto requestedTab = m_initialTab; update(true); @@ -369,7 +377,7 @@ void ModInfoDialog::update(bool firstTime) } if (ui->tabWidget->currentIndex() == oldTab) { - if (auto* tabInfo=currentTab()) { + if (auto* tabInfo = currentTab()) { // activated() has to be fired manually because the tab index hasn't been // changed tabInfo->tab->activated(); @@ -390,7 +398,7 @@ void ModInfoDialog::setTabsVisibility(bool firstTime) bool changed = false; - for (std::size_t i=0; itab->tabID(); } @@ -436,8 +444,7 @@ void ModInfoDialog::setTabsVisibility(bool firstTime) reAddTabs(visibility, sel); } -void ModInfoDialog::reAddTabs( - const std::vector& visibility, ModInfoTabIDs sel) +void ModInfoDialog::reAddTabs(const std::vector& visibility, ModInfoTabIDs sel) { Q_ASSERT(visibility.size() == m_tabs.size()); @@ -453,7 +460,7 @@ void ModInfoDialog::reAddTabs( // gathering visible tabs std::vector visibleTabs; - for (std::size_t i=0; iwidget->objectName()); + auto aItor = + std::find(orderedNames.begin(), orderedNames.end(), a->widget->objectName()); - auto bItor = std::find( - orderedNames.begin(), orderedNames.end(), - b->widget->objectName()); + auto bItor = + std::find(orderedNames.begin(), orderedNames.end(), b->widget->objectName()); // this shouldn't happen, it was checked above Q_ASSERT(aItor != orderedNames.end() && bItor != orderedNames.end()); @@ -495,7 +500,6 @@ void ModInfoDialog::reAddTabs( }); } - // removing all tabs ui->tabWidget->clear(); @@ -505,7 +509,7 @@ void ModInfoDialog::reAddTabs( } // add visible tabs - for (std::size_t i=0; itab->setMod(m_mod, origin); @@ -606,9 +609,9 @@ void ModInfoDialog::setTabsColors() continue; } - const QColor color = tabInfo.tab->hasData() ? - QColor::Invalid : - p.color(QPalette::Disabled, QPalette::WindowText); + const QColor color = tabInfo.tab->hasData() + ? QColor::Invalid + : p.color(QPalette::Disabled, QPalette::WindowText); ui->tabWidget->tabBar()->setTabTextColor(tabInfo.realPos, color); } @@ -682,7 +685,7 @@ void ModInfoDialog::saveTabOrder() const QString names; - for (int i=0; itabWidget->count(); ++i) { + for (int i = 0; i < ui->tabWidget->count(); ++i) { if (!names.isEmpty()) { names += " "; } @@ -707,7 +710,7 @@ void ModInfoDialog::onOriginModified(int originID) void ModInfoDialog::onDeleteShortcut() { // forward the request to the current tab - if (auto* tabInfo=currentTab()) { + if (auto* tabInfo = currentTab()) { tabInfo->tab->deleteRequested(); } } @@ -753,7 +756,7 @@ void ModInfoDialog::onTabSelectionChanged() } // this will call firstActivation() on the tab if needed - if (auto* tabInfo=currentTab()) { + if (auto* tabInfo = currentTab()) { tabInfo->tab->activated(); } } @@ -766,7 +769,7 @@ void ModInfoDialog::onTabMoved() } // for each tab in the widget - for (int i=0; itabWidget->count(); ++i) { + for (int i = 0; i < ui->tabWidget->count(); ++i) { const auto* w = ui->tabWidget->widget(i); bool found = false; @@ -775,7 +778,7 @@ void ModInfoDialog::onTabMoved() for (auto& tabInfo : m_tabs) { if (tabInfo.widget == w) { tabInfo.realPos = i; - found = true; + found = true; break; } } diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 6d408f05e..e894efb0c 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -1,260 +1,254 @@ -/* -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 MODINFODIALOG_H -#define MODINFODIALOG_H - - -#include "modinfo.h" -#include "tutorabledialog.h" -#include "filerenamer.h" -#include "modinfodialogfwd.h" - -namespace Ui { class ModInfoDialog; } -namespace MOShared { class FilesOrigin; } - -class PluginContainer; -class OrganizerCore; -class Settings; -class ModInfoDialogTab; -class ModListView; - -/** - * this is a larger dialog used to visualise information about the mod. - * @todo this would probably a good place for a plugin-system - **/ -class ModInfoDialog : public MOBase::TutorableDialog -{ - Q_OBJECT; - - // creates a tab, it's a friend because it uses a bunch of member variables - // to create ModInfoDialogTabContext - // - template - friend std::unique_ptr createTab( - ModInfoDialog& d, ModInfoTabIDs index); - -public: - ModInfoDialog( - OrganizerCore& core, PluginContainer& plugin, - ModInfo::Ptr mod, ModListView* view, QWidget* parent = nullptr); - - ~ModInfoDialog(); - - // switches to the tab with the given id - // - void selectTab(ModInfoTabIDs id); - - // updates all tabs, selects the initial tab, opens the dialog and - // saves/restores geometry - // - int exec() override; - -signals: - // emitted when a tab changes the origin - // - void originModified(int originID); - - // emitted when the mod of the dialog is changed - // - void modChanged(unsigned int modIndex); - -protected: - // forwards to tryClose() - // - void closeEvent(QCloseEvent* e); - -private: - // represents a single tab - // - struct TabInfo - { - // tab implementation - std::unique_ptr tab; - - // actual position in the tab bar, updated every time a tab is moved - int realPos; - - // widget used by the QTabWidget for this tab - // - // because QTabWidget doesn't support simply hiding tabs, they have to be - // completely removed from the widget when they don't support the current - // mod - // - // therefore, `widget, `caption` and `icon` are remembered so tabs can be - // removed and re-added when navigating between mods - // - // `widget` is also used figure out which tab is where when they're - // re-ordered - QWidget* widget; - - // caption for this tab, see `widget` - QString caption; - - // icon for this tab, see `widget` - QIcon icon; - - - TabInfo(std::unique_ptr tab); - - // returns whether this tab is part of the tab widget - // - bool isVisible() const; - }; - - std::unique_ptr ui; - OrganizerCore& m_core; - PluginContainer& m_plugin; - ModListView* m_modListView; - ModInfo::Ptr m_mod; - std::vector m_tabs; - - // initial tab requested by the main window when the dialog is opened; whether - // the request can be honoured depends on what tabs are present - ModInfoTabIDs m_initialTab; - - // set to true when tabs are being removed and re-added while navigating - // between mods; since the current index changes while this is happening, - // onTabSelectionChanged() will be called repeatedly - // - // however, it will check this flag and ignore the event so first activations - // are not fired incorrectly - bool m_arrangingTabs; - - - // creates all the tabs and connects events - // - void createTabs(); - - - // saves the dialog state and calls saveState() on all tabs - // - void saveState() const; - - // restores the dialog state and calls restoreState() on all tabs - // - void restoreState(); - - - // sets the currently selected mod; resets first activation, but doesn't - // update anything - // - void setMod(ModInfo::Ptr mod); - - // sets the currently selected mod, if found; forwards to setMod() above - // - void setMod(const QString& name); - - // returns the origin of the current mod, may be null - // - MOShared::FilesOrigin* getOrigin(); - - - // returns the currently selected tab, taking re-ordering in to account; - // shouldn't be null, but could be - // - TabInfo* currentTab(); - - - // fully updates the dialog; sets the title, the tab visibility and updates - // all the tabs; used when the current mod changes - // - // see setTabsVisibility() for firstTime - // - void update(bool firstTime=false); - - // builds the list of visible tabs; if the list is different from what's - // currently displayed, or firstTime is true, forwards to reAddTabs() - void setTabsVisibility(bool firstTime); - - // clears the tab widgets and re-adds the tabs having the visible flag in - // the given vector, following the tab order from the settings - // - void reAddTabs(const std::vector& visibility, ModInfoTabIDs sel); - - // called by update(); clears tabs, feeds files and calls update() on all - // tabs, then setTabsColors() - // - void updateTabs(bool becauseOriginChanged=false); - - // goes through all files on the filesystem for the current mod and calls - // feedFile() on every tab until one accepts it - // - void feedFiles(std::vector& interestedTabs); - - // goes through all tabs and sets the tab text colour depending on whether - // they have data or not - // - void setTabsColors(); - - - // called when the delete key is pressed anywhere in the dialog; forwards to - // ModInfoDialogTab::deleteRequest() for the currently selected tab - // - void onDeleteShortcut(); - - - // finds the tab with the given id and selects it - // - void switchToTab(ModInfoTabIDs id); - - - // saves the current tab order; used by saveState(), but also by - // setTabsVisibility() to make sure any changes to order are saved before - // re-adding tabs - // - void saveTabOrder() const; - - // asks all the tabs if they accept closing the dialog, returns false if one - // objected - // - bool tryClose(); - - - // called when the user clicks the close button; closing the dialog by other - // means ends up in closeEvent(); forwards to tryClose() - // - void onCloseButton(); - - // called when the user clicks the previous button; asks the main window for - // the previous mod in the list and loads it - // - void onPreviousMod(); - - // called when the user clicks the next button; asks the main window for the - // next mod in the list and loads it - // - void onNextMod(); - - // called when the selects a tab; handles first activation - // - void onTabSelectionChanged(); - - // called when the user re-orders tabs; sets the correct TabInfo::realPos for - // all tabs - // - void onTabMoved(); - - // called when a tab has modified the origin; emits originModified() and - // updates all the tabs that use origin files - // - void onOriginModified(int originID); -}; - -#endif // MODINFODIALOG_H +/* +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 MODINFODIALOG_H +#define MODINFODIALOG_H + +#include "filerenamer.h" +#include "modinfo.h" +#include "modinfodialogfwd.h" +#include "tutorabledialog.h" + +namespace Ui +{ +class ModInfoDialog; +} +namespace MOShared +{ +class FilesOrigin; +} + +class PluginContainer; +class OrganizerCore; +class Settings; +class ModInfoDialogTab; +class ModListView; + +/** + * this is a larger dialog used to visualise information about the mod. + * @todo this would probably a good place for a plugin-system + **/ +class ModInfoDialog : public MOBase::TutorableDialog +{ + Q_OBJECT; + + // creates a tab, it's a friend because it uses a bunch of member variables + // to create ModInfoDialogTabContext + // + template + friend std::unique_ptr createTab(ModInfoDialog& d, + ModInfoTabIDs index); + +public: + ModInfoDialog(OrganizerCore& core, PluginContainer& plugin, ModInfo::Ptr mod, + ModListView* view, QWidget* parent = nullptr); + + ~ModInfoDialog(); + + // switches to the tab with the given id + // + void selectTab(ModInfoTabIDs id); + + // updates all tabs, selects the initial tab, opens the dialog and + // saves/restores geometry + // + int exec() override; + +signals: + // emitted when a tab changes the origin + // + void originModified(int originID); + + // emitted when the mod of the dialog is changed + // + void modChanged(unsigned int modIndex); + +protected: + // forwards to tryClose() + // + void closeEvent(QCloseEvent* e); + +private: + // represents a single tab + // + struct TabInfo + { + // tab implementation + std::unique_ptr tab; + + // actual position in the tab bar, updated every time a tab is moved + int realPos; + + // widget used by the QTabWidget for this tab + // + // because QTabWidget doesn't support simply hiding tabs, they have to be + // completely removed from the widget when they don't support the current + // mod + // + // therefore, `widget, `caption` and `icon` are remembered so tabs can be + // removed and re-added when navigating between mods + // + // `widget` is also used figure out which tab is where when they're + // re-ordered + QWidget* widget; + + // caption for this tab, see `widget` + QString caption; + + // icon for this tab, see `widget` + QIcon icon; + + TabInfo(std::unique_ptr tab); + + // returns whether this tab is part of the tab widget + // + bool isVisible() const; + }; + + std::unique_ptr ui; + OrganizerCore& m_core; + PluginContainer& m_plugin; + ModListView* m_modListView; + ModInfo::Ptr m_mod; + std::vector m_tabs; + + // initial tab requested by the main window when the dialog is opened; whether + // the request can be honoured depends on what tabs are present + ModInfoTabIDs m_initialTab; + + // set to true when tabs are being removed and re-added while navigating + // between mods; since the current index changes while this is happening, + // onTabSelectionChanged() will be called repeatedly + // + // however, it will check this flag and ignore the event so first activations + // are not fired incorrectly + bool m_arrangingTabs; + + // creates all the tabs and connects events + // + void createTabs(); + + // saves the dialog state and calls saveState() on all tabs + // + void saveState() const; + + // restores the dialog state and calls restoreState() on all tabs + // + void restoreState(); + + // sets the currently selected mod; resets first activation, but doesn't + // update anything + // + void setMod(ModInfo::Ptr mod); + + // sets the currently selected mod, if found; forwards to setMod() above + // + void setMod(const QString& name); + + // returns the origin of the current mod, may be null + // + MOShared::FilesOrigin* getOrigin(); + + // returns the currently selected tab, taking re-ordering in to account; + // shouldn't be null, but could be + // + TabInfo* currentTab(); + + // fully updates the dialog; sets the title, the tab visibility and updates + // all the tabs; used when the current mod changes + // + // see setTabsVisibility() for firstTime + // + void update(bool firstTime = false); + + // builds the list of visible tabs; if the list is different from what's + // currently displayed, or firstTime is true, forwards to reAddTabs() + void setTabsVisibility(bool firstTime); + + // clears the tab widgets and re-adds the tabs having the visible flag in + // the given vector, following the tab order from the settings + // + void reAddTabs(const std::vector& visibility, ModInfoTabIDs sel); + + // called by update(); clears tabs, feeds files and calls update() on all + // tabs, then setTabsColors() + // + void updateTabs(bool becauseOriginChanged = false); + + // goes through all files on the filesystem for the current mod and calls + // feedFile() on every tab until one accepts it + // + void feedFiles(std::vector& interestedTabs); + + // goes through all tabs and sets the tab text colour depending on whether + // they have data or not + // + void setTabsColors(); + + // called when the delete key is pressed anywhere in the dialog; forwards to + // ModInfoDialogTab::deleteRequest() for the currently selected tab + // + void onDeleteShortcut(); + + // finds the tab with the given id and selects it + // + void switchToTab(ModInfoTabIDs id); + + // saves the current tab order; used by saveState(), but also by + // setTabsVisibility() to make sure any changes to order are saved before + // re-adding tabs + // + void saveTabOrder() const; + + // asks all the tabs if they accept closing the dialog, returns false if one + // objected + // + bool tryClose(); + + // called when the user clicks the close button; closing the dialog by other + // means ends up in closeEvent(); forwards to tryClose() + // + void onCloseButton(); + + // called when the user clicks the previous button; asks the main window for + // the previous mod in the list and loads it + // + void onPreviousMod(); + + // called when the user clicks the next button; asks the main window for the + // next mod in the list and loads it + // + void onNextMod(); + + // called when the selects a tab; handles first activation + // + void onTabSelectionChanged(); + + // called when the user re-orders tabs; sets the correct TabInfo::realPos for + // all tabs + // + void onTabMoved(); + + // called when a tab has modified the origin; emits originModified() and + // updates all the tabs that use origin files + // + void onOriginModified(int originID); +}; + +#endif // MODINFODIALOG_H diff --git a/src/modinfodialogcategories.cpp b/src/modinfodialogcategories.cpp index 5dcc744b0..5665df9fb 100644 --- a/src/modinfodialogcategories.cpp +++ b/src/modinfodialogcategories.cpp @@ -1,19 +1,20 @@ #include "modinfodialogcategories.h" -#include "ui_modinfodialog.h" #include "categories.h" #include "modinfo.h" +#include "ui_modinfodialog.h" CategoriesTab::CategoriesTab(ModInfoDialogTabContext cx) - : ModInfoDialogTab(std::move(cx)) + : ModInfoDialogTab(std::move(cx)) { - connect( - ui->categories, &QTreeWidget::itemChanged, - [&](auto* item, int col){ onCategoryChanged(item, col); }); - - connect( - ui->primaryCategories, - static_cast(&QComboBox::currentIndexChanged), - [&](int index){ onPrimaryChanged(index); }); + connect(ui->categories, &QTreeWidget::itemChanged, [&](auto* item, int col) { + onCategoryChanged(item, col); + }); + + connect(ui->primaryCategories, + static_cast(&QComboBox::currentIndexChanged), + [&](int index) { + onPrimaryChanged(index); + }); } void CategoriesTab::clear() @@ -27,9 +28,8 @@ void CategoriesTab::update() { clear(); - add( - CategoryFactory::instance(), mod().getCategories(), - ui->categories->invisibleRootItem(), 0); + add(CategoryFactory::instance(), mod().getCategories(), + ui->categories->invisibleRootItem(), 0); updatePrimary(); } @@ -44,26 +44,26 @@ bool CategoriesTab::usesOriginFiles() const return false; } -void CategoriesTab::add( - const CategoryFactory *factory, const std::set& enabledCategories, - QTreeWidgetItem* root, int rootLevel) +void CategoriesTab::add(const CategoryFactory* factory, + const std::set& enabledCategories, QTreeWidgetItem* root, + int rootLevel) { - for (int i=0; i(factory->numCategories()); ++i) { + for (int i = 0; i < static_cast(factory->numCategories()); ++i) { if (factory->getParentID(i) != rootLevel) { continue; } int categoryID = factory->getCategoryID(i); - QTreeWidgetItem *newItem - = new QTreeWidgetItem(QStringList(factory->getCategoryName(i))); + QTreeWidgetItem* newItem = + new QTreeWidgetItem(QStringList(factory->getCategoryName(i))); newItem->setFlags(newItem->flags() | Qt::ItemIsUserCheckable); - newItem->setCheckState(0, enabledCategories.find(categoryID) - != enabledCategories.end() - ? Qt::Checked - : Qt::Unchecked); + newItem->setCheckState(0, + enabledCategories.find(categoryID) != enabledCategories.end() + ? Qt::Checked + : Qt::Unchecked); newItem->setData(0, Qt::UserRole, categoryID); @@ -96,7 +96,7 @@ void CategoriesTab::updatePrimary() void CategoriesTab::addChecked(QTreeWidgetItem* tree) { for (int i = 0; i < tree->childCount(); ++i) { - QTreeWidgetItem *child = tree->child(i); + QTreeWidgetItem* child = tree->child(i); if (child->checkState(0) == Qt::Checked) { ui->primaryCategories->addItem(child->text(0), child->data(0, Qt::UserRole)); addChecked(child); @@ -107,10 +107,10 @@ void CategoriesTab::addChecked(QTreeWidgetItem* tree) void CategoriesTab::save(QTreeWidgetItem* currentNode) { for (int i = 0; i < currentNode->childCount(); ++i) { - QTreeWidgetItem *childNode = currentNode->child(i); + QTreeWidgetItem* childNode = currentNode->child(i); - mod().setCategory( - childNode->data(0, Qt::UserRole).toInt(), childNode->checkState(0)); + mod().setCategory(childNode->data(0, Qt::UserRole).toInt(), + childNode->checkState(0)); save(childNode); } @@ -118,9 +118,10 @@ void CategoriesTab::save(QTreeWidgetItem* currentNode) void CategoriesTab::onCategoryChanged(QTreeWidgetItem* item, int) { - QTreeWidgetItem *parent = item->parent(); + QTreeWidgetItem* parent = item->parent(); - while ((parent != nullptr) && ((parent->flags() & Qt::ItemIsUserCheckable) != 0) && (parent->checkState(0) == Qt::Unchecked)) { + while ((parent != nullptr) && ((parent->flags() & Qt::ItemIsUserCheckable) != 0) && + (parent->checkState(0) == Qt::Unchecked)) { parent->setCheckState(0, Qt::Checked); parent = parent->parent(); } diff --git a/src/modinfodialogcategories.h b/src/modinfodialogcategories.h index e73bfa32f..b390146cf 100644 --- a/src/modinfodialogcategories.h +++ b/src/modinfodialogcategories.h @@ -13,9 +13,8 @@ class CategoriesTab : public ModInfoDialogTab bool usesOriginFiles() const override; private: - void add( - const CategoryFactory* factory, const std::set& enabledCategories, - QTreeWidgetItem* root, int rootLevel); + void add(const CategoryFactory* factory, const std::set& enabledCategories, + QTreeWidgetItem* root, int rootLevel); void updatePrimary(); void addChecked(QTreeWidgetItem* tree); diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index 923e9778d..715214b33 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -1,13 +1,13 @@ #include "modinfodialogconflicts.h" -#include "ui_modinfodialog.h" -#include "modinfodialogconflictsmodels.h" #include "modinfodialog.h" -#include "utility.h" -#include "settings.h" +#include "modinfodialogconflictsmodels.h" #include "organizercore.h" +#include "settings.h" #include "shared/directoryentry.h" -#include "shared/filesorigin.h" #include "shared/fileentry.h" +#include "shared/filesorigin.h" +#include "ui_modinfodialog.h" +#include "utility.h" using namespace MOShared; using namespace MOBase; @@ -20,7 +20,7 @@ std::size_t smallSelectionSize(const QTreeView* tree) { const std::size_t too_many = std::numeric_limits::max(); - std::size_t n = 0; + std::size_t n = 0; const auto* sel = tree->selectionModel(); for (const auto& range : sel->selection()) { @@ -63,18 +63,17 @@ void forEachInSelection(QTreeView* tree, F&& f) } } - -ConflictsTab::ConflictsTab(ModInfoDialogTabContext cx) : - ModInfoDialogTab(cx), // don't move, cx is used again - m_general(this, cx.ui, cx.core), m_advanced(this, cx.ui, cx.core) +ConflictsTab::ConflictsTab(ModInfoDialogTabContext cx) + : ModInfoDialogTab(cx), // don't move, cx is used again + m_general(this, cx.ui, cx.core), m_advanced(this, cx.ui, cx.core) { - connect( - &m_general, &GeneralConflictsTab::modOpen, - [&](const QString& name){ emitModOpen(name); }); + connect(&m_general, &GeneralConflictsTab::modOpen, [&](const QString& name) { + emitModOpen(name); + }); - connect( - &m_advanced, &AdvancedConflictsTab::modOpen, - [&](const QString& name){ emitModOpen(name); }); + connect(&m_advanced, &AdvancedConflictsTab::modOpen, [&](const QString& name) { + emitModOpen(name); + }); } void ConflictsTab::update() @@ -114,7 +113,7 @@ bool ConflictsTab::canHandleUnmanaged() const void ConflictsTab::changeItemsVisibility(QTreeView* tree, bool visible) { bool changed = false; - bool stop = false; + bool stop = false; const auto n = smallSelectionSize(tree); @@ -132,7 +131,7 @@ void ConflictsTab::changeItemsVisibility(QTreeView* tree, bool visible) } QFlags flags = - (visible ? FileRenamer::UNHIDE : FileRenamer::HIDE); + (visible ? FileRenamer::UNHIDE : FileRenamer::HIDE); if (n > 1) { flags |= FileRenamer::MULTIPLE; @@ -179,22 +178,22 @@ void ConflictsTab::changeItemsVisibility(QTreeView* tree, bool visible) } switch (result) { - case FileRenamer::RESULT_OK: { - // will trigger a refresh at the end - changed = true; - break; - } + case FileRenamer::RESULT_OK: { + // will trigger a refresh at the end + changed = true; + break; + } - case FileRenamer::RESULT_SKIP: { - // nop - break; - } + case FileRenamer::RESULT_SKIP: { + // nop + break; + } - case FileRenamer::RESULT_CANCEL: { - // stop right now, but make sure to refresh if needed - stop = true; - break; - } + case FileRenamer::RESULT_CANCEL: { + // stop right now, but make sure to refresh if needed + stop = true; + break; + } } return true; @@ -244,11 +243,12 @@ void ConflictsTab::openItems(QTreeView* tree, bool hooked) void ConflictsTab::openItem(const ConflictItem* item, bool hooked) { - core().processRunner() - .setFromFile(parentWidget(), QFileInfo(item->fileName())) - .setHooked(hooked) - .setWaitForCompletion() - .run(); + core() + .processRunner() + .setFromFile(parentWidget(), QFileInfo(item->fileName())) + .setHooked(hooked) + .setWaitForCompletion() + .run(); } void ConflictsTab::previewItems(QTreeView* tree) @@ -276,7 +276,7 @@ void ConflictsTab::exploreItems(QTreeView* tree) }); } -void ConflictsTab::showContextMenu(const QPoint &pos, QTreeView* tree) +void ConflictsTab::showContextMenu(const QPoint& pos, QTreeView* tree) { auto actions = createMenuActions(tree); @@ -284,19 +284,20 @@ void ConflictsTab::showContextMenu(const QPoint &pos, QTreeView* tree) // open if (actions.open) { - connect(actions.open, &QAction::triggered, [&]{ + connect(actions.open, &QAction::triggered, [&] { openItems(tree, false); }); } // preview if (actions.preview) { - connect(actions.preview, &QAction::triggered, [&]{ + connect(actions.preview, &QAction::triggered, [&] { previewItems(tree); }); } - if ((actions.open && actions.open->isEnabled()) && (actions.preview && actions.preview->isEnabled())) { + if ((actions.open && actions.open->isEnabled()) && + (actions.preview && actions.preview->isEnabled())) { if (Settings::instance().interface().doubleClicksOpenPreviews()) { menu.addAction(actions.preview); menu.addAction(actions.open); @@ -316,7 +317,7 @@ void ConflictsTab::showContextMenu(const QPoint &pos, QTreeView* tree) // run hooked if (actions.runHooked) { - connect(actions.runHooked, &QAction::triggered, [&]{ + connect(actions.runHooked, &QAction::triggered, [&] { openItems(tree, true); }); @@ -328,9 +329,9 @@ void ConflictsTab::showContextMenu(const QPoint &pos, QTreeView* tree) menu.addMenu(actions.gotoMenu); for (auto* a : actions.gotoActions) { - connect(a, &QAction::triggered, [&, name=a->text()]{ + connect(a, &QAction::triggered, [&, name = a->text()] { emitModOpen(name); - }); + }); actions.gotoMenu->addAction(a); } @@ -338,7 +339,7 @@ void ConflictsTab::showContextMenu(const QPoint &pos, QTreeView* tree) // explore if (actions.explore) { - connect(actions.explore, &QAction::triggered, [&]{ + connect(actions.explore, &QAction::triggered, [&] { exploreItems(tree); }); @@ -349,7 +350,7 @@ void ConflictsTab::showContextMenu(const QPoint &pos, QTreeView* tree) // hide if (actions.hide) { - connect(actions.hide, &QAction::triggered, [&]{ + connect(actions.hide, &QAction::triggered, [&] { changeItemsVisibility(tree, false); }); @@ -358,7 +359,7 @@ void ConflictsTab::showContextMenu(const QPoint &pos, QTreeView* tree) // unhide if (actions.unhide) { - connect(actions.unhide, &QAction::triggered, [&]{ + connect(actions.unhide, &QAction::triggered, [&] { changeItemsVisibility(tree, true); }); @@ -369,7 +370,7 @@ void ConflictsTab::showContextMenu(const QPoint &pos, QTreeView* tree) if (actions.open || actions.preview || actions.runHooked) { // bold the first option auto* top = menu.actions()[0]; - auto f = top->font(); + auto f = top->font(); f.setBold(true); top->setFont(f); } @@ -384,13 +385,13 @@ ConflictsTab::Actions ConflictsTab::createMenuActions(QTreeView* tree) return {}; } - bool enableHide = true; - bool enableUnhide = true; - bool enableRun = true; - bool enableOpen = true; + bool enableHide = true; + bool enableUnhide = true; + bool enableRun = true; + bool enableOpen = true; bool enablePreview = true; bool enableExplore = true; - bool enableGoto = true; + bool enableGoto = true; const auto n = smallSelectionSize(tree); @@ -410,29 +411,27 @@ ConflictsTab::Actions ConflictsTab::createMenuActions(QTreeView* tree) auto modelSel = proxy->mapSelectionToSource(tree->selectionModel()->selection()); - if (n == 1) { // this is a single selection - const auto* item = model->getItem(static_cast( - modelSel.indexes()[0].row())); + const auto* item = + model->getItem(static_cast(modelSel.indexes()[0].row())); if (!item) { return {}; } - enableHide = item->canHide(); - enableUnhide = item->canUnhide(); - enableRun = item->canRun(); - enableOpen = item->canOpen(); + enableHide = item->canHide(); + enableUnhide = item->canUnhide(); + enableRun = item->canRun(); + enableOpen = item->canOpen(); enablePreview = item->canPreview(plugin()); enableExplore = item->canExplore(); - enableGoto = item->hasAlts(); - } - else { + enableGoto = item->hasAlts(); + } else { // this is a multiple selection, don't show open/preview so users don't open // a thousand files - enableRun = false; - enableOpen = false; + enableRun = false; + enableOpen = false; enablePreview = false; // can't explore multiple files @@ -444,7 +443,7 @@ ConflictsTab::Actions ConflictsTab::createMenuActions(QTreeView* tree) if (n <= max_small_selection) { // if the number of selected items is low, checking them to accurately // show the menu items is worth it - enableHide = false; + enableHide = false; enableUnhide = false; forEachInSelection(tree, [&](const ConflictItem* item) { @@ -469,10 +468,10 @@ ConflictsTab::Actions ConflictsTab::createMenuActions(QTreeView* tree) Actions actions; if (enableRun) { - actions.open = new QAction(tr("&Execute"), parentWidget()); + actions.open = new QAction(tr("&Execute"), parentWidget()); actions.runHooked = new QAction(tr("Execute with &VFS"), parentWidget()); } else if (enableOpen) { - actions.open = new QAction(tr("&Open"), parentWidget()); + actions.open = new QAction(tr("&Open"), parentWidget()); actions.runHooked = new QAction(tr("Open with &VFS"), parentWidget()); } @@ -494,8 +493,8 @@ ConflictsTab::Actions ConflictsTab::createMenuActions(QTreeView* tree) actions.unhide->setEnabled(enableUnhide); if (enableGoto && n == 1) { - const auto* item = model->getItem(static_cast( - modelSel.indexes()[0].row())); + const auto* item = + model->getItem(static_cast(modelSel.indexes()[0].row())); actions.gotoActions = createGotoActions(item); } @@ -514,7 +513,6 @@ std::vector ConflictsTab::createGotoActions(const ConflictItem* item) return {}; } - std::vector mods; const auto& ds = *core().directoryStructure(); @@ -534,7 +532,7 @@ std::vector ConflictsTab::createGotoActions(const ConflictItem* item) std::sort(mods.begin(), mods.end(), [](const auto& a, const auto& b) { return (QString::localeAwareCompare(a, b) < 0); - }); + }); std::vector actions; @@ -545,13 +543,12 @@ std::vector ConflictsTab::createGotoActions(const ConflictItem* item) return actions; } - -GeneralConflictsTab::GeneralConflictsTab( - ConflictsTab* tab, Ui::ModInfoDialog* pui, OrganizerCore& oc) : - m_tab(tab), ui(pui), m_core(oc), - m_overwriteModel(new OverwriteConflictListModel(ui->overwriteTree)), - m_overwrittenModel(new OverwrittenConflictListModel(ui->overwrittenTree)), - m_noConflictModel(new NoConflictListModel(ui->noConflictTree)) +GeneralConflictsTab::GeneralConflictsTab(ConflictsTab* tab, Ui::ModInfoDialog* pui, + OrganizerCore& oc) + : m_tab(tab), ui(pui), m_core(oc), + m_overwriteModel(new OverwriteConflictListModel(ui->overwriteTree)), + m_overwrittenModel(new OverwrittenConflictListModel(ui->overwrittenTree)), + m_noConflictModel(new NoConflictListModel(ui->noConflictTree)) { m_expanders.overwrite.set(ui->overwriteExpander, ui->overwriteTree, true); m_expanders.overwritten.set(ui->overwrittenExpander, ui->overwrittenTree, true); @@ -569,29 +566,32 @@ GeneralConflictsTab::GeneralConflictsTab( m_filterNoConflicts.setList(ui->noConflictTree); m_filterNoConflicts.setUseSourceSort(true); - QObject::connect( - ui->overwriteTree, &QTreeView::doubleClicked, - [&](auto&&){ m_tab->activateItems(ui->overwriteTree); }); + QObject::connect(ui->overwriteTree, &QTreeView::doubleClicked, [&](auto&&) { + m_tab->activateItems(ui->overwriteTree); + }); - QObject::connect( - ui->overwrittenTree, &QTreeView::doubleClicked, - [&](auto&& item){ m_tab->activateItems(ui->overwrittenTree); }); + QObject::connect(ui->overwrittenTree, &QTreeView::doubleClicked, [&](auto&& item) { + m_tab->activateItems(ui->overwrittenTree); + }); - QObject::connect( - ui->noConflictTree, &QTreeView::doubleClicked, - [&](auto&& item){ m_tab->activateItems(ui->noConflictTree); }); + QObject::connect(ui->noConflictTree, &QTreeView::doubleClicked, [&](auto&& item) { + m_tab->activateItems(ui->noConflictTree); + }); - QObject::connect( - ui->overwriteTree, &QTreeView::customContextMenuRequested, - [&](const QPoint& p){ m_tab->showContextMenu(p, ui->overwriteTree); }); + QObject::connect(ui->overwriteTree, &QTreeView::customContextMenuRequested, + [&](const QPoint& p) { + m_tab->showContextMenu(p, ui->overwriteTree); + }); - QObject::connect( - ui->overwrittenTree, &QTreeView::customContextMenuRequested, - [&](const QPoint& p){ m_tab->showContextMenu(p, ui->overwrittenTree); }); + QObject::connect(ui->overwrittenTree, &QTreeView::customContextMenuRequested, + [&](const QPoint& p) { + m_tab->showContextMenu(p, ui->overwrittenTree); + }); - QObject::connect( - ui->noConflictTree, &QTreeView::customContextMenuRequested, - [&](const QPoint& p){ m_tab->showContextMenu(p, ui->noConflictTree); }); + QObject::connect(ui->noConflictTree, &QTreeView::customContextMenuRequested, + [&](const QPoint& p) { + m_tab->showContextMenu(p, ui->noConflictTree); + }); } void GeneralConflictsTab::clear() @@ -636,10 +636,11 @@ bool GeneralConflictsTab::update() for (const auto& file : m_tab->origin()->getFiles()) { // careful: these two strings are moved into createXItem() below - QString relativeName = QDir::fromNativeSeparators(ToQString(file->getRelativePath())); + QString relativeName = + QDir::fromNativeSeparators(ToQString(file->getRelativePath())); QString fileName = rootPath + relativeName; - bool archive = false; + bool archive = false; const int fileOrigin = file->getOrigin(archive); ++m_counts.numTotalFiles; @@ -651,55 +652,52 @@ bool GeneralConflictsTab::update() (archive) ? ++m_counts.numTotalArchive : ++m_counts.numTotalLoose; if (!alternatives.empty()) { - m_overwriteModel->add(createOverwriteItem( - file->getIndex(), archive, - std::move(fileName), std::move(relativeName), alternatives)); + m_overwriteModel->add( + createOverwriteItem(file->getIndex(), archive, std::move(fileName), + std::move(relativeName), alternatives)); ++m_counts.numOverwrite; if (archive) { ++m_counts.numOverwriteArchive; - } - else { + } else { ++m_counts.numOverwriteLoose; } } else { // otherwise, put the file in the noconflict tree - m_noConflictModel->add(createNoConflictItem( - file->getIndex(), archive, - std::move(fileName), std::move(relativeName))); + m_noConflictModel->add(createNoConflictItem( + file->getIndex(), archive, std::move(fileName), std::move(relativeName))); ++m_counts.numNonConflicting; if (archive) { ++m_counts.numNonConflictingArchive; - } - else { + } else { ++m_counts.numNonConflictingLoose; } } } else { - auto currId = m_tab->origin()->getID(); + auto currId = m_tab->origin()->getID(); auto currModAlt = std::find_if(alternatives.begin(), alternatives.end(), - [&currId](auto const& alt) { - return currId == alt.originID(); - }); + [&currId](auto const& alt) { + return currId == alt.originID(); + }); if (currModAlt == alternatives.end()) { - log::error("Mod {} not found in the list of origins for file {}", m_tab->origin()->getName(), fileName); + log::error("Mod {} not found in the list of origins for file {}", + m_tab->origin()->getName(), fileName); continue; } bool currModFileArchive = currModAlt->isFromArchive(); - m_overwrittenModel->add(createOverwrittenItem( - file->getIndex(), fileOrigin, archive, - std::move(fileName), std::move(relativeName))); + m_overwrittenModel->add(createOverwrittenItem(file->getIndex(), fileOrigin, + archive, std::move(fileName), + std::move(relativeName))); ++m_counts.numOverwritten; if (currModFileArchive) { ++m_counts.numOverwrittenArchive; ++m_counts.numTotalArchive; - } - else { + } else { ++m_counts.numOverwrittenLoose; ++m_counts.numTotalLoose; } @@ -717,8 +715,8 @@ bool GeneralConflictsTab::update() } ConflictItem GeneralConflictsTab::createOverwriteItem( - FileIndex index, bool archive, QString fileName, QString relativeName, - const MOShared::AlternativesVector& alternatives) + FileIndex index, bool archive, QString fileName, QString relativeName, + const MOShared::AlternativesVector& alternatives) { const auto& ds = *m_core.directoryStructure(); std::wstring altString; @@ -733,35 +731,34 @@ ConflictItem GeneralConflictsTab::createOverwriteItem( auto origin = ToQString(ds.getOriginByID(alternatives.back().originID()).getName()); - return ConflictItem( - ToQString(altString), std::move(relativeName), QString(), index, - std::move(fileName), true, std::move(origin), archive); + return ConflictItem(ToQString(altString), std::move(relativeName), QString(), index, + std::move(fileName), true, std::move(origin), archive); } -ConflictItem GeneralConflictsTab::createNoConflictItem( - FileIndex index, bool archive, QString fileName, QString relativeName) +ConflictItem GeneralConflictsTab::createNoConflictItem(FileIndex index, bool archive, + QString fileName, + QString relativeName) { - return ConflictItem( - QString(), std::move(relativeName), QString(), index, - std::move(fileName), false, QString(), archive); + return ConflictItem(QString(), std::move(relativeName), QString(), index, + std::move(fileName), false, QString(), archive); } -ConflictItem GeneralConflictsTab::createOverwrittenItem( - FileIndex index, int fileOrigin, bool archive, - QString fileName, QString relativeName) +ConflictItem GeneralConflictsTab::createOverwrittenItem(FileIndex index, int fileOrigin, + bool archive, QString fileName, + QString relativeName) { - const auto& ds = *m_core.directoryStructure(); + const auto& ds = *m_core.directoryStructure(); const FilesOrigin& realOrigin = ds.getOriginByID(fileOrigin); - QString after = ToQString(realOrigin.getName()); + QString after = ToQString(realOrigin.getName()); QString altOrigin = after; - return ConflictItem( - QString(), std::move(relativeName), std::move(after), - index, std::move(fileName), true, std::move(altOrigin), archive); + return ConflictItem(QString(), std::move(relativeName), std::move(after), index, + std::move(fileName), true, std::move(altOrigin), archive); } -QString percent(int a, int b) { +QString percent(int a, int b) +{ if (b == 0) { return QString::number(0, 'f', 2); } @@ -774,49 +771,52 @@ void GeneralConflictsTab::updateUICounters() ui->overwrittenCount->display(m_counts.numOverwritten); ui->noConflictCount->display(m_counts.numNonConflicting); - QString tooltipBase = tr("" - "" - "" - "" - "" - "" - "" - "" - "
      Type%1TotalPercent
      Loose files: %2%3%4%
      Archive files: %5%6%7%
      Combined: %8%9%10%
      "); - - QString tooltipOverwrite = tooltipBase.arg(tr("Winning")) - .arg(m_counts.numOverwriteLoose) - .arg(m_counts.numTotalLoose) - .arg(percent(m_counts.numOverwriteLoose, m_counts.numTotalLoose)) - .arg(m_counts.numOverwriteArchive) - .arg(m_counts.numTotalArchive) - .arg(percent(m_counts.numOverwriteArchive, m_counts.numTotalArchive)) - .arg(m_counts.numOverwrite) - .arg(m_counts.numTotalFiles) - .arg(percent(m_counts.numOverwrite, m_counts.numTotalFiles)); - - QString tooltipOverwritten = tooltipBase.arg(tr("Losing")) - .arg(m_counts.numOverwrittenLoose) - .arg(m_counts.numTotalLoose) - .arg(percent(m_counts.numOverwrittenLoose, m_counts.numTotalLoose)) - .arg(m_counts.numOverwrittenArchive) - .arg(m_counts.numTotalArchive) - .arg(percent(m_counts.numOverwrittenArchive, m_counts.numTotalArchive)) - .arg(m_counts.numOverwritten) - .arg(m_counts.numTotalFiles) - .arg(percent(m_counts.numOverwritten, m_counts.numTotalFiles)); - - - QString tooltipNonConflict = tooltipBase.arg(tr("Non conflicting")) - .arg(m_counts.numNonConflictingLoose) - .arg(m_counts.numTotalLoose) - .arg(percent(m_counts.numNonConflictingLoose, m_counts.numTotalLoose)) - .arg(m_counts.numNonConflictingArchive) - .arg(m_counts.numTotalArchive) - .arg(percent(m_counts.numNonConflictingArchive, m_counts.numTotalArchive)) - .arg(m_counts.numNonConflicting) - .arg(m_counts.numTotalFiles) - .arg(percent(m_counts.numNonConflicting, m_counts.numTotalFiles)); + QString tooltipBase = + tr("" + "" + "" + "" + "" + "" + "" + "" + "
      Type%1TotalPercent
      Loose files: %2%3%4%
      Archive files: %5%6%7%
      Combined: %8%9%10%
      "); + + QString tooltipOverwrite = + tooltipBase.arg(tr("Winning")) + .arg(m_counts.numOverwriteLoose) + .arg(m_counts.numTotalLoose) + .arg(percent(m_counts.numOverwriteLoose, m_counts.numTotalLoose)) + .arg(m_counts.numOverwriteArchive) + .arg(m_counts.numTotalArchive) + .arg(percent(m_counts.numOverwriteArchive, m_counts.numTotalArchive)) + .arg(m_counts.numOverwrite) + .arg(m_counts.numTotalFiles) + .arg(percent(m_counts.numOverwrite, m_counts.numTotalFiles)); + + QString tooltipOverwritten = + tooltipBase.arg(tr("Losing")) + .arg(m_counts.numOverwrittenLoose) + .arg(m_counts.numTotalLoose) + .arg(percent(m_counts.numOverwrittenLoose, m_counts.numTotalLoose)) + .arg(m_counts.numOverwrittenArchive) + .arg(m_counts.numTotalArchive) + .arg(percent(m_counts.numOverwrittenArchive, m_counts.numTotalArchive)) + .arg(m_counts.numOverwritten) + .arg(m_counts.numTotalFiles) + .arg(percent(m_counts.numOverwritten, m_counts.numTotalFiles)); + + QString tooltipNonConflict = + tooltipBase.arg(tr("Non conflicting")) + .arg(m_counts.numNonConflictingLoose) + .arg(m_counts.numTotalLoose) + .arg(percent(m_counts.numNonConflictingLoose, m_counts.numTotalLoose)) + .arg(m_counts.numNonConflictingArchive) + .arg(m_counts.numTotalArchive) + .arg(percent(m_counts.numNonConflictingArchive, m_counts.numTotalArchive)) + .arg(m_counts.numNonConflicting) + .arg(m_counts.numTotalFiles) + .arg(percent(m_counts.numNonConflicting, m_counts.numTotalFiles)); ui->overwriteCount->setToolTip(tooltipOverwrite); ui->overwrittenCount->setToolTip(tooltipOverwritten); @@ -883,11 +883,10 @@ void GeneralConflictsTab::onOverwrittenActivated(const QModelIndex& index) } } - -AdvancedConflictsTab::AdvancedConflictsTab( - ConflictsTab* tab, Ui::ModInfoDialog* pui, OrganizerCore& oc) : - m_tab(tab), ui(pui), m_core(oc), - m_model(new AdvancedConflictListModel(ui->conflictsAdvancedList)) +AdvancedConflictsTab::AdvancedConflictsTab(ConflictsTab* tab, Ui::ModInfoDialog* pui, + OrganizerCore& oc) + : m_tab(tab), ui(pui), m_core(oc), + m_model(new AdvancedConflictListModel(ui->conflictsAdvancedList)) { m_filter.setEdit(ui->conflictsAdvancedFilter); m_filter.setList(ui->conflictsAdvancedList); @@ -895,33 +894,34 @@ AdvancedConflictsTab::AdvancedConflictsTab( // left-elide the overwrites column so that the nearest are visible ui->conflictsAdvancedList->setItemDelegateForColumn( - 0, new ElideLeftDelegate(ui->conflictsAdvancedList)); + 0, new ElideLeftDelegate(ui->conflictsAdvancedList)); // left-elide the file column to see filenames ui->conflictsAdvancedList->setItemDelegateForColumn( - 1, new ElideLeftDelegate(ui->conflictsAdvancedList)); + 1, new ElideLeftDelegate(ui->conflictsAdvancedList)); // don't elide the overwritten by column so that the nearest are visible - QObject::connect( - ui->conflictsAdvancedShowNoConflict, &QCheckBox::clicked, - [&]{ update(); }); + QObject::connect(ui->conflictsAdvancedShowNoConflict, &QCheckBox::clicked, [&] { + update(); + }); - QObject::connect( - ui->conflictsAdvancedShowAll, &QRadioButton::clicked, - [&]{ update(); }); + QObject::connect(ui->conflictsAdvancedShowAll, &QRadioButton::clicked, [&] { + update(); + }); - QObject::connect( - ui->conflictsAdvancedShowNearest, &QRadioButton::clicked, - [&]{ update(); }); + QObject::connect(ui->conflictsAdvancedShowNearest, &QRadioButton::clicked, [&] { + update(); + }); - QObject::connect( - ui->conflictsAdvancedList, &QTreeView::activated, - [&]{ m_tab->activateItems(ui->conflictsAdvancedList); }); + QObject::connect(ui->conflictsAdvancedList, &QTreeView::activated, [&] { + m_tab->activateItems(ui->conflictsAdvancedList); + }); - QObject::connect( - ui->conflictsAdvancedList, &QTreeView::customContextMenuRequested, - [&](const QPoint& p){ m_tab->showContextMenu(p, ui->conflictsAdvancedList); }); + QObject::connect(ui->conflictsAdvancedList, &QTreeView::customContextMenuRequested, + [&](const QPoint& p) { + m_tab->showContextMenu(p, ui->conflictsAdvancedList); + }); } void AdvancedConflictsTab::clear() @@ -957,16 +957,16 @@ void AdvancedConflictsTab::update() for (const auto& file : files) { // careful: these two strings are moved into createItem() below - QString relativeName = QDir::fromNativeSeparators(ToQString(file->getRelativePath())); + QString relativeName = + QDir::fromNativeSeparators(ToQString(file->getRelativePath())); QString fileName = rootPath + relativeName; - bool archive = false; - const int fileOrigin = file->getOrigin(archive); + bool archive = false; + const int fileOrigin = file->getOrigin(archive); const auto& alternatives = file->getAlternatives(); - auto item = createItem( - file->getIndex(), fileOrigin, archive, - std::move(fileName), std::move(relativeName), alternatives); + auto item = createItem(file->getIndex(), fileOrigin, archive, std::move(fileName), + std::move(relativeName), alternatives); if (item) { m_model->add(std::move(*item)); @@ -977,16 +977,16 @@ void AdvancedConflictsTab::update() } } -std::optional AdvancedConflictsTab::createItem( - FileIndex index, int fileOrigin, bool archive, - QString fileName, QString relativeName, - const MOShared::AlternativesVector& alternatives) +std::optional +AdvancedConflictsTab::createItem(FileIndex index, int fileOrigin, bool archive, + QString fileName, QString relativeName, + const MOShared::AlternativesVector& alternatives) { const auto& ds = *m_core.directoryStructure(); std::wstring before, after; - auto currOrigin = m_tab->origin(); + auto currOrigin = m_tab->origin(); bool isCurrOrigArchive = archive; if (!alternatives.empty()) { @@ -996,8 +996,7 @@ std::optional AdvancedConflictsTab::createItem( // current origin is the active winner, all alternatives go in 'before' if (showAllAlts) { - for (const auto& alt : alternatives) - { + for (const auto& alt : alternatives) { const auto& altOrigin = ds.getOriginByID(alt.originID()); if (!before.empty()) { before += L", "; @@ -1005,27 +1004,26 @@ std::optional AdvancedConflictsTab::createItem( before += altOrigin.getName(); } - } - else { + } else { // only add nearest, which is the last element of alternatives const auto& altOrigin = ds.getOriginByID(alternatives.back().originID()); before += altOrigin.getName(); } - } - else { + } else { // current mod is one of the alternatives, find its position auto currOrgId = currOrigin->getID(); auto currModIter = std::find_if(alternatives.begin(), alternatives.end(), - [&currOrgId](auto const& alt) { - return currOrgId == alt.originID(); - }); + [&currOrgId](auto const& alt) { + return currOrgId == alt.originID(); + }); if (currModIter == alternatives.end()) { - log::error("Mod {} not found in the list of origins for file {}", currOrigin->getName(), fileName); + log::error("Mod {} not found in the list of origins for file {}", + currOrigin->getName(), fileName); return {}; } @@ -1048,8 +1046,7 @@ std::optional AdvancedConflictsTab::createItem( } before += altOrigin.getName(); - } - else if (iter > currModIter) { + } else if (iter > currModIter) { // mod comes after current if (!after.empty()) { @@ -1066,14 +1063,12 @@ std::optional AdvancedConflictsTab::createItem( } after += ds.getOriginByID(fileOrigin).getName(); - - } - else { + } else { // only show nearest origins // before if (currModIter > alternatives.begin()) { - auto previousOrigId = (currModIter-1)->originID(); + auto previousOrigId = (currModIter - 1)->originID(); before += ds.getOriginByID(previousOrigId).getName(); } @@ -1081,13 +1076,11 @@ std::optional AdvancedConflictsTab::createItem( if (currModIter < (alternatives.end() - 1)) { auto followingOrigId = (currModIter + 1)->originID(); after += ds.getOriginByID(followingOrigId).getName(); - } - else { + } else { // current mod is last of alternatives, so closest to the active winner after += ds.getOriginByID(fileOrigin).getName(); } - } } } @@ -1103,9 +1096,9 @@ std::optional AdvancedConflictsTab::createItem( } auto beforeQS = QString::fromStdWString(before); - auto afterQS = QString::fromStdWString(after); + auto afterQS = QString::fromStdWString(after); - return ConflictItem( - std::move(beforeQS), std::move(relativeName), std::move(afterQS), - index, std::move(fileName), hasAlts, QString(), isCurrOrigArchive); + return ConflictItem(std::move(beforeQS), std::move(relativeName), std::move(afterQS), + index, std::move(fileName), hasAlts, QString(), + isCurrOrigArchive); } diff --git a/src/modinfodialogconflicts.h b/src/modinfodialogconflicts.h index db195e3de..f1edd33ae 100644 --- a/src/modinfodialogconflicts.h +++ b/src/modinfodialogconflicts.h @@ -1,9 +1,9 @@ #ifndef MODINFODIALOGCONFLICTS_H #define MODINFODIALOGCONFLICTS_H -#include "modinfodialogtab.h" #include "expanderwidget.h" #include "filterwidget.h" +#include "modinfodialogtab.h" #include "shared/fileregisterfwd.h" #include #include @@ -20,8 +20,7 @@ class GeneralConflictsTab : public QObject Q_OBJECT; public: - GeneralConflictsTab( - ConflictsTab* tab, Ui::ModInfoDialog* ui, OrganizerCore& oc); + GeneralConflictsTab(ConflictsTab* tab, Ui::ModInfoDialog* ui, OrganizerCore& oc); void clear(); void saveState(Settings& s); @@ -53,38 +52,34 @@ class GeneralConflictsTab : public QObject struct GeneralConflictNumbers { - int numTotalFiles = 0; - int numTotalLoose = 0; - int numTotalArchive = 0; - int numNonConflicting = 0; - int numNonConflictingLoose = 0; + int numTotalFiles = 0; + int numTotalLoose = 0; + int numTotalArchive = 0; + int numNonConflicting = 0; + int numNonConflictingLoose = 0; int numNonConflictingArchive = 0; - int numOverwrite = 0; - int numOverwriteLoose = 0; - int numOverwriteArchive = 0; - int numOverwritten = 0; - int numOverwrittenLoose = 0; - int numOverwrittenArchive = 0; - - void clear() { - *this = {}; - }; + int numOverwrite = 0; + int numOverwriteLoose = 0; + int numOverwriteArchive = 0; + int numOverwritten = 0; + int numOverwrittenLoose = 0; + int numOverwrittenArchive = 0; + + void clear() { *this = {}; }; }; GeneralConflictNumbers m_counts; - ConflictItem createOverwriteItem( - MOShared::FileIndex index, bool archive, - QString fileName, QString relativeName, - const MOShared::AlternativesVector& alternatives); + ConflictItem createOverwriteItem(MOShared::FileIndex index, bool archive, + QString fileName, QString relativeName, + const MOShared::AlternativesVector& alternatives); - ConflictItem createNoConflictItem( - MOShared::FileIndex index, bool archive, - QString fileName, QString relativeName); + ConflictItem createNoConflictItem(MOShared::FileIndex index, bool archive, + QString fileName, QString relativeName); - ConflictItem createOverwrittenItem( - MOShared::FileIndex index, int fileOrigin, bool archive, - QString fileName, QString relativeName); + ConflictItem createOverwrittenItem(MOShared::FileIndex index, int fileOrigin, + bool archive, QString fileName, + QString relativeName); void updateUICounters(); @@ -92,14 +87,12 @@ class GeneralConflictsTab : public QObject void onOverwrittenActivated(const QModelIndex& index); }; - class AdvancedConflictsTab : public QObject { Q_OBJECT; public: - AdvancedConflictsTab( - ConflictsTab* tab, Ui::ModInfoDialog* ui, OrganizerCore& oc); + AdvancedConflictsTab(ConflictsTab* tab, Ui::ModInfoDialog* ui, OrganizerCore& oc); void clear(); void saveState(Settings& s); @@ -117,13 +110,11 @@ class AdvancedConflictsTab : public QObject FilterWidget m_filter; ConflictListModel* m_model; - std::optional createItem( - MOShared::FileIndex index, int fileOrigin, bool archive, - QString fileName, QString relativeName, - const MOShared::AlternativesVector& alternatives); + std::optional + createItem(MOShared::FileIndex index, int fileOrigin, bool archive, QString fileName, + QString relativeName, const MOShared::AlternativesVector& alternatives); }; - class ConflictsTab : public ModInfoDialogTab { Q_OBJECT; @@ -146,18 +137,18 @@ class ConflictsTab : public ModInfoDialogTab void previewItem(const ConflictItem* item); void changeItemsVisibility(QTreeView* tree, bool visible); - void showContextMenu(const QPoint &pos, QTreeView* tree); + void showContextMenu(const QPoint& pos, QTreeView* tree); private: struct Actions { - QAction* hide = nullptr; - QAction* unhide = nullptr; - QAction* open = nullptr; + QAction* hide = nullptr; + QAction* unhide = nullptr; + QAction* open = nullptr; QAction* runHooked = nullptr; - QAction* preview = nullptr; - QAction* explore = nullptr; - QMenu* gotoMenu = nullptr; + QAction* preview = nullptr; + QAction* explore = nullptr; + QMenu* gotoMenu = nullptr; std::vector gotoActions; }; @@ -169,4 +160,4 @@ class ConflictsTab : public ModInfoDialogTab std::vector createGotoActions(const ConflictItem* item); }; -#endif // MODINFODIALOGCONFLICTS_H +#endif // MODINFODIALOGCONFLICTS_H diff --git a/src/modinfodialogconflictsmodels.cpp b/src/modinfodialogconflictsmodels.cpp index 464abbd34..a1806e61f 100644 --- a/src/modinfodialogconflictsmodels.cpp +++ b/src/modinfodialogconflictsmodels.cpp @@ -4,20 +4,14 @@ using MOBase::naturalCompare; -ConflictItem::ConflictItem( - QString before, QString relativeName, QString after, - MOShared::FileIndex index, QString fileName, - bool hasAltOrigins, QString altOrigin, bool archive) : - m_before(std::move(before)), - m_relativeName(std::move(relativeName)), - m_after(std::move(after)), - m_index(index), - m_fileName(std::move(fileName)), - m_hasAltOrigins(hasAltOrigins), - m_altOrigin(std::move(altOrigin)), +ConflictItem::ConflictItem(QString before, QString relativeName, QString after, + MOShared::FileIndex index, QString fileName, + bool hasAltOrigins, QString altOrigin, bool archive) + : m_before(std::move(before)), m_relativeName(std::move(relativeName)), + m_after(std::move(after)), m_index(index), m_fileName(std::move(fileName)), + m_hasAltOrigins(hasAltOrigins), m_altOrigin(std::move(altOrigin)), m_isArchive(archive) -{ -} +{} const QString& ConflictItem::before() const { @@ -89,10 +83,9 @@ bool ConflictItem::canExplore() const return canExploreFile(isArchive(), fileName()); } - -ConflictListModel::ConflictListModel(QTreeView* tree, std::vector columns) : - m_tree(tree), m_columns(std::move(columns)), - m_sortColumn(-1), m_sortOrder(Qt::AscendingOrder) +ConflictListModel::ConflictListModel(QTreeView* tree, std::vector columns) + : m_tree(tree), m_columns(std::move(columns)), m_sortColumn(-1), + m_sortOrder(Qt::AscendingOrder) { m_tree->setModel(this); } @@ -133,8 +126,7 @@ int ConflictListModel::columnCount(const QModelIndex&) const return static_cast(m_columns.size()); } -const ConflictItem* ConflictListModel::itemFromIndex( - const QModelIndex& index) const +const ConflictItem* ConflictListModel::itemFromIndex(const QModelIndex& index) const { const auto row = index.row(); if (row < 0) { @@ -149,10 +141,9 @@ const ConflictItem* ConflictListModel::itemFromIndex( return &m_items[i]; } -QModelIndex ConflictListModel::indexFromItem( - const ConflictItem* item, int col) +QModelIndex ConflictListModel::indexFromItem(const ConflictItem* item, int col) { - for (std::size_t i=0; i(i), col); } @@ -214,7 +205,7 @@ QVariant ConflictListModel::headerData(int col, Qt::Orientation, int role) const void ConflictListModel::sort(int colIndex, Qt::SortOrder order) { m_sortColumn = colIndex; - m_sortOrder = order; + m_sortOrder = order; emit layoutAboutToBeChanged({}, QAbstractItemModel::VerticalSortHint); @@ -224,7 +215,7 @@ void ConflictListModel::sort(int colIndex, Qt::SortOrder order) const auto itemCount = oldList.size(); oldItems.reserve(static_cast(itemCount)); - for (int i=0; i(i)]; newList.append(indexFromItem(pair.first, pair.second)); } @@ -299,38 +290,22 @@ void ConflictListModel::doSort() } } - OverwriteConflictListModel::OverwriteConflictListModel(QTreeView* tree) - : ConflictListModel(tree, { - {tr("File"), &ConflictItem::relativeName}, - {tr("Overwritten Mods"), &ConflictItem::before} - }) -{ -} - + : ConflictListModel(tree, {{tr("File"), &ConflictItem::relativeName}, + {tr("Overwritten Mods"), &ConflictItem::before}}) +{} OverwrittenConflictListModel::OverwrittenConflictListModel(QTreeView* tree) - : ConflictListModel(tree, { - {tr("File"), &ConflictItem::relativeName}, - {tr("Providing Mod"), &ConflictItem::after} - }) -{ -} - + : ConflictListModel(tree, {{tr("File"), &ConflictItem::relativeName}, + {tr("Providing Mod"), &ConflictItem::after}}) +{} NoConflictListModel::NoConflictListModel(QTreeView* tree) - : ConflictListModel(tree, { - {tr("File"), &ConflictItem::relativeName} - }) -{ -} - + : ConflictListModel(tree, {{tr("File"), &ConflictItem::relativeName}}) +{} AdvancedConflictListModel::AdvancedConflictListModel(QTreeView* tree) - : ConflictListModel(tree, { - {tr("Overwrites"), &ConflictItem::before}, - {tr("File"), &ConflictItem::relativeName}, - {tr("Overwritten By"), &ConflictItem::after} - }) -{ -} + : ConflictListModel(tree, {{tr("Overwrites"), &ConflictItem::before}, + {tr("File"), &ConflictItem::relativeName}, + {tr("Overwritten By"), &ConflictItem::after}}) +{} diff --git a/src/modinfodialogconflictsmodels.h b/src/modinfodialogconflictsmodels.h index e12460b15..263723285 100644 --- a/src/modinfodialogconflictsmodels.h +++ b/src/modinfodialogconflictsmodels.h @@ -5,10 +5,9 @@ class PluginContainer; class ConflictItem { public: - ConflictItem( - QString before, QString relativeName, QString after, - MOShared::FileIndex index, QString fileName, - bool hasAltOrigins, QString altOrigin, bool archive); + ConflictItem(QString before, QString relativeName, QString after, + MOShared::FileIndex index, QString fileName, bool hasAltOrigins, + QString altOrigin, bool archive); const QString& before() const; const QString& relativeName() const; @@ -40,7 +39,6 @@ class ConflictItem bool m_isArchive; }; - class ConflictListModel : public QAbstractItemModel { Q_OBJECT; @@ -57,14 +55,14 @@ class ConflictListModel : public QAbstractItemModel void clear(); void reserve(std::size_t s); - QModelIndex index(int row, int col, const QModelIndex& ={}) const override; + QModelIndex index(int row, int col, const QModelIndex& = {}) const override; QModelIndex parent(const QModelIndex&) const override; - int rowCount(const QModelIndex& parent={}) const override; - int columnCount(const QModelIndex& ={}) const override; + int rowCount(const QModelIndex& parent = {}) const override; + int columnCount(const QModelIndex& = {}) const override; QVariant data(const QModelIndex& index, int role) const override; QVariant headerData(int col, Qt::Orientation, int role) const; - void sort(int colIndex, Qt::SortOrder order=Qt::AscendingOrder); + void sort(int colIndex, Qt::SortOrder order = Qt::AscendingOrder); void add(ConflictItem item); void finished(); @@ -84,7 +82,6 @@ class ConflictListModel : public QAbstractItemModel void doSort(); }; - class OverwriteConflictListModel : public ConflictListModel { Q_OBJECT; @@ -93,7 +90,6 @@ class OverwriteConflictListModel : public ConflictListModel OverwriteConflictListModel(QTreeView* tree); }; - class OverwrittenConflictListModel : public ConflictListModel { Q_OBJECT; @@ -102,7 +98,6 @@ class OverwrittenConflictListModel : public ConflictListModel OverwrittenConflictListModel(QTreeView* tree); }; - class NoConflictListModel : public ConflictListModel { Q_OBJECT; @@ -111,7 +106,6 @@ class NoConflictListModel : public ConflictListModel NoConflictListModel(QTreeView* tree); }; - class AdvancedConflictListModel : public ConflictListModel { Q_OBJECT; diff --git a/src/modinfodialogesps.cpp b/src/modinfodialogesps.cpp index e5f1637f1..f9a3ba8c8 100644 --- a/src/modinfodialogesps.cpp +++ b/src/modinfodialogesps.cpp @@ -1,9 +1,9 @@ #include "modinfodialogesps.h" -#include "ui_modinfodialog.h" #include "modinfodialog.h" #include "settings.h" -#include +#include "ui_modinfodialog.h" #include +#include using namespace MOBase; @@ -11,22 +11,19 @@ class ESPItem { public: ESPItem(QString rootPath, QString relativePath) - : m_rootPath(std::move(rootPath)), m_active(false) + : m_rootPath(std::move(rootPath)), m_active(false) { if (relativePath.contains('/') || relativePath.contains('\\')) { m_inactivePath = relativePath; } else { m_activePath = relativePath; - m_active = true; + m_active = true; } pathChanged(); } - const QString& rootPath() const - { - return m_rootPath; - } + const QString& rootPath() const { return m_rootPath; } const QString& relativePath() const { @@ -37,37 +34,22 @@ class ESPItem } } - const QString& filename() const - { - return m_filename; - } + const QString& filename() const { return m_filename; } - const QString& activePath() const - { - return m_activePath; - } + const QString& activePath() const { return m_activePath; } - const QString& inactivePath() const - { - return m_inactivePath; - } + const QString& inactivePath() const { return m_inactivePath; } - const QFileInfo& fileInfo() const - { - return m_fileInfo; - } + const QFileInfo& fileInfo() const { return m_fileInfo; } - bool isActive() const - { - return m_active; - } + bool isActive() const { return m_active; } bool activate(const QString& newName) { QDir root(m_rootPath); if (root.rename(m_inactivePath, newName)) { - m_active = true; + m_active = true; m_activePath = newName; if (QFileInfo(m_inactivePath).fileName() != newName) { @@ -88,7 +70,7 @@ class ESPItem QDir root(m_rootPath); if (root.rename(m_activePath, newName)) { - m_active = false; + m_active = false; m_inactivePath = newName; pathChanged(); return true; @@ -112,7 +94,6 @@ class ESPItem } }; - class ESPListModel : public QAbstractItemModel { public: @@ -122,30 +103,24 @@ class ESPListModel : public QAbstractItemModel endResetModel(); } - QModelIndex index(int row, int col, const QModelIndex& ={}) const override + QModelIndex index(int row, int col, const QModelIndex& = {}) const override { return createIndex(row, col); } - QModelIndex parent(const QModelIndex&) const override - { - return {}; - } + QModelIndex parent(const QModelIndex&) const override { return {}; } - int rowCount(const QModelIndex& ={}) const override + int rowCount(const QModelIndex& = {}) const override { return static_cast(m_esps.size()); } - int columnCount(const QModelIndex& ={}) const override - { - return 1; - } + int columnCount(const QModelIndex& = {}) const override { return 1; } QVariant data(const QModelIndex& index, int role) const override { if (role == Qt::DisplayRole) { - if (auto* esp=getESP(index)) { + if (auto* esp = getESP(index)) { return esp->filename(); } } @@ -153,11 +128,7 @@ class ESPListModel : public QAbstractItemModel return {}; } - - void add(ESPItem esp) - { - m_esps.emplace_back(std::move(esp)); - } + void add(ESPItem esp) { m_esps.emplace_back(std::move(esp)); } void addOne(ESPItem esp) { @@ -179,9 +150,7 @@ class ESPListModel : public QAbstractItemModel return false; } - const auto end = std::min( - start + static_cast(count), - m_esps.size()); + const auto end = std::min(start + static_cast(count), m_esps.size()); beginRemoveRows({}, static_cast(start), static_cast(end)); m_esps.erase(m_esps.begin() + start, m_esps.begin() + end); @@ -223,20 +192,20 @@ class ESPListModel : public QAbstractItemModel std::deque m_esps; }; - - -ESPsTab::ESPsTab(ModInfoDialogTabContext cx) : - ModInfoDialogTab(std::move(cx)), - m_inactiveModel(new ESPListModel), m_activeModel(new ESPListModel) +ESPsTab::ESPsTab(ModInfoDialogTabContext cx) + : ModInfoDialogTab(std::move(cx)), m_inactiveModel(new ESPListModel), + m_activeModel(new ESPListModel) { ui->inactiveESPList->setModel(m_inactiveModel); ui->activeESPList->setModel(m_activeModel); - QObject::connect( - ui->activateESP, &QToolButton::clicked, [&]{ onActivate(); }); + QObject::connect(ui->activateESP, &QToolButton::clicked, [&] { + onActivate(); + }); - QObject::connect( - ui->deactivateESP, &QToolButton::clicked, [&]{ onDeactivate(); }); + QObject::connect(ui->deactivateESP, &QToolButton::clicked, [&] { + onDeactivate(); + }); } void ESPsTab::clear() @@ -311,10 +280,9 @@ void ESPsTab::onActivate() bool okClicked = false; newName = QInputDialog::getText( - parentWidget(), - QObject::tr("File Exists"), - QObject::tr("A file with that name exists, please enter a new one"), - QLineEdit::Normal, file.fileName(), &okClicked); + parentWidget(), QObject::tr("File Exists"), + QObject::tr("A file with that name exists, please enter a new one"), + QLineEdit::Normal, file.fileName(), &okClicked); if (!okClicked) { return; @@ -387,7 +355,7 @@ void ESPsTab::onDeactivate() void ESPsTab::selectRow(QListView* list, int row) { const auto* model = list->model(); - const auto count = model->rowCount(); + const auto count = model->rowCount(); if (count == 0) { return; } diff --git a/src/modinfodialogesps.h b/src/modinfodialogesps.h index b128f2798..d65cb5805 100644 --- a/src/modinfodialogesps.h +++ b/src/modinfodialogesps.h @@ -28,4 +28,4 @@ class ESPsTab : public ModInfoDialogTab void selectRow(QListView* list, int row); }; -#endif // MODINFODIALOGESPS_H +#endif // MODINFODIALOGESPS_H diff --git a/src/modinfodialogfiletree.cpp b/src/modinfodialogfiletree.cpp index 9f39447d5..140b813d3 100644 --- a/src/modinfodialogfiletree.cpp +++ b/src/modinfodialogfiletree.cpp @@ -1,11 +1,11 @@ #include "modinfodialogfiletree.h" -#include "ui_modinfodialog.h" +#include "filerenamer.h" #include "modinfodialog.h" #include "organizercore.h" -#include "filerenamer.h" -#include -#include +#include "ui_modinfodialog.h" #include +#include +#include using namespace MOBase; namespace shell = MOBase::shell; @@ -15,7 +15,7 @@ namespace shell = MOBase::shell; const int max_scan_for_context_menu = 50; FileTreeTab::FileTreeTab(ModInfoDialogTabContext cx) - : ModInfoDialogTab(std::move(cx)), m_fs(nullptr) + : ModInfoDialogTab(std::move(cx)), m_fs(nullptr) { m_fs = new QFileSystemModel(this); m_fs->setReadOnly(false); @@ -23,36 +23,58 @@ FileTreeTab::FileTreeTab(ModInfoDialogTabContext cx) ui->filetree->setColumnWidth(0, 300); m_actions.newFolder = new QAction(tr("&New Folder"), ui->filetree); - m_actions.open = new QAction(tr("&Open/Execute"), ui->filetree); + m_actions.open = new QAction(tr("&Open/Execute"), ui->filetree); m_actions.runHooked = new QAction(tr("Open with &VFS"), ui->filetree); - m_actions.preview = new QAction(tr("&Preview"), ui->filetree); - m_actions.explore = new QAction(tr("Open in &Explorer"), ui->filetree); - m_actions.rename = new QAction(tr("&Rename"), ui->filetree); - m_actions.del = new QAction(tr("&Delete"), ui->filetree); - m_actions.hide = new QAction(tr("&Hide"), ui->filetree); - m_actions.unhide = new QAction(tr("&Unhide"), ui->filetree); - - connect(m_actions.newFolder, &QAction::triggered, [&]{ onCreateDirectory(); }); - connect(m_actions.open, &QAction::triggered, [&]{ onOpen(); }); - connect(m_actions.runHooked, &QAction::triggered, [&]{ onRunHooked(); }); - connect(m_actions.preview, &QAction::triggered, [&]{ onPreview(); }); - connect(m_actions.explore, &QAction::triggered, [&]{ onExplore(); }); - connect(m_actions.rename, &QAction::triggered, [&]{ onRename(); }); - connect(m_actions.del, &QAction::triggered, [&]{ onDelete(); }); - connect(m_actions.hide, &QAction::triggered, [&]{ onHide(); }); - connect(m_actions.unhide, &QAction::triggered, [&]{ onUnhide(); }); - - connect(ui->openInExplorer, &QToolButton::clicked, [&]{ onOpenInExplorer(); }); - - connect( - ui->filetree, &QTreeView::customContextMenuRequested, - [&](const QPoint& pos){ onContextMenu(pos); }); + m_actions.preview = new QAction(tr("&Preview"), ui->filetree); + m_actions.explore = new QAction(tr("Open in &Explorer"), ui->filetree); + m_actions.rename = new QAction(tr("&Rename"), ui->filetree); + m_actions.del = new QAction(tr("&Delete"), ui->filetree); + m_actions.hide = new QAction(tr("&Hide"), ui->filetree); + m_actions.unhide = new QAction(tr("&Unhide"), ui->filetree); + + connect(m_actions.newFolder, &QAction::triggered, [&] { + onCreateDirectory(); + }); + connect(m_actions.open, &QAction::triggered, [&] { + onOpen(); + }); + connect(m_actions.runHooked, &QAction::triggered, [&] { + onRunHooked(); + }); + connect(m_actions.preview, &QAction::triggered, [&] { + onPreview(); + }); + connect(m_actions.explore, &QAction::triggered, [&] { + onExplore(); + }); + connect(m_actions.rename, &QAction::triggered, [&] { + onRename(); + }); + connect(m_actions.del, &QAction::triggered, [&] { + onDelete(); + }); + connect(m_actions.hide, &QAction::triggered, [&] { + onHide(); + }); + connect(m_actions.unhide, &QAction::triggered, [&] { + onUnhide(); + }); + + connect(ui->openInExplorer, &QToolButton::clicked, [&] { + onOpenInExplorer(); + }); + + connect(ui->filetree, &QTreeView::customContextMenuRequested, [&](const QPoint& pos) { + onContextMenu(pos); + }); // disable renaming on double click, open the file instead - ui->filetree->setEditTriggers( - ui->filetree->editTriggers() & (~QAbstractItemView::DoubleClicked)); + ui->filetree->setEditTriggers(ui->filetree->editTriggers() & + (~QAbstractItemView::DoubleClicked)); - connect(ui->filetree, &QTreeView::activated, [&](auto&&){ onActivated(); }); + connect(ui->filetree, &QTreeView::activated, [&](auto&&) { + onActivated(); + }); } void FileTreeTab::clear() @@ -119,15 +141,15 @@ void FileTreeTab::onCreateDirectory() } QModelIndex index = m_fs->isDir(selection) ? selection : selection.parent(); - index = index.sibling(index.row(), 0); + index = index.sibling(index.row(), 0); QString name = tr("New Folder"); QString path = m_fs->filePath(index).append("/"); QModelIndex existingIndex = m_fs->index(path + name); - int suffix = 1; + int suffix = 1; while (existingIndex.isValid()) { - name = tr("New Folder") + QString::number(suffix++); + name = tr("New Folder") + QString::number(suffix++); existingIndex = m_fs->index(path + name); } @@ -153,7 +175,7 @@ void FileTreeTab::onActivated() return; } - const auto path = m_fs->filePath(selection); + const auto path = m_fs->filePath(selection); const auto tryPreview = core().settings().interface().doubleClicksOpenPreviews(); if (tryPreview && canPreviewFile(plugin(), false, path)) { @@ -171,11 +193,12 @@ void FileTreeTab::onOpen() } const auto path = m_fs->filePath(selection); - core().processRunner() - .setFromFile(parentWidget(), QFileInfo(path)) - .setHooked(false) - .setWaitForCompletion() - .run(); + core() + .processRunner() + .setFromFile(parentWidget(), QFileInfo(path)) + .setHooked(false) + .setWaitForCompletion() + .run(); } void FileTreeTab::onRunHooked() @@ -186,11 +209,12 @@ void FileTreeTab::onRunHooked() } const auto path = m_fs->filePath(selection); - core().processRunner() - .setFromFile(parentWidget(), QFileInfo(path)) - .setHooked(true) - .setWaitForCompletion() - .run(); + core() + .processRunner() + .setFromFile(parentWidget(), QFileInfo(path)) + .setHooked(true) + .setWaitForCompletion() + .run(); } void FileTreeTab::onPreview() @@ -240,12 +264,13 @@ void FileTreeTab::onDelete() if (rows.count() == 1) { QString fileName = m_fs->fileName(rows[0]); - message = tr("Are you sure you want to delete \"%1\"?").arg(fileName); + message = tr("Are you sure you want to delete \"%1\"?").arg(fileName); } else { message = tr("Are you sure you want to delete the selected files?"); } - if (QMessageBox::question(parentWidget(), tr("Confirm"), message) != QMessageBox::Yes) { + if (QMessageBox::question(parentWidget(), tr("Confirm"), message) != + QMessageBox::Yes) { return; } @@ -288,7 +313,7 @@ bool FileTreeTab::deleteFile(const QModelIndex& index) bool FileTreeTab::deleteFileRecursive(const QModelIndex& parent) { - for (int row = 0; rowrowCount(parent); ++row) { + for (int row = 0; row < m_fs->rowCount(parent); ++row) { QModelIndex index = m_fs->index(row, 0, parent); if (m_fs->isDir(index)) { @@ -317,14 +342,13 @@ void FileTreeTab::changeVisibility(bool visible) const auto selection = ui->filetree->selectionModel()->selectedRows(); bool changed = false; - bool stop = false; + bool stop = false; - log::debug( - "{} {} filetree files", - (visible ? "unhiding" : "hiding"), selection.size()); + log::debug("{} {} filetree files", (visible ? "unhiding" : "hiding"), + selection.size()); QFlags flags = - (visible ? FileRenamer::UNHIDE : FileRenamer::HIDE); + (visible ? FileRenamer::UNHIDE : FileRenamer::HIDE); if (selection.size() > 1) { flags |= FileRenamer::MULTIPLE; @@ -338,7 +362,7 @@ void FileTreeTab::changeVisibility(bool visible) } const QString path = m_fs->filePath(index); - auto result = FileRenamer::RESULT_CANCEL; + auto result = FileRenamer::RESULT_CANCEL; if (visible) { if (!canUnhideFile(false, path)) { @@ -355,22 +379,22 @@ void FileTreeTab::changeVisibility(bool visible) } switch (result) { - case FileRenamer::RESULT_OK: { - // will trigger a refresh at the end - changed = true; - break; - } + case FileRenamer::RESULT_OK: { + // will trigger a refresh at the end + changed = true; + break; + } - case FileRenamer::RESULT_SKIP: { - // nop - break; - } + case FileRenamer::RESULT_SKIP: { + // nop + break; + } - case FileRenamer::RESULT_CANCEL: { - // stop right now, but make sure to refresh if needed - stop = true; - break; - } + case FileRenamer::RESULT_CANCEL: { + // stop right now, but make sure to refresh if needed + stop = true; + break; + } } } @@ -383,31 +407,31 @@ void FileTreeTab::changeVisibility(bool visible) } } -void FileTreeTab::onContextMenu(const QPoint &pos) +void FileTreeTab::onContextMenu(const QPoint& pos) { const auto selection = ui->filetree->selectionModel()->selectedRows(); QMenu menu(ui->filetree); bool enableNewFolder = false; - bool enableRun = false; - bool enableOpen = false; - bool enablePreview = false; - bool enableExplore = false; - bool enableRename = false; - bool enableDelete = false; - bool enableHide = false; - bool enableUnhide = false; + bool enableRun = false; + bool enableOpen = false; + bool enablePreview = false; + bool enableExplore = false; + bool enableRename = false; + bool enableDelete = false; + bool enableHide = false; + bool enableUnhide = false; if (selection.size() == 0) { // no selection, only new folder and explore enableNewFolder = true; - enableExplore = true; + enableExplore = true; } else if (selection.size() == 1) { // single selection enableNewFolder = true; - enableRename = true; - enableDelete = true; + enableRename = true; + enableDelete = true; // only enable open action if a file is selected bool hasFiles = false; @@ -424,13 +448,13 @@ void FileTreeTab::onContextMenu(const QPoint &pos) enablePreview = canPreviewFile(plugin(), false, fileName); enableExplore = canExploreFile(false, fileName); - enableHide = canHideFile(false, fileName); - enableUnhide = canUnhideFile(false, fileName); + enableHide = canHideFile(false, fileName); + enableUnhide = canUnhideFile(false, fileName); } else { // this is a multiple selection, don't show open or explore actions so users // don't open a thousand files enableNewFolder = true; - enableDelete = true; + enableDelete = true; if (selection.size() < max_scan_for_context_menu) { // if the number of selected items is low, checking them to accurately @@ -458,7 +482,7 @@ void FileTreeTab::onContextMenu(const QPoint &pos) bool enableRunHooked = false; if (enableRun || enableOpen) { - if (auto* p=core().currentProfile()) { + if (auto* p = core().currentProfile()) { if (mod().canBeEnabled()) { const auto index = ModInfo::getIndex(mod().name()); if (index == UINT_MAX) { @@ -526,8 +550,8 @@ void FileTreeTab::onContextMenu(const QPoint &pos) if (enableOpen || enableRun || enablePreview) { // bold the first option, unbold all the others - for (int i=0; ifont(); f.setBold(i == 0); a->setFont(f); diff --git a/src/modinfodialogfiletree.h b/src/modinfodialogfiletree.h index c3c84ed49..a7cfbcd68 100644 --- a/src/modinfodialogfiletree.h +++ b/src/modinfodialogfiletree.h @@ -21,14 +21,14 @@ class FileTreeTab : public ModInfoDialogTab struct Actions { QAction* newFolder = nullptr; - QAction* open = nullptr; + QAction* open = nullptr; QAction* runHooked = nullptr; - QAction* preview = nullptr; - QAction* explore = nullptr; - QAction* rename = nullptr; - QAction* del = nullptr; - QAction* hide = nullptr; - QAction* unhide = nullptr; + QAction* preview = nullptr; + QAction* explore = nullptr; + QAction* rename = nullptr; + QAction* del = nullptr; + QAction* hide = nullptr; + QAction* unhide = nullptr; }; QFileSystemModel* m_fs; @@ -45,7 +45,7 @@ class FileTreeTab : public ModInfoDialogTab void onHide(); void onUnhide(); void onOpenInExplorer(); - void onContextMenu(const QPoint &pos); + void onContextMenu(const QPoint& pos); QModelIndex singleSelection() const; bool deleteFile(const QModelIndex& index); @@ -53,4 +53,4 @@ class FileTreeTab : public ModInfoDialogTab void changeVisibility(bool visible); }; -#endif // MODINFODIALOGFILETREE_H +#endif // MODINFODIALOGFILETREE_H diff --git a/src/modinfodialogfwd.h b/src/modinfodialogfwd.h index 805e8a06c..086263236 100644 --- a/src/modinfodialogfwd.h +++ b/src/modinfodialogfwd.h @@ -9,7 +9,7 @@ using ModInfoPtr = QSharedPointer; enum class ModInfoTabIDs { - None = -1, + None = -1, TextFiles = 0, IniFiles, Images, @@ -23,17 +23,18 @@ enum class ModInfoTabIDs class PluginContainer; -bool canPreviewFile(const PluginContainer& pluginContainer, bool isArchive, const QString& filename); +bool canPreviewFile(const PluginContainer& pluginContainer, bool isArchive, + const QString& filename); bool canRunFile(bool isArchive, const QString& filename); bool canOpenFile(bool isArchive, const QString& filename); bool canExploreFile(bool isArchive, const QString& filename); bool canHideFile(bool isArchive, const QString& filename); bool canUnhideFile(bool isArchive, const QString& filename); -FileRenamer::RenameResults hideFile(FileRenamer& renamer, const QString &oldName); +FileRenamer::RenameResults hideFile(FileRenamer& renamer, const QString& oldName); FileRenamer::RenameResults unhideFile(FileRenamer& renamer, const QString& oldName); -FileRenamer::RenameResults restoreHiddenFilesRecursive(FileRenamer& renamer, const QString &targetDir); - +FileRenamer::RenameResults restoreHiddenFilesRecursive(FileRenamer& renamer, + const QString& targetDir); class ElideLeftDelegate : public QStyledItemDelegate { @@ -48,4 +49,4 @@ class ElideLeftDelegate : public QStyledItemDelegate } }; -#endif // MODINFODIALOGFWD_H +#endif // MODINFODIALOGFWD_H diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index 530d23d52..e48a34d33 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -1,6 +1,6 @@ #include "modinfodialogimages.h" -#include "ui_modinfodialog.h" #include "settings.h" +#include "ui_modinfodialog.h" #include "utility.h" #include @@ -9,37 +9,31 @@ using namespace ImagesTabHelpers; QSize resizeWithAspectRatio(const QSize& original, const QSize& available) { - const auto ratio = std::min({ - 1.0, - static_cast(available.width()) / original.width(), - static_cast(available.height()) / original.height()}); + const auto ratio = + std::min({1.0, static_cast(available.width()) / original.width(), + static_cast(available.height()) / original.height()}); - const QSize scaledSize( - static_cast(std::round(original.width() * ratio)), - static_cast(std::round(original.height() * ratio))); + const QSize scaledSize(static_cast(std::round(original.width() * ratio)), + static_cast(std::round(original.height() * ratio))); return scaledSize; } QRect centeredRect(const QRect& rect, const QSize& size) { - return QRect( - (rect.left()+rect.width()/2) - size.width()/2, - (rect.top()+rect.height()/2) - size.height()/2, - size.width(), - size.height()); + return QRect((rect.left() + rect.width() / 2) - size.width() / 2, + (rect.top() + rect.height() / 2) - size.height() / 2, size.width(), + size.height()); } QString dimensionString(const QSize& s) { - return QString::fromUtf8("%1 \xc3\x97 %2") - .arg(s.width()).arg(s.height()); + return QString::fromUtf8("%1 \xc3\x97 %2").arg(s.width()).arg(s.height()); } - -ImagesTab::ImagesTab(ModInfoDialogTabContext cx) : - ModInfoDialogTab(std::move(cx)), m_image(new ScalableImage), - m_ddsAvailable(false), m_ddsEnabled(false) +ImagesTab::ImagesTab(ModInfoDialogTabContext cx) + : ModInfoDialogTab(std::move(cx)), m_image(new ScalableImage), + m_ddsAvailable(false), m_ddsEnabled(false) { getSupportedFormats(); @@ -58,16 +52,26 @@ ImagesTab::ImagesTab(ModInfoDialogTabContext cx) : ui->imagesThumbnails->setTab(this); ui->imagesScrollerVBar->setTab(this); - connect(ui->imagesScrollerVBar, &QScrollBar::valueChanged, [&]{ onScrolled(); }); + connect(ui->imagesScrollerVBar, &QScrollBar::valueChanged, [&] { + onScrolled(); + }); ui->imagesShowDDS->setEnabled(m_ddsAvailable); m_filter.setEdit(ui->imagesFilter); - connect(&m_filter, &FilterWidget::changed, [&]{ onFilterChanged(); }); - - connect(ui->imagesExplore, &QAbstractButton::clicked, [&]{ onExplore(); }); - connect(ui->imagesShowDDS, &QCheckBox::toggled, [&]{ onShowDDS(); }); - connect(ui->previewPluginButton, &QAbstractButton::clicked, [&] { onPreviewButton(); }); + connect(&m_filter, &FilterWidget::changed, [&] { + onFilterChanged(); + }); + + connect(ui->imagesExplore, &QAbstractButton::clicked, [&] { + onExplore(); + }); + connect(ui->imagesShowDDS, &QCheckBox::toggled, [&] { + onShowDDS(); + }); + connect(ui->previewPluginButton, &QAbstractButton::clicked, [&] { + onPreviewButton(); + }); ui->imagesShowDDS->setEnabled(m_ddsAvailable); @@ -78,12 +82,12 @@ ImagesTab::ImagesTab(ModInfoDialogTabContext cx) : auto list = std::make_unique(); parentWidget()->style()->polish(list.get()); - m_theme.borderColor = QColor(Qt::black); + m_theme.borderColor = QColor(Qt::black); m_theme.backgroundColor = QColor(Qt::black); - m_theme.textColor = list->palette().color(QPalette::WindowText); + m_theme.textColor = list->palette().color(QPalette::WindowText); m_theme.highlightBackgroundColor = list->palette().color(QPalette::Highlight); - m_theme.highlightTextColor = list->palette().color(QPalette::HighlightedText); + m_theme.highlightTextColor = list->palette().color(QPalette::HighlightedText); m_theme.font = list->font(); @@ -246,7 +250,7 @@ void ImagesTab::select(std::size_t i, Visibility v) { m_files.select(i); - if (auto* f=m_files.selectedFile()) { + if (auto* f = m_files.selectedFile()) { // when jumping elsewhere in the list, such as with page down/up, the file // might not be visible yet, which means it hasn't been loaded and would // pass a null image in setImage() below @@ -254,7 +258,8 @@ void ImagesTab::select(std::size_t i, Visibility v) ui->imagesPath->setText(QDir::toNativeSeparators(f->path())); ui->imagesExplore->setEnabled(true); - if (plugin().previewGenerator().previewSupported(QFileInfo(f->path()).suffix().toLower())) + if (plugin().previewGenerator().previewSupported( + QFileInfo(f->path()).suffix().toLower())) ui->previewPluginButton->setEnabled(true); else ui->previewPluginButton->setEnabled(false); @@ -269,9 +274,11 @@ void ImagesTab::select(std::size_t i, Visibility v) paint.fillRect(0, 0, 300, 100, QBrush(QColor(0, 0, 0, 255))); paint.setPen(m_theme.textColor); paint.setFont(m_theme.font); - paint.drawImage(QPoint(150-16, 50-20-16), QImage(":/MO/gui/warning")); + paint.drawImage(QPoint(150 - 16, 50 - 20 - 16), QImage(":/MO/gui/warning")); const auto flags = Qt::AlignHCenter | Qt::AlignVCenter | Qt::TextWordWrap; - paint.drawText(0, 46, 300, 54, flags, "This image format is not supported by Qt, but the preview plugin may be able to display it. Use the button above."); + paint.drawText(0, 46, 300, 54, flags, + "This image format is not supported by Qt, but the preview plugin " + "may be able to display it. Use the button above."); paint.end(); m_image->setImage(image); @@ -329,12 +336,12 @@ void ImagesTab::ensureVisible(std::size_t i, Visibility v) const auto geo = makeGeometry(); - const auto fullyVisible = geo.fullyVisibleCount(); + const auto fullyVisible = geo.fullyVisibleCount(); const auto partiallyVisible = fullyVisible + 1; const auto first = ui->imagesScrollerVBar->value(); - const auto last = (v == Visibility::Full ? - first + fullyVisible : first + partiallyVisible); + const auto last = + (v == Visibility::Full ? first + fullyVisible : first + partiallyVisible); if (i < first) { // go up @@ -390,23 +397,22 @@ void ImagesTab::paintThumbnailsArea(QPaintEvent* e) { PaintContext cx(ui->imagesThumbnails, makeGeometry()); - cx.painter.fillRect( - ui->imagesThumbnails->rect(), - ui->imagesThumbnails->palette().color(QPalette::Window)); + cx.painter.fillRect(ui->imagesThumbnails->rect(), + ui->imagesThumbnails->palette().color(QPalette::Window)); const auto visible = cx.geo.fullyVisibleCount() + 1; - const auto first = ui->imagesScrollerVBar->value(); + const auto first = ui->imagesScrollerVBar->value(); - for (std::size_t i=0; iloadIfNeeded(cx.geo); - const auto imageRect = cx.geo.imageRect(cx.thumbIndex); - const auto scaledThumbRect = centeredRect( - imageRect, cx.file->thumbnail().size()); + const auto imageRect = cx.geo.imageRect(cx.thumbIndex); + const auto scaledThumbRect = centeredRect(imageRect, cx.file->thumbnail().size()); cx.painter.fillRect(scaledThumbRect, m_theme.backgroundColor); cx.painter.drawImage(scaledThumbRect, cx.file->thumbnail()); @@ -471,8 +476,7 @@ void ImagesTab::paintThumbnailText(const PaintContext& cx) QFontMetrics fm(m_theme.font); - const auto text = fm.elidedText( - cx.file->filename(), Qt::ElideRight, tr.width()); + const auto text = fm.elidedText(cx.file->filename(), Qt::ElideRight, tr.width()); const auto flags = Qt::AlignHCenter | Qt::AlignVCenter | Qt::TextSingleLine; @@ -503,45 +507,44 @@ void ImagesTab::thumbnailAreaWheelEvent(QWheelEvent* e) { const auto d = (e->angleDelta() / 8).y(); - ui->imagesScrollerVBar->setValue( - ui->imagesScrollerVBar->value() + (d > 0 ? -1 : 1)); + ui->imagesScrollerVBar->setValue(ui->imagesScrollerVBar->value() + (d > 0 ? -1 : 1)); } bool ImagesTab::thumbnailAreaKeyPressEvent(QKeyEvent* e) { switch (e->key()) { - case Qt::Key_Down: { - moveSelection(ui->imagesScrollerVBar->singleStep()); - return true; - } - - case Qt::Key_Up: { - moveSelection(-ui->imagesScrollerVBar->singleStep()); - return true; - } + case Qt::Key_Down: { + moveSelection(ui->imagesScrollerVBar->singleStep()); + return true; + } - case Qt::Key_PageDown: { - moveSelection(ui->imagesScrollerVBar->pageStep()); - return true; - } + case Qt::Key_Up: { + moveSelection(-ui->imagesScrollerVBar->singleStep()); + return true; + } - case Qt::Key_PageUp: { - moveSelection(-ui->imagesScrollerVBar->pageStep()); - return true; - } + case Qt::Key_PageDown: { + moveSelection(ui->imagesScrollerVBar->pageStep()); + return true; + } - case Qt::Key_Home: { - select(0); - return true; - } + case Qt::Key_PageUp: { + moveSelection(-ui->imagesScrollerVBar->pageStep()); + return true; + } - case Qt::Key_End: { - if (!m_files.empty()) { - select(m_files.size() - 1); - } + case Qt::Key_Home: { + select(0); + return true; + } - return true; + case Qt::Key_End: { + if (!m_files.empty()) { + select(m_files.size() - 1); } + + return true; + } } return false; @@ -562,15 +565,15 @@ void ImagesTab::showTooltip(QHelpEvent* e) } const auto s = QString("%1 (%2)") - .arg(QDir::toNativeSeparators(f->path())) - .arg(dimensionString(f->original().size())); + .arg(QDir::toNativeSeparators(f->path())) + .arg(dimensionString(f->original().size())); QToolTip::showText(e->globalPos(), s, ui->imagesThumbnails); } void ImagesTab::onExplore() { - if (auto* f=m_files.selectedFile()) { + if (auto* f = m_files.selectedFile()) { shell::Explore(f->path()); } } @@ -602,9 +605,9 @@ void ImagesTab::updateScrollbar() return; } - const auto geo = makeGeometry(); + const auto geo = makeGeometry(); const auto availableSize = ui->imagesThumbnails->size(); - const auto fullyVisible = geo.fullyVisibleCount(); + const auto fullyVisible = geo.fullyVisibleCount(); if (fullyVisible >= m_files.size()) { ui->imagesScrollerVBar->setRange(0, 0); @@ -618,7 +621,6 @@ void ImagesTab::updateScrollbar() } } - namespace ImagesTabHelpers { @@ -634,7 +636,6 @@ void Scrollbar::wheelEvent(QWheelEvent* e) } } - void ThumbnailsWidget::setTab(ImagesTab* tab) { m_tab = tab; @@ -689,9 +690,7 @@ bool ThumbnailsWidget::event(QEvent* e) return QWidget::event(e); } - -ScalableImage::ScalableImage(QString path) - : m_path(std::move(path)), m_border(1) +ScalableImage::ScalableImage(QString path) : m_path(std::move(path)), m_border(1) { auto sp = sizePolicy(); sp.setHeightForWidth(true); @@ -700,9 +699,9 @@ ScalableImage::ScalableImage(QString path) void ScalableImage::setImage(const QString& path) { - m_path = path; + m_path = path; m_original = {}; - m_scaled = {}; + m_scaled = {}; update(); } @@ -711,7 +710,7 @@ void ScalableImage::setImage(QImage image) { m_path.clear(); m_original = std::move(image); - m_scaled = {}; + m_scaled = {}; update(); } @@ -733,7 +732,7 @@ int ScalableImage::heightForWidth(int w) const void ScalableImage::setColors(const QColor& border, const QColor& background) { - m_borderColor = border; + m_borderColor = border; m_backgroundColor = background; } @@ -752,20 +751,17 @@ void ScalableImage::paintEvent(QPaintEvent* e) } const QRect widgetRect = rect(); - const QRect imageRect = widgetRect.adjusted( - m_border, m_border, -m_border, -m_border); + const QRect imageRect = widgetRect.adjusted(m_border, m_border, -m_border, -m_border); - const QSize scaledSize = resizeWithAspectRatio( - m_original.size(), imageRect.size()); + const QSize scaledSize = resizeWithAspectRatio(m_original.size(), imageRect.size()); if (m_scaled.isNull() || m_scaled.size() != scaledSize) { - m_scaled = m_original.scaled( - scaledSize.width(), scaledSize.height(), - Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + m_scaled = m_original.scaled(scaledSize.width(), scaledSize.height(), + Qt::IgnoreAspectRatio, Qt::SmoothTransformation); } const QRect drawBorderRect = widgetRect.adjusted(0, 0, -1, -1); - const QRect drawImageRect = centeredRect(imageRect, m_scaled.size()); + const QRect drawImageRect = centeredRect(imageRect, m_scaled.size()); QPainter painter(this); @@ -780,41 +776,24 @@ void ScalableImage::paintEvent(QPaintEvent* e) painter.drawImage(drawImageRect, m_scaled); } - -Metrics::Metrics() : - margins(3), - border(1), - padding(0), - spacing(5), - textSpacing(2), - textHeight(0) -{ -} - +Metrics::Metrics() + : margins(3), border(1), padding(0), spacing(5), textSpacing(2), textHeight(0) +{} Geometry::Geometry(QSize widgetSize, Metrics metrics) - : m_widgetSize(widgetSize), m_metrics(metrics), m_topRect(calcTopRect()) -{ -} + : m_widgetSize(widgetSize), m_metrics(metrics), m_topRect(calcTopRect()) +{} QRect Geometry::calcTopRect() const { const auto thumbWidth = m_widgetSize.width(); - const auto& m = m_metrics; + const auto& m = m_metrics; const auto imageSize = - thumbWidth - - (m.margins * 2) - - (m.border * 2) - - (m.padding * 2); - - const auto thumbHeight = - m.margins + - m.border + m.padding + - imageSize + - m.padding + m.border + - m.textSpacing + m.textHeight + - m.margins; + thumbWidth - (m.margins * 2) - (m.border * 2) - (m.padding * 2); + + const auto thumbHeight = m.margins + m.border + m.padding + imageSize + m.padding + + m.border + m.textSpacing + m.textHeight + m.margins; return {0, 0, thumbWidth, thumbHeight}; } @@ -824,7 +803,7 @@ std::size_t Geometry::fullyVisibleCount() const const auto r = thumbRect(0); const auto thumbWithSpacing = r.height() + m_metrics.spacing; - const auto visible = (m_widgetSize.height() / thumbWithSpacing); + const auto visible = (m_widgetSize.height() / thumbWithSpacing); return static_cast(visible); } @@ -843,7 +822,7 @@ QRect Geometry::thumbRect(std::size_t i) const QRect Geometry::borderRect(std::size_t i) const { - auto r = thumbRect(i); + auto r = thumbRect(i); const auto& m = m_metrics; // remove margins and text @@ -870,11 +849,8 @@ QRect Geometry::textRect(std::size_t i) const { const auto r = borderRect(i); - return QRect( - r.left(), - r.bottom() + m_metrics.textSpacing, - r.width(), - m_metrics.textHeight); + return QRect(r.left(), r.bottom() + m_metrics.textSpacing, r.width(), + m_metrics.textHeight); } QRect Geometry::selectionRect(std::size_t i) const @@ -882,11 +858,7 @@ QRect Geometry::selectionRect(std::size_t i) const const auto br = borderRect(i); const auto tr = textRect(i); - return QRect( - br.left(), - br.top(), - br.width(), - tr.bottom() - br.top()); + return QRect(br.left(), br.top(), br.width(), tr.bottom() - br.top()); } std::size_t Geometry::indexAt(const QPoint& p) const @@ -907,11 +879,7 @@ QSize Geometry::scaledImageSize(const QSize& originalSize) const return resizeWithAspectRatio(originalSize, availableSize); } - -File::File(QString path) - : m_path(std::move(path)), m_failed(false) -{ -} +File::File(QString path) : m_path(std::move(path)), m_failed(false) {} void File::ensureOriginalLoaded() { @@ -923,9 +891,8 @@ void File::ensureOriginalLoaded() QImageReader reader(m_path); if (!reader.read(&m_original)) { - log::error( - "failed to load '{}'\n{} (error {})", - m_path, reader.errorString(), static_cast(reader.error())); + log::error("failed to load '{}'\n{} (error {})", m_path, reader.errorString(), + static_cast(reader.error())); m_failed = true; } @@ -990,28 +957,24 @@ void File::load(const Geometry& geo) QImage warning(":/MO/gui/warning"); const auto scaledSize = geo.scaledImageSize(warning.size()); - m_thumbnail = warning.scaled( - scaledSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + m_thumbnail = + warning.scaled(scaledSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); } else { const auto scaledSize = geo.scaledImageSize(m_original.size()); - m_thumbnail = m_original.scaled( - scaledSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + m_thumbnail = + m_original.scaled(scaledSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); } } - -Files::Files() - : m_selection(BadIndex), m_filtered(false) -{ -} +Files::Files() : m_selection(BadIndex), m_filtered(false) {} void Files::clear() { m_allFiles.clear(); m_filteredFiles.clear(); m_selection = BadIndex; - m_filtered = false; + m_filtered = false; } void Files::add(File f) @@ -1077,13 +1040,13 @@ File* Files::get(std::size_t i) std::size_t Files::indexOf(const File* f) const { if (m_filtered) { - for (std::size_t i=0; i -#include "plugincontainer.h" +#include "modinfodialogtab.h" #include "organizercore.h" +#include "plugincontainer.h" +#include using namespace MOBase; @@ -34,7 +34,6 @@ class Scrollbar : public QScrollBar ImagesTab* m_tab = nullptr; }; - // widget inside the scroller, calls ImagesTab::paintThumbnailArea() when // needed and also forwards mouse clicks and tooltip events // @@ -75,7 +74,6 @@ class ThumbnailsWidget : public QWidget ImagesTab* m_tab = nullptr; }; - // a widget that draws an image scaled to fit while keeping the aspect ratio // class ScalableImage : public QWidget @@ -83,7 +81,7 @@ class ScalableImage : public QWidget Q_OBJECT; public: - ScalableImage(QString path={}); + ScalableImage(QString path = {}); // sets the image to draw void setImage(const QString& path); @@ -109,7 +107,6 @@ class ScalableImage : public QWidget QColor m_borderColor, m_backgroundColor; }; - struct Theme { QColor borderColor, backgroundColor, textColor; @@ -117,7 +114,6 @@ struct Theme QFont font; }; - struct Metrics { // space outside the thumbnail border @@ -141,7 +137,6 @@ struct Metrics Metrics(); }; - // handles all the geometry calculations by ImagesTab for painting or handling // mouse clicks // @@ -227,13 +222,11 @@ class Geometry // rectangle of the first thumbnail on top const QRect m_topRect; - // calculates the top rectangle // QRect calcTopRect() const; }; - class File { public: @@ -259,7 +252,6 @@ class File void load(const Geometry& geo); }; - class Files { public: @@ -296,7 +288,6 @@ class Files bool m_filtered; }; - struct PaintContext { mutable QPainter painter; @@ -308,8 +299,7 @@ struct PaintContext PaintContext(QWidget* w, Geometry geo); }; -} // namespace - +} // namespace ImagesTabHelpers class ImagesTab : public ModInfoDialogTab { @@ -335,12 +325,12 @@ class ImagesTab : public ModInfoDialogTab }; using ScalableImage = ImagesTabHelpers::ScalableImage; - using Files = ImagesTabHelpers::Files; - using File = ImagesTabHelpers::File; - using Theme = ImagesTabHelpers::Theme; - using Metrics = ImagesTabHelpers::Metrics; - using PaintContext = ImagesTabHelpers::PaintContext; - using Geometry = ImagesTabHelpers::Geometry; + using Files = ImagesTabHelpers::Files; + using File = ImagesTabHelpers::File; + using Theme = ImagesTabHelpers::Theme; + using Metrics = ImagesTabHelpers::Metrics; + using PaintContext = ImagesTabHelpers::PaintContext; + using Geometry = ImagesTabHelpers::Geometry; ScalableImage* m_image; std::vector m_supportedFormats; @@ -366,7 +356,7 @@ class ImagesTab : public ModInfoDialogTab void onPreviewButton(); void onFilterChanged(); - void select(std::size_t i, Visibility v=Visibility::Full); + void select(std::size_t i, Visibility v = Visibility::Full); void moveSelection(int by); void ensureVisible(std::size_t i, Visibility v); @@ -387,4 +377,4 @@ class ImagesTab : public ModInfoDialogTab void updateScrollbar(); }; -#endif // MODINFODIALOGIMAGES_H +#endif // MODINFODIALOGIMAGES_H diff --git a/src/modinfodialognexus.cpp b/src/modinfodialognexus.cpp index fdcb2c039..566361458 100644 --- a/src/modinfodialognexus.cpp +++ b/src/modinfodialognexus.cpp @@ -1,12 +1,12 @@ #include "modinfodialognexus.h" -#include "ui_modinfodialog.h" -#include "settings.h" -#include "organizercore.h" -#include "iplugingame.h" #include "bbcode.h" -#include -#include +#include "iplugingame.h" +#include "organizercore.h" +#include "settings.h" +#include "ui_modinfodialog.h" #include +#include +#include using namespace MOBase; @@ -15,28 +15,46 @@ bool isValidModID(int id) return (id > 0); } -NexusTab::NexusTab(ModInfoDialogTabContext cx) : - ModInfoDialogTab(std::move(cx)), m_requestStarted(false), m_loading(false) +NexusTab::NexusTab(ModInfoDialogTabContext cx) + : ModInfoDialogTab(std::move(cx)), m_requestStarted(false), m_loading(false) { ui->modID->setValidator(new QIntValidator(ui->modID)); ui->endorse->setVisible(core().settings().nexus().endorsementIntegration()); ui->track->setVisible(core().settings().nexus().trackedIntegration()); - connect(ui->modID, &QLineEdit::editingFinished, [&]{ onModIDChanged(); }); - connect( - ui->sourceGame, - static_cast(&QComboBox::currentIndexChanged), - [&]{ onSourceGameChanged(); }); - connect(ui->version, &QLineEdit::editingFinished, [&]{ onVersionChanged(); }); - - connect(ui->refresh, &QPushButton::clicked, [&]{ onRefreshBrowser(); }); - connect(ui->visitNexus, &QPushButton::clicked, [&]{ onVisitNexus(); }); - connect(ui->endorse, &QPushButton::clicked, [&]{ onEndorse(); }); - connect(ui->track, &QPushButton::clicked, [&]{ onTrack(); }); - - connect(ui->hasCustomURL, &QCheckBox::toggled, [&]{ onCustomURLToggled(); }); - connect(ui->customURL, &QLineEdit::editingFinished, [&]{ onCustomURLChanged(); }); - connect(ui->visitCustomURL, &QPushButton::clicked, [&]{ onVisitCustomURL(); }); + connect(ui->modID, &QLineEdit::editingFinished, [&] { + onModIDChanged(); + }); + connect(ui->sourceGame, + static_cast(&QComboBox::currentIndexChanged), [&] { + onSourceGameChanged(); + }); + connect(ui->version, &QLineEdit::editingFinished, [&] { + onVersionChanged(); + }); + + connect(ui->refresh, &QPushButton::clicked, [&] { + onRefreshBrowser(); + }); + connect(ui->visitNexus, &QPushButton::clicked, [&] { + onVisitNexus(); + }); + connect(ui->endorse, &QPushButton::clicked, [&] { + onEndorse(); + }); + connect(ui->track, &QPushButton::clicked, [&] { + onTrack(); + }); + + connect(ui->hasCustomURL, &QCheckBox::toggled, [&] { + onCustomURLToggled(); + }); + connect(ui->customURL, &QLineEdit::editingFinished, [&] { + onCustomURLChanged(); + }); + connect(ui->visitCustomURL, &QPushButton::clicked, [&] { + onVisitCustomURL(); + }); } NexusTab::~NexusTab() @@ -72,9 +90,8 @@ void NexusTab::update() ui->modID->setText(QString("%1").arg(mod().nexusId())); QString gameName = mod().gameName(); - ui->sourceGame->addItem( - core().managedGame()->gameName(), - core().managedGame()->gameShortName()); + ui->sourceGame->addItem(core().managedGame()->gameName(), + core().managedGame()->gameShortName()); if (core().managedGame()->validShortNames().size() == 0) { ui->sourceGame->setDisabled(true); @@ -94,13 +111,12 @@ void NexusTab::update() auto* page = new NexusTabWebpage(ui->browser); ui->browser->setPage(page); - connect( - page, &NexusTabWebpage::linkClicked, - [&](const QUrl& url){ shell::Open(url); }); + connect(page, &NexusTabWebpage::linkClicked, [&](const QUrl& url) { + shell::Open(url); + }); - ui->endorse->setEnabled( - (mod().endorsedState() == EndorsedState::ENDORSED_FALSE) || - (mod().endorsedState() == EndorsedState::ENDORSED_NEVER)); + ui->endorse->setEnabled((mod().endorsedState() == EndorsedState::ENDORSED_FALSE) || + (mod().endorsedState() == EndorsedState::ENDORSED_NEVER)); setHasData(mod().nexusId() >= 0); } @@ -116,8 +132,9 @@ void NexusTab::setMod(ModInfoPtr mod, MOShared::FilesOrigin* origin) ModInfoDialogTab::setMod(mod, origin); - m_modConnection = connect( - mod.data(), &ModInfo::modDetailsUpdated, [&]{ onModChanged(); }); + m_modConnection = connect(mod.data(), &ModInfo::modDetailsUpdated, [&] { + onModChanged(); + }); } bool NexusTab::usesOriginFiles() const @@ -129,8 +146,8 @@ void NexusTab::updateVersionColor() { if (mod().version() != mod().newestVersion()) { ui->version->setStyleSheet("color: red"); - ui->version->setToolTip(tr("Current Version: %1").arg( - mod().newestVersion().canonicalString())); + ui->version->setToolTip( + tr("Current Version: %1").arg(mod().newestVersion().canonicalString())); } else { ui->version->setStyleSheet("color: green"); ui->version->setToolTip(tr("No update available")); @@ -142,8 +159,8 @@ void NexusTab::updateWebpage() const int modID = mod().nexusId(); if (isValidModID(modID)) { - const QString nexusLink = NexusInterface::instance() - .getModURL(modID, mod().gameName()); + const QString nexusLink = + NexusInterface::instance().getModURL(modID, mod().gameName()); ui->visitNexus->setToolTip(nexusLink); refreshData(modID); @@ -287,8 +304,7 @@ void NexusTab::onModChanged() page for it in the "Custom URL" box below.

      )")); } else { - descriptionAsHTML = descriptionAsHTML.arg( - BBCode::convertToHTML(nexusDescription)); + descriptionAsHTML = descriptionAsHTML.arg(BBCode::convertToHTML(nexusDescription)); } ui->browser->page()->setHtml(descriptionAsHTML); @@ -305,7 +321,7 @@ void NexusTab::onModIDChanged() const int oldID = mod().nexusId(); const int newID = ui->modID->text().toInt(); - if (oldID != newID){ + if (oldID != newID) { mod().setNexusID(newID); mod().setLastNexusQuery(QDateTime::fromSecsSinceEpoch(0)); @@ -361,8 +377,8 @@ void NexusTab::onVisitNexus() const int modID = mod().nexusId(); if (isValidModID(modID)) { - const QString nexusLink = NexusInterface::instance() - .getModURL(modID, mod().gameName()); + const QString nexusLink = + NexusInterface::instance().getModURL(modID, mod().gameName()); shell::Open(QUrl(nexusLink)); } @@ -372,14 +388,16 @@ void NexusTab::onEndorse() { // use modPtr() instead of mod() or this because the callback may be // executed after the dialog is closed - core().loggedInAction(parentWidget(), [m=modPtr()]{ m->endorse(true); }); + core().loggedInAction(parentWidget(), [m = modPtr()] { + m->endorse(true); + }); } void NexusTab::onTrack() { // use modPtr() instead of mod() or this because the callback may be // executed after the dialog is closed - core().loggedInAction(parentWidget(), [m=modPtr()] { + core().loggedInAction(parentWidget(), [m = modPtr()] { if (m->trackedState() == TrackedState::TRACKED_TRUE) { m->track(false); } else { diff --git a/src/modinfodialognexus.h b/src/modinfodialognexus.h index 1cfa20574..e9a8720f3 100644 --- a/src/modinfodialognexus.h +++ b/src/modinfodialognexus.h @@ -8,16 +8,12 @@ class NexusTabWebpage : public QWebEnginePage Q_OBJECT public: - NexusTabWebpage(QObject* parent = 0) - : QWebEnginePage(parent) - { - } + NexusTabWebpage(QObject* parent = 0) : QWebEnginePage(parent) {} - bool acceptNavigationRequest( - const QUrl & url, QWebEnginePage::NavigationType type, bool) override + bool acceptNavigationRequest(const QUrl& url, QWebEnginePage::NavigationType type, + bool) override { - if (type == QWebEnginePage::NavigationTypeLinkClicked) - { + if (type == QWebEnginePage::NavigationTypeLinkClicked) { emit linkClicked(url); return false; } @@ -29,7 +25,6 @@ class NexusTabWebpage : public QWebEnginePage void linkClicked(const QUrl&); }; - class NexusTab : public ModInfoDialogTab { Q_OBJECT; @@ -73,4 +68,4 @@ class NexusTab : public ModInfoDialogTab void onVisitCustomURL(); }; -#endif // MODINFODIALOGNEXUS_H +#endif // MODINFODIALOGNEXUS_H diff --git a/src/modinfodialogtab.cpp b/src/modinfodialogtab.cpp index e5ced4f75..c443a389b 100644 --- a/src/modinfodialogtab.cpp +++ b/src/modinfodialogtab.cpp @@ -1,14 +1,13 @@ #include "modinfodialogtab.h" -#include "ui_modinfodialog.h" -#include "texteditor.h" #include "modinfo.h" #include "shared/filesorigin.h" +#include "texteditor.h" +#include "ui_modinfodialog.h" -ModInfoDialogTab::ModInfoDialogTab(ModInfoDialogTabContext cx) : - ui(cx.ui), m_core(cx.core), m_plugin(cx.plugin), m_parent(cx.parent), - m_origin(cx.origin), m_tabID(cx.id), m_hasData(false), m_firstActivation(true) -{ -} +ModInfoDialogTab::ModInfoDialogTab(ModInfoDialogTabContext cx) + : ui(cx.ui), m_core(cx.core), m_plugin(cx.plugin), m_parent(cx.parent), + m_origin(cx.origin), m_tabID(cx.id), m_hasData(false), m_firstActivation(true) +{} void ModInfoDialogTab::activated() { @@ -77,7 +76,7 @@ bool ModInfoDialogTab::usesOriginFiles() const void ModInfoDialogTab::setMod(ModInfoPtr mod, MOShared::FilesOrigin* origin) { - m_mod = mod; + m_mod = mod; m_origin = origin; } @@ -148,20 +147,26 @@ void ModInfoDialogTab::setFocus() emit wantsFocus(); } - -NotesTab::NotesTab(ModInfoDialogTabContext cx) - : ModInfoDialogTab(std::move(cx)) +NotesTab::NotesTab(ModInfoDialogTabContext cx) : ModInfoDialogTab(std::move(cx)) { - connect(ui->comments, &QLineEdit::editingFinished, [&]{ onComments(); }); - connect(ui->notes, &HTMLEditor::editingFinished, [&]{ onNotes(); }); - connect(ui->setColorButton, &QPushButton::clicked, [&] { onSetColor(); }); - connect(ui->resetColorButton, &QPushButton::clicked, [&] { onResetColor(); }); + connect(ui->comments, &QLineEdit::editingFinished, [&] { + onComments(); + }); + connect(ui->notes, &HTMLEditor::editingFinished, [&] { + onNotes(); + }); + connect(ui->setColorButton, &QPushButton::clicked, [&] { + onSetColor(); + }); + connect(ui->resetColorButton, &QPushButton::clicked, [&] { + onResetColor(); + }); } void NotesTab::updateCommentsColor(bool clear) { QPalette commentPalette = QPalette(); - + if (!clear) { auto modColor = mod().color(); if (modColor.isValid()) { @@ -183,7 +188,7 @@ void NotesTab::clear() void NotesTab::update() { const auto comments = mod().comments(); - const auto notes = mod().notes(); + const auto notes = mod().notes(); ui->comments->setText(comments); ui->notes->setText(notes); @@ -220,7 +225,7 @@ void NotesTab::onSetColor() dialog.setOption(QColorDialog::ShowAlphaChannel); QColor currentColor = mod().color(); - + if (currentColor.isValid()) { dialog.setCurrentColor(currentColor); } @@ -253,8 +258,6 @@ bool NotesTab::usesOriginFiles() const void NotesTab::checkHasData() { - setHasData( - !ui->comments->text().isEmpty() || - !ui->notes->toPlainText().isEmpty() || - mod().color().isValid()); + setHasData(!ui->comments->text().isEmpty() || !ui->notes->toPlainText().isEmpty() || + mod().color().isValid()); } diff --git a/src/modinfodialogtab.h b/src/modinfodialogtab.h index 7a42bf7ed..dff1732d0 100644 --- a/src/modinfodialogtab.h +++ b/src/modinfodialogtab.h @@ -4,8 +4,14 @@ #include "modinfodialogfwd.h" #include -namespace MOShared { class FilesOrigin; } -namespace Ui { class ModInfoDialog; } +namespace MOShared +{ +class FilesOrigin; +} +namespace Ui +{ +class ModInfoDialog; +} class Settings; class OrganizerCore; @@ -22,21 +28,14 @@ struct ModInfoDialogTabContext ModInfoPtr mod; MOShared::FilesOrigin* origin; - ModInfoDialogTabContext( - OrganizerCore& core, - PluginContainer& plugin, - QWidget* parent, - Ui::ModInfoDialog* ui, - ModInfoTabIDs id, - ModInfoPtr mod, - MOShared::FilesOrigin* origin) : - core(core), plugin(plugin), parent(parent), ui(ui), id(id), - mod(mod), origin(origin) - { - } + ModInfoDialogTabContext(OrganizerCore& core, PluginContainer& plugin, QWidget* parent, + Ui::ModInfoDialog* ui, ModInfoTabIDs id, ModInfoPtr mod, + MOShared::FilesOrigin* origin) + : core(core), plugin(plugin), parent(parent), ui(ui), id(id), mod(mod), + origin(origin) + {} }; - // base class for all tabs in the mod info dialog // // when the dialog is opened or when next/previous is clicked, the sequence is: @@ -72,11 +71,11 @@ class ModInfoDialogTab : public QObject Q_OBJECT; public: - ModInfoDialogTab(const ModInfoDialogTab&) = delete; + ModInfoDialogTab(const ModInfoDialogTab&) = delete; ModInfoDialogTab& operator=(const ModInfoDialogTab&) = delete; - ModInfoDialogTab(ModInfoDialogTab&&) = default; - ModInfoDialogTab& operator=(ModInfoDialogTab&&) = default; - virtual ~ModInfoDialogTab() = default; + ModInfoDialogTab(ModInfoDialogTab&&) = default; + ModInfoDialogTab& operator=(ModInfoDialogTab&&) = default; + virtual ~ModInfoDialogTab() = default; // called by ModInfoDialog every time this tab is selected; this will call // firstActivation() the first time it's called, until resetFirstActivation() @@ -89,7 +88,6 @@ class ModInfoDialogTab : public QObject // void resetFirstActivation(); - // called when the selected mod changed, `mod` can never be empty, but // `origin` can (if the mod is not active, for example) // @@ -143,7 +141,6 @@ class ModInfoDialogTab : public QObject // virtual bool canClose(); - // called after the dialog is closed, tabs should save whatever UI state they // want // @@ -154,7 +151,6 @@ class ModInfoDialogTab : public QObject // virtual void restoreState(const Settings& s); - // called on the selected tab when the Delete key is pressed on the keyboard; // tabs _must_ check which widget currently has focus to decide whether this // should be handled or not; do not blindly delete stuff when this is called @@ -163,7 +159,6 @@ class ModInfoDialogTab : public QObject // virtual bool deleteRequested(); - // return true if this tab can handle a separator mod, defaults to false; // when this returns false, the tab is removed from the widget entirely // @@ -188,7 +183,6 @@ class ModInfoDialogTab : public QObject // virtual bool usesOriginFiles() const; - // returns the currently selected mod // ModInfo& mod() const; @@ -202,7 +196,6 @@ class ModInfoDialogTab : public QObject // MOShared::FilesOrigin* origin() const; - // return this tab's ID // ModInfoTabIDs tabID() const; @@ -279,7 +272,6 @@ class ModInfoDialogTab : public QObject bool m_firstActivation; }; - // the Notes tab // class NotesTab : public ModInfoDialogTab @@ -307,4 +299,4 @@ class NotesTab : public ModInfoDialogTab void checkHasData(); }; -#endif // MODINFODIALOGTAB_H +#endif // MODINFODIALOGTAB_H diff --git a/src/modinfodialogtextfiles.cpp b/src/modinfodialogtextfiles.cpp index 564c2cf74..cc13e2d26 100644 --- a/src/modinfodialogtextfiles.cpp +++ b/src/modinfodialogtextfiles.cpp @@ -1,7 +1,7 @@ #include "modinfodialogtextfiles.h" -#include "ui_modinfodialog.h" #include "modinfodialog.h" #include "settings.h" +#include "ui_modinfodialog.h" #include class FileListModel : public QAbstractItemModel @@ -13,17 +13,14 @@ class FileListModel : public QAbstractItemModel endResetModel(); } - QModelIndex index(int row, int col, const QModelIndex& ={}) const override + QModelIndex index(int row, int col, const QModelIndex& = {}) const override { return createIndex(row, col); } - QModelIndex parent(const QModelIndex&) const override - { - return {}; - } + QModelIndex parent(const QModelIndex&) const override { return {}; } - int rowCount(const QModelIndex& index={}) const override + int rowCount(const QModelIndex& index = {}) const override { // no child nodes if (index.isValid()) @@ -32,10 +29,7 @@ class FileListModel : public QAbstractItemModel return static_cast(m_files.size()); } - int columnCount(const QModelIndex& ={}) const override - { - return 1; - } + int columnCount(const QModelIndex& = {}) const override { return 1; } QVariant data(const QModelIndex& index, int role) const override { @@ -92,22 +86,16 @@ class FileListModel : public QAbstractItemModel QString fullPath; QString text; - File(QString fp, QString t) - : fullPath(std::move(fp)), text(std::move(t)) - { - } + File(QString fp, QString t) : fullPath(std::move(fp)), text(std::move(t)) {} }; std::deque m_files; }; - -GenericFilesTab::GenericFilesTab( - ModInfoDialogTabContext cx, - QListView* list, QSplitter* sp, - TextEditor* e, QLineEdit* filter) : - ModInfoDialogTab(std::move(cx)), - m_list(list), m_editor(e), m_splitter(sp), m_model(new FileListModel) +GenericFilesTab::GenericFilesTab(ModInfoDialogTabContext cx, QListView* list, + QSplitter* sp, TextEditor* e, QLineEdit* filter) + : ModInfoDialogTab(std::move(cx)), m_list(list), m_editor(e), m_splitter(sp), + m_model(new FileListModel) { m_list->setModel(m_model); m_editor->setupToolbar(); @@ -119,9 +107,10 @@ GenericFilesTab::GenericFilesTab( m_filter.setEdit(filter); m_filter.setList(m_list); - QObject::connect( - m_list->selectionModel(), &QItemSelectionModel::currentRowChanged, - [&](auto current, auto previous){ onSelection(current, previous); }); + QObject::connect(m_list->selectionModel(), &QItemSelectionModel::currentRowChanged, + [&](auto current, auto previous) { + onSelection(current, previous); + }); } void GenericFilesTab::clear() @@ -140,10 +129,9 @@ bool GenericFilesTab::canClose() setFocus(); const int res = QMessageBox::question( - parentWidget(), - QObject::tr("Save changes?"), - QObject::tr("Save changes to \"%1\"?").arg(m_editor->filename()), - QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); + parentWidget(), QObject::tr("Save changes?"), + QObject::tr("Save changes to \"%1\"?").arg(m_editor->filename()), + QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); if (res == QMessageBox::Cancel) { return false; @@ -182,8 +170,8 @@ void GenericFilesTab::restoreState(const Settings& s) s.geometry().restoreState(m_splitter); } -void GenericFilesTab::onSelection( - const QModelIndex& current, const QModelIndex& previous) +void GenericFilesTab::onSelection(const QModelIndex& current, + const QModelIndex& previous) { if (!canClose()) { m_list->selectionModel()->select(previous, QItemSelectionModel::Current); @@ -205,23 +193,14 @@ void GenericFilesTab::select(const QModelIndex& index) m_editor->load(m_model->fullPath(m_filter.mapToSource(index))); } - TextFilesTab::TextFilesTab(ModInfoDialogTabContext cx) - : GenericFilesTab(cx, - cx.ui->textFileList, cx.ui->tabTextSplitter, - cx.ui->textFileEditor, cx.ui->textFileFilter) -{ -} + : GenericFilesTab(cx, cx.ui->textFileList, cx.ui->tabTextSplitter, + cx.ui->textFileEditor, cx.ui->textFileFilter) +{} bool TextFilesTab::wantsFile(const QString& rootPath, const QString& fullPath) const { - static const QString extensions[] = { - ".txt", - ".json", - ".cfg", - ".log", - ".toml" - }; + static const QString extensions[] = {".txt", ".json", ".cfg", ".log", ".toml"}; for (const auto& e : extensions) { if (fullPath.endsWith(e, Qt::CaseInsensitive)) { @@ -233,11 +212,9 @@ bool TextFilesTab::wantsFile(const QString& rootPath, const QString& fullPath) c } IniFilesTab::IniFilesTab(ModInfoDialogTabContext cx) - : GenericFilesTab(cx, - cx.ui->iniFileList, cx.ui->tabIniSplitter, - cx.ui->iniFileEditor, cx.ui->iniFileFilter) -{ -} + : GenericFilesTab(cx, cx.ui->iniFileList, cx.ui->tabIniSplitter, + cx.ui->iniFileEditor, cx.ui->iniFileFilter) +{} bool IniFilesTab::wantsFile(const QString& rootPath, const QString& fullPath) const { diff --git a/src/modinfodialogtextfiles.h b/src/modinfodialogtextfiles.h index d57bf4907..f6bd7d7b4 100644 --- a/src/modinfodialogtextfiles.h +++ b/src/modinfodialogtextfiles.h @@ -1,10 +1,10 @@ #ifndef MODINFODIALOGTEXTFILES_H #define MODINFODIALOGTEXTFILES_H -#include "modinfodialogtab.h" #include "filterwidget.h" -#include +#include "modinfodialogtab.h" #include +#include using namespace MOBase; @@ -31,10 +31,8 @@ class GenericFilesTab : public ModInfoDialogTab FileListModel* m_model; FilterWidget m_filter; - GenericFilesTab( - ModInfoDialogTabContext cx, - QListView* list, QSplitter* splitter, - TextEditor* editor, QLineEdit* filter); + GenericFilesTab(ModInfoDialogTabContext cx, QListView* list, QSplitter* splitter, + TextEditor* editor, QLineEdit* filter); virtual bool wantsFile(const QString& rootPath, const QString& fullPath) const = 0; @@ -43,7 +41,6 @@ class GenericFilesTab : public ModInfoDialogTab void select(const QModelIndex& index); }; - class TextFilesTab : public GenericFilesTab { public: @@ -53,7 +50,6 @@ class TextFilesTab : public GenericFilesTab bool wantsFile(const QString& rootPath, const QString& fullPath) const override; }; - class IniFilesTab : public GenericFilesTab { public: @@ -63,4 +59,4 @@ class IniFilesTab : public GenericFilesTab bool wantsFile(const QString& rootPath, const QString& fullPath) const override; }; -#endif // MODINFODIALOGTEXTFILES_H +#endif // MODINFODIALOGTEXTFILES_H diff --git a/src/modinfoforeign.cpp b/src/modinfoforeign.cpp index 97d3f4e1f..db90aa416 100644 --- a/src/modinfoforeign.cpp +++ b/src/modinfoforeign.cpp @@ -15,8 +15,8 @@ QDateTime ModInfoForeign::creationTime() const QString ModInfoForeign::absolutePath() const { - //I ought to store this, it's used elsewhere - IPluginGame const *game = qApp->property("managed_game").value(); + // I ought to store this, it's used elsewhere + IPluginGame const* game = qApp->property("managed_game").value(); return game->dataDirectory().absolutePath(); } @@ -39,28 +39,28 @@ int ModInfoForeign::getHighlight() const QString ModInfoForeign::getDescription() const { - return tr("This pseudo mod represents content managed outside MO. It isn't modified by MO."); + return tr("This pseudo mod represents content managed outside MO. It isn't modified " + "by MO."); } -ModInfoForeign::ModInfoForeign( - const QString &modName, const QString &referenceFile, - const QStringList &archives, ModInfo::EModType modType, - OrganizerCore& core) - : ModInfoWithConflictInfo(core), - m_ReferenceFile(referenceFile), m_Archives(archives), m_ModType(modType) +ModInfoForeign::ModInfoForeign(const QString& modName, const QString& referenceFile, + const QStringList& archives, ModInfo::EModType modType, + OrganizerCore& core) + : ModInfoWithConflictInfo(core), m_ReferenceFile(referenceFile), + m_Archives(archives), m_ModType(modType) { m_CreationTime = QFileInfo(referenceFile).birthTime(); switch (modType) { case ModInfo::EModType::MOD_DLC: - m_Name = tr("DLC: ") + modName; + m_Name = tr("DLC: ") + modName; m_InternalName = QString("DLC: ") + modName; break; case ModInfo::EModType::MOD_CC: - m_Name = tr("Creation Club: ") + modName; + m_Name = tr("Creation Club: ") + modName; m_InternalName = QString("Creation Club: ") + modName; break; default: - m_Name = tr("Unmanaged: ") + modName; + m_Name = tr("Unmanaged: ") + modName; m_InternalName = QString("Unmanaged: ") + modName; } } diff --git a/src/modinfoforeign.h b/src/modinfoforeign.h index 3acebeafd..2c956fb7f 100644 --- a/src/modinfoforeign.h +++ b/src/modinfoforeign.h @@ -5,7 +5,7 @@ #include "modinfowithconflictinfo.h" -class ModInfoForeign: public ModInfoWithConflictInfo +class ModInfoForeign : public ModInfoWithConflictInfo { Q_OBJECT @@ -13,7 +13,6 @@ class ModInfoForeign: public ModInfoWithConflictInfo friend class ModInfo; public: - virtual bool updateAvailable() const override { return false; } virtual bool updateIgnored() const override { return false; } virtual bool downgradeAvailable() const override { return false; } @@ -50,7 +49,10 @@ class ModInfoForeign: public ModInfoWithConflictInfo virtual int nexusId() const override { return -1; } virtual bool isForeign() const override { return true; } virtual QDateTime getExpires() const override { return QDateTime(); } - virtual std::vector getIniTweaks() const override { return std::vector(); } + virtual std::vector getIniTweaks() const override + { + return std::vector(); + } virtual std::vector getFlags() const override; virtual int getHighlight() const override; virtual QString getDescription() const override; @@ -64,25 +66,43 @@ class ModInfoForeign: public ModInfoWithConflictInfo virtual void setNexusLastModified(QDateTime) override {} virtual QString getNexusDescription() const override { return QString(); } virtual QStringList archives(bool = false) override { return m_Archives; } - virtual QStringList stealFiles() const override { return m_Archives + QStringList(m_ReferenceFile); } + virtual QStringList stealFiles() const override + { + return m_Archives + QStringList(m_ReferenceFile); + } virtual bool alwaysEnabled() const override { return true; } virtual void addInstalledFile(int, int) override {} virtual std::set> installedFiles() const override { return {}; } - virtual QVariant pluginSetting(const QString& pluginName, const QString& key, const QVariant& defaultValue) const override { return defaultValue; } - virtual std::map pluginSettings(const QString& pluginName) const override { return {}; } - virtual bool setPluginSetting(const QString& pluginName, const QString& key, const QVariant& value) override { return false; } - virtual std::map clearPluginSettings(const QString& pluginName) override { return {}; } + virtual QVariant pluginSetting(const QString& pluginName, const QString& key, + const QVariant& defaultValue) const override + { + return defaultValue; + } + virtual std::map + pluginSettings(const QString& pluginName) const override + { + return {}; + } + virtual bool setPluginSetting(const QString& pluginName, const QString& key, + const QVariant& value) override + { + return false; + } + virtual std::map + clearPluginSettings(const QString& pluginName) override + { + return {}; + } ModInfo::EModType modType() const { return m_ModType; } protected: - ModInfoForeign(const QString &modName, const QString &referenceFile, - const QStringList &archives, ModInfo::EModType modType, - OrganizerCore &core); + ModInfoForeign(const QString& modName, const QString& referenceFile, + const QStringList& archives, ModInfo::EModType modType, + OrganizerCore& core); private: - QString m_Name; QString m_InternalName; QString m_ReferenceFile; @@ -92,4 +112,4 @@ class ModInfoForeign: public ModInfoWithConflictInfo ModInfo::EModType m_ModType; }; -#endif // MODINFOFOREIGN_H +#endif // MODINFOFOREIGN_H diff --git a/src/modinfooverwrite.cpp b/src/modinfooverwrite.cpp index 5ee7a0d9d..9d0ed5f9d 100644 --- a/src/modinfooverwrite.cpp +++ b/src/modinfooverwrite.cpp @@ -1,21 +1,22 @@ #include "modinfooverwrite.h" -#include "shared/appconfig.h" #include "settings.h" +#include "shared/appconfig.h" #include #include ModInfoOverwrite::ModInfoOverwrite(OrganizerCore& core) : ModInfoWithConflictInfo(core) -{ -} +{} bool ModInfoOverwrite::isEmpty() const { QDirIterator iter(absolutePath(), QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs); - if (!iter.hasNext()) return true; + if (!iter.hasNext()) + return true; iter.next(); - if ((iter.fileName() == "meta.ini") && !iter.hasNext()) return true; + if ((iter.fileName() == "meta.ini") && !iter.hasNext()) + return true; return false; } @@ -46,9 +47,11 @@ std::vector ModInfoOverwrite::getConflictFlags() const int ModInfoOverwrite::getHighlight() const { - int highlight = (isValid() ? HIGHLIGHT_IMPORTANT : HIGHLIGHT_INVALID) | HIGHLIGHT_CENTER; + int highlight = + (isValid() ? HIGHLIGHT_IMPORTANT : HIGHLIGHT_INVALID) | HIGHLIGHT_CENTER; auto flags = getFlags(); - if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_PLUGIN_SELECTED) != flags.end()) + if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_PLUGIN_SELECTED) != + flags.end()) highlight |= HIGHLIGHT_PLUGIN; return highlight; } @@ -63,7 +66,7 @@ QStringList ModInfoOverwrite::archives(bool checkOnDisk) { QStringList result; QDir dir(this->absolutePath()); - for (const QString &archive : dir.entryList(QStringList({ "*.bsa", "*.ba2" }))) { + for (const QString& archive : dir.entryList(QStringList({"*.bsa", "*.ba2"}))) { result.append(this->absolutePath() + "/" + archive); } return result; diff --git a/src/modinfooverwrite.h b/src/modinfooverwrite.h index 997e24ea5..02154902a 100644 --- a/src/modinfooverwrite.h +++ b/src/modinfooverwrite.h @@ -15,7 +15,6 @@ class ModInfoOverwrite : public ModInfoWithConflictInfo friend class ModInfo; public: - virtual bool updateAvailable() const override { return false; } virtual bool updateIgnored() const override { return false; } virtual bool downgradeAvailable() const override { return false; } @@ -52,7 +51,10 @@ class ModInfoOverwrite : public ModInfoWithConflictInfo virtual int nexusId() const override { return -1; } virtual bool isOverwrite() const override { return true; } virtual QDateTime getExpires() const override { return QDateTime(); } - virtual std::vector getIniTweaks() const override { return std::vector(); } + virtual std::vector getIniTweaks() const override + { + return std::vector(); + } virtual std::vector getFlags() const override; virtual std::vector getConflictFlags() const override; virtual int getHighlight() const override; @@ -70,14 +72,29 @@ class ModInfoOverwrite : public ModInfoWithConflictInfo virtual void addInstalledFile(int, int) override {} virtual std::set> installedFiles() const override { return {}; } - virtual QVariant pluginSetting(const QString& pluginName, const QString& key, const QVariant& defaultValue) const override { return defaultValue; } - virtual std::map pluginSettings(const QString& pluginName) const override { return {}; } - virtual bool setPluginSetting(const QString& pluginName, const QString& key, const QVariant& value) override { return false; } - virtual std::map clearPluginSettings(const QString& pluginName) override { return {}; } + virtual QVariant pluginSetting(const QString& pluginName, const QString& key, + const QVariant& defaultValue) const override + { + return defaultValue; + } + virtual std::map + pluginSettings(const QString& pluginName) const override + { + return {}; + } + virtual bool setPluginSetting(const QString& pluginName, const QString& key, + const QVariant& value) override + { + return false; + } + virtual std::map + clearPluginSettings(const QString& pluginName) override + { + return {}; + } private: ModInfoOverwrite(OrganizerCore& core); - }; -#endif // MODINFOOVERWRITE_H +#endif // MODINFOOVERWRITE_H diff --git a/src/modinforegular.cpp b/src/modinforegular.cpp index a0155dffe..2ee9a762a 100644 --- a/src/modinforegular.cpp +++ b/src/modinforegular.cpp @@ -2,11 +2,11 @@ #include "categories.h" #include "messagedialog.h" -#include "report.h" #include "moddatacontent.h" -#include "settings.h" #include "organizercore.h" #include "plugincontainer.h" +#include "report.h" +#include "settings.h" #include #include @@ -18,28 +18,24 @@ using namespace MOBase; using namespace MOShared; -namespace { - //Arguably this should be a class static or we should be using FileString rather - //than QString for the names. Or both. - static bool ByName(const ModInfo::Ptr &LHS, const ModInfo::Ptr &RHS) - { - return QString::compare(LHS->name(), RHS->name(), Qt::CaseInsensitive) < 0; - } -} - -ModInfoRegular::ModInfoRegular(const QDir &path, OrganizerCore& core) - : ModInfoWithConflictInfo(core) - , m_Name(path.dirName()) - , m_Path(path.absolutePath()) - , m_Repository() - , m_GameName(core.managedGame()->gameShortName()) - , m_IsAlternate(false) - , m_Converted(false) - , m_Validated(false) - , m_MetaInfoChanged(false) - , m_EndorsedState(EndorsedState::ENDORSED_UNKNOWN) - , m_TrackedState(TrackedState::TRACKED_UNKNOWN) - , m_NexusBridge(&core.pluginContainer()) +namespace +{ +// Arguably this should be a class static or we should be using FileString rather +// than QString for the names. Or both. +static bool ByName(const ModInfo::Ptr& LHS, const ModInfo::Ptr& RHS) +{ + return QString::compare(LHS->name(), RHS->name(), Qt::CaseInsensitive) < 0; +} +} // namespace + +ModInfoRegular::ModInfoRegular(const QDir& path, OrganizerCore& core) + : ModInfoWithConflictInfo(core), m_Name(path.dirName()), + m_Path(path.absolutePath()), m_Repository(), + m_GameName(core.managedGame()->gameShortName()), m_IsAlternate(false), + m_Converted(false), m_Validated(false), m_MetaInfoChanged(false), + m_EndorsedState(EndorsedState::ENDORSED_UNKNOWN), + m_TrackedState(TrackedState::TRACKED_UNKNOWN), + m_NexusBridge(&core.pluginContainer()) { m_CreationTime = QFileInfo(path.absolutePath()).birthTime(); // read out the meta-file for information @@ -48,28 +44,29 @@ ModInfoRegular::ModInfoRegular(const QDir &path, OrganizerCore& core) if (!core.managedGame()->primarySources().contains(m_GameName, Qt::CaseInsensitive)) m_IsAlternate = true; - //populate m_Archives + // populate m_Archives m_Archives = QStringList(); if (Settings::instance().archiveParsing()) { archives(true); } - connect(&m_NexusBridge, SIGNAL(descriptionAvailable(QString,int,QVariant,QVariant)) - , this, SLOT(nxmDescriptionAvailable(QString,int,QVariant,QVariant))); - connect(&m_NexusBridge, SIGNAL(endorsementToggled(QString,int,QVariant,QVariant)) - , this, SLOT(nxmEndorsementToggled(QString,int,QVariant,QVariant))); - connect(&m_NexusBridge, SIGNAL(trackingToggled(QString,int,QVariant,bool)) - , this, SLOT(nxmTrackingToggled(QString,int,QVariant,bool))); - connect(&m_NexusBridge, SIGNAL(requestFailed(QString,int,int,QVariant,int,QString)) - , this, SLOT(nxmRequestFailed(QString,int,int,QVariant,int,QString))); + connect(&m_NexusBridge, + SIGNAL(descriptionAvailable(QString, int, QVariant, QVariant)), this, + SLOT(nxmDescriptionAvailable(QString, int, QVariant, QVariant))); + connect(&m_NexusBridge, SIGNAL(endorsementToggled(QString, int, QVariant, QVariant)), + this, SLOT(nxmEndorsementToggled(QString, int, QVariant, QVariant))); + connect(&m_NexusBridge, SIGNAL(trackingToggled(QString, int, QVariant, bool)), this, + SLOT(nxmTrackingToggled(QString, int, QVariant, bool))); + connect(&m_NexusBridge, + SIGNAL(requestFailed(QString, int, int, QVariant, int, QString)), this, + SLOT(nxmRequestFailed(QString, int, int, QVariant, int, QString))); } - ModInfoRegular::~ModInfoRegular() { try { saveMeta(); - } catch (const std::exception &e) { + } catch (const std::exception& e) { log::error("failed to save meta information for \"{}\": {}", m_Name, e.what()); } } @@ -77,21 +74,23 @@ ModInfoRegular::~ModInfoRegular() bool ModInfoRegular::isEmpty() const { QDirIterator iter(m_Path, QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs); - if (!iter.hasNext()) return true; + if (!iter.hasNext()) + return true; iter.next(); - if ((iter.fileName() == "meta.ini") && !iter.hasNext()) return true; + if ((iter.fileName() == "meta.ini") && !iter.hasNext()) + return true; return false; } - void ModInfoRegular::readMeta() { QSettings metaFile(m_Path + "/meta.ini", QSettings::IniFormat); - m_Comments = metaFile.value("comments", "").toString(); - m_Notes = metaFile.value("notes", "").toString(); + m_Comments = metaFile.value("comments", "").toString(); + m_Notes = metaFile.value("notes", "").toString(); QString tempGameName = metaFile.value("gameName", m_GameName).toString(); - if (tempGameName.size()) m_GameName = tempGameName; - m_NexusID = metaFile.value("modid", -1).toInt(); + if (tempGameName.size()) + m_GameName = tempGameName; + m_NexusID = metaFile.value("modid", -1).toInt(); m_Version.parse(metaFile.value("version", "").toString()); m_NewestVersion = metaFile.value("newestVersion", "").toString(); m_IgnoredVersion = metaFile.value("ignoredVersion", "").toString(); @@ -165,36 +164,54 @@ void ModInfoRegular::readMeta() } } - m_LastNexusQuery = QDateTime::fromString(metaFile.value("lastNexusQuery", "").toString(), Qt::ISODate); - m_LastNexusUpdate = QDateTime::fromString(metaFile.value("lastNexusUpdate", "").toString(), Qt::ISODate); - m_NexusLastModified = QDateTime::fromString(metaFile.value("nexusLastModified", QDateTime::currentDateTimeUtc()).toString(), Qt::ISODate); - m_Color = metaFile.value("color",QColor()).value(); - m_TrackedState = metaFile.value("tracked", false).toBool() ? TrackedState::TRACKED_TRUE : TrackedState::TRACKED_FALSE; + m_LastNexusQuery = QDateTime::fromString( + metaFile.value("lastNexusQuery", "").toString(), Qt::ISODate); + m_LastNexusUpdate = QDateTime::fromString( + metaFile.value("lastNexusUpdate", "").toString(), Qt::ISODate); + m_NexusLastModified = QDateTime::fromString( + metaFile.value("nexusLastModified", QDateTime::currentDateTimeUtc()).toString(), + Qt::ISODate); + m_Color = metaFile.value("color", QColor()).value(); + m_TrackedState = metaFile.value("tracked", false).toBool() + ? TrackedState::TRACKED_TRUE + : TrackedState::TRACKED_FALSE; if (metaFile.contains("endorsed")) { if (metaFile.value("endorsed").canConvert()) { using ut = std::underlying_type_t; switch (metaFile.value("endorsed").toInt()) { - case static_cast(EndorsedState::ENDORSED_FALSE): m_EndorsedState = EndorsedState::ENDORSED_FALSE; break; - case static_cast(EndorsedState::ENDORSED_TRUE): m_EndorsedState = EndorsedState::ENDORSED_TRUE; break; - case static_cast(EndorsedState::ENDORSED_NEVER): m_EndorsedState = EndorsedState::ENDORSED_NEVER; break; - default: m_EndorsedState = EndorsedState::ENDORSED_UNKNOWN; break; + case static_cast(EndorsedState::ENDORSED_FALSE): + m_EndorsedState = EndorsedState::ENDORSED_FALSE; + break; + case static_cast(EndorsedState::ENDORSED_TRUE): + m_EndorsedState = EndorsedState::ENDORSED_TRUE; + break; + case static_cast(EndorsedState::ENDORSED_NEVER): + m_EndorsedState = EndorsedState::ENDORSED_NEVER; + break; + default: + m_EndorsedState = EndorsedState::ENDORSED_UNKNOWN; + break; } } else { - m_EndorsedState = metaFile.value("endorsed", false).toBool() ? EndorsedState::ENDORSED_TRUE : EndorsedState::ENDORSED_FALSE; + m_EndorsedState = metaFile.value("endorsed", false).toBool() + ? EndorsedState::ENDORSED_TRUE + : EndorsedState::ENDORSED_FALSE; } } QString categoriesString = metaFile.value("category", "").toString(); QStringList categories = categoriesString.split(',', Qt::SkipEmptyParts); - for (QStringList::iterator iter = categories.begin(); iter != categories.end(); ++iter) { - bool ok = false; + for (QStringList::iterator iter = categories.begin(); iter != categories.end(); + ++iter) { + bool ok = false; int categoryID = iter->toInt(&ok); if (categoryID < 0) { // ignore invalid id continue; } - if (ok && (categoryID != 0) && (CategoryFactory::instance()->categoryExists(categoryID))) { + if (ok && (categoryID != 0) && + (CategoryFactory::instance()->categoryExists(categoryID))) { m_Categories.insert(categoryID); if (iter == categories.begin()) { m_PrimaryCategory = categoryID; @@ -205,13 +222,14 @@ void ModInfoRegular::readMeta() int numFiles = metaFile.beginReadArray("installedFiles"); for (int i = 0; i < numFiles; ++i) { metaFile.setArrayIndex(i); - m_InstalledFileIDs.insert(std::make_pair(metaFile.value("modid").toInt(), metaFile.value("fileid").toInt())); + m_InstalledFileIDs.insert(std::make_pair(metaFile.value("modid").toInt(), + metaFile.value("fileid").toInt())); } metaFile.endArray(); // Plugin settings: metaFile.beginGroup("Plugins"); - for (auto pluginName: metaFile.childGroups()) { + for (auto pluginName : metaFile.childGroups()) { metaFile.beginGroup(pluginName); for (auto settingKey : metaFile.childKeys()) { m_PluginSettings[pluginName][settingKey] = metaFile.value(settingKey); @@ -231,7 +249,8 @@ void ModInfoRegular::saveMeta() 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("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()); @@ -252,16 +271,20 @@ void ModInfoRegular::saveMeta() metaFile.setValue("validated", m_Validated); metaFile.setValue("color", m_Color); if (m_EndorsedState != EndorsedState::ENDORSED_UNKNOWN) { - metaFile.setValue("endorsed", static_cast>(m_EndorsedState)); + metaFile.setValue( + "endorsed", + static_cast>(m_EndorsedState)); } if (m_TrackedState != TrackedState::TRACKED_UNKNOWN) { - metaFile.setValue("tracked", static_cast>(m_TrackedState)); + metaFile.setValue("tracked", static_cast>( + m_TrackedState)); } metaFile.remove("installedFiles"); metaFile.beginWriteArray("installedFiles"); int idx = 0; - for (auto iter = m_InstalledFileIDs.begin(); iter != m_InstalledFileIDs.end(); ++iter) { + for (auto iter = m_InstalledFileIDs.begin(); iter != m_InstalledFileIDs.end(); + ++iter) { metaFile.setArrayIndex(idx++); metaFile.setValue("modid", iter->first); metaFile.setValue("fileid", iter->second); @@ -271,33 +294,30 @@ void ModInfoRegular::saveMeta() // Plugin settings: metaFile.remove("Plugins"); metaFile.beginGroup("Plugins"); - for (const auto& [pluginName, pluginSettings]: m_PluginSettings) { + for (const auto& [pluginName, pluginSettings] : m_PluginSettings) { metaFile.beginGroup(pluginName); - for (const auto& [settingName, settingValue]: pluginSettings) { + for (const auto& [settingName, settingValue] : pluginSettings) { metaFile.setValue(settingName, settingValue); } metaFile.endGroup(); } metaFile.endGroup(); - metaFile.sync(); // sync needs to be called to ensure the file is created + metaFile.sync(); // sync needs to be called to ensure the file is created if (metaFile.status() == QSettings::NoError) { m_MetaInfoChanged = false; } else { - log::error( - "failed to write {}/meta.ini: error {}", - absolutePath(), metaFile.status()); + log::error("failed to write {}/meta.ini: error {}", absolutePath(), + metaFile.status()); } } else { - log::error( - "failed to write {}/meta.ini: error {}", - absolutePath(), metaFile.status()); + log::error("failed to write {}/meta.ini: error {}", absolutePath(), + metaFile.status()); } } } - bool ModInfoRegular::updateAvailable() const { if (m_IgnoredVersion.isValid() && (m_IgnoredVersion == m_NewestVersion)) { @@ -309,7 +329,6 @@ bool ModInfoRegular::updateAvailable() const return m_NewestVersion.isValid() && (m_Version < m_NewestVersion); } - bool ModInfoRegular::downgradeAvailable() const { if (m_IgnoredVersion.isValid() && (m_IgnoredVersion == m_NewestVersion)) { @@ -318,14 +337,15 @@ bool ModInfoRegular::downgradeAvailable() const return m_NewestVersion.isValid() && (m_NewestVersion < m_Version); } - -void ModInfoRegular::nxmDescriptionAvailable(QString, int, QVariant, QVariant resultData) +void ModInfoRegular::nxmDescriptionAvailable(QString, int, QVariant, + QVariant resultData) { QVariantMap result = resultData.toMap(); setNexusDescription(result["description"].toString()); - if ((m_EndorsedState != EndorsedState::ENDORSED_NEVER) && (result.contains("endorsement"))) { - QVariantMap endorsement = result["endorsement"].toMap(); + if ((m_EndorsedState != EndorsedState::ENDORSED_NEVER) && + (result.contains("endorsement"))) { + QVariantMap endorsement = result["endorsement"].toMap(); QString endorsementStatus = endorsement["endorse_status"].toString(); if (endorsementStatus.compare("Endorsed", Qt::CaseInsensitive) == 00) setEndorsedState(EndorsedState::ENDORSED_TRUE); @@ -335,14 +355,14 @@ void ModInfoRegular::nxmDescriptionAvailable(QString, int, QVariant, QVariant re setEndorsedState(EndorsedState::ENDORSED_FALSE); } m_LastNexusQuery = QDateTime::currentDateTimeUtc(); - m_NexusLastModified = QDateTime::fromSecsSinceEpoch(result["updated_timestamp"].toInt(), Qt::UTC); + m_NexusLastModified = + QDateTime::fromSecsSinceEpoch(result["updated_timestamp"].toInt(), Qt::UTC); m_MetaInfoChanged = true; saveMeta(); disconnect(sender(), SIGNAL(descriptionAvailable(QString, int, QVariant, QVariant))); emit modDetailsUpdated(true); } - void ModInfoRegular::nxmEndorsementToggled(QString, int, QVariant, QVariant resultData) { QMap results = resultData.toMap(); @@ -358,7 +378,6 @@ void ModInfoRegular::nxmEndorsementToggled(QString, int, QVariant, QVariant resu emit modDetailsUpdated(true); } - void ModInfoRegular::nxmTrackingToggled(QString, int, QVariant, bool tracked) { if (tracked) @@ -370,12 +389,13 @@ void ModInfoRegular::nxmTrackingToggled(QString, int, QVariant, bool tracked) emit modDetailsUpdated(true); } - -void ModInfoRegular::nxmRequestFailed(QString, int, int, QVariant userData, int errorCode, const QString &errorMessage) +void ModInfoRegular::nxmRequestFailed(QString, int, int, QVariant userData, + int errorCode, const QString& errorMessage) { QString fullMessage = errorMessage; if (userData.canConvert() && (userData.toInt() == 1)) { - fullMessage += "\nNexus will reject endorsements within 15 Minutes of a failed attempt, the error message may be misleading."; + fullMessage += "\nNexus will reject endorsements within 15 Minutes of a failed " + "attempt, the error message may be misleading."; } if (QApplication::activeWindow() != nullptr) { MessageDialog::showMessage(fullMessage, QApplication::activeWindow()); @@ -383,7 +403,6 @@ void ModInfoRegular::nxmRequestFailed(QString, int, int, QVariant userData, int emit modDetailsUpdated(false); } - bool ModInfoRegular::updateNXMInfo() { if (needsDescriptionUpdate()) { @@ -397,7 +416,7 @@ bool ModInfoRegular::updateNXMInfo() bool ModInfoRegular::needsDescriptionUpdate() const { if (m_NexusID > 0) { - QDateTime time = QDateTime::currentDateTimeUtc(); + QDateTime time = QDateTime::currentDateTimeUtc(); QDateTime target = m_LastNexusQuery.addDays(1); if (time >= target) { @@ -432,14 +451,14 @@ void ModInfoRegular::setCategory(int categoryID, bool active) } } - -bool ModInfoRegular::setName(const QString &name) +bool ModInfoRegular::setName(const QString& name) { if (name.contains('/') || name.contains('\\')) { return false; } - QString newPath = m_Path.mid(0).replace(m_Path.length() - m_Name.length(), m_Name.length(), name); + QString newPath = + m_Path.mid(0).replace(m_Path.length() - m_Name.length(), m_Name.length(), name); QDir modDir(m_Path.mid(0, m_Path.length() - m_Name.length())); if (m_Name.compare(name, Qt::CaseInsensitive) == 0) { @@ -452,7 +471,8 @@ bool ModInfoRegular::setName(const QString &name) return false; } if (!modDir.rename(tempName, name)) { - log::error("rename to final name failed after successful rename to intermediate name"); + log::error( + "rename to final name failed after successful rename to intermediate name"); modDir.rename(tempName, m_Name); return false; } @@ -477,7 +497,7 @@ bool ModInfoRegular::setName(const QString &name) std::sort(s_Collection.begin(), s_Collection.end(), ModInfo::ByName); updateIndices(); - } else { // otherwise mod isn't registered yet? + } else { // otherwise mod isn't registered yet? m_Name = name; m_Path = newPath; } @@ -485,56 +505,56 @@ bool ModInfoRegular::setName(const QString &name) return true; } -void ModInfoRegular::setComments(const QString &comments) +void ModInfoRegular::setComments(const QString& comments) { - m_Comments = comments; + m_Comments = comments; m_MetaInfoChanged = true; } -void ModInfoRegular::setNotes(const QString ¬es) +void ModInfoRegular::setNotes(const QString& notes) { - m_Notes = notes; + m_Notes = notes; m_MetaInfoChanged = true; } void ModInfoRegular::setGameName(const QString& gameName) { - m_GameName = gameName; + m_GameName = gameName; m_MetaInfoChanged = true; } void ModInfoRegular::setNexusID(int modID) { - m_NexusID = modID; + m_NexusID = modID; m_MetaInfoChanged = true; } -void ModInfoRegular::setVersion(const VersionInfo &version) +void ModInfoRegular::setVersion(const VersionInfo& version) { - m_Version = version; + m_Version = version; m_MetaInfoChanged = true; } -void ModInfoRegular::setNewestVersion(const VersionInfo &version) +void ModInfoRegular::setNewestVersion(const VersionInfo& version) { if (version != m_NewestVersion) { - m_NewestVersion = version; + m_NewestVersion = version; m_MetaInfoChanged = true; } } -void ModInfoRegular::setNexusDescription(const QString &description) +void ModInfoRegular::setNexusDescription(const QString& description) { if (qHash(description) != qHash(m_NexusDescription)) { m_NexusDescription = description; - m_MetaInfoChanged = true; + m_MetaInfoChanged = true; } } void ModInfoRegular::setEndorsedState(EndorsedState endorsedState) { if (endorsedState != m_EndorsedState) { - m_EndorsedState = endorsedState; + m_EndorsedState = endorsedState; m_MetaInfoChanged = true; } } @@ -542,15 +562,15 @@ void ModInfoRegular::setEndorsedState(EndorsedState endorsedState) void ModInfoRegular::setTrackedState(TrackedState trackedState) { if (trackedState != m_TrackedState) { - m_TrackedState = trackedState; + m_TrackedState = trackedState; m_MetaInfoChanged = true; } } -void ModInfoRegular::setInstallationFile(const QString &fileName) +void ModInfoRegular::setInstallationFile(const QString& fileName) { m_InstallationFile = fileName; - m_MetaInfoChanged = true; + m_MetaInfoChanged = true; } void ModInfoRegular::addNexusCategory(int categoryID) @@ -561,14 +581,15 @@ void ModInfoRegular::addNexusCategory(int categoryID) void ModInfoRegular::setIsEndorsed(bool endorsed) { if (m_EndorsedState != EndorsedState::ENDORSED_NEVER) { - m_EndorsedState = endorsed ? EndorsedState::ENDORSED_TRUE : EndorsedState::ENDORSED_FALSE; + m_EndorsedState = + endorsed ? EndorsedState::ENDORSED_TRUE : EndorsedState::ENDORSED_FALSE; m_MetaInfoChanged = true; } } void ModInfoRegular::setNeverEndorse() { - m_EndorsedState = EndorsedState::ENDORSED_NEVER; + m_EndorsedState = EndorsedState::ENDORSED_NEVER; m_MetaInfoChanged = true; } @@ -582,7 +603,7 @@ void ModInfoRegular::setIsTracked(bool tracked) void ModInfoRegular::setColor(QColor color) { - m_Color = color; + m_Color = color; m_MetaInfoChanged = true; } @@ -594,7 +615,8 @@ QColor ModInfoRegular::color() const void ModInfoRegular::endorse(bool doEndorse) { if (doEndorse != (m_EndorsedState == EndorsedState::ENDORSED_TRUE)) { - m_NexusBridge.requestToggleEndorsement(m_GameName, nexusId(), m_Version.canonicalString(), doEndorse, QVariant(1)); + m_NexusBridge.requestToggleEndorsement( + m_GameName, nexusId(), m_Version.canonicalString(), doEndorse, QVariant(1)); } } @@ -607,7 +629,7 @@ void ModInfoRegular::track(bool doTrack) void ModInfoRegular::markConverted(bool converted) { - m_Converted = converted; + m_Converted = converted; m_MetaInfoChanged = true; saveMeta(); emit modDetailsUpdated(true); @@ -615,7 +637,7 @@ void ModInfoRegular::markConverted(bool converted) void ModInfoRegular::markValidated(bool validated) { - m_Validated = validated; + m_Validated = validated; m_MetaInfoChanged = true; saveMeta(); emit modDetailsUpdated(true); @@ -638,7 +660,7 @@ void ModInfoRegular::ignoreUpdate(bool ignore) bool ModInfoRegular::canBeUpdated() const { - QDateTime now = QDateTime::currentDateTimeUtc(); + QDateTime now = QDateTime::currentDateTimeUtc(); QDateTime target = getExpires(); if (now >= target) return m_NexusID > 0; @@ -653,13 +675,11 @@ QDateTime ModInfoRegular::getExpires() const std::vector ModInfoRegular::getFlags() const { std::vector result = ModInfoWithConflictInfo::getFlags(); - if ((m_NexusID > 0) && - (endorsedState() == EndorsedState::ENDORSED_FALSE) && + if ((m_NexusID > 0) && (endorsedState() == EndorsedState::ENDORSED_FALSE) && Settings::instance().nexus().endorsementIntegration()) { result.push_back(ModInfo::FLAG_NOTENDORSED); } - if ((m_NexusID > 0) && - (trackedState() == TrackedState::TRACKED_TRUE) && + if ((m_NexusID > 0) && (trackedState() == TrackedState::TRACKED_TRUE) && Settings::instance().nexus().trackedIntegration()) { result.push_back(ModInfo::FLAG_TRACKED); } @@ -678,7 +698,6 @@ std::vector ModInfoRegular::getFlags() const return result; } - std::set ModInfoRegular::doGetContents() const { ModDataContent* contentFeature = m_Core.managedGame()->feature(); @@ -691,33 +710,36 @@ std::set ModInfoRegular::doGetContents() const return {}; } - int ModInfoRegular::getHighlight() const { if (!isValid() && !m_Validated) return HIGHLIGHT_INVALID; auto flags = getFlags(); - if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_PLUGIN_SELECTED) != flags.end()) + if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_PLUGIN_SELECTED) != + flags.end()) return HIGHLIGHT_PLUGIN; return HIGHLIGHT_NONE; } - QString ModInfoRegular::getDescription() const { - if (!isValid() && !m_Validated) { - return tr("%1 contains no esp/esm/esl and no asset (textures, meshes, interface, ...) directory").arg(name()); + if (!isValid() && !m_Validated) { + return tr("%1 contains no esp/esm/esl and no asset (textures, meshes, interface, " + "...) directory") + .arg(name()); } else { - const std::set &categories = getCategories(); + const std::set& categories = getCategories(); std::wostringstream categoryString; categoryString << ToWString(tr("Categories:
      ")); - CategoryFactory *categoryFactory = CategoryFactory::instance(); + CategoryFactory* categoryFactory = CategoryFactory::instance(); for (std::set::const_iterator catIter = categories.begin(); catIter != categories.end(); ++catIter) { if (catIter != categories.begin()) { categoryString << " , "; } - categoryString << "" << ToWString(categoryFactory->getCategoryName(categoryFactory->getCategoryIndex(*catIter))) << ""; + categoryString << "" + << ToWString(categoryFactory->getCategoryName(categoryFactory->getCategoryIndex(*catIter))) + << ""; } return ToQString(categoryString.str()); @@ -792,7 +814,7 @@ QDateTime ModInfoRegular::getLastNexusQuery() const void ModInfoRegular::setLastNexusQuery(QDateTime time) { - m_LastNexusQuery = time; + m_LastNexusQuery = time; m_MetaInfoChanged = true; saveMeta(); emit modDetailsUpdated(true); @@ -806,14 +828,14 @@ QDateTime ModInfoRegular::getNexusLastModified() const void ModInfoRegular::setNexusLastModified(QDateTime time) { m_NexusLastModified = time; - m_MetaInfoChanged = true; + m_MetaInfoChanged = true; saveMeta(); emit modDetailsUpdated(true); } -void ModInfoRegular::setCustomURL(QString const &url) +void ModInfoRegular::setCustomURL(QString const& url) { - m_CustomURL = url; + m_CustomURL = url; m_MetaInfoChanged = true; } @@ -824,7 +846,7 @@ QString ModInfoRegular::url() const void ModInfoRegular::setHasCustomURL(bool b) { - m_HasCustomURL = b; + m_HasCustomURL = b; m_MetaInfoChanged = true; } @@ -838,8 +860,8 @@ QStringList ModInfoRegular::archives(bool checkOnDisk) if (checkOnDisk) { QStringList result; QDir dir(this->absolutePath()); - QStringList bsaList = dir.entryList(QStringList({ "*.bsa", "*.ba2" })); - for (const QString &archive : bsaList) { + QStringList bsaList = dir.entryList(QStringList({"*.bsa", "*.ba2"})); + for (const QString& archive : bsaList) { result.append(this->absolutePath() + "/" + archive); } m_Archives = result; @@ -863,22 +885,22 @@ std::vector ModInfoRegular::getIniTweaks() const int numTweaks = metaFile.beginReadArray("INI Tweaks"); if (numTweaks != 0) { - log::debug( - "{} active ini tweaks in {}", - numTweaks, QDir::toNativeSeparators(metaFileName)); + log::debug("{} active ini tweaks in {}", numTweaks, + QDir::toNativeSeparators(metaFileName)); } for (int i = 0; i < numTweaks; ++i) { metaFile.setArrayIndex(i); - QString filename = absolutePath().append("/INI Tweaks/").append(metaFile.value("name").toString()); + QString filename = + absolutePath().append("/INI Tweaks/").append(metaFile.value("name").toString()); result.push_back(filename); } metaFile.endArray(); return result; } - -std::map ModInfoRegular::pluginSettings(const QString& pluginName) const +std::map +ModInfoRegular::pluginSettings(const QString& pluginName) const { auto itp = m_PluginSettings.find(pluginName); if (itp == std::end(m_PluginSettings)) { @@ -887,7 +909,8 @@ std::map ModInfoRegular::pluginSettings(const QString& plugin return itp->second; } -QVariant ModInfoRegular::pluginSetting(const QString& pluginName, const QString& key, const QVariant& defaultValue) const +QVariant ModInfoRegular::pluginSetting(const QString& pluginName, const QString& key, + const QVariant& defaultValue) const { auto itp = m_PluginSettings.find(pluginName); if (itp == std::end(m_PluginSettings)) { @@ -902,15 +925,17 @@ QVariant ModInfoRegular::pluginSetting(const QString& pluginName, const QString& return its->second; } -bool ModInfoRegular::setPluginSetting(const QString& pluginName, const QString& key, const QVariant& value) +bool ModInfoRegular::setPluginSetting(const QString& pluginName, const QString& key, + const QVariant& value) { m_PluginSettings[pluginName][key] = value; - m_MetaInfoChanged = true; + m_MetaInfoChanged = true; saveMeta(); return true; } -std::map ModInfoRegular::clearPluginSettings(const QString& pluginName) +std::map +ModInfoRegular::clearPluginSettings(const QString& pluginName) { auto itp = m_PluginSettings.find(pluginName); if (itp == std::end(m_PluginSettings)) { diff --git a/src/modinforegular.h b/src/modinforegular.h index ef91ba691..6c85d66c3 100644 --- a/src/modinforegular.h +++ b/src/modinforegular.h @@ -21,7 +21,6 @@ class ModInfoRegular : public ModInfoWithConflictInfo friend class ModInfo; public: - ~ModInfoRegular(); virtual bool isRegular() const override { return true; } @@ -36,8 +35,9 @@ class ModInfoRegular : public ModInfoWithConflictInfo * @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 + * 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 **/ @@ -46,14 +46,18 @@ class ModInfoRegular : public ModInfoWithConflictInfo /** * @return true if the current update is being ignored */ - virtual bool updateIgnored() const override { return m_IgnoredVersion.isValid() && m_IgnoredVersion == m_NewestVersion; } + virtual bool updateIgnored() const override + { + return m_IgnoredVersion.isValid() && 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 + * 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 **/ @@ -65,7 +69,8 @@ class ModInfoRegular : public ModInfoWithConflictInfo * This requests mod information from the nexus. This is an asynchronous request, * so there is no immediate effect of this call. * - * @return returns true if information for this mod will be updated, false if there is no nexus mod id to use + * @return returns true if information for this mod will be updated, false if there is + *no nexus mod id to use **/ bool updateNXMInfo() override; @@ -76,7 +81,8 @@ class ModInfoRegular : public ModInfoWithConflictInfo * * @param categoryID id of the category to set * @param active determines wheter the category is assigned or unassigned - * @note this function does not test whether categoryID actually identifies a valid category + * @note this function does not test whether categoryID actually identifies a valid + *category **/ void setCategory(int categoryID, bool active) override; @@ -90,19 +96,20 @@ class ModInfoRegular : public ModInfoWithConflictInfo * @return true on success, false if the new name can't be used (i.e. because the new * directory name wouldn't be valid) **/ - bool setName(const QString &name) override; + bool setName(const QString& name) override; /** - * @brief changes the comments (manually set information displayed in the mod list) for this mod - * @param comments new comments - */ - void setComments(const QString &comments) override; + * @brief changes the comments (manually set information displayed in the mod list) + * for this mod + * @param comments new comments + */ + void setComments(const QString& comments) override; /** * @brief change the notes (manually set information) for this mod * @param notes new notes */ - void setNotes(const QString ¬es) override; + void setNotes(const QString& notes) override; /** * @brief set/change the source game of this mod @@ -126,7 +133,7 @@ class ModInfoRegular : public ModInfoWithConflictInfo * * @param version the new version to use **/ - void setVersion(const MOBase::VersionInfo &version) override; + void setVersion(const MOBase::VersionInfo& version) override; /** * @brief set the newest version of this mod on the nexus @@ -135,21 +142,22 @@ class ModInfoRegular : public ModInfoWithConflictInfo * updating the mod * * @param version the new version to use - * @todo this function should be made obsolete. All queries for mod information should go through - * this class so no public function for this change is required + * @todo this function should be made obsolete. All queries for mod information should + *go through this class so no public function for this change is required **/ - void setNewestVersion(const MOBase::VersionInfo &version) override; + void setNewestVersion(const MOBase::VersionInfo& version) override; /** * @brief changes/updates the nexus description text * @param description the current description text */ - virtual void setNexusDescription(const QString &description) override; + virtual void setNexusDescription(const QString& description) override; - virtual void setInstallationFile(const QString &fileName) override; + virtual void setInstallationFile(const QString& fileName) override; /** - * @brief sets the category id from a nexus category id. Conversion to MO id happens internally + * @brief sets the category id from a nexus category id. Conversion to MO id happens + * internally * @param categoryID the nexus category id * @note if a mapping is not possible, the category is set to the default value */ @@ -159,13 +167,20 @@ class ModInfoRegular : public ModInfoWithConflictInfo * @brief sets the new primary category of the mod * @param categoryID the category to set */ - virtual void setPrimaryCategory(int categoryID) override { m_PrimaryCategory = categoryID; m_MetaInfoChanged = true; } + virtual void setPrimaryCategory(int categoryID) override + { + m_PrimaryCategory = categoryID; + m_MetaInfoChanged = true; + } /** * @brief sets the download repository * @param repository */ - virtual void setRepository(const QString &repository) override { m_Repository = repository; } + virtual void setRepository(const QString& repository) override + { + m_Repository = repository; + } /** * update the endorsement state for the mod. This only changes the @@ -175,7 +190,8 @@ class ModInfoRegular : public ModInfoWithConflictInfo virtual void setIsEndorsed(bool endorsed) override; /** - * set the mod to "i don't intend to endorse". The mod will not show as unendorsed but can still be endorsed + * set the mod to "i don't intend to endorse". The mod will not show as unendorsed but + * can still be endorsed */ virtual void setNeverEndorse() override; @@ -194,20 +210,22 @@ class ModInfoRegular : public ModInfoWithConflictInfo virtual void endorse(bool doEndorse) override; /** - * @brief track or untrack the mod. This will sync with nexus! - * @param doTrack if true, the mod is tracked, if false, it's untracked. - * @note if doTrack doesn't differ from the current value, nothing happens. - */ + * @brief track or untrack the mod. This will sync with nexus! + * @param doTrack if true, the mod is tracked, if false, it's untracked. + * @note if doTrack doesn't differ from the current value, nothing happens. + */ virtual void track(bool doTrack) override; /** - * @brief updates the mod to flag it as converted in order to ignore the alternate game warning - */ + * @brief updates the mod to flag it as converted in order to ignore the alternate + * game warning + */ virtual void markConverted(bool converted) override; /** - * @brief updates the mod to flag it as valid in order to ignore the invalid game data flag - */ + * @brief updates the mod to flag it as valid in order to ignore the invalid game data + * flag + */ virtual void markValidated(bool validated) override; /** @@ -285,13 +303,11 @@ class ModInfoRegular : public ModInfoWithConflictInfo */ virtual QString getDescription() const override; - /** * @return the nexus file status (aka category ID) */ virtual int getNexusFileStatus() const override; - /** * @brief sets the file status (category ID) from Nexus * @param status the status id of the installed file @@ -299,8 +315,8 @@ class ModInfoRegular : public ModInfoWithConflictInfo virtual void setNexusFileStatus(int status) override; /** - * @return comments for this mod - */ + * @return comments for this mod + */ virtual QString comments() const override; /** @@ -380,42 +396,47 @@ class ModInfoRegular : public ModInfoWithConflictInfo virtual void setHasCustomURL(bool b) override; virtual bool hasCustomURL() const override; - virtual void setCustomURL(QString const &) override; + virtual void setCustomURL(QString const&) override; virtual QString url() const override; virtual QString gameName() const override { return m_GameName; } virtual QString installationFile() const override { return m_InstallationFile; } virtual bool converted() const override { return m_Converted; } virtual bool validated() const override { return m_Validated; } - virtual std::set> installedFiles() const override { return m_InstalledFileIDs; } - -public: // Plugin operations: - - virtual QVariant pluginSetting(const QString& pluginName, const QString& key, const QVariant& defaultValue) const override; - virtual std::map pluginSettings(const QString& pluginName) const override; - virtual bool setPluginSetting(const QString& pluginName, const QString& key, const QVariant& value) override; - virtual std::map clearPluginSettings(const QString& pluginName) override; + virtual std::set> installedFiles() const override + { + return m_InstalledFileIDs; + } + +public: // Plugin operations: + virtual QVariant pluginSetting(const QString& pluginName, const QString& key, + const QVariant& defaultValue) const override; + virtual std::map + pluginSettings(const QString& pluginName) const override; + virtual bool setPluginSetting(const QString& pluginName, const QString& key, + const QVariant& value) override; + virtual std::map + clearPluginSettings(const QString& pluginName) override; private: - void setEndorsedState(MOBase::EndorsedState endorsedState); void setTrackedState(MOBase::TrackedState trackedState); private slots: - void nxmDescriptionAvailable(QString, int modID, QVariant userData, QVariant resultData); + void nxmDescriptionAvailable(QString, int modID, QVariant userData, + QVariant resultData); void nxmEndorsementToggled(QString, int, QVariant userData, QVariant resultData); void nxmTrackingToggled(QString, int, QVariant userData, bool tracked); - void nxmRequestFailed(QString, int modID, int fileID, QVariant userData, int errorCode, const QString &errorMessage); + void nxmRequestFailed(QString, int modID, int fileID, QVariant userData, + int errorCode, const QString& errorMessage); protected: - virtual std::set doGetContents() const override; ModInfoRegular(const QDir& path, OrganizerCore& core); private: - QString m_Name; QString m_Path; QString m_InstallationFile; @@ -432,7 +453,6 @@ private slots: mutable QStringList m_Archives; - QDateTime m_CreationTime; QDateTime m_LastNexusQuery; QDateTime m_LastNexusUpdate; @@ -462,5 +482,4 @@ private slots: bool needsDescriptionUpdate() const; }; - -#endif // MODINFOREGULAR_H +#endif // MODINFOREGULAR_H diff --git a/src/modinfoseparator.cpp b/src/modinfoseparator.cpp index 02a67ecd8..a647c6351 100644 --- a/src/modinfoseparator.cpp +++ b/src/modinfoseparator.cpp @@ -29,8 +29,6 @@ QString ModInfoSeparator::name() const return ModInfoRegular::name(); } - ModInfoSeparator::ModInfoSeparator(const QDir& path, OrganizerCore& core) - : ModInfoRegular(path, core) -{ -} + : ModInfoRegular(path, core) +{} diff --git a/src/modinfoseparator.h b/src/modinfoseparator.h index eaff7c426..c34dd92bc 100644 --- a/src/modinfoseparator.h +++ b/src/modinfoseparator.h @@ -3,21 +3,19 @@ #include "modinforegular.h" -class ModInfoSeparator: - public ModInfoRegular +class ModInfoSeparator : public ModInfoRegular { Q_OBJECT; friend class ModInfo; public: - virtual bool updateAvailable() const override { return false; } virtual bool updateIgnored() const override { return false; } virtual bool downgradeAvailable() const override { return false; } virtual bool updateNXMInfo() override { return false; } virtual bool isValid() const override { return true; } - //TODO: Fix renaming method to avoid priority reset + // TODO: Fix renaming method to avoid priority reset virtual bool setName(const QString& name); virtual int nexusId() const override { return -1; } @@ -28,7 +26,10 @@ class ModInfoSeparator: virtual bool canBeUpdated() const override { return false; } virtual QDateTime getExpires() const override { return QDateTime(); } virtual bool canBeEnabled() const override { return false; } - virtual std::vector getIniTweaks() const override { return std::vector(); } + virtual std::vector getIniTweaks() const override + { + return std::vector(); + } virtual std::vector getFlags() const override; virtual int getHighlight() const override; virtual QString getDescription() const override; @@ -46,14 +47,13 @@ class ModInfoSeparator: virtual void setNexusLastModified(QDateTime) override {} virtual QDateTime creationTime() const override { return QDateTime(); } virtual QString getNexusDescription() const override { return QString(); } - virtual void addInstalledFile(int /*modId*/, int /*fileId*/) override { } + virtual void addInstalledFile(int /*modId*/, int /*fileId*/) override {} virtual bool isSeparator() const override { return true; } protected: virtual bool doIsValid() const override { return true; } private: - ModInfoSeparator(const QDir& path, OrganizerCore& core); }; diff --git a/src/modinfowithconflictinfo.cpp b/src/modinfowithconflictinfo.cpp index 7cdee6e68..8d4963917 100644 --- a/src/modinfowithconflictinfo.cpp +++ b/src/modinfowithconflictinfo.cpp @@ -1,25 +1,33 @@ #include "modinfowithconflictinfo.h" -#include "utility.h" #include "shared/directoryentry.h" -#include "shared/filesorigin.h" #include "shared/fileentry.h" +#include "shared/filesorigin.h" +#include "utility.h" #include -#include "organizercore.h" #include "iplugingame.h" #include "moddatachecker.h" +#include "organizercore.h" #include "qdirfiletree.h" using namespace MOBase; using namespace MOShared; namespace fs = std::filesystem; -ModInfoWithConflictInfo::ModInfoWithConflictInfo(OrganizerCore& core) : - ModInfo(core), - m_FileTree([this]() { return QDirFileTree::makeTree(absolutePath()); }), - m_Valid([this]() { return doIsValid(); }), - m_Contents([this]() { return doGetContents(); }), - m_Conflicts([this]() { return doConflictCheck(); }) { } +ModInfoWithConflictInfo::ModInfoWithConflictInfo(OrganizerCore& core) + : ModInfo(core), m_FileTree([this]() { + return QDirFileTree::makeTree(absolutePath()); + }), + m_Valid([this]() { + return doIsValid(); + }), + m_Contents([this]() { + return doGetContents(); + }), + m_Conflicts([this]() { + return doConflictCheck(); + }) +{} void ModInfoWithConflictInfo::clearCaches() { @@ -39,66 +47,68 @@ std::vector ModInfoWithConflictInfo::getConflictFlags() { std::vector result; switch (isConflicted()) { - case CONFLICT_MIXED: { - result.push_back(ModInfo::FLAG_CONFLICT_MIXED); - } break; - case CONFLICT_OVERWRITE: { - result.push_back(ModInfo::FLAG_CONFLICT_OVERWRITE); - } break; - case CONFLICT_OVERWRITTEN: { - result.push_back(ModInfo::FLAG_CONFLICT_OVERWRITTEN); - } break; - case CONFLICT_REDUNDANT: { - result.push_back(ModInfo::FLAG_CONFLICT_REDUNDANT); - } break; - default: { /* NOP */ } + case CONFLICT_MIXED: { + result.push_back(ModInfo::FLAG_CONFLICT_MIXED); + } break; + case CONFLICT_OVERWRITE: { + result.push_back(ModInfo::FLAG_CONFLICT_OVERWRITE); + } break; + case CONFLICT_OVERWRITTEN: { + result.push_back(ModInfo::FLAG_CONFLICT_OVERWRITTEN); + } break; + case CONFLICT_REDUNDANT: { + result.push_back(ModInfo::FLAG_CONFLICT_REDUNDANT); + } break; + default: { /* NOP */ + } } switch (isLooseArchiveConflicted()) { - case CONFLICT_MIXED: { - result.push_back(ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITE); - result.push_back(ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITTEN); - } break; - case CONFLICT_OVERWRITE: { - result.push_back(ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITE); - } break; - case CONFLICT_OVERWRITTEN: { - result.push_back(ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITTEN); - } break; - default: { /* NOP */ } + case CONFLICT_MIXED: { + result.push_back(ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITE); + result.push_back(ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITTEN); + } break; + case CONFLICT_OVERWRITE: { + result.push_back(ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITE); + } break; + case CONFLICT_OVERWRITTEN: { + result.push_back(ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITTEN); + } break; + default: { /* NOP */ + } } switch (isArchiveConflicted()) { - case CONFLICT_MIXED: { - result.push_back(ModInfo::FLAG_ARCHIVE_CONFLICT_MIXED); - } break; - case CONFLICT_OVERWRITE: { - result.push_back(ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITE); - } break; - case CONFLICT_OVERWRITTEN: { - result.push_back(ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITTEN); - } break; - default: { /* NOP */ } + case CONFLICT_MIXED: { + result.push_back(ModInfo::FLAG_ARCHIVE_CONFLICT_MIXED); + } break; + case CONFLICT_OVERWRITE: { + result.push_back(ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITE); + } break; + case CONFLICT_OVERWRITTEN: { + result.push_back(ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITTEN); + } break; + default: { /* NOP */ + } } return result; } - ModInfoWithConflictInfo::Conflicts ModInfoWithConflictInfo::doConflictCheck() const { Conflicts conflicts; bool providesAnything = false; - bool hasHiddenFiles = false; + bool hasHiddenFiles = false; int dataID = 0; if (m_Core.directoryStructure()->originExists(L"data")) { dataID = m_Core.directoryStructure()->getOriginByName(L"data").getID(); } - std::wstring name = ToWString(this->name()); + std::wstring name = ToWString(this->name()); const std::wstring hideExt = ToWString(ModInfo::s_HiddenExt); if (m_Core.directoryStructure()->originExists(name)) { - FilesOrigin &origin = m_Core.directoryStructure()->getOriginByName(name); + FilesOrigin& origin = m_Core.directoryStructure()->getOriginByName(name); std::vector files = origin.getFiles(); std::set checkedDirs; @@ -111,8 +121,7 @@ ModInfoWithConflictInfo::Conflicts ModInfoWithConflictInfo::doConflictCheck() co if (nameAsPath.extension().wstring().compare(hideExt) == 0) { hasHiddenFiles = true; - } - else { + } else { const DirectoryEntry* parent = file->getParent(); // iterate on all parent direEntries to check for .mohiddden @@ -120,10 +129,10 @@ ModInfoWithConflictInfo::Conflicts ModInfoWithConflictInfo::doConflictCheck() co auto insertResult = checkedDirs.insert(parent); if (insertResult.second == false) { - // if already present break as we can assume to have checked the parents as well + // if already present break as we can assume to have checked the parents + // as well break; - } - else { + } else { const fs::path dirPath(parent->getName()); if (dirPath.extension().wstring().compare(hideExt) == 0) { hasHiddenFiles = true; @@ -155,15 +164,15 @@ ModInfoWithConflictInfo::Conflicts ModInfoWithConflictInfo::doConflictCheck() co // If this is not the origin then determine the correct overwrite if (file->getOrigin() != origin.getID()) { - FilesOrigin &altOrigin = m_Core.directoryStructure()->getOriginByID(file->getOrigin()); + FilesOrigin& altOrigin = + m_Core.directoryStructure()->getOriginByID(file->getOrigin()); unsigned int altIndex = ModInfo::getIndex(ToQString(altOrigin.getName())); if (!file->isFromArchive()) { if (!archiveData.isValid()) conflicts.m_OverwrittenList.insert(altIndex); else conflicts.m_ArchiveLooseOverwrittenList.insert(altIndex); - } - else { + } else { conflicts.m_ArchiveOverwrittenList.insert(altIndex); } } else { @@ -172,8 +181,10 @@ ModInfoWithConflictInfo::Conflicts ModInfoWithConflictInfo::doConflictCheck() co // Sort out the alternatives for (const auto& altInfo : alternatives) { - if ((altInfo.originID() != dataID) && (altInfo.originID() != origin.getID())) { - FilesOrigin &altOrigin = m_Core.directoryStructure()->getOriginByID(altInfo.originID()); + if ((altInfo.originID() != dataID) && + (altInfo.originID() != origin.getID())) { + FilesOrigin& altOrigin = + m_Core.directoryStructure()->getOriginByID(altInfo.originID()); QString altOriginName = ToQString(altOrigin.getName()); unsigned int altIndex = ModInfo::getIndex(altOriginName); if (!altInfo.isFromArchive()) { @@ -205,21 +216,24 @@ ModInfoWithConflictInfo::Conflicts ModInfoWithConflictInfo::doConflictCheck() co if (files.size() != 0) { if (!providesAnything) conflicts.m_CurrentConflictState = CONFLICT_REDUNDANT; - else if (!conflicts.m_OverwriteList.empty() && !conflicts.m_OverwrittenList.empty()) + else if (!conflicts.m_OverwriteList.empty() && + !conflicts.m_OverwrittenList.empty()) conflicts.m_CurrentConflictState = CONFLICT_MIXED; else if (!conflicts.m_OverwriteList.empty()) conflicts.m_CurrentConflictState = CONFLICT_OVERWRITE; else if (!conflicts.m_OverwrittenList.empty()) conflicts.m_CurrentConflictState = CONFLICT_OVERWRITTEN; - if (!conflicts.m_ArchiveOverwriteList.empty() && !conflicts.m_ArchiveOverwrittenList.empty()) + if (!conflicts.m_ArchiveOverwriteList.empty() && + !conflicts.m_ArchiveOverwrittenList.empty()) conflicts.m_ArchiveConflictState = CONFLICT_MIXED; else if (!conflicts.m_ArchiveOverwriteList.empty()) conflicts.m_ArchiveConflictState = CONFLICT_OVERWRITE; else if (!conflicts.m_ArchiveOverwrittenList.empty()) conflicts.m_ArchiveConflictState = CONFLICT_OVERWRITTEN; - if (!conflicts.m_ArchiveLooseOverwrittenList.empty() && !conflicts.m_ArchiveLooseOverwriteList.empty()) + if (!conflicts.m_ArchiveLooseOverwrittenList.empty() && + !conflicts.m_ArchiveLooseOverwriteList.empty()) conflicts.m_ArchiveConflictLooseState = CONFLICT_MIXED; else if (!conflicts.m_ArchiveLooseOverwrittenList.empty()) conflicts.m_ArchiveConflictLooseState = CONFLICT_OVERWRITTEN; @@ -238,24 +252,25 @@ ModInfoWithConflictInfo::EConflictType ModInfoWithConflictInfo::isConflicted() c return m_Conflicts.value().m_CurrentConflictState; } -ModInfoWithConflictInfo::EConflictType ModInfoWithConflictInfo::isArchiveConflicted() const +ModInfoWithConflictInfo::EConflictType +ModInfoWithConflictInfo::isArchiveConflicted() const { return m_Conflicts.value().m_ArchiveConflictState; } -ModInfoWithConflictInfo::EConflictType ModInfoWithConflictInfo::isLooseArchiveConflicted() const +ModInfoWithConflictInfo::EConflictType +ModInfoWithConflictInfo::isLooseArchiveConflicted() const { return m_Conflicts.value().m_ArchiveConflictLooseState; } - bool ModInfoWithConflictInfo::isRedundant() const { std::wstring name = ToWString(this->name()); if (m_Core.directoryStructure()->originExists(name)) { - FilesOrigin &origin = m_Core.directoryStructure()->getOriginByName(name); + FilesOrigin& origin = m_Core.directoryStructure()->getOriginByName(name); std::vector files = origin.getFiles(); - bool ignore = false; + bool ignore = false; for (auto iter = files.begin(); iter != files.end(); ++iter) { if ((*iter)->getOrigin(ignore) == origin.getID()) { return false; @@ -267,25 +282,27 @@ bool ModInfoWithConflictInfo::isRedundant() const } } - bool ModInfoWithConflictInfo::hasHiddenFiles() const { return m_Conflicts.value().m_HasHiddenFiles; } -void ModInfoWithConflictInfo::diskContentModified() { +void ModInfoWithConflictInfo::diskContentModified() +{ m_FileTree.invalidate(); m_Valid.invalidate(); m_Contents.invalidate(); } -void ModInfoWithConflictInfo::prefetch() { +void ModInfoWithConflictInfo::prefetch() +{ // Populating the tree to 1-depth (IFileTree is lazy, so size() forces the // tree to populate the first level): fileTree()->size(); } -bool ModInfoWithConflictInfo::doIsValid() const { +bool ModInfoWithConflictInfo::doIsValid() const +{ auto mdc = m_Core.managedGame()->feature(); if (mdc) { @@ -296,19 +313,24 @@ bool ModInfoWithConflictInfo::doIsValid() const { return true; } -std::shared_ptr ModInfoWithConflictInfo::fileTree() const { +std::shared_ptr ModInfoWithConflictInfo::fileTree() const +{ return m_FileTree.value(); } -bool ModInfoWithConflictInfo::isValid() const { +bool ModInfoWithConflictInfo::isValid() const +{ return m_Valid.value(); } -const std::set& ModInfoWithConflictInfo::getContents() const { +const std::set& ModInfoWithConflictInfo::getContents() const +{ return m_Contents.value(); } -bool ModInfoWithConflictInfo::hasContent(int content) const { +bool ModInfoWithConflictInfo::hasContent(int content) const +{ auto& contents = m_Contents.value(); - return std::find(std::begin(contents), std::end(contents), content) != std::end(contents); + return std::find(std::begin(contents), std::end(contents), content) != + std::end(contents); } diff --git a/src/modinfowithconflictinfo.h b/src/modinfowithconflictinfo.h index 2136e093b..c1d7a9451 100644 --- a/src/modinfowithconflictinfo.h +++ b/src/modinfowithconflictinfo.h @@ -6,19 +6,19 @@ #include "memoizedlock.h" #include "modinfo.h" -#include #include +#include class ModInfoWithConflictInfo : public ModInfo { public: - std::vector getConflictFlags() const override; virtual std::vector getFlags() const override; /** - * @return true if this mod is considered "valid", that is: it contains data used by the game + * @return true if this mod is considered "valid", that is: it contains data used by + *the game **/ virtual bool isValid() const override; @@ -48,18 +48,35 @@ class ModInfoWithConflictInfo : public ModInfo std::shared_ptr fileTree() const override; public: - /** * @brief clear all caches held for this mod */ void clearCaches() override; - const std::set& getModOverwrite() const override { return m_Conflicts.value().m_OverwriteList; } - const std::set& getModOverwritten() const override { return m_Conflicts.value().m_OverwrittenList; } - const std::set& getModArchiveOverwrite() const override { return m_Conflicts.value().m_ArchiveOverwriteList; } - const std::set& getModArchiveOverwritten() const override { return m_Conflicts.value().m_ArchiveOverwrittenList; } - const std::set& getModArchiveLooseOverwrite() const override { return m_Conflicts.value().m_ArchiveLooseOverwriteList; } - const std::set& getModArchiveLooseOverwritten() const override { return m_Conflicts.value().m_ArchiveLooseOverwrittenList; } + const std::set& getModOverwrite() const override + { + return m_Conflicts.value().m_OverwriteList; + } + const std::set& getModOverwritten() const override + { + return m_Conflicts.value().m_OverwrittenList; + } + const std::set& getModArchiveOverwrite() const override + { + return m_Conflicts.value().m_ArchiveOverwriteList; + } + const std::set& getModArchiveOverwritten() const override + { + return m_Conflicts.value().m_ArchiveOverwrittenList; + } + const std::set& getModArchiveLooseOverwrite() const override + { + return m_Conflicts.value().m_ArchiveLooseOverwriteList; + } + const std::set& getModArchiveLooseOverwritten() const override + { + return m_Conflicts.value().m_ArchiveLooseOverwrittenList; + } public slots: @@ -69,7 +86,6 @@ public slots: virtual void diskContentModified(); protected: - // check if the content of this mod is valid // virtual bool doIsValid() const; @@ -84,8 +100,8 @@ public slots: ModInfoWithConflictInfo(OrganizerCore& core); private: - - enum EConflictType { + enum EConflictType + { CONFLICT_NONE, CONFLICT_OVERWRITE, CONFLICT_OVERWRITTEN, @@ -95,7 +111,6 @@ public slots: }; private: - /** * @return true if there is a conflict for files in this mod */ @@ -119,7 +134,6 @@ public slots: bool hasHiddenFiles() const; protected: - /** * @brief Prefetch content for this mod. * @@ -130,20 +144,26 @@ public slots: virtual void prefetch() override; private: - - struct Conflicts { - EConflictType m_CurrentConflictState = CONFLICT_NONE; - EConflictType m_ArchiveConflictState = CONFLICT_NONE; + struct Conflicts + { + EConflictType m_CurrentConflictState = CONFLICT_NONE; + EConflictType m_ArchiveConflictState = CONFLICT_NONE; EConflictType m_ArchiveConflictLooseState = CONFLICT_NONE; - bool m_HasLooseOverwrite = false; - bool m_HasHiddenFiles = false; - - std::set m_OverwriteList; // indices of mods overritten by this mod - std::set m_OverwrittenList; // indices of mods overwriting this mod - std::set m_ArchiveOverwriteList; // indices of mods with archive files overritten by this mod - std::set m_ArchiveOverwrittenList; // indices of mods with archive files overwriting this mod - std::set m_ArchiveLooseOverwriteList; // indices of mods with archives being overwritten by this mod's loose files - std::set m_ArchiveLooseOverwrittenList; // indices of mods with loose files overwriting this mod's archive files + bool m_HasLooseOverwrite = false; + bool m_HasHiddenFiles = false; + + std::set m_OverwriteList; // indices of mods overritten by this mod + std::set m_OverwrittenList; // indices of mods overwriting this mod + std::set m_ArchiveOverwriteList; // indices of mods with archive + // files overritten by this mod + std::set m_ArchiveOverwrittenList; // indices of mods with archive + // files overwriting this mod + std::set + m_ArchiveLooseOverwriteList; // indices of mods with archives being overwritten + // by this mod's loose files + std::set + m_ArchiveLooseOverwrittenList; // indices of mods with loose files overwriting + // this mod's archive files }; Conflicts doConflictCheck() const; @@ -152,9 +172,6 @@ public slots: MOBase::MemoizedLocked m_Valid; MOBase::MemoizedLocked> m_Contents; MOBase::MemoizedLocked m_Conflicts; - }; - - -#endif // MODINFOWITHCONFLICTINFO_H +#endif // MODINFOWITHCONFLICTINFO_H diff --git a/src/modlist.cpp b/src/modlist.cpp index f48b52508..9f64cc711 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -19,57 +19,51 @@ along with Mod Organizer. If not, see . #include "modlist.h" -#include "widgetutility.h" #include "messagedialog.h" -#include "qtgroupingproxy.h" -#include "viewmarkingscrollbar.h" +#include "modinforegular.h" +#include "modlistdropinfo.h" #include "modlistsortproxy.h" +#include "organizercore.h" #include "pluginlist.h" +#include "qtgroupingproxy.h" #include "settings.h" -#include "organizercore.h" -#include "modinforegular.h" -#include "modlistdropinfo.h" #include "shared/directoryentry.h" #include "shared/fileentry.h" #include "shared/filesorigin.h" +#include "viewmarkingscrollbar.h" +#include "widgetutility.h" -#include "shared/appconfig.h" #include "filesystemutilities.h" +#include "shared/appconfig.h" #include -#include +#include +#include +#include +#include #include #include +#include +#include +#include +#include +#include #include +#include #include -#include #include -#include -#include -#include -#include #include -#include -#include -#include -#include +#include #include #include -#include - using namespace MOBase; -ModList::ModList(PluginContainer *pluginContainer, OrganizerCore *organizer) - : QAbstractItemModel(organizer) - , m_Organizer(organizer) - , m_Profile(nullptr) - , m_NexusInterface(nullptr) - , m_Modified(false) - , m_InNotifyChange(false) - , m_FontMetrics(QFont()) - , m_PluginContainer(pluginContainer) +ModList::ModList(PluginContainer* pluginContainer, OrganizerCore* organizer) + : QAbstractItemModel(organizer), m_Organizer(organizer), m_Profile(nullptr), + m_NexusInterface(nullptr), m_Modified(false), m_InNotifyChange(false), + m_FontMetrics(QFont()), m_PluginContainer(pluginContainer) { m_LastCheck.start(); } @@ -82,12 +76,12 @@ ModList::~ModList() m_ModMoved.disconnect_all_slots(); } -void ModList::setProfile(Profile *profile) +void ModList::setProfile(Profile* profile) { m_Profile = profile; } -int ModList::rowCount(const QModelIndex &parent) const +int ModList::rowCount(const QModelIndex& parent) const { if (!parent.isValid()) { return ModInfo::getNumMods(); @@ -96,7 +90,7 @@ int ModList::rowCount(const QModelIndex &parent) const } } -bool ModList::hasChildren(const QModelIndex &parent) const +bool ModList::hasChildren(const QModelIndex& parent) const { if (!parent.isValid()) { return ModInfo::getNumMods() > 0; @@ -105,8 +99,7 @@ bool ModList::hasChildren(const QModelIndex &parent) const } } - -int ModList::columnCount(const QModelIndex &) const +int ModList::columnCount(const QModelIndex&) const { return COL_LASTCOLUMN + 1; } @@ -131,220 +124,212 @@ QString ModList::makeInternalName(ModInfo::Ptr info, QString name) const QString ModList::getFlagText(ModInfo::EFlag flag, ModInfo::Ptr modInfo) const { switch (flag) { - case ModInfo::FLAG_BACKUP: return tr("Backup"); - case ModInfo::FLAG_SEPARATOR: return tr("Separator"); - case ModInfo::FLAG_INVALID: return tr("No valid game data"); - case ModInfo::FLAG_NOTENDORSED: return tr("Not endorsed yet"); - case ModInfo::FLAG_NOTES: { - QStringList output; - if (!modInfo->comments().isEmpty()) - output << QString("%1").arg(modInfo->comments()); - if (!modInfo->notes().isEmpty()) - output << QString("%1").arg(modInfo->notes()); - return output.join(""); - } - case ModInfo::FLAG_ALTERNATE_GAME: return tr("This mod is for a different
      game, " - "make sure it's compatible or it could cause crashes."); - case ModInfo::FLAG_TRACKED: return tr("Mod is being tracked on the website"); - case ModInfo::FLAG_HIDDEN_FILES: return tr("Contains hidden files"); - default: return ""; + case ModInfo::FLAG_BACKUP: + return tr("Backup"); + case ModInfo::FLAG_SEPARATOR: + return tr("Separator"); + case ModInfo::FLAG_INVALID: + return tr("No valid game data"); + case ModInfo::FLAG_NOTENDORSED: + return tr("Not endorsed yet"); + case ModInfo::FLAG_NOTES: { + QStringList output; + if (!modInfo->comments().isEmpty()) + output << QString("%1").arg(modInfo->comments()); + if (!modInfo->notes().isEmpty()) + output << QString("%1").arg(modInfo->notes()); + return output.join(""); + } + case ModInfo::FLAG_ALTERNATE_GAME: + return tr("This mod is for a different
      game, " + "make sure it's compatible or it could cause crashes."); + case ModInfo::FLAG_TRACKED: + return tr("Mod is being tracked on the website"); + case ModInfo::FLAG_HIDDEN_FILES: + return tr("Contains hidden files"); + default: + return ""; } } - -QString ModList::getConflictFlagText(ModInfo::EConflictFlag flag, ModInfo::Ptr modInfo) const +QString ModList::getConflictFlagText(ModInfo::EConflictFlag flag, + ModInfo::Ptr modInfo) const { switch (flag) { - case ModInfo::FLAG_CONFLICT_OVERWRITE: return tr("Overwrites loose files"); - case ModInfo::FLAG_CONFLICT_OVERWRITTEN: return tr("Overwritten loose files"); - case ModInfo::FLAG_CONFLICT_MIXED: return tr("Loose files Overwrites & Overwritten"); - case ModInfo::FLAG_CONFLICT_REDUNDANT: return tr("Redundant"); - case ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITE: return tr("Overwrites an archive with loose files"); - case ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITTEN: return tr("Archive is overwritten by loose files"); - case ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITE: return tr("Overwrites another archive file"); - case ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITTEN: return tr("Overwritten by another archive file"); - case ModInfo::FLAG_ARCHIVE_CONFLICT_MIXED: return tr("Archive files overwrites & overwritten"); - default: return ""; + case ModInfo::FLAG_CONFLICT_OVERWRITE: + return tr("Overwrites loose files"); + case ModInfo::FLAG_CONFLICT_OVERWRITTEN: + return tr("Overwritten loose files"); + case ModInfo::FLAG_CONFLICT_MIXED: + return tr("Loose files Overwrites & Overwritten"); + case ModInfo::FLAG_CONFLICT_REDUNDANT: + return tr("Redundant"); + case ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITE: + return tr("Overwrites an archive with loose files"); + case ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITTEN: + return tr("Archive is overwritten by loose files"); + case ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITE: + return tr("Overwrites another archive file"); + case ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITTEN: + return tr("Overwritten by another archive file"); + case ModInfo::FLAG_ARCHIVE_CONFLICT_MIXED: + return tr("Archive files overwrites & overwritten"); + default: + return ""; } } -QVariant ModList::data(const QModelIndex &modelIndex, int role) const +QVariant ModList::data(const QModelIndex& modelIndex, int role) const { - if (m_Profile == nullptr) return QVariant(); - if (!modelIndex.isValid()) return QVariant(); + if (m_Profile == nullptr) + return QVariant(); + if (!modelIndex.isValid()) + return QVariant(); unsigned int modIndex = modelIndex.row(); - int column = modelIndex.column(); + int column = modelIndex.column(); ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); - if ((role == Qt::DisplayRole) || - (role == Qt::EditRole)) { - if ((column == COL_FLAGS) - || (column == COL_CONTENT) - || (column == COL_CONFLICTFLAGS)) { + if ((role == Qt::DisplayRole) || (role == Qt::EditRole)) { + if ((column == COL_FLAGS) || (column == COL_CONTENT) || + (column == COL_CONFLICTFLAGS)) { return QVariant(); - } - else if (column == COL_NAME) { + } else if (column == COL_NAME) { return getDisplayName(modInfo); - } - else if (column == COL_VERSION) { + } else if (column == COL_VERSION) { VersionInfo verInfo = modInfo->version(); - QString version = verInfo.displayString(); + QString version = verInfo.displayString(); if (role != Qt::EditRole) { if (version.isEmpty() && modInfo->canBeUpdated()) { version = "?"; } } return version; - } - else if (column == COL_PRIORITY) { + } else if (column == COL_PRIORITY) { if (modInfo->hasAutomaticPriority()) { - return QVariant(); // hide priority for mods where it's fixed - } - else { + return QVariant(); // hide priority for mods where it's fixed + } else { return m_Profile->getModPriority(modIndex); } - } - else if (column == COL_MODID) { + } else if (column == COL_MODID) { int modID = modInfo->nexusId(); if (modID > 0) { return modID; - } - else { + } else { return QVariant(); } - } - else if (column == COL_GAME) { + } else if (column == COL_GAME) { if (m_PluginContainer != nullptr) { for (auto game : m_PluginContainer->plugins()) { - if (game->gameShortName().compare(modInfo->gameName(), Qt::CaseInsensitive) == 0) + if (game->gameShortName().compare(modInfo->gameName(), Qt::CaseInsensitive) == + 0) return game->gameName(); } } return modInfo->gameName(); - } - else if (column == COL_CATEGORY) { + } else if (column == COL_CATEGORY) { if (modInfo->hasFlag(ModInfo::FLAG_FOREIGN)) { return tr("Non-MO"); - } - else { + } else { int category = modInfo->primaryCategory(); if (category != -1) { - CategoryFactory *categoryFactory = CategoryFactory::instance(); + CategoryFactory* categoryFactory = CategoryFactory::instance(); if (categoryFactory->categoryExists(category)) { try { int categoryIdx = categoryFactory->getCategoryIndex(category); return categoryFactory->getCategoryName(categoryIdx); - } catch (const std::exception &e) { + } catch (const std::exception& e) { log::error("failed to retrieve category name: {}", e.what()); return QString(); } - } - else { + } else { log::warn("category {} doesn't exist (may have been removed)", category); modInfo->setCategory(category, false); return QString(); } - } - else { + } else { return QVariant(); } } - } - else if (column == COL_INSTALLTIME) { + } else if (column == COL_INSTALLTIME) { // display installation time for mods that can be updated if (modInfo->creationTime().isValid()) { return modInfo->creationTime(); - } - else { + } else { return QVariant(); } - } - else if (column == COL_NOTES) { + } else if (column == COL_NOTES) { return modInfo->comments(); - } - else { + } else { return tr("invalid"); } - } - else if ((role == Qt::CheckStateRole) && (column == 0)) { + } else if ((role == Qt::CheckStateRole) && (column == 0)) { if (modInfo->canBeEnabled()) { return m_Profile->modEnabled(modIndex) ? Qt::Checked : Qt::Unchecked; - } - else { + } else { return QVariant(); } - } - else if (role == Qt::TextAlignmentRole) { + } else if (role == Qt::TextAlignmentRole) { if (column == COL_NAME) { if (modInfo->getHighlight() & ModInfo::HIGHLIGHT_CENTER) { return QVariant(Qt::AlignCenter | Qt::AlignVCenter); - } - else { + } else { return QVariant(Qt::AlignLeft | Qt::AlignVCenter); } - } - else if (column == COL_VERSION) { + } else if (column == COL_VERSION) { return QVariant(Qt::AlignRight | Qt::AlignVCenter); - } - else if (column == COL_NOTES) { + } else if (column == COL_NOTES) { return QVariant(Qt::AlignLeft | Qt::AlignVCenter); - } - else { + } else { return QVariant(Qt::AlignCenter | Qt::AlignVCenter); } - } - else if (role == GroupingRole) { + } else if (role == GroupingRole) { if (column == COL_CATEGORY) { QVariantList categoryNames; - std::set categories = modInfo->getCategories(); - CategoryFactory *categoryFactory = CategoryFactory::instance(); + std::set categories = modInfo->getCategories(); + CategoryFactory* categoryFactory = CategoryFactory::instance(); for (auto iter = categories.begin(); iter != categories.end(); ++iter) { - categoryNames.append(categoryFactory->getCategoryName(categoryFactory->getCategoryIndex(*iter))); + categoryNames.append( + categoryFactory->getCategoryName(categoryFactory->getCategoryIndex(*iter))); } if (categoryNames.count() != 0) { return categoryNames; - } - else { + } else { return QVariant(); } - } - else { + } else { return modInfo->nexusId(); } - } - else if (role == IndexRole) { + } else if (role == IndexRole) { return modIndex; - } - else if (role == AggrRole) { + } else if (role == AggrRole) { switch (column) { - case COL_MODID: return QtGroupingProxy::AGGR_FIRST; - case COL_VERSION: return QtGroupingProxy::AGGR_MAX; - case COL_CATEGORY: return QtGroupingProxy::AGGR_FIRST; - case COL_PRIORITY: return QtGroupingProxy::AGGR_MIN; - default: return QtGroupingProxy::AGGR_NONE; - } - } - else if (role == GameNameRole) { + case COL_MODID: + return QtGroupingProxy::AGGR_FIRST; + case COL_VERSION: + return QtGroupingProxy::AGGR_MAX; + case COL_CATEGORY: + return QtGroupingProxy::AGGR_FIRST; + case COL_PRIORITY: + return QtGroupingProxy::AGGR_MIN; + default: + return QtGroupingProxy::AGGR_NONE; + } + } else if (role == GameNameRole) { return modInfo->gameName(); - } - else if (role == PriorityRole) { + } else if (role == PriorityRole) { return m_Profile->getModPriority(modIndex); - } - else if (role == Qt::FontRole) { + } else if (role == Qt::FontRole) { QFont result; if (column == COL_NAME) { if (modInfo->isSeparator()) { result.setItalic(true); result.setBold(true); - } - else if (modInfo->getHighlight() & ModInfo::HIGHLIGHT_INVALID) { + } else if (modInfo->getHighlight() & ModInfo::HIGHLIGHT_INVALID) { result.setItalic(true); } - } - else if (column == COL_CATEGORY && modInfo->isForeign()) { + } else if (column == COL_CATEGORY && modInfo->isForeign()) { result.setItalic(true); - } - else if (column == COL_VERSION) { + } else if (column == COL_VERSION) { if (modInfo->updateAvailable() || modInfo->downgradeAvailable()) { result.setWeight(QFont::Bold); } @@ -353,147 +338,144 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const } } return result; - } - else if (role == Qt::DecorationRole) { + } else if (role == Qt::DecorationRole) { if (column == COL_VERSION) { if (modInfo->updateAvailable()) { return QIcon(":/MO/gui/update_available"); - } - else if (modInfo->downgradeAvailable()) { + } else if (modInfo->downgradeAvailable()) { return QIcon(":/MO/gui/warning"); - } - else if (modInfo->version().scheme() == VersionInfo::SCHEME_DATE) { + } else if (modInfo->version().scheme() == VersionInfo::SCHEME_DATE) { return QIcon(":/MO/gui/version_date"); } } return QVariant(); - } - else if (role == Qt::ForegroundRole) { + } else if (role == Qt::ForegroundRole) { if (column == COL_NAME) { int highlight = modInfo->getHighlight(); if (highlight & ModInfo::HIGHLIGHT_IMPORTANT) { return QBrush(Qt::darkRed); - } - else if (highlight & ModInfo::HIGHLIGHT_INVALID) { + } else if (highlight & ModInfo::HIGHLIGHT_INVALID) { return QBrush(Qt::darkGray); } - } - else if (column == COL_VERSION) { + } else if (column == COL_VERSION) { if (!modInfo->newestVersion().isValid()) { return QVariant(); - } - else if (modInfo->updateAvailable() || modInfo->downgradeAvailable()) { + } else if (modInfo->updateAvailable() || modInfo->downgradeAvailable()) { return QBrush(Qt::red); - } - else { + } else { return QBrush(Qt::darkGreen); } } return QVariant(); - } - else if (role == Qt::BackgroundRole || role == ScrollMarkRole) { + } else if (role == Qt::BackgroundRole || role == ScrollMarkRole) { if (column == COL_NOTES && modInfo->color().isValid()) { return modInfo->color(); - } - else if (modInfo->isSeparator() && modInfo->color().isValid() - && (role != ScrollMarkRole || Settings::instance().colors().colorSeparatorScrollbar())) { + } else if (modInfo->isSeparator() && modInfo->color().isValid() && + (role != ScrollMarkRole || + Settings::instance().colors().colorSeparatorScrollbar())) { return modInfo->color(); - } - else { + } else { return QVariant(); } - } - else if (role == Qt::ToolTipRole) { + } else if (role == Qt::ToolTipRole) { if (column == COL_FLAGS) { QString result; for (ModInfo::EFlag flag : modInfo->getFlags()) { - if (result.length() != 0) result += "
      "; + if (result.length() != 0) + result += "
      "; result += getFlagText(flag, modInfo); } return result; - } - else if (column == COL_CONFLICTFLAGS) { + } else if (column == COL_CONFLICTFLAGS) { QString result; for (ModInfo::EConflictFlag flag : modInfo->getConflictFlags()) { - if (result.length() != 0) result += "
      "; + if (result.length() != 0) + result += "
      "; result += getConflictFlagText(flag, modInfo); } return result; - } - else if (column == COL_NAME) { + } else if (column == COL_NAME) { try { return modInfo->getDescription(); - } - catch (const std::exception &e) { + } catch (const std::exception& e) { log::error("invalid mod description: {}", e.what()); return QString(); } - } - else if (column == COL_VERSION) { - QString text = tr("installed version: \"%1\", newest version: \"%2\"").arg(modInfo->version().displayString(3)).arg(modInfo->newestVersion().displayString(3)); + } else if (column == COL_VERSION) { + QString text = tr("installed version: \"%1\", newest version: \"%2\"") + .arg(modInfo->version().displayString(3)) + .arg(modInfo->newestVersion().displayString(3)); 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\"."); + 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\"."); } if (modInfo->getNexusFileStatus() == NexusInterface::FileStatus::OLD_VERSION) { - text += "
      " + tr("This file has been marked as \"Old\". There is most likely an updated version of this file available."); - } - else if (modInfo->getNexusFileStatus() == NexusInterface::FileStatus::REMOVED || - modInfo->getNexusFileStatus() == NexusInterface::FileStatus::ARCHIVED || - modInfo->getNexusFileStatus() == NexusInterface::FileStatus::ARCHIVED_HIDDEN) { - text += "
      " + tr("This file has been marked as \"Deleted\"! You may want to check for an update or remove the nexus ID from this mod!"); + text += "
      " + tr("This file has been marked as \"Old\". There is most " + "likely an updated version of this file available."); + } else if (modInfo->getNexusFileStatus() == NexusInterface::FileStatus::REMOVED || + modInfo->getNexusFileStatus() == + NexusInterface::FileStatus::ARCHIVED || + modInfo->getNexusFileStatus() == + NexusInterface::FileStatus::ARCHIVED_HIDDEN) { + text += + "
      " + tr("This file has been marked as \"Deleted\"! You may want to " + "check for an update or remove the nexus ID from this mod!"); } if (modInfo->nexusId() > 0) { if (!modInfo->canBeUpdated()) { - qint64 remains = QDateTime::currentDateTimeUtc().secsTo(modInfo->getExpires()); + qint64 remains = + QDateTime::currentDateTimeUtc().secsTo(modInfo->getExpires()); qint64 minutes = remains / 60; qint64 seconds = remains % 60; - QString remainsStr(tr("%1 minute(s) and %2 second(s)").arg(minutes).arg(seconds)); - text += "
      " + tr("This mod will be available to check in %2.") - .arg(remainsStr); + QString remainsStr( + tr("%1 minute(s) and %2 second(s)").arg(minutes).arg(seconds)); + text += + "
      " + tr("This mod will be available to check in %2.").arg(remainsStr); } } return text; - } - else if (column == COL_CATEGORY) { - const std::set &categories = modInfo->getCategories(); + } else if (column == COL_CATEGORY) { + const std::set& categories = modInfo->getCategories(); std::wostringstream categoryString; categoryString << ToWString(tr("Categories:
      ")); - CategoryFactory *categoryFactory = CategoryFactory::instance(); + CategoryFactory* categoryFactory = CategoryFactory::instance(); for (std::set::const_iterator catIter = categories.begin(); catIter != categories.end(); ++catIter) { if (catIter != categories.begin()) { categoryString << " , "; } try { - categoryString << "" << ToWString(categoryFactory->getCategoryName(categoryFactory->getCategoryIndex(*catIter))) << ""; - } catch (const std::exception &e) { + categoryString << "" + << ToWString(categoryFactory->getCategoryName( + categoryFactory->getCategoryIndex(*catIter))) + << ""; + } catch (const std::exception& e) { log::error("failed to generate tooltip: {}", e.what()); return QString(); } } return ToQString(categoryString.str()); - } - else if (column == COL_NOTES) { + } else if (column == COL_NOTES) { return getFlagText(ModInfo::FLAG_NOTES, modInfo); - } - else { + } else { return QVariant(); } - } - else { + } else { return QVariant(); } } - -bool ModList::renameMod(int index, const QString &newName) +bool ModList::renameMod(int index, const QString& newName) { QString nameFixed = newName; if (!fixDirectoryName(nameFixed) || nameFixed.isEmpty()) { @@ -501,18 +483,18 @@ bool ModList::renameMod(int index, const QString &newName) return false; } - if (ModList::allMods().contains(nameFixed, Qt::CaseInsensitive) && nameFixed.toLower()!=ModInfo::getByIndex(index)->name().toLower() ) { - MessageDialog::showMessage(tr("Name is already in use by another mod"), nullptr); - return false; + if (ModList::allMods().contains(nameFixed, Qt::CaseInsensitive) && + nameFixed.toLower() != ModInfo::getByIndex(index)->name().toLower()) { + MessageDialog::showMessage(tr("Name is already in use by another mod"), nullptr); + return false; } ModInfo::Ptr modInfo = ModInfo::getByIndex(index); - QString oldName = modInfo->name(); + QString oldName = modInfo->name(); if (nameFixed != oldName) { // before we rename, ensure there is no scheduled asynchronous to rewrite m_Profile->cancelModlistWrite(); - if (modInfo->setName(nameFixed)) { // Notice there is a good chance that setName() updated the modinfo indexes // the modRenamed() call will refresh the indexes in the current profile @@ -526,9 +508,10 @@ bool ModList::renameMod(int index, const QString &newName) return true; } -bool ModList::setData(const QModelIndex &index, const QVariant &value, int role) +bool ModList::setData(const QModelIndex& index, const QVariant& value, int role) { - if (m_Profile == nullptr) return false; + if (m_Profile == nullptr) + return false; if (static_cast(index.row()) >= ModInfo::getNumMods()) { return false; @@ -536,7 +519,7 @@ bool ModList::setData(const QModelIndex &index, const QVariant &value, int role) int modID = index.row(); - ModInfo::Ptr info = ModInfo::getByIndex(modID); + ModInfo::Ptr info = ModInfo::getByIndex(modID); IModList::ModStates oldState = state(modID); bool result = false; @@ -553,53 +536,51 @@ bool ModList::setData(const QModelIndex &index, const QVariant &value, int role) } result = true; emit dataChanged(index, index); - } - else if (role == Qt::EditRole) { + } else if (role == Qt::EditRole) { switch (index.column()) { - case COL_NAME: { - result = renameMod(modID, makeInternalName(info, value.toString())); - } break; - case COL_PRIORITY: { - bool ok = false; - int newPriority = value.toInt(&ok); - if (ok) { - changeModPriority(modID, newPriority); - result = true; - } else { - result = false; - } - } break; - case COL_MODID: { - bool ok = false; - int newID = value.toInt(&ok); - if (ok) { - info->setNexusID(newID); - emit tutorialModlistUpdate(); - result = true; - } else { - result = false; - } - } break; - case COL_VERSION: { - VersionInfo::VersionScheme scheme = info->version().scheme(); - VersionInfo version(value.toString(), scheme, true); - if (version.isValid()) { - info->setVersion(version); - result = true; - } else { - result = false; - } - } break; - case COL_NOTES: { - info->setComments(value.toString()); + case COL_NAME: { + result = renameMod(modID, makeInternalName(info, value.toString())); + } break; + case COL_PRIORITY: { + bool ok = false; + int newPriority = value.toInt(&ok); + if (ok) { + changeModPriority(modID, newPriority); result = true; - } break; - default: { - log::warn( - "edit on column \"{}\" not supported", - getColumnName(index.column()).toUtf8().constData()); + } else { result = false; - } break; + } + } break; + case COL_MODID: { + bool ok = false; + int newID = value.toInt(&ok); + if (ok) { + info->setNexusID(newID); + emit tutorialModlistUpdate(); + result = true; + } else { + result = false; + } + } break; + case COL_VERSION: { + VersionInfo::VersionScheme scheme = info->version().scheme(); + VersionInfo version(value.toString(), scheme, true); + if (version.isValid()) { + info->setVersion(version); + result = true; + } else { + result = false; + } + } break; + case COL_NOTES: { + info->setComments(value.toString()); + result = true; + } break; + default: { + log::warn("edit on column \"{}\" not supported", + getColumnName(index.column()).toUtf8().constData()); + result = false; + } break; } if (result) { emit dataChanged(index, index); @@ -610,9 +591,7 @@ bool ModList::setData(const QModelIndex &index, const QVariant &value, int role) return result; } - -QVariant ModList::headerData(int section, Qt::Orientation orientation, - int role) const +QVariant ModList::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal) { if (role == Qt::DisplayRole) { @@ -624,8 +603,7 @@ QVariant ModList::headerData(int section, Qt::Orientation orientation, } else if (role == MOBase::EnabledColumnRole) { if (section == COL_CONTENT) { return !m_Organizer->modDataContents().empty(); - } - else { + } else { return true; } } @@ -633,7 +611,7 @@ QVariant ModList::headerData(int section, Qt::Orientation orientation, return QAbstractItemModel::headerData(section, orientation, role); } -Qt::ItemFlags ModList::flags(const QModelIndex &modelIndex) const +Qt::ItemFlags ModList::flags(const QModelIndex& modelIndex) const { Qt::ItemFlags result = QAbstractItemModel::flags(modelIndex); if (modelIndex.internalId() < 0) { @@ -645,27 +623,24 @@ Qt::ItemFlags ModList::flags(const QModelIndex &modelIndex) const result |= Qt::ItemIsDragEnabled; result |= Qt::ItemIsUserCheckable; if ((modelIndex.column() == COL_PRIORITY) || - (modelIndex.column() == COL_VERSION) || - (modelIndex.column() == COL_MODID)) { + (modelIndex.column() == COL_VERSION) || (modelIndex.column() == COL_MODID)) { result |= Qt::ItemIsEditable; } - if ((modelIndex.column() == COL_NAME || modelIndex.column() == COL_NOTES) - && !modInfo->isForeign()) { + if ((modelIndex.column() == COL_NAME || modelIndex.column() == COL_NOTES) && + !modInfo->isForeign()) { result |= Qt::ItemIsEditable; } } if (modInfo->isSeparator() || m_DropOnMod) { result |= Qt::ItemIsDropEnabled; } - } - else if (!m_DropOnMod) { + } else if (!m_DropOnMod) { result |= Qt::ItemIsDropEnabled; } return result; } - QStringList ModList::mimeTypes() const { QStringList result = QAbstractItemModel::mimeTypes(); @@ -673,24 +648,25 @@ QStringList ModList::mimeTypes() const return result; } -QMimeData *ModList::mimeData(const QModelIndexList &indexes) const +QMimeData* ModList::mimeData(const QModelIndexList& indexes) const { - QMimeData *result = QAbstractItemModel::mimeData(indexes); + QMimeData* result = QAbstractItemModel::mimeData(indexes); result->setData("text/plain", ModListDropInfo::ModText); return result; } void ModList::changeModPriority(std::vector sourceIndices, int newPriority) { - if (m_Profile == nullptr) return; + if (m_Profile == nullptr) + return; emit layoutAboutToBeChanged(); // sort the moving mods by ascending priorities std::sort(sourceIndices.begin(), sourceIndices.end(), - [=](const int &LHS, const int &RHS) { - return m_Profile->getModPriority(LHS) > m_Profile->getModPriority(RHS); - }); + [=](const int& LHS, const int& RHS) { + return m_Profile->getModPriority(LHS) > m_Profile->getModPriority(RHS); + }); // move mods that are decreasing in priority for (const auto& index : sourceIndices) { @@ -704,9 +680,9 @@ void ModList::changeModPriority(std::vector sourceIndices, int newPriority) // sort the moving mods by descending priorities std::sort(sourceIndices.begin(), sourceIndices.end(), - [=](const int &LHS, const int &RHS) { - return m_Profile->getModPriority(LHS) < m_Profile->getModPriority(RHS); - }); + [=](const int& LHS, const int& RHS) { + return m_Profile->getModPriority(LHS) < m_Profile->getModPriority(RHS); + }); // if at least one mod is increasing in priority, the target index is // that of the row BELOW the dropped location, otherwise it's the one above @@ -738,19 +714,19 @@ void ModList::changeModPriority(std::vector sourceIndices, int newPriority) emit modPrioritiesChanged(indices); } - void ModList::changeModPriority(int sourceIndex, int newPriority) { - if (m_Profile == nullptr) return; + if (m_Profile == nullptr) + return; emit layoutAboutToBeChanged(); m_Profile->setModPriority(sourceIndex, newPriority); emit layoutChanged(); - emit modPrioritiesChanged({ index(sourceIndex, 0) }); + emit modPrioritiesChanged({index(sourceIndex, 0)}); } -void ModList::setPluginContainer(PluginContainer *pluginContianer) +void ModList::setPluginContainer(PluginContainer* pluginContianer) { m_PluginContainer = pluginContianer; } @@ -758,7 +734,7 @@ void ModList::setPluginContainer(PluginContainer *pluginContianer) bool ModList::modInfoAboutToChange(ModInfo::Ptr info) { if (m_ChangeInfo.name.isEmpty()) { - m_ChangeInfo.name = info->name(); + m_ChangeInfo.name = info->name(); m_ChangeInfo.state = state(info->name()); return true; } else { @@ -771,7 +747,7 @@ void ModList::modInfoChanged(ModInfo::Ptr info) if (info->name() == m_ChangeInfo.name) { IModList::ModStates newState = state(info->name()); if (m_ChangeInfo.state != newState) { - m_ModStateChanged({ {info->name(), newState} }); + m_ModStateChanged({{info->name(), newState}}); } int row = ModInfo::getIndex(info->name()); @@ -785,7 +761,8 @@ void ModList::modInfoChanged(ModInfo::Ptr info) m_ChangeInfo.name = QString(); } -void ModList::disconnectSlots() { +void ModList::disconnectSlots() +{ m_ModMoved.disconnect_all_slots(); m_ModStateChanged.disconnect_all_slots(); } @@ -811,7 +788,8 @@ IModList::ModStates ModList::state(unsigned int modIndex) const result |= IModList::STATE_VALID; } if (modInfo->isRegular()) { - QSharedPointer modInfoRegular = modInfo.staticCast(); + QSharedPointer modInfoRegular = + modInfo.staticCast(); if (modInfoRegular->isAlternate() && !modInfoRegular->isConverted()) result |= IModList::STATE_ALTERNATE; if (!modInfo->isValid() && modInfoRegular->isValidated()) @@ -828,7 +806,7 @@ IModList::ModStates ModList::state(unsigned int modIndex) const return result; } -QString ModList::displayName(const QString &internalName) const +QString ModList::displayName(const QString& internalName) const { unsigned int modIndex = ModInfo::getIndex(internalName); if (modIndex == UINT_MAX) { @@ -850,8 +828,8 @@ QStringList ModList::allMods() const QStringList ModList::allModsByProfilePriority(MOBase::IProfile* profile) const { - Profile* mo2Profile = profile == nullptr ? - m_Organizer->currentProfile() : static_cast(profile); + Profile* mo2Profile = profile == nullptr ? m_Organizer->currentProfile() + : static_cast(profile); QStringList res; for (auto& [priority, index] : mo2Profile->getAllIndexesByPriority()) { @@ -878,41 +856,39 @@ bool ModList::removeMod(MOBase::IModInterface* mod) return result; } -MOBase::IModInterface* ModList::renameMod(MOBase::IModInterface* mod, const QString& name) +MOBase::IModInterface* ModList::renameMod(MOBase::IModInterface* mod, + const QString& name) { unsigned int index = ModInfo::getIndex(mod->name()); if (index == UINT_MAX) { if (auto* p = dynamic_cast(mod)) { p->setName(name); return p; - } - else { + } else { return nullptr; } - } - else { + } else { if (renameMod(index, name)) { return ModInfo::getByName(name).get(); - } - else { + } else { return nullptr; } } } -IModList::ModStates ModList::state(const QString &name) const +IModList::ModStates ModList::state(const QString& name) const { unsigned int modIndex = ModInfo::getIndex(name); return state(modIndex); } -bool ModList::setActive(const QString &name, bool active) +bool ModList::setActive(const QString& name, bool active) { unsigned int modIndex = ModInfo::getIndex(name); if (modIndex == UINT_MAX) { log::debug("Trying to {} mod {} which does not exist.", - active ? "enable" : "disable", name); + active ? "enable" : "disable", name); return false; } else { m_Profile->setModEnabled(modIndex, active); @@ -920,7 +896,8 @@ bool ModList::setActive(const QString &name, bool active) } } -int ModList::setActive(const QStringList& names, bool active) { +int ModList::setActive(const QStringList& names, bool active) +{ // We only add indices for mods that exist (modIndex != UINT_MAX) // and that can be enabled / disabled. @@ -929,24 +906,22 @@ int ModList::setActive(const QStringList& names, bool active) { auto modIndex = ModInfo::getIndex(name); if (modIndex != UINT_MAX) { indices.append(modIndex); - } - else { + } else { log::debug("Trying to {} mod {} which does not exist.", - active ? "enable" : "disable", name); + active ? "enable" : "disable", name); } } if (active) { m_Profile->setModsEnabled(indices, {}); - } - else { + } else { m_Profile->setModsEnabled({}, indices); } return indices.size(); } -int ModList::priority(const QString &name) const +int ModList::priority(const QString& name) const { unsigned int modIndex = ModInfo::getIndex(name); if (modIndex == UINT_MAX) { @@ -956,7 +931,7 @@ int ModList::priority(const QString &name) const } } -bool ModList::setPriority(const QString &name, int newPriority) +bool ModList::setPriority(const QString& name, int newPriority) { unsigned int index = ModInfo::getIndex(name); if (index == UINT_MAX) { @@ -969,17 +944,20 @@ bool ModList::setPriority(const QString &name, int newPriority) } } -boost::signals2::connection ModList::onModInstalled(const std::function& func) +boost::signals2::connection +ModList::onModInstalled(const std::function& func) { return m_ModInstalled.connect(func); } -boost::signals2::connection ModList::onModRemoved(const std::function& func) +boost::signals2::connection +ModList::onModRemoved(const std::function& func) { return m_ModRemoved.connect(func); } -boost::signals2::connection ModList::onModStateChanged(const std::function&)>& func) +boost::signals2::connection ModList::onModStateChanged( + const std::function&)>& func) { return m_ModStateChanged.connect(func); } @@ -1008,7 +986,8 @@ void ModList::notifyModStateChanged(QList modIndices) const m_ModStateChanged(mods); } -boost::signals2::connection ModList::onModMoved(const std::function &func) +boost::signals2::connection +ModList::onModMoved(const std::function& func) { return m_ModMoved.connect(func); } @@ -1027,8 +1006,7 @@ int ModList::dropPriority(int row, const QModelIndex& parent) const { if (row < 0 || row >= rowCount()) { newPriority = Profile::MaximumPriority; - } - else { + } else { newPriority = m_Profile->getModPriority(row); } } @@ -1036,17 +1014,18 @@ int ModList::dropPriority(int row, const QModelIndex& parent) const return newPriority; } -bool ModList::dropLocalFiles(const ModListDropInfo& dropInfo, int row, const QModelIndex &parent) +bool ModList::dropLocalFiles(const ModListDropInfo& dropInfo, int row, + const QModelIndex& parent) { if (row == -1) { row = parent.row(); } ModInfo::Ptr modInfo = ModInfo::getByIndex(row); - QDir modDir = QDir(modInfo->absolutePath()); + QDir modDir = QDir(modInfo->absolutePath()); QStringList sourceList; QStringList targetList; - QList> relativePathList; + QList> relativePathList; for (auto localUrl : dropInfo.localUrls()) { @@ -1056,7 +1035,8 @@ bool ModList::dropLocalFiles(const ModListDropInfo& dropInfo, int row, const QMo QFileInfo targetInfo(modDir.absoluteFilePath(localUrl.relativePath)); sourceList << sourceFile; targetList << targetInfo.absoluteFilePath(); - relativePathList << QPair(localUrl.relativePath, localUrl.originName); + relativePathList << QPair(localUrl.relativePath, + localUrl.originName); } if (sourceList.count()) { @@ -1082,7 +1062,8 @@ void ModList::onDragEnter(const QMimeData* mimeData) m_DropOnMod = ModListDropInfo(mimeData, *m_Organizer).isLocalFileDrop(); } -bool ModList::canDropMimeData(const QMimeData* mimeData, Qt::DropAction action, int row, int column, const QModelIndex& parent) const +bool ModList::canDropMimeData(const QMimeData* mimeData, Qt::DropAction action, int row, + int column, const QModelIndex& parent) const { if (action == Qt::IgnoreAction) { return false; @@ -1095,17 +1076,14 @@ bool ModList::canDropMimeData(const QMimeData* mimeData, Qt::DropAction action, ModInfo::Ptr modInfo = ModInfo::getByIndex(parent.row()); return modInfo->isRegular() && !modInfo->isSeparator(); } - } - else if (dropInfo.isValid()) { + } else if (dropInfo.isValid()) { // drop on item if (row == -1 && parent.isValid()) { return true; - } - else if (hasIndex(row, column, parent)) { + } else if (hasIndex(row, column, parent)) { ModInfo::Ptr modInfo = ModInfo::getByIndex(row); return !modInfo->isBackup() && (modInfo->isSeparator() || !parent.isValid()); - } - else { + } else { return true; } } @@ -1113,7 +1091,8 @@ bool ModList::canDropMimeData(const QMimeData* mimeData, Qt::DropAction action, return false; } -bool ModList::dropMimeData(const QMimeData *mimeData, Qt::DropAction action, int row, int, const QModelIndex &parent) +bool ModList::dropMimeData(const QMimeData* mimeData, Qt::DropAction action, int row, + int, const QModelIndex& parent) { if (action == Qt::IgnoreAction) { return true; @@ -1132,33 +1111,29 @@ bool ModList::dropMimeData(const QMimeData *mimeData, Qt::DropAction action, int if (dropInfo.isLocalFileDrop()) { return dropLocalFiles(dropInfo, row, parent); - } - else { + } else { if (dropInfo.isModDrop()) { changeModPriority(dropInfo.rows(), dropPriority); - } - else if (dropInfo.isDownloadDrop()) { + } else if (dropInfo.isDownloadDrop()) { emit downloadArchiveDropped(dropInfo.download(), dropPriority); - } - else if (dropInfo.isExternalArchiveDrop()) { + } else if (dropInfo.isExternalArchiveDrop()) { emit externalArchiveDropped(dropInfo.externalUrl(), dropPriority); - } - else if (dropInfo.isExternalFolderDrop()) { + } else if (dropInfo.isExternalFolderDrop()) { emit externalFolderDropped(dropInfo.externalUrl(), dropPriority); - } - else { + } else { return false; } } return false; } -void ModList::removeRowForce(int row, const QModelIndex &parent) +void ModList::removeRowForce(int row, const QModelIndex& parent) { if (static_cast(row) >= ModInfo::getNumMods()) { return; } - if (m_Profile == nullptr) return; + if (m_Profile == nullptr) + return; ModInfo::Ptr modInfo = ModInfo::getByIndex(row); @@ -1171,7 +1146,8 @@ void ModList::removeRowForce(int row, const QModelIndex &parent) ModInfo::removeMod(row); m_Profile->refreshModStatus(); // removes the mod from the status list endRemoveRows(); - m_Profile->writeModlist(); // this ensures the modified list gets written back before new mods can be installed + m_Profile->writeModlist(); // this ensures the modified list gets written back before + // new mods can be installed notifyModRemoved(modInfo->name()); @@ -1183,7 +1159,7 @@ void ModList::removeRowForce(int row, const QModelIndex &parent) } } -bool ModList::removeRows(int row, int count, const QModelIndex &parent) +bool ModList::removeRows(int row, int count, const QModelIndex& parent) { if (static_cast(row) >= ModInfo::getNumMods()) { return false; @@ -1210,9 +1186,10 @@ bool ModList::removeRows(int row, int count, const QModelIndex &parent) success = true; - QMessageBox confirmBox(QMessageBox::Question, tr("Confirm"), - tr("Are you sure you want to remove \"%1\"?").arg(getDisplayName(modInfo)), - QMessageBox::Yes | QMessageBox::No); + QMessageBox confirmBox( + QMessageBox::Question, tr("Confirm"), + tr("Are you sure you want to remove \"%1\"?").arg(getDisplayName(modInfo)), + QMessageBox::Yes | QMessageBox::No); if (confirmBox.exec() == QMessageBox::Yes) { m_Profile->setModEnabled(row + i, false); @@ -1223,7 +1200,6 @@ bool ModList::removeRows(int row, int count, const QModelIndex &parent) return success; } - void ModList::notifyChange(int rowStart, int rowEnd) { // this function can emit dataChanged(), which can eventually recurse back @@ -1250,7 +1226,9 @@ void ModList::notifyChange(int rowStart, int rowEnd) } m_InNotifyChange = true; - Guard g([&]{ m_InNotifyChange = false; }); + Guard g([&] { + m_InNotifyChange = false; + }); if (rowStart < 0) { beginResetModel(); @@ -1259,11 +1237,11 @@ void ModList::notifyChange(int rowStart, int rowEnd) if (rowEnd == -1) { rowEnd = rowStart; } - emit dataChanged(this->index(rowStart, 0), this->index(rowEnd, this->columnCount() - 1)); + emit dataChanged(this->index(rowStart, 0), + this->index(rowEnd, this->columnCount() - 1)); } } - QModelIndex ModList::index(int row, int column, const QModelIndex&) const { if ((row < 0) || (row >= rowCount()) || (column < 0) || (column >= columnCount())) { @@ -1273,13 +1251,12 @@ QModelIndex ModList::index(int row, int column, const QModelIndex&) const return res; } - QModelIndex ModList::parent(const QModelIndex&) const { return QModelIndex(); } -QMap ModList::itemData(const QModelIndex &index) const +QMap ModList::itemData(const QModelIndex& index) const { QMap result = QAbstractItemModel::itemData(index); for (int role = Qt::UserRole; role < ModUserRole; ++role) { @@ -1291,49 +1268,74 @@ QMap ModList::itemData(const QModelIndex &index) const QString ModList::getColumnName(int column) { switch (column) { - case COL_CONFLICTFLAGS: return tr("Conflicts"); - case COL_FLAGS: return tr("Flags"); - case COL_CONTENT: return tr("Content"); - case COL_NAME: return tr("Mod Name"); - case COL_VERSION: return tr("Version"); - case COL_PRIORITY: return tr("Priority"); - case COL_CATEGORY: return tr("Category"); - case COL_GAME: return tr("Source Game"); - case COL_MODID: return tr("Nexus ID"); - case COL_INSTALLTIME: return tr("Installation"); - case COL_NOTES: return tr("Notes"); - default: return tr("unknown"); + case COL_CONFLICTFLAGS: + return tr("Conflicts"); + case COL_FLAGS: + return tr("Flags"); + case COL_CONTENT: + return tr("Content"); + case COL_NAME: + return tr("Mod Name"); + case COL_VERSION: + return tr("Version"); + case COL_PRIORITY: + return tr("Priority"); + case COL_CATEGORY: + return tr("Category"); + case COL_GAME: + return tr("Source Game"); + case COL_MODID: + return tr("Nexus ID"); + case COL_INSTALLTIME: + return tr("Installation"); + case COL_NOTES: + return tr("Notes"); + default: + return tr("unknown"); } } - QString ModList::getColumnToolTip(int column) const { switch (column) { - case COL_NAME: return tr("Name of your mods"); - case COL_VERSION: return tr("Version of the mod (if available)"); - case COL_PRIORITY: return tr("Installation priority of your mod. The higher, the more \"important\" it is and thus " - "overwrites files from mods with lower priority."); - case COL_CATEGORY: return tr("Primary category of the mod."); - case COL_GAME: return tr("The source game which was the origin of this mod."); - case COL_MODID: return tr("Id of the mod as used on Nexus."); - case COL_CONFLICTFLAGS: return tr("Indicators of file conflicts between mods."); - case COL_FLAGS: return tr("Emblems to highlight things that might require attention."); - case COL_CONTENT: { - auto& contents = m_Organizer->modDataContents(); - if (m_Organizer->modDataContents().empty()) { - return QString(); - } - QString result = tr("Depicts the content of the mod:") + "
      " + ""; - m_Organizer->modDataContents().forEachContent([&result](auto const& content) { - result += QString("") - .arg(content.icon()).arg(content.name()); - }); - return result + "
      %2
      "; - }; - case COL_INSTALLTIME: return tr("Time this mod was installed"); - case COL_NOTES: return tr("User notes about the mod"); - default: return tr("unknown"); + case COL_NAME: + return tr("Name of your mods"); + case COL_VERSION: + return tr("Version of the mod (if available)"); + case COL_PRIORITY: + return tr("Installation priority of your mod. The higher, the more \"important\" " + "it is and thus " + "overwrites files from mods with lower priority."); + case COL_CATEGORY: + return tr("Primary category of the mod."); + case COL_GAME: + return tr("The source game which was the origin of this mod."); + case COL_MODID: + return tr("Id of the mod as used on Nexus."); + case COL_CONFLICTFLAGS: + return tr("Indicators of file conflicts between mods."); + case COL_FLAGS: + return tr("Emblems to highlight things that might require attention."); + case COL_CONTENT: { + auto& contents = m_Organizer->modDataContents(); + if (m_Organizer->modDataContents().empty()) { + return QString(); + } + QString result = + tr("Depicts the content of the mod:") + "
      " + ""; + m_Organizer->modDataContents().forEachContent([&result](auto const& content) { + result += QString("") + .arg(content.icon()) + .arg(content.name()); + }); + return result + "
      %2
      "; + }; + case COL_INSTALLTIME: + return tr("Time this mod was installed"); + case COL_NOTES: + return tr("User notes about the mod"); + default: + return tr("unknown"); } } @@ -1384,8 +1386,7 @@ void ModList::changeModsPriority(const QModelIndexList& indices, int priority) if (allIndex.size() == 1) { changeModPriority(allIndex[0], priority); - } - else { + } else { changeModPriority(allIndex, priority); } } @@ -1428,8 +1429,7 @@ void ModList::setActive(const QModelIndexList& indices, bool active) if (active) { m_Profile->setModsEnabled(mods, {}); - } - else { + } else { m_Profile->setModsEnabled({}, mods); } } diff --git a/src/modlist.h b/src/modlist.h index 6a3a2901b..112070e12 100644 --- a/src/modlist.h +++ b/src/modlist.h @@ -1,407 +1,420 @@ -/* -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 MODLIST_H -#define MODLIST_H - -#include "moddatacontent.h" -#include "categories.h" -#include "nexusinterface.h" -#include "modinfo.h" -#include "profile.h" - -#include - -#include -#include -#include -#include -#include -#ifndef Q_MOC_RUN -#include -#endif -#include -#include -#include - - -class QSortFilterProxyModel; -class PluginContainer; -class OrganizerCore; -class ModListDropInfo; - -/** - * Model presenting an overview of the installed mod - * This is used in a view in the main window of MO. It combines general information about - * the mods from ModInfo with status information from the Profile - **/ -class ModList : public QAbstractItemModel -{ - Q_OBJECT - -public: - - enum ModListRole { - - // data(GroupingRole) contains the "group" role - This is used by the - // category and Nexus ID grouping proxy (but not the ByPriority proxy) - GroupingRole = Qt::UserRole, - - IndexRole = Qt::UserRole + 1, - - // data(AggrRole) contains aggregation information (for - // grouping I assume?) - AggrRole = Qt::UserRole + 2, - - GameNameRole = Qt::UserRole + 3, - PriorityRole = Qt::UserRole + 4, - - // marking role for the scrollbar - ScrollMarkRole = Qt::UserRole + 5, - - // this is the first available role - ModUserRole = Qt::UserRole + 6 - }; - - enum EColumn { - COL_NAME, - COL_CONFLICTFLAGS, - COL_FLAGS, - COL_CONTENT, - COL_CATEGORY, - COL_MODID, - COL_GAME, - COL_VERSION, - COL_INSTALLTIME, - COL_PRIORITY, - COL_NOTES, - COL_LASTCOLUMN = COL_NOTES, - }; - - using SignalModInstalled = boost::signals2::signal; - using SignalModRemoved = boost::signals2::signal; - using SignalModStateChanged = boost::signals2::signal&)>; - using SignalModMoved = boost::signals2::signal; - -public: - - /** - * @brief constructor - * @todo ensure this view works without a profile set, otherwise there are intransparent dependencies on the initialisation order - **/ - ModList(PluginContainer *pluginContainer, OrganizerCore *parent); - - ~ModList(); - - /** - * @brief set the profile used for status information - * - * @param profile the profile to use - **/ - void setProfile(Profile *profile); - - /** - * @brief retrieve the current sorting mode - * @note this is used to store the sorting mode between sessions - * @return current sorting mode, encoded to be compatible to previous versions - **/ - int getCurrentSortingMode() const; - - /** - * @brief remove the specified mod without asking for confirmation - * @param row the row to remove - */ - void removeRowForce(int row, const QModelIndex &parent); - - void notifyChange(int rowStart, int rowEnd = -1); - static QString getColumnName(int column); - - void changeModPriority(int sourceIndex, int newPriority); - void changeModPriority(std::vector sourceIndices, int newPriority); - - void setPluginContainer(PluginContainer *pluginContainer); - - bool modInfoAboutToChange(ModInfo::Ptr info); - void modInfoChanged(ModInfo::Ptr info); - - void disconnectSlots(); - - int timeElapsedSinceLastChecked() const; - -public: - - /** - * @brief Notify the mod list that the given mod has been installed. This is used - * to notify the plugin that registered through onModInstalled(). - * - * @param mod The installed mod. - */ - void notifyModInstalled(MOBase::IModInterface *mod) const; - - /** - * @brief Notify the mod list that a mod has been removed. This is used - * to notify the plugin that registered through onModRemoved(). - * - * @param modName Name of the removed mod. - */ - void notifyModRemoved(QString const& modName) const; - - /** - * @brief Notify the mod list that the state of the specified mods has changed. This is used - * to notify the plugin that registered through onModStateChanged(). - * - * @param modIndices Indices of the mods that changed. - */ - void notifyModStateChanged(QList modIndices) const; - -public: - - /// \copydoc MOBase::IModList::displayName - QString displayName(const QString &internalName) const; - - /// \copydoc MOBase::IModList::allMods - QStringList allMods() const; - QStringList allModsByProfilePriority(MOBase::IProfile* profile = nullptr) const; - - // \copydoc MOBase::IModList::getMod - MOBase::IModInterface* getMod(const QString& name) const; - - // \copydoc MOBase::IModList::remove - bool removeMod(MOBase::IModInterface* mod); - - // \copydoc MOBase::IModList::renameMod - MOBase::IModInterface* renameMod(MOBase::IModInterface* mod, const QString& name); - - /// \copydoc MOBase::IModList::state - MOBase::IModList::ModStates state(const QString &name) const; - - /// \copydoc MOBase::IModList::setActive - bool setActive(const QString &name, bool active); - - /// \copydoc MOBase::IModList::setActive - int setActive(const QStringList& names, bool active); - - /// \copydoc MOBase::IModList::priority - int priority(const QString &name) const; - - /// \copydoc MOBase::IModList::setPriority - bool setPriority(const QString &name, int newPriority); - - boost::signals2::connection onModInstalled(const std::function& func); - boost::signals2::connection onModRemoved(const std::function& func); - boost::signals2::connection onModStateChanged(const std::function&)>& func); - boost::signals2::connection onModMoved(const std::function &func); - -public: // implementation of virtual functions of QAbstractItemModel - - virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; - virtual bool hasChildren(const QModelIndex &parent = QModelIndex()) const; - virtual int columnCount(const QModelIndex &parent = QModelIndex()) const; - virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; - virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); - virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; - virtual Qt::ItemFlags flags(const QModelIndex &modelIndex) const; - virtual bool removeRows(int row, int count, const QModelIndex& parent); - - Qt::DropActions supportedDropActions() const override { return Qt::MoveAction | Qt::CopyAction; } - QStringList mimeTypes() const override; - QMimeData *mimeData(const QModelIndexList &indexes) const override; - bool canDropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) const override; - bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override; - - virtual QModelIndex index(int row, int column, - const QModelIndex &parent = QModelIndex()) const; - virtual QModelIndex parent(const QModelIndex &child) const; - - virtual QMap itemData(const QModelIndex &index) const; - -public slots: - - void onDragEnter(const QMimeData* data); - - // enable/disable mods at the given indices. - // - void setActive(const QModelIndexList& indices, bool active); - - // shift the priority of mods at the given indices by the given offset - // - void shiftModsPriority(const QModelIndexList& indices, int offset); - - // change the priority of the mods specified by the given indices - // - void changeModsPriority(const QModelIndexList& indices, int priority); - - // toggle the active state of mods at the given indices - // - bool toggleState(const QModelIndexList& indices); - -signals: - - // emitted when the priority of one or multiple mods have changed - // - // the sorting of the list can only be manually changed if the list is sorted by priority - // in which case the move is intended to change the priority of a mod. - // - void modPrioritiesChanged(const QModelIndexList& indices) const; - - // emitted when the state (active/inactive) of one or multiple mods have changed - // - void modStatesChanged(const QModelIndexList& indices) const; - - /** - * @brief emitted when the model wants a text to be displayed by the UI - * - * @param message the message to display - **/ - void showMessage(const QString &message); - - /** - * @brief signals change to the count of headers - */ - void resizeHeaders(); - - /** - * @brief emitted to remove a file origin - * @param name name of the orign to remove - */ - void removeOrigin(const QString &name); - - /** - * @brief emitted after a mod has been renamed - * This signal MUST be used to fix the mod names in profiles (except the active one) and to invalidate/refresh other - * structures that may have become invalid with the rename - * - * @param oldName the old name of the mod - * @param newName new name of the mod - */ - void modRenamed(const QString &oldName, const QString &newName); - - /** - * @brief emitted after a mod has been uninstalled - * @param fileName filename of the mod being uninstalled - */ - void modUninstalled(const QString &fileName); - - /** - * @brief QML seems to handle overloaded signals poorly - create unique signal for tutorials - */ - void tutorialModlistUpdate(); - - /** - * @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); - - /** - * @brief emitted to have the overwrite folder cleared - */ - void clearOverwrite(); - - void aboutToChangeData(); - - void postDataChanged(); - - // emitted when an item is dropped from the download list, the row is from the - // download list - // - void downloadArchiveDropped(int row, int priority); - - // emitted when an external archive is dropped on the mod list - // - void externalArchiveDropped(const QUrl& url, int priority); - - // emitted when an external folder is dropped on the mod list - // - void externalFolderDropped(const QUrl& url, int priority); - -private: - - // retrieve the display name of a mod or convert from a user-provided - // name to internal name - // - QString getDisplayName(ModInfo::Ptr info) const; - QString makeInternalName(ModInfo::Ptr info, QString name) const; - - QString getFlagText(ModInfo::EFlag flag, ModInfo::Ptr modInfo) const; - - QString getConflictFlagText(ModInfo::EConflictFlag flag, ModInfo::Ptr modInfo) const; - - QString getColumnToolTip(int column) const; - - bool renameMod(int index, const QString &newName); - - MOBase::IModList::ModStates state(unsigned int modIndex) const; - - // handle dropping of local URLs files - // - bool dropLocalFiles(const ModListDropInfo& dropInfo, int row, const QModelIndex& parent); - - // return the priority of the mod for a drop event - // - int dropPriority(int row, const QModelIndex& parent) const; - -private: - - struct TModInfo { - TModInfo(unsigned int index, ModInfo::Ptr modInfo) - : modInfo(modInfo), nameOrder(index), priorityOrder(0), modIDOrder(0), categoryOrder(0) {} - ModInfo::Ptr modInfo; - unsigned int nameOrder; - unsigned int priorityOrder; - unsigned int modIDOrder; - unsigned int categoryOrder; - }; - - struct TModInfoChange { - QString name; - QFlags state; - }; - -private: - - OrganizerCore *m_Organizer; - Profile *m_Profile; - - NexusInterface *m_NexusInterface; - std::set m_RequestIDs; - - mutable bool m_Modified; - bool m_InNotifyChange; - bool m_DropOnMod = false; - - QFontMetrics m_FontMetrics; - - TModInfoChange m_ChangeInfo; - - SignalModInstalled m_ModInstalled; - SignalModMoved m_ModMoved; - SignalModRemoved m_ModRemoved; - SignalModStateChanged m_ModStateChanged; - - QElapsedTimer m_LastCheck; - - PluginContainer *m_PluginContainer; - -}; - -#endif // MODLIST_H - +/* +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 MODLIST_H +#define MODLIST_H + +#include "categories.h" +#include "moddatacontent.h" +#include "modinfo.h" +#include "nexusinterface.h" +#include "profile.h" + +#include + +#include +#include +#include +#include +#include +#ifndef Q_MOC_RUN +#include +#endif +#include +#include +#include + +class QSortFilterProxyModel; +class PluginContainer; +class OrganizerCore; +class ModListDropInfo; + +/** + * Model presenting an overview of the installed mod + * This is used in a view in the main window of MO. It combines general information + *about the mods from ModInfo with status information from the Profile + **/ +class ModList : public QAbstractItemModel +{ + Q_OBJECT + +public: + enum ModListRole + { + + // data(GroupingRole) contains the "group" role - This is used by the + // category and Nexus ID grouping proxy (but not the ByPriority proxy) + GroupingRole = Qt::UserRole, + + IndexRole = Qt::UserRole + 1, + + // data(AggrRole) contains aggregation information (for + // grouping I assume?) + AggrRole = Qt::UserRole + 2, + + GameNameRole = Qt::UserRole + 3, + PriorityRole = Qt::UserRole + 4, + + // marking role for the scrollbar + ScrollMarkRole = Qt::UserRole + 5, + + // this is the first available role + ModUserRole = Qt::UserRole + 6 + }; + + enum EColumn + { + COL_NAME, + COL_CONFLICTFLAGS, + COL_FLAGS, + COL_CONTENT, + COL_CATEGORY, + COL_MODID, + COL_GAME, + COL_VERSION, + COL_INSTALLTIME, + COL_PRIORITY, + COL_NOTES, + COL_LASTCOLUMN = COL_NOTES, + }; + + using SignalModInstalled = boost::signals2::signal; + using SignalModRemoved = boost::signals2::signal; + using SignalModStateChanged = boost::signals2::signal&)>; + using SignalModMoved = boost::signals2::signal; + +public: + /** + * @brief constructor + * @todo ensure this view works without a profile set, otherwise there are + *intransparent dependencies on the initialisation order + **/ + ModList(PluginContainer* pluginContainer, OrganizerCore* parent); + + ~ModList(); + + /** + * @brief set the profile used for status information + * + * @param profile the profile to use + **/ + void setProfile(Profile* profile); + + /** + * @brief retrieve the current sorting mode + * @note this is used to store the sorting mode between sessions + * @return current sorting mode, encoded to be compatible to previous versions + **/ + int getCurrentSortingMode() const; + + /** + * @brief remove the specified mod without asking for confirmation + * @param row the row to remove + */ + void removeRowForce(int row, const QModelIndex& parent); + + void notifyChange(int rowStart, int rowEnd = -1); + static QString getColumnName(int column); + + void changeModPriority(int sourceIndex, int newPriority); + void changeModPriority(std::vector sourceIndices, int newPriority); + + void setPluginContainer(PluginContainer* pluginContainer); + + bool modInfoAboutToChange(ModInfo::Ptr info); + void modInfoChanged(ModInfo::Ptr info); + + void disconnectSlots(); + + int timeElapsedSinceLastChecked() const; + +public: + /** + * @brief Notify the mod list that the given mod has been installed. This is used + * to notify the plugin that registered through onModInstalled(). + * + * @param mod The installed mod. + */ + void notifyModInstalled(MOBase::IModInterface* mod) const; + + /** + * @brief Notify the mod list that a mod has been removed. This is used + * to notify the plugin that registered through onModRemoved(). + * + * @param modName Name of the removed mod. + */ + void notifyModRemoved(QString const& modName) const; + + /** + * @brief Notify the mod list that the state of the specified mods has changed. This + * is used to notify the plugin that registered through onModStateChanged(). + * + * @param modIndices Indices of the mods that changed. + */ + void notifyModStateChanged(QList modIndices) const; + +public: + /// \copydoc MOBase::IModList::displayName + QString displayName(const QString& internalName) const; + + /// \copydoc MOBase::IModList::allMods + QStringList allMods() const; + QStringList allModsByProfilePriority(MOBase::IProfile* profile = nullptr) const; + + // \copydoc MOBase::IModList::getMod + MOBase::IModInterface* getMod(const QString& name) const; + + // \copydoc MOBase::IModList::remove + bool removeMod(MOBase::IModInterface* mod); + + // \copydoc MOBase::IModList::renameMod + MOBase::IModInterface* renameMod(MOBase::IModInterface* mod, const QString& name); + + /// \copydoc MOBase::IModList::state + MOBase::IModList::ModStates state(const QString& name) const; + + /// \copydoc MOBase::IModList::setActive + bool setActive(const QString& name, bool active); + + /// \copydoc MOBase::IModList::setActive + int setActive(const QStringList& names, bool active); + + /// \copydoc MOBase::IModList::priority + int priority(const QString& name) const; + + /// \copydoc MOBase::IModList::setPriority + bool setPriority(const QString& name, int newPriority); + + boost::signals2::connection + onModInstalled(const std::function& func); + boost::signals2::connection + onModRemoved(const std::function& func); + boost::signals2::connection onModStateChanged( + const std::function&)>& + func); + boost::signals2::connection + onModMoved(const std::function& func); + +public: // implementation of virtual functions of QAbstractItemModel + virtual int rowCount(const QModelIndex& parent = QModelIndex()) const; + virtual bool hasChildren(const QModelIndex& parent = QModelIndex()) const; + virtual int columnCount(const QModelIndex& parent = QModelIndex()) const; + virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; + virtual bool setData(const QModelIndex& index, const QVariant& value, + int role = Qt::EditRole); + virtual QVariant headerData(int section, Qt::Orientation orientation, + int role = Qt::DisplayRole) const; + virtual Qt::ItemFlags flags(const QModelIndex& modelIndex) const; + virtual bool removeRows(int row, int count, const QModelIndex& parent); + + Qt::DropActions supportedDropActions() const override + { + return Qt::MoveAction | Qt::CopyAction; + } + QStringList mimeTypes() const override; + QMimeData* mimeData(const QModelIndexList& indexes) const override; + bool canDropMimeData(const QMimeData* data, Qt::DropAction action, int row, + int column, const QModelIndex& parent) const override; + bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, + const QModelIndex& parent) override; + + virtual QModelIndex index(int row, int column, + const QModelIndex& parent = QModelIndex()) const; + virtual QModelIndex parent(const QModelIndex& child) const; + + virtual QMap itemData(const QModelIndex& index) const; + +public slots: + + void onDragEnter(const QMimeData* data); + + // enable/disable mods at the given indices. + // + void setActive(const QModelIndexList& indices, bool active); + + // shift the priority of mods at the given indices by the given offset + // + void shiftModsPriority(const QModelIndexList& indices, int offset); + + // change the priority of the mods specified by the given indices + // + void changeModsPriority(const QModelIndexList& indices, int priority); + + // toggle the active state of mods at the given indices + // + bool toggleState(const QModelIndexList& indices); + +signals: + + // emitted when the priority of one or multiple mods have changed + // + // the sorting of the list can only be manually changed if the list is sorted by + // priority in which case the move is intended to change the priority of a mod. + // + void modPrioritiesChanged(const QModelIndexList& indices) const; + + // emitted when the state (active/inactive) of one or multiple mods have changed + // + void modStatesChanged(const QModelIndexList& indices) const; + + /** + * @brief emitted when the model wants a text to be displayed by the UI + * + * @param message the message to display + **/ + void showMessage(const QString& message); + + /** + * @brief signals change to the count of headers + */ + void resizeHeaders(); + + /** + * @brief emitted to remove a file origin + * @param name name of the orign to remove + */ + void removeOrigin(const QString& name); + + /** + * @brief emitted after a mod has been renamed + * This signal MUST be used to fix the mod names in profiles (except the active one) + * and to invalidate/refresh other structures that may have become invalid with the + * rename + * + * @param oldName the old name of the mod + * @param newName new name of the mod + */ + void modRenamed(const QString& oldName, const QString& newName); + + /** + * @brief emitted after a mod has been uninstalled + * @param fileName filename of the mod being uninstalled + */ + void modUninstalled(const QString& fileName); + + /** + * @brief QML seems to handle overloaded signals poorly - create unique signal for + * tutorials + */ + void tutorialModlistUpdate(); + + /** + * @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); + + /** + * @brief emitted to have the overwrite folder cleared + */ + void clearOverwrite(); + + void aboutToChangeData(); + + void postDataChanged(); + + // emitted when an item is dropped from the download list, the row is from the + // download list + // + void downloadArchiveDropped(int row, int priority); + + // emitted when an external archive is dropped on the mod list + // + void externalArchiveDropped(const QUrl& url, int priority); + + // emitted when an external folder is dropped on the mod list + // + void externalFolderDropped(const QUrl& url, int priority); + +private: + // retrieve the display name of a mod or convert from a user-provided + // name to internal name + // + QString getDisplayName(ModInfo::Ptr info) const; + QString makeInternalName(ModInfo::Ptr info, QString name) const; + + QString getFlagText(ModInfo::EFlag flag, ModInfo::Ptr modInfo) const; + + QString getConflictFlagText(ModInfo::EConflictFlag flag, ModInfo::Ptr modInfo) const; + + QString getColumnToolTip(int column) const; + + bool renameMod(int index, const QString& newName); + + MOBase::IModList::ModStates state(unsigned int modIndex) const; + + // handle dropping of local URLs files + // + bool dropLocalFiles(const ModListDropInfo& dropInfo, int row, + const QModelIndex& parent); + + // return the priority of the mod for a drop event + // + int dropPriority(int row, const QModelIndex& parent) const; + +private: + struct TModInfo + { + TModInfo(unsigned int index, ModInfo::Ptr modInfo) + : modInfo(modInfo), nameOrder(index), priorityOrder(0), modIDOrder(0), + categoryOrder(0) + {} + ModInfo::Ptr modInfo; + unsigned int nameOrder; + unsigned int priorityOrder; + unsigned int modIDOrder; + unsigned int categoryOrder; + }; + + struct TModInfoChange + { + QString name; + QFlags state; + }; + +private: + OrganizerCore* m_Organizer; + Profile* m_Profile; + + NexusInterface* m_NexusInterface; + std::set m_RequestIDs; + + mutable bool m_Modified; + bool m_InNotifyChange; + bool m_DropOnMod = false; + + QFontMetrics m_FontMetrics; + + TModInfoChange m_ChangeInfo; + + SignalModInstalled m_ModInstalled; + SignalModMoved m_ModMoved; + SignalModRemoved m_ModRemoved; + SignalModStateChanged m_ModStateChanged; + + QElapsedTimer m_LastCheck; + + PluginContainer* m_PluginContainer; +}; + +#endif // MODLIST_H diff --git a/src/modlistbypriorityproxy.cpp b/src/modlistbypriorityproxy.cpp index 24e58ed0a..ba53ee749 100644 --- a/src/modlistbypriorityproxy.cpp +++ b/src/modlistbypriorityproxy.cpp @@ -1,20 +1,18 @@ #include "modlistbypriorityproxy.h" +#include "log.h" #include "modinfo.h" -#include "profile.h" -#include "organizercore.h" #include "modlist.h" #include "modlistdropinfo.h" -#include "log.h" +#include "organizercore.h" +#include "profile.h" -ModListByPriorityProxy::ModListByPriorityProxy(Profile* profile, OrganizerCore& core, QObject* parent) : - QAbstractProxyModel(parent), m_core(core), m_profile(profile) -{ -} +ModListByPriorityProxy::ModListByPriorityProxy(Profile* profile, OrganizerCore& core, + QObject* parent) + : QAbstractProxyModel(parent), m_core(core), m_profile(profile) +{} -ModListByPriorityProxy::~ModListByPriorityProxy() -{ -} +ModListByPriorityProxy::~ModListByPriorityProxy() {} void ModListByPriorityProxy::setSourceModel(QAbstractItemModel* model) { @@ -25,10 +23,14 @@ void ModListByPriorityProxy::setSourceModel(QAbstractItemModel* model) QAbstractProxyModel::setSourceModel(model); if (sourceModel()) { - connect(sourceModel(), &QAbstractItemModel::layoutChanged, this, &ModListByPriorityProxy::onModelLayoutChanged, Qt::UniqueConnection); - connect(sourceModel(), &QAbstractItemModel::rowsRemoved, this, &ModListByPriorityProxy::onModelRowsRemoved, Qt::UniqueConnection); - connect(sourceModel(), &QAbstractItemModel::modelReset, this, &ModListByPriorityProxy::onModelReset, Qt::UniqueConnection); - connect(sourceModel(), &QAbstractItemModel::dataChanged, this, &ModListByPriorityProxy::onModelDataChanged, Qt::UniqueConnection); + connect(sourceModel(), &QAbstractItemModel::layoutChanged, this, + &ModListByPriorityProxy::onModelLayoutChanged, Qt::UniqueConnection); + connect(sourceModel(), &QAbstractItemModel::rowsRemoved, this, + &ModListByPriorityProxy::onModelRowsRemoved, Qt::UniqueConnection); + connect(sourceModel(), &QAbstractItemModel::modelReset, this, + &ModListByPriorityProxy::onModelReset, Qt::UniqueConnection); + connect(sourceModel(), &QAbstractItemModel::dataChanged, this, + &ModListByPriorityProxy::onModelDataChanged, Qt::UniqueConnection); onModelReset(); } @@ -51,16 +53,18 @@ void ModListByPriorityProxy::buildMapping() { m_IndexToItem.clear(); for (unsigned int index = 0; index < ModInfo::getNumMods(); ++index) { - m_IndexToItem[index] = std::make_unique(ModInfo::getByIndex(index), index); + m_IndexToItem[index] = + std::make_unique(ModInfo::getByIndex(index), index); } } void ModListByPriorityProxy::buildTree() { - if (!sourceModel()) return; + if (!sourceModel()) + return; // reset the root - m_Root = { }; + m_Root = {}; // clear all children for (auto& [index, item] : m_IndexToItem) { @@ -73,25 +77,22 @@ void ModListByPriorityProxy::buildTree() auto fn = [&](const auto& p) { auto& [priority, index] = p; - ModInfo::Ptr modInfo = ModInfo::getByIndex(index); - TreeItem* item = m_IndexToItem[index].get(); + ModInfo::Ptr modInfo = ModInfo::getByIndex(index); + TreeItem* item = m_IndexToItem[index].get(); if (modInfo->isSeparator()) { item->parent = &m_Root; m_Root.children.push_back(item); root = item; - } - else if (modInfo->isOverwrite()) { + } else if (modInfo->isOverwrite()) { // do not push here, because the overwrite is usually not at the right position item->parent = &m_Root; - overwrite = item; - } - else if (modInfo->isBackup()) { + overwrite = item; + } else if (modInfo->isBackup()) { // do not push here, because backups are usually not at the right position item->parent = &m_Root; backups.push_back(item); - } - else { + } else { item->parent = root; root->children.push_back(item); } @@ -101,23 +102,26 @@ void ModListByPriorityProxy::buildTree() if (m_sortOrder == Qt::AscendingOrder) { std::for_each(ibp.begin(), ibp.end(), fn); m_Root.children.insert(m_Root.children.begin(), - std::make_move_iterator(backups.begin()), std::make_move_iterator(backups.end())); + std::make_move_iterator(backups.begin()), + std::make_move_iterator(backups.end())); m_Root.children.push_back(std::move(overwrite)); - } - else { + } else { std::for_each(ibp.rbegin(), ibp.rend(), fn); m_Root.children.insert(m_Root.children.begin(), std::move(overwrite)); m_Root.children.insert(m_Root.children.end(), - std::make_move_iterator(backups.begin()), std::make_move_iterator(backups.end())); + std::make_move_iterator(backups.begin()), + std::make_move_iterator(backups.end())); } } -void ModListByPriorityProxy::onModelRowsRemoved(const QModelIndex& parent, int first, int last) +void ModListByPriorityProxy::onModelRowsRemoved(const QModelIndex& parent, int first, + int last) { onModelReset(); } -void ModListByPriorityProxy::onModelLayoutChanged(const QList&, LayoutChangeHint hint) +void ModListByPriorityProxy::onModelLayoutChanged(const QList&, + LayoutChangeHint hint) { emit layoutAboutToBeChanged(); auto persistent = persistentIndexList(); @@ -127,7 +131,8 @@ void ModListByPriorityProxy::onModelLayoutChanged(const QList(idx.internalPointer()); - toPersistent.append(createIndex(item->parent->childIndex(item), idx.column(), item)); + toPersistent.append( + createIndex(item->parent->childIndex(item), idx.column(), item)); } changePersistentIndexList(persistent, toPersistent); @@ -142,7 +147,9 @@ void ModListByPriorityProxy::onModelReset() endResetModel(); } -void ModListByPriorityProxy::onModelDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight, const QVector& roles) +void ModListByPriorityProxy::onModelDataChanged(const QModelIndex& topLeft, + const QModelIndex& bottomRight, + const QVector& roles) { QModelIndex proxyTopLeft = mapFromSource(topLeft); if (!proxyTopLeft.isValid()) { @@ -151,8 +158,7 @@ void ModListByPriorityProxy::onModelDataChanged(const QModelIndex& topLeft, cons if (topLeft == bottomRight) { emit dataChanged(proxyTopLeft, proxyTopLeft); - } - else { + } else { QModelIndex proxyBottomRight = mapFromSource(bottomRight); emit dataChanged(proxyTopLeft, proxyBottomRight); } @@ -222,7 +228,9 @@ bool ModListByPriorityProxy::hasChildren(const QModelIndex& parent) const return item->children.size() > 0; } -bool ModListByPriorityProxy::canDropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) const +bool ModListByPriorityProxy::canDropMimeData(const QMimeData* data, + Qt::DropAction action, int row, int column, + const QModelIndex& parent) const { ModListDropInfo dropInfo(data, m_core); @@ -231,23 +239,26 @@ bool ModListByPriorityProxy::canDropMimeData(const QMimeData* data, Qt::DropActi } if (dropInfo.isModDrop()) { - bool hasSeparator = false; + bool hasSeparator = false; unsigned int firstRowIndex = -1; int firstRowPriority = Profile::MaximumPriority; for (auto sourceRow : dropInfo.rows()) { hasSeparator = hasSeparator || ModInfo::getByIndex(sourceRow)->isSeparator(); - if (m_sortOrder == Qt::AscendingOrder && m_profile->getModPriority(sourceRow) < firstRowPriority - || m_sortOrder == Qt::DescendingOrder && m_profile->getModPriority(sourceRow) > firstRowPriority) { - firstRowIndex = sourceRow; + if (m_sortOrder == Qt::AscendingOrder && + m_profile->getModPriority(sourceRow) < firstRowPriority || + m_sortOrder == Qt::DescendingOrder && + m_profile->getModPriority(sourceRow) > firstRowPriority) { + firstRowIndex = sourceRow; firstRowPriority = m_profile->getModPriority(sourceRow); } } - bool firstRowSeparator = firstRowIndex != -1 && ModInfo::getByIndex(firstRowIndex)->isSeparator(); + bool firstRowSeparator = + firstRowIndex != -1 && ModInfo::getByIndex(firstRowIndex)->isSeparator(); - // row = -1 and valid parent means we're dropping onto an item, we don't want to drop - // separators onto items or items into their own separator + // row = -1 and valid parent means we're dropping onto an item, we don't want to + // drop separators onto items or items into their own separator if (row == -1 && parent.isValid()) { auto* parentItem = static_cast(parent.internalPointer()); if (hasSeparator) { @@ -276,7 +287,9 @@ bool ModListByPriorityProxy::canDropMimeData(const QMimeData* data, Qt::DropActi return QAbstractProxyModel::canDropMimeData(data, action, row, column, parent); } -bool ModListByPriorityProxy::dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) +bool ModListByPriorityProxy::dropMimeData(const QMimeData* data, Qt::DropAction action, + int row, int column, + const QModelIndex& parent) { // we need to fix the source model row if we are dropping at a // given priority (not a local file) @@ -287,8 +300,7 @@ bool ModListByPriorityProxy::dropMimeData(const QMimeData* data, Qt::DropAction if (parent.isValid()) { sourceRow = static_cast(parent.internalPointer())->index; } - } - else { + } else { if (row >= 0) { if (!parent.isValid()) { @@ -301,31 +313,26 @@ bool ModListByPriorityProxy::dropMimeData(const QMimeData* data, Qt::DropAction // // by default, Qt consider it's a drop at the end of that separator // but we want to make it a drop at the beginning - if (row > 0 - && m_sortOrder == Qt::AscendingOrder - && m_Root.children[row - 1]->mod->isSeparator() - && !m_Root.children[row - 1]->children.empty() - && m_dropExpanded - && m_dropPosition == ModListView::DropPosition::BelowItem) { + if (row > 0 && m_sortOrder == Qt::AscendingOrder && + m_Root.children[row - 1]->mod->isSeparator() && + !m_Root.children[row - 1]->children.empty() && m_dropExpanded && + m_dropPosition == ModListView::DropPosition::BelowItem) { sourceRow = m_Root.children[row - 1]->children[0]->index; } - } - else { + } else { sourceRow = m_Root.children[row - 1]->index; // fix drop below a collapsed separator or at the end of an expanded // separator, above the next item - if (row > 0 - && m_sortOrder == Qt::DescendingOrder - && m_Root.children[row - 1]->mod->isSeparator() - && !m_Root.children[row - 1]->children.empty() - && (!m_dropExpanded - || m_dropPosition == ModListView::DropPosition::AboveItem)) { + if (row > 0 && m_sortOrder == Qt::DescendingOrder && + m_Root.children[row - 1]->mod->isSeparator() && + !m_Root.children[row - 1]->children.empty() && + (!m_dropExpanded || + m_dropPosition == ModListView::DropPosition::AboveItem)) { sourceRow = m_Root.children[row - 1]->children.back()->index; } } - } - else { + } else { sourceRow = ModInfo::getNumMods(); } } @@ -336,11 +343,10 @@ bool ModListByPriorityProxy::dropMimeData(const QMimeData* data, Qt::DropAction // we usually need to decrement the row in descending priority, but if // it's the first row, we need to go back to the separator itself - if (m_sortOrder == Qt::DescendingOrder - && row == 0 && m_dropPosition == ModListView::DropPosition::AboveItem) { + if (m_sortOrder == Qt::DescendingOrder && row == 0 && + m_dropPosition == ModListView::DropPosition::AboveItem) { sourceRow = item->index; - } - else { + } else { // in descending priority, we decrement the row to fix the drop position // because this is not done by the sort proxy for us @@ -350,13 +356,10 @@ bool ModListByPriorityProxy::dropMimeData(const QMimeData* data, Qt::DropAction if (row < item->children.size()) { sourceRow = item->children[row]->index; - } - else if (parent.row() + 1 < m_Root.children.size()) { + } else if (parent.row() + 1 < m_Root.children.size()) { sourceRow = m_Root.children[parent.row() + 1]->index; } } - - } } @@ -387,7 +390,8 @@ bool ModListByPriorityProxy::dropMimeData(const QMimeData* data, Qt::DropAction return sourceModel()->dropMimeData(data, action, sourceRow, column, QModelIndex()); } -QModelIndex ModListByPriorityProxy::index(int row, int column, const QModelIndex& parent) const +QModelIndex ModListByPriorityProxy::index(int row, int column, + const QModelIndex& parent) const { if (!hasIndex(row, column, parent)) { return QModelIndex(); @@ -396,15 +400,15 @@ QModelIndex ModListByPriorityProxy::index(int row, int column, const QModelIndex const TreeItem* parentItem; if (!parent.isValid()) { parentItem = &m_Root; - } - else { + } else { parentItem = static_cast(parent.internalPointer()); } return createIndex(row, column, parentItem->children[row]); } -void ModListByPriorityProxy::onDropEnter(const QMimeData*, bool dropExpanded, ModListView::DropPosition dropPosition) +void ModListByPriorityProxy::onDropEnter(const QMimeData*, bool dropExpanded, + ModListView::DropPosition dropPosition) { m_dropExpanded = dropExpanded; m_dropPosition = dropPosition; diff --git a/src/modlistbypriorityproxy.h b/src/modlistbypriorityproxy.h index 0629bef7e..5617d2222 100644 --- a/src/modlistbypriorityproxy.h +++ b/src/modlistbypriorityproxy.h @@ -6,11 +6,11 @@ #include #include +#include #include #include -#include -#include #include +#include #include "modinfo.h" #include "modlistview.h" @@ -24,7 +24,8 @@ class ModListByPriorityProxy : public QAbstractProxyModel Q_OBJECT public: - explicit ModListByPriorityProxy(Profile* profile, OrganizerCore& core, QObject* parent = nullptr); + explicit ModListByPriorityProxy(Profile* profile, OrganizerCore& core, + QObject* parent = nullptr); ~ModListByPriorityProxy(); void setProfile(Profile* profile); @@ -37,29 +38,35 @@ class ModListByPriorityProxy : public QAbstractProxyModel int rowCount(const QModelIndex& parent = QModelIndex()) const override; QModelIndex parent(const QModelIndex& child) const override; - QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override; + QModelIndex index(int row, int column, + const QModelIndex& parent = QModelIndex()) const override; int columnCount(const QModelIndex& index) const override; bool hasChildren(const QModelIndex& parent) const override; - bool canDropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) const override; - bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) override; + bool canDropMimeData(const QMimeData* data, Qt::DropAction action, int row, + int column, const QModelIndex& parent) const override; + bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, + const QModelIndex& parent) override; QModelIndex mapFromSource(const QModelIndex& sourceIndex) const override; QModelIndex mapToSource(const QModelIndex& proxyIndex) const override; public slots: - void onDropEnter(const QMimeData* data, bool dropExpanded, ModListView::DropPosition dropPosition); + void onDropEnter(const QMimeData* data, bool dropExpanded, + ModListView::DropPosition dropPosition); protected slots: void onModelRowsRemoved(const QModelIndex& parent, int first, int last); - void onModelLayoutChanged(const QList& parents = {}, LayoutChangeHint hint = LayoutChangeHint::NoLayoutChangeHint); + void + onModelLayoutChanged(const QList& parents = {}, + LayoutChangeHint hint = LayoutChangeHint::NoLayoutChangeHint); void onModelReset(); - void onModelDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight, const QVector& roles = QVector()); + void onModelDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight, + const QVector& roles = QVector()); private: - // fill the mapping from index to item (required by buildTree), should // only be used for full model reset because it destroys existing references // to tree items @@ -71,13 +78,15 @@ protected slots: // void buildTree(); - struct TreeItem { + struct TreeItem + { ModInfo::Ptr mod; unsigned int index; std::vector children; TreeItem* parent; - std::size_t childIndex(TreeItem* child) const { + std::size_t childIndex(TreeItem* child) const + { for (std::size_t i = 0; i < children.size(); ++i) { if (children[i] == child) { return i; @@ -86,9 +95,10 @@ protected slots: return -1; } - TreeItem() : TreeItem(nullptr, -1) { } - TreeItem(ModInfo::Ptr mod, unsigned int index, TreeItem* parent = nullptr) : - mod(mod), index(index), parent(parent) { } + TreeItem() : TreeItem(nullptr, -1) {} + TreeItem(ModInfo::Ptr mod, unsigned int index, TreeItem* parent = nullptr) + : mod(mod), index(index), parent(parent) + {} }; TreeItem m_Root; @@ -98,9 +108,9 @@ protected slots: OrganizerCore& m_core; Profile* m_profile; - Qt::SortOrder m_sortOrder = Qt::AscendingOrder; - bool m_dropExpanded = false; + Qt::SortOrder m_sortOrder = Qt::AscendingOrder; + bool m_dropExpanded = false; ModListView::DropPosition m_dropPosition = ModListView::DropPosition::OnItem; }; -#endif //GROUPINGPROXY_H +#endif // GROUPINGPROXY_H diff --git a/src/modlistcontextmenu.cpp b/src/modlistcontextmenu.cpp index ecde940f8..28f34b2c3 100644 --- a/src/modlistcontextmenu.cpp +++ b/src/modlistcontextmenu.cpp @@ -9,18 +9,24 @@ using namespace MOBase; -ModListGlobalContextMenu::ModListGlobalContextMenu(OrganizerCore& core, ModListView* view, QWidget* parent) - : ModListGlobalContextMenu(core, view, QModelIndex(), parent) +ModListGlobalContextMenu::ModListGlobalContextMenu(OrganizerCore& core, + ModListView* view, QWidget* parent) + : ModListGlobalContextMenu(core, view, QModelIndex(), parent) +{} + +ModListGlobalContextMenu::ModListGlobalContextMenu(OrganizerCore& core, + ModListView* view, + const QModelIndex& index, + QWidget* parent) + : QMenu(parent) { + connect(this, &QMenu::aboutToShow, [=, &core] { + populate(core, view, index); + }); } -ModListGlobalContextMenu::ModListGlobalContextMenu(OrganizerCore& core, ModListView* view, const QModelIndex& index, QWidget* parent) - : QMenu(parent) -{ - connect(this, &QMenu::aboutToShow, [=, &core] { populate(core, view, index); }); -} - -void ModListGlobalContextMenu::populate(OrganizerCore& core, ModListView* view, const QModelIndex& index) +void ModListGlobalContextMenu::populate(OrganizerCore& core, ModListView* view, + const QModelIndex& index) { clear(); @@ -32,25 +38,35 @@ void ModListGlobalContextMenu::populate(OrganizerCore& core, ModListView* view, // the mod are not created/installed at the same position depending // on the clicked mod and the sort order QString installText = tr("Install mod above... "); - QString createText = tr("Create empty mod above"); + QString createText = tr("Create empty mod above"); if (info->isSeparator()) { installText = tr("Install mod inside... "); - createText = tr("Create empty mod inside"); - } - else if (view->sortOrder() == Qt::DescendingOrder) { + createText = tr("Create empty mod inside"); + } else if (view->sortOrder() == Qt::DescendingOrder) { installText = tr("Install mod below... "); - createText = tr("Create empty mod below"); + createText = tr("Create empty mod below"); } - addAction(installText, [=]() { view->actions().installMod("", index); }); - addAction(createText, [=]() { view->actions().createEmptyMod(index); }); - addAction(tr("Create separator above"), [=]() { view->actions().createSeparator(index); }); + addAction(installText, [=]() { + view->actions().installMod("", index); + }); + addAction(createText, [=]() { + view->actions().createEmptyMod(index); + }); + addAction(tr("Create separator above"), [=]() { + view->actions().createSeparator(index); + }); } - } - else { - addAction(tr("Install mod..."), [=]() { view->actions().installMod(); }); - addAction(tr("Create empty mod"), [=]() { view->actions().createEmptyMod(); }); - addAction(tr("Create separator"), [=]() { view->actions().createSeparator(); }); + } else { + addAction(tr("Install mod..."), [=]() { + view->actions().installMod(); + }); + addAction(tr("Create empty mod"), [=]() { + view->actions().createEmptyMod(); + }); + addAction(tr("Create separator"), [=]() { + view->actions().createSeparator(); + }); } if (view->hasCollapsibleSeparators()) { @@ -61,26 +77,35 @@ void ModListGlobalContextMenu::populate(OrganizerCore& core, ModListView* view, addSeparator(); - QString enableTxt = tr("Enable all"), - disableTxt = tr("Disable all"); + QString enableTxt = tr("Enable all"), disableTxt = tr("Disable all"); if (view->isFilterActive()) { - enableTxt = tr("Enable all matching mods"); + enableTxt = tr("Enable all matching mods"); disableTxt = tr("Disable all matching mods"); } - addAction(enableTxt, [=] { view->actions().setAllMatchingModsEnabled(true); }); - addAction(disableTxt, [=] { view->actions().setAllMatchingModsEnabled(false); }); + addAction(enableTxt, [=] { + view->actions().setAllMatchingModsEnabled(true); + }); + addAction(disableTxt, [=] { + view->actions().setAllMatchingModsEnabled(false); + }); - addAction(tr("Check for updates"), [=]() { view->actions().checkModsForUpdates(); }); - addAction(tr("Auto assign categories"), [=]() { view->actions().assignCategories(); }); + addAction(tr("Check for updates"), [=]() { + view->actions().checkModsForUpdates(); + }); + addAction(tr("Auto assign categories"), [=]() { + view->actions().assignCategories(); + }); addAction(tr("Refresh"), &core, &OrganizerCore::profileRefresh); - addAction(tr("Export to csv..."), [=]() { view->actions().exportModListCSV(); }); + addAction(tr("Export to csv..."), [=]() { + view->actions().exportModListCSV(); + }); } - -ModListChangeCategoryMenu::ModListChangeCategoryMenu(CategoryFactory* categories, ModInfo::Ptr mod, QMenu* parent) - : QMenu(tr("Change Categories"), parent) +ModListChangeCategoryMenu::ModListChangeCategoryMenu(CategoryFactory* categories, + ModInfo::Ptr mod, QMenu* parent) + : QMenu(tr("Change Categories"), parent) { populate(this, categories, mod); } @@ -90,15 +115,15 @@ std::vector> ModListChangeCategoryMenu::categories() const return categories(this); } -std::vector> ModListChangeCategoryMenu::categories(const QMenu* menu) const +std::vector> +ModListChangeCategoryMenu::categories(const QMenu* menu) const { std::vector> cats; for (QAction* action : menu->actions()) { if (action->menu() != nullptr) { auto pcats = categories(action->menu()); cats.insert(cats.end(), pcats.begin(), pcats.end()); - } - else { + } else { QWidgetAction* widgetAction = qobject_cast(action); if (widgetAction != nullptr) { QCheckBox* checkbox = qobject_cast(widgetAction->defaultWidget()); @@ -109,7 +134,8 @@ std::vector> ModListChangeCategoryMenu::categories(const QM return cats; } -bool ModListChangeCategoryMenu::populate(QMenu* menu, CategoryFactory* factory, ModInfo::Ptr mod, int targetId) +bool ModListChangeCategoryMenu::populate(QMenu* menu, CategoryFactory* factory, + ModInfo::Ptr mod, int targetId) { const std::set& categories = mod->getCategories(); @@ -146,28 +172,30 @@ bool ModListChangeCategoryMenu::populate(QMenu* menu, CategoryFactory* factory, return childEnabled; } -ModListPrimaryCategoryMenu::ModListPrimaryCategoryMenu(CategoryFactory *categories, ModInfo::Ptr mod, QMenu* parent) - : QMenu(tr("Primary Category"), parent) +ModListPrimaryCategoryMenu::ModListPrimaryCategoryMenu(CategoryFactory* categories, + ModInfo::Ptr mod, QMenu* parent) + : QMenu(tr("Primary Category"), parent) { - connect(this, &QMenu::aboutToShow, [=]() { populate(categories, mod); }); + connect(this, &QMenu::aboutToShow, [=]() { + populate(categories, mod); + }); } -void ModListPrimaryCategoryMenu::populate(const CategoryFactory *factory, ModInfo::Ptr mod) +void ModListPrimaryCategoryMenu::populate(const CategoryFactory* factory, + ModInfo::Ptr mod) { clear(); const std::set& categories = mod->getCategories(); for (int categoryID : categories) { - int catIdx = factory->getCategoryIndex(categoryID); + int catIdx = factory->getCategoryIndex(categoryID); QWidgetAction* action = new QWidgetAction(this); try { - QRadioButton* categoryBox = new QRadioButton( - factory->getCategoryName(catIdx).replace('&', "&&"), - this); + QRadioButton* categoryBox = + new QRadioButton(factory->getCategoryName(catIdx).replace('&', "&&"), this); categoryBox->setChecked(categoryID == mod->primaryCategory()); action->setDefaultWidget(categoryBox); action->setData(categoryID); - } - catch (const std::exception& e) { + } catch (const std::exception& e) { log::error("failed to create category checkbox: {}", e.what()); } @@ -190,25 +218,22 @@ int ModListPrimaryCategoryMenu::primaryCategory() const return -1; } -ModListContextMenu::ModListContextMenu( - const QModelIndex& index, OrganizerCore& core, CategoryFactory* categories, ModListView* view) : - QMenu(view) - , m_core(core) - , m_categories(categories) - , m_index(index.model() == view->model() ? view->indexViewToModel(index) : index) - , m_view(view) - , m_actions(view->actions()) +ModListContextMenu::ModListContextMenu(const QModelIndex& index, OrganizerCore& core, + CategoryFactory* categories, ModListView* view) + : QMenu(view), m_core(core), m_categories(categories), + m_index(index.model() == view->model() ? view->indexViewToModel(index) : index), + m_view(view), m_actions(view->actions()) { if (view->selectionModel()->hasSelection()) { m_selected = view->indexViewToModel(view->selectionModel()->selectedRows()); - } - else { - m_selected = { index }; + } else { + m_selected = {index}; } ModInfo::Ptr info = ModInfo::getByIndex(index.data(ModList::IndexRole).toInt()); - QMenu* allMods = new ModListGlobalContextMenu(core, view, m_index, view->topLevelWidget()); + QMenu* allMods = + new ModListGlobalContextMenu(core, view, m_index, view->topLevelWidget()); allMods->setTitle(tr("All Mods")); addMenu(allMods); @@ -229,23 +254,21 @@ ModListContextMenu::ModListContextMenu( // Add type-specific items if (info->isOverwrite()) { addOverwriteActions(info); - } - else if (info->isBackup()) { + } else if (info->isBackup()) { addBackupActions(info); - } - else if (info->isSeparator()) { + } else if (info->isSeparator()) { addSeparatorActions(info); - } - else if (info->isForeign()) { + } else if (info->isForeign()) { addForeignActions(info); - } - else { + } else { addRegularActions(info); } // add information for all except foreign if (!info->isForeign()) { - QAction* infoAction = addAction(tr("Information..."), [=]() { view->actions().displayModInformation(m_index.data(ModList::IndexRole).toInt()); }); + QAction* infoAction = addAction(tr("Information..."), [=]() { + view->actions().displayModInformation(m_index.data(ModList::IndexRole).toInt()); + }); setDefaultAction(infoAction); } } @@ -262,28 +285,26 @@ void ModListContextMenu::addMenuAsPushButton(QMenu* menu) void ModListContextMenu::addSendToContextMenu() { static const std::vector overwritten_flags{ - ModInfo::EConflictFlag::FLAG_CONFLICT_MIXED, - ModInfo::EConflictFlag::FLAG_CONFLICT_OVERWRITTEN, - ModInfo::EConflictFlag::FLAG_CONFLICT_REDUNDANT - }; + ModInfo::EConflictFlag::FLAG_CONFLICT_MIXED, + ModInfo::EConflictFlag::FLAG_CONFLICT_OVERWRITTEN, + ModInfo::EConflictFlag::FLAG_CONFLICT_REDUNDANT}; static const std::vector overwrite_flags{ - ModInfo::EConflictFlag::FLAG_CONFLICT_MIXED, - ModInfo::EConflictFlag::FLAG_CONFLICT_OVERWRITE - }; + ModInfo::EConflictFlag::FLAG_CONFLICT_MIXED, + ModInfo::EConflictFlag::FLAG_CONFLICT_OVERWRITE}; bool overwrite = false, overwritten = false; for (auto& idx : m_selected) { auto index = idx.data(ModList::IndexRole); if (index.isValid()) { - auto info = ModInfo::getByIndex(index.toInt()); + auto info = ModInfo::getByIndex(index.toInt()); auto flags = info->getConflictFlags(); - if (std::find_first_of(flags.begin(), flags.end(), - overwritten_flags.begin(), overwritten_flags.end()) != flags.end()) { + if (std::find_first_of(flags.begin(), flags.end(), overwritten_flags.begin(), + overwritten_flags.end()) != flags.end()) { overwritten = true; } - if (std::find_first_of(flags.begin(), flags.end(), - overwrite_flags.begin(), overwrite_flags.end()) != flags.end()) { + if (std::find_first_of(flags.begin(), flags.end(), overwrite_flags.begin(), + overwrite_flags.end()) != flags.end()) { overwrite = true; } } @@ -291,28 +312,42 @@ void ModListContextMenu::addSendToContextMenu() QMenu* menu = new QMenu(m_view); menu->setTitle(tr("Send to... ")); - menu->addAction(tr("Lowest priority"), [this] { m_actions.sendModsToTop(m_selected); }); - menu->addAction(tr("Highest priority"), [this] { m_actions.sendModsToBottom(m_selected); }); - menu->addAction(tr("Priority..."), [this] { m_actions.sendModsToPriority(m_selected); }); - menu->addAction(tr("Separator..."), [this] { m_actions.sendModsToSeparator(m_selected); }); + menu->addAction(tr("Lowest priority"), [this] { + m_actions.sendModsToTop(m_selected); + }); + menu->addAction(tr("Highest priority"), [this] { + m_actions.sendModsToBottom(m_selected); + }); + menu->addAction(tr("Priority..."), [this] { + m_actions.sendModsToPriority(m_selected); + }); + menu->addAction(tr("Separator..."), [this] { + m_actions.sendModsToSeparator(m_selected); + }); if (overwrite) { - menu->addAction(tr("First conflict"), [this] { m_actions.sendModsToFirstConflict(m_selected); }); + menu->addAction(tr("First conflict"), [this] { + m_actions.sendModsToFirstConflict(m_selected); + }); } if (overwritten) { - menu->addAction(tr("Last conflict"), [this] { m_actions.sendModsToLastConflict(m_selected); }); + menu->addAction(tr("Last conflict"), [this] { + m_actions.sendModsToLastConflict(m_selected); + }); } addMenu(menu); } void ModListContextMenu::addCategoryContextMenus(ModInfo::Ptr mod) { - ModListChangeCategoryMenu* categoriesMenu = new ModListChangeCategoryMenu(m_categories, mod, this); + ModListChangeCategoryMenu* categoriesMenu = + new ModListChangeCategoryMenu(m_categories, mod, this); connect(categoriesMenu, &QMenu::aboutToHide, [=]() { m_actions.setCategories(m_selected, m_index, categoriesMenu->categories()); }); addMenuAsPushButton(categoriesMenu); - ModListPrimaryCategoryMenu* primaryCategoryMenu = new ModListPrimaryCategoryMenu(m_categories, mod, this); + ModListPrimaryCategoryMenu* primaryCategoryMenu = + new ModListPrimaryCategoryMenu(m_categories, mod, this); connect(primaryCategoryMenu, &QMenu::aboutToHide, [=]() { int category = primaryCategoryMenu->primaryCategory(); if (category != -1) { @@ -325,12 +360,22 @@ void ModListContextMenu::addCategoryContextMenus(ModInfo::Ptr mod) void ModListContextMenu::addOverwriteActions(ModInfo::Ptr mod) { if (QDir(mod->absolutePath()).count() > 2) { - addAction(tr("Sync to Mods..."), [=]() { m_core.syncOverwrite(); }); - addAction(tr("Create Mod..."), [=]() { m_actions.createModFromOverwrite(); }); - addAction(tr("Move content to Mod..."), [=]() { m_actions.moveOverwriteContentToExistingMod(); }); - addAction(tr("Clear Overwrite..."), [=]() { m_actions.clearOverwrite(); }); + addAction(tr("Sync to Mods..."), [=]() { + m_core.syncOverwrite(); + }); + addAction(tr("Create Mod..."), [=]() { + m_actions.createModFromOverwrite(); + }); + addAction(tr("Move content to Mod..."), [=]() { + m_actions.moveOverwriteContentToExistingMod(); + }); + addAction(tr("Clear Overwrite..."), [=]() { + m_actions.clearOverwrite(); + }); } - addAction(tr("Open in Explorer"), [=]() { m_actions.openExplorer(m_selected); }); + addAction(tr("Open in Explorer"), [=]() { + m_actions.openExplorer(m_selected); + }); } void ModListContextMenu::addSeparatorActions(ModInfo::Ptr mod) @@ -338,19 +383,26 @@ void ModListContextMenu::addSeparatorActions(ModInfo::Ptr mod) addCategoryContextMenus(mod); addSeparator(); - - addAction(tr("Rename Separator..."), [=]() { m_actions.renameMod(m_index); }); - addAction(tr("Remove Separator..."), [=]() { m_actions.removeMods(m_selected); }); + addAction(tr("Rename Separator..."), [=]() { + m_actions.renameMod(m_index); + }); + addAction(tr("Remove Separator..."), [=]() { + m_actions.removeMods(m_selected); + }); addSeparator(); if (m_view->sortColumn() == ModList::COL_PRIORITY) { addSendToContextMenu(); addSeparator(); } - addAction(tr("Select Color..."), [=]() { m_actions.setColor(m_selected, m_index); }); + addAction(tr("Select Color..."), [=]() { + m_actions.setColor(m_selected, m_index); + }); if (mod->color().isValid()) { - addAction(tr("Reset Color"), [=]() { m_actions.resetColor(m_selected); }); + addAction(tr("Reset Color"), [=]() { + m_actions.resetColor(m_selected); + }); } addSeparator(); @@ -366,26 +418,41 @@ void ModListContextMenu::addForeignActions(ModInfo::Ptr mod) void ModListContextMenu::addBackupActions(ModInfo::Ptr mod) { auto flags = mod->getFlags(); - addAction(tr("Restore Backup"), [=]() { m_actions.restoreBackup(m_index); }); - addAction(tr("Remove Backup..."), [=]() { m_actions.removeMods(m_selected); }); + addAction(tr("Restore Backup"), [=]() { + m_actions.restoreBackup(m_index); + }); + addAction(tr("Remove Backup..."), [=]() { + m_actions.removeMods(m_selected); + }); addSeparator(); if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_INVALID) != flags.end()) { - addAction(tr("Ignore missing data"), [=]() { m_actions.ignoreMissingData(m_selected); }); + addAction(tr("Ignore missing data"), [=]() { + m_actions.ignoreMissingData(m_selected); + }); } - if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_ALTERNATE_GAME) != flags.end()) { - addAction(tr("Mark as converted/working"), [=]() { m_actions.markConverted(m_selected); }); + if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_ALTERNATE_GAME) != + flags.end()) { + addAction(tr("Mark as converted/working"), [=]() { + m_actions.markConverted(m_selected); + }); } addSeparator(); if (mod->nexusId() > 0) { - addAction(tr("Visit on Nexus"), [=]() { m_actions.visitOnNexus(m_selected); }); + addAction(tr("Visit on Nexus"), [=]() { + m_actions.visitOnNexus(m_selected); + }); } const auto url = mod->parseCustomURL(); if (url.isValid()) { - addAction(tr("Visit on %1").arg(url.host()), [=]() { m_actions.visitWebPage(m_selected); }); + addAction(tr("Visit on %1").arg(url.host()), [=]() { + m_actions.visitWebPage(m_selected); + }); } - addAction(tr("Open in Explorer"), [=]() { m_actions.openExplorer(m_selected); }); + addAction(tr("Open in Explorer"), [=]() { + m_actions.openExplorer(m_selected); + }); } void ModListContextMenu::addRegularActions(ModInfo::Ptr mod) @@ -396,47 +463,72 @@ void ModListContextMenu::addRegularActions(ModInfo::Ptr mod) addSeparator(); if (mod->downgradeAvailable()) { - addAction(tr("Change versioning scheme"), [=]() { m_actions.changeVersioningScheme(m_index); }); + addAction(tr("Change versioning scheme"), [=]() { + m_actions.changeVersioningScheme(m_index); + }); } if (mod->nexusId() > 0) - addAction(tr("Force-check updates"), [=]() { m_actions.checkModsForUpdates(m_selected); }); + addAction(tr("Force-check updates"), [=]() { + m_actions.checkModsForUpdates(m_selected); + }); if (mod->updateIgnored()) { - addAction(tr("Un-ignore update"), [=]() { m_actions.setIgnoreUpdate(m_selected, false); }); - } - else { + addAction(tr("Un-ignore update"), [=]() { + m_actions.setIgnoreUpdate(m_selected, false); + }); + } else { if (mod->updateAvailable() || mod->downgradeAvailable()) { - addAction(tr("Ignore update"), [=]() { m_actions.setIgnoreUpdate(m_selected, true); }); + addAction(tr("Ignore update"), [=]() { + m_actions.setIgnoreUpdate(m_selected, true); + }); } } addSeparator(); - addAction(tr("Enable selected"), [=]() { m_core.modList()->setActive(m_selected, true); }); - addAction(tr("Disable selected"), [=]() { m_core.modList()->setActive(m_selected, false); }); + addAction(tr("Enable selected"), [=]() { + m_core.modList()->setActive(m_selected, true); + }); + addAction(tr("Disable selected"), [=]() { + m_core.modList()->setActive(m_selected, false); + }); addSeparator(); - if (m_view->sortColumn() == ModList::COL_PRIORITY) { addSendToContextMenu(); addSeparator(); } - addAction(tr("Rename Mod..."), [=]() { m_actions.renameMod(m_index); }); - addAction(tr("Reinstall Mod"), [=]() { m_actions.reinstallMod(m_index); }); - addAction(tr("Remove Mod..."), [=]() { m_actions.removeMods(m_selected); }); - addAction(tr("Create Backup"), [=]() { m_actions.createBackup(m_index); }); + addAction(tr("Rename Mod..."), [=]() { + m_actions.renameMod(m_index); + }); + addAction(tr("Reinstall Mod"), [=]() { + m_actions.reinstallMod(m_index); + }); + addAction(tr("Remove Mod..."), [=]() { + m_actions.removeMods(m_selected); + }); + addAction(tr("Create Backup"), [=]() { + m_actions.createBackup(m_index); + }); - if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_HIDDEN_FILES) != flags.end()) { - addAction(tr("Restore hidden files"), [=]() { m_actions.restoreHiddenFiles(m_selected); }); + if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_HIDDEN_FILES) != + flags.end()) { + addAction(tr("Restore hidden files"), [=]() { + m_actions.restoreHiddenFiles(m_selected); + }); } addSeparator(); if (m_index.column() == ModList::COL_NOTES) { - addAction(tr("Select Color..."), [=]() { m_actions.setColor(m_selected, m_index); }); + addAction(tr("Select Color..."), [=]() { + m_actions.setColor(m_selected, m_index); + }); if (mod->color().isValid()) { - addAction(tr("Reset Color"), [=]() { m_actions.resetColor(m_selected); }); + addAction(tr("Reset Color"), [=]() { + m_actions.resetColor(m_selected); + }); } addSeparator(); } @@ -444,14 +536,22 @@ void ModListContextMenu::addRegularActions(ModInfo::Ptr mod) if (mod->nexusId() > 0 && Settings::instance().nexus().endorsementIntegration()) { switch (mod->endorsedState()) { case EndorsedState::ENDORSED_TRUE: { - addAction(tr("Un-Endorse"), [=]() { m_actions.setEndorsed(m_selected, false); }); + addAction(tr("Un-Endorse"), [=]() { + m_actions.setEndorsed(m_selected, false); + }); } break; case EndorsedState::ENDORSED_FALSE: { - addAction(tr("Endorse"), [=]() { m_actions.setEndorsed(m_selected, true); }); - addAction(tr("Won't endorse"), [=]() { m_actions.willNotEndorsed(m_selected); }); + addAction(tr("Endorse"), [=]() { + m_actions.setEndorsed(m_selected, true); + }); + addAction(tr("Won't endorse"), [=]() { + m_actions.willNotEndorsed(m_selected); + }); } break; case EndorsedState::ENDORSED_NEVER: { - addAction(tr("Endorse"), [=]() { m_actions.setEndorsed(m_selected, true); }); + addAction(tr("Endorse"), [=]() { + m_actions.setEndorsed(m_selected, true); + }); } break; default: { QAction* action = new QAction(tr("Endorsement state unknown"), this); @@ -468,10 +568,14 @@ void ModListContextMenu::addRegularActions(ModInfo::Ptr mod) if (mod->nexusId() > 0 && Settings::instance().nexus().trackedIntegration()) { switch (mod->trackedState()) { case TrackedState::TRACKED_FALSE: { - addAction(tr("Start tracking"), [=]() { m_actions.setTracked(m_selected, true); }); + addAction(tr("Start tracking"), [=]() { + m_actions.setTracked(m_selected, true); + }); } break; case TrackedState::TRACKED_TRUE: { - addAction(tr("Stop tracking"), [=]() { m_actions.setTracked(m_selected, false); }); + addAction(tr("Stop tracking"), [=]() { + m_actions.setTracked(m_selected, false); + }); } break; default: { QAction* action = new QAction(tr("Tracked state unknown"), this); @@ -484,23 +588,34 @@ void ModListContextMenu::addRegularActions(ModInfo::Ptr mod) addSeparator(); if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_INVALID) != flags.end()) { - addAction(tr("Ignore missing data"), [=]() { m_actions.ignoreMissingData(m_selected); }); + addAction(tr("Ignore missing data"), [=]() { + m_actions.ignoreMissingData(m_selected); + }); } - if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_ALTERNATE_GAME) != flags.end()) { - addAction(tr("Mark as converted/working"), [=]() { m_actions.markConverted(m_selected); }); + if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_ALTERNATE_GAME) != + flags.end()) { + addAction(tr("Mark as converted/working"), [=]() { + m_actions.markConverted(m_selected); + }); } addSeparator(); if (mod->nexusId() > 0) { - addAction(tr("Visit on Nexus"), [=]() { m_actions.visitOnNexus(m_selected); }); + addAction(tr("Visit on Nexus"), [=]() { + m_actions.visitOnNexus(m_selected); + }); } const auto url = mod->parseCustomURL(); if (url.isValid()) { - addAction(tr("Visit on %1").arg(url.host()), [=]() { m_actions.visitWebPage(m_selected); }); + addAction(tr("Visit on %1").arg(url.host()), [=]() { + m_actions.visitWebPage(m_selected); + }); } - addAction(tr("Open in Explorer"), [=]() { m_actions.openExplorer(m_selected); }); + addAction(tr("Open in Explorer"), [=]() { + m_actions.openExplorer(m_selected); + }); } diff --git a/src/modlistcontextmenu.h b/src/modlistcontextmenu.h index 9bc3a6038..c2fe5bbfb 100644 --- a/src/modlistcontextmenu.h +++ b/src/modlistcontextmenu.h @@ -18,39 +18,37 @@ class ModListGlobalContextMenu : public QMenu { Q_OBJECT public: - - ModListGlobalContextMenu(OrganizerCore& core, ModListView* view, QWidget* parent = nullptr); + ModListGlobalContextMenu(OrganizerCore& core, ModListView* view, + QWidget* parent = nullptr); protected: - friend class ModListContextMenu; // populate the menu void populate(OrganizerCore& core, ModListView* view, const QModelIndex& index); // creates a "All mods" context menu for the given index (can be invalid). - ModListGlobalContextMenu(OrganizerCore& core, ModListView* view, const QModelIndex& index, QWidget* parent = nullptr); - + ModListGlobalContextMenu(OrganizerCore& core, ModListView* view, + const QModelIndex& index, QWidget* parent = nullptr); }; class ModListChangeCategoryMenu : public QMenu { Q_OBJECT public: - - ModListChangeCategoryMenu( - CategoryFactory* categories, ModInfo::Ptr mod, QMenu* parent = nullptr); + ModListChangeCategoryMenu(CategoryFactory* categories, ModInfo::Ptr mod, + QMenu* parent = nullptr); // return a list of pair from the menu // std::vector> categories() const; private: - // populate the tree with the category, using the enabled/disabled state from the // given mod // - bool populate(QMenu* menu, CategoryFactory* categories, ModInfo::Ptr mod, int targetId = 0); + bool populate(QMenu* menu, CategoryFactory* categories, ModInfo::Ptr mod, + int targetId = 0); // internal implementation of categories() for recursion // @@ -61,19 +59,17 @@ class ModListPrimaryCategoryMenu : public QMenu { Q_OBJECT public: - - ModListPrimaryCategoryMenu(CategoryFactory* categories, ModInfo::Ptr mod, QMenu* parent = nullptr); + ModListPrimaryCategoryMenu(CategoryFactory* categories, ModInfo::Ptr mod, + QMenu* parent = nullptr); // return the selected primary category // int primaryCategory() const; private: - // populate the categories // void populate(const CategoryFactory* categories, ModInfo::Ptr mod); - }; class ModListContextMenu : public QMenu @@ -81,14 +77,13 @@ class ModListContextMenu : public QMenu Q_OBJECT public: - - // creates a new context menu, the given index is the one for the click and should be valid + // creates a new context menu, the given index is the one for the click and should be + // valid // - ModListContextMenu( - const QModelIndex& index, OrganizerCore& core, CategoryFactory* categories, ModListView* modListView); + ModListContextMenu(const QModelIndex& index, OrganizerCore& core, + CategoryFactory* categories, ModListView* modListView); private: - // adds the "Send to... " context menu // void addSendToContextMenu(); @@ -101,7 +96,6 @@ class ModListContextMenu : public QMenu // void addMenuAsPushButton(QMenu* menu); - // add actions/menus to this menu for each type of mod // void addOverwriteActions(ModInfo::Ptr mod); @@ -115,8 +109,7 @@ class ModListContextMenu : public QMenu QModelIndex m_index; QModelIndexList m_selected; ModListView* m_view; - ModListViewActions& m_actions; // shortcut for m_view->actions() - + ModListViewActions& m_actions; // shortcut for m_view->actions() }; #endif diff --git a/src/modlistdropinfo.cpp b/src/modlistdropinfo.cpp index a247194f6..f0bb51bc0 100644 --- a/src/modlistdropinfo.cpp +++ b/src/modlistdropinfo.cpp @@ -2,12 +2,13 @@ #include "organizercore.h" -ModListDropInfo::ModListDropInfo(const QMimeData* mimeData, OrganizerCore& core) : - m_rows{}, m_download{ -1 }, m_localUrls{}, m_url{} +ModListDropInfo::ModListDropInfo(const QMimeData* mimeData, OrganizerCore& core) + : m_rows{}, m_download{-1}, m_localUrls{}, m_url{} { // this only check if the drop is valid, not if the content of the drop // matches the target, a drop is valid if either - // 1. it contains items from another model (drag&drop in modlist or from download list) + // 1. it contains items from another model (drag&drop in modlist or from download + // list) // 2. it contains URLs from MO2 (overwrite or from another mod) // 3. it contains a single URL to an external folder // 4. it contains a single URL to a valid archive for MO2 @@ -27,15 +28,14 @@ ModListDropInfo::ModListDropInfo(const QMimeData* mimeData, OrganizerCore& core) QFileInfo info(url.toLocalFile()); if (info.isDir()) { m_url = url; - } - else if (core.installationManager()->getSupportedExtensions().contains(info.suffix(), Qt::CaseInsensitive)) { + } else if (core.installationManager()->getSupportedExtensions().contains( + info.suffix(), Qt::CaseInsensitive)) { m_url = url; } } } - } - else if (mimeData->hasText()) { + } else if (mimeData->hasText()) { QByteArray encoded = mimeData->data("application/x-qabstractitemmodeldatalist"); QDataStream stream(&encoded, QIODevice::ReadOnly); @@ -55,16 +55,16 @@ ModListDropInfo::ModListDropInfo(const QMimeData* mimeData, OrganizerCore& core) m_rows = {}; } } - } - catch (std::exception const&) { - m_rows = {}; + } catch (std::exception const&) { + m_rows = {}; m_download = -1; m_localUrls.clear(); m_url = {}; } } -std::optional ModListDropInfo::relativeUrl(const QUrl& url) const +std::optional +ModListDropInfo::relativeUrl(const QUrl& url) const { if (!url.isLocalFile()) { return {}; @@ -82,12 +82,12 @@ std::optional ModListDropInfo::relativeUrl(const Q if (sourceFile.startsWith(allModsDir.canonicalPath())) { QDir relativeDir(allModsDir.relativeFilePath(sourceFile)); QStringList splitPath = relativeDir.path().split("/"); - originName = splitPath[0]; + originName = splitPath[0]; splitPath.pop_front(); - return { { url, splitPath.join("/"), originName } }; - } - else if (sourceFile.startsWith(overwriteDir.canonicalPath())) { - return { { url, overwriteDir.relativeFilePath(sourceFile), ModInfo::getOverwrite()->name() } }; + return {{url, splitPath.join("/"), originName}}; + } else if (sourceFile.startsWith(overwriteDir.canonicalPath())) { + return {{url, overwriteDir.relativeFilePath(sourceFile), + ModInfo::getOverwrite()->name()}}; } return {}; diff --git a/src/modlistdropinfo.h b/src/modlistdropinfo.h index 5d2af92fa..3082e2a19 100644 --- a/src/modlistdropinfo.h +++ b/src/modlistdropinfo.h @@ -12,25 +12,24 @@ class OrganizerCore; // small class that extract information from mimeData // -class ModListDropInfo { +class ModListDropInfo +{ public: - // text value for the mime-data for the various possible // origin (not for external drops) // - static constexpr const char* ModText = "mod"; + static constexpr const char* ModText = "mod"; static constexpr const char* DownloadText = "download"; public: - - struct RelativeUrl { + struct RelativeUrl + { const QUrl url; const QString relativePath; const QString originName; }; public: - ModListDropInfo(const QMimeData* mimeData, OrganizerCore& core); // returns true if this drop is valid @@ -68,7 +67,6 @@ class ModListDropInfo { const auto& externalUrl() const { return m_url; } private: - friend class ModList; // retrieve the relative path of file and its origin given a URL from Mime data @@ -77,10 +75,9 @@ class ModListDropInfo { std::optional relativeUrl(const QUrl&) const; private: - // rows for drag&drop between views std::vector m_rows; - int m_download; // -1 if invalid + int m_download; // -1 if invalid // local URLs from the data (relative path + origin name) std::vector m_localUrls; diff --git a/src/modlistproxy.cpp b/src/modlistproxy.cpp index cae5b1f7b..1d8481ba6 100644 --- a/src/modlistproxy.cpp +++ b/src/modlistproxy.cpp @@ -1,13 +1,14 @@ #include "modlistproxy.h" +#include "modlist.h" #include "organizerproxy.h" #include "proxyutils.h" -#include "modlist.h" using namespace MOBase; using namespace MOShared; -ModListProxy::ModListProxy(OrganizerProxy* oproxy, ModList* modlist) : - m_OrganizerProxy(oproxy), m_Proxied(modlist) { } +ModListProxy::ModListProxy(OrganizerProxy* oproxy, ModList* modlist) + : m_OrganizerProxy(oproxy), m_Proxied(modlist) +{} ModListProxy::~ModListProxy() { @@ -16,10 +17,14 @@ ModListProxy::~ModListProxy() void ModListProxy::connectSignals() { - m_Connections.push_back(m_Proxied->onModInstalled(callSignalIfPluginActive(m_OrganizerProxy, m_ModInstalled))); - m_Connections.push_back(m_Proxied->onModMoved(callSignalIfPluginActive(m_OrganizerProxy, m_ModMoved))); - m_Connections.push_back(m_Proxied->onModRemoved(callSignalIfPluginActive(m_OrganizerProxy, m_ModRemoved))); - m_Connections.push_back(m_Proxied->onModStateChanged(callSignalIfPluginActive(m_OrganizerProxy, m_ModStateChanged))); + m_Connections.push_back(m_Proxied->onModInstalled( + callSignalIfPluginActive(m_OrganizerProxy, m_ModInstalled))); + m_Connections.push_back( + m_Proxied->onModMoved(callSignalIfPluginActive(m_OrganizerProxy, m_ModMoved))); + m_Connections.push_back(m_Proxied->onModRemoved( + callSignalIfPluginActive(m_OrganizerProxy, m_ModRemoved))); + m_Connections.push_back(m_Proxied->onModStateChanged( + callSignalIfPluginActive(m_OrganizerProxy, m_ModStateChanged))); } void ModListProxy::disconnectSignals() @@ -55,7 +60,8 @@ bool ModListProxy::removeMod(MOBase::IModInterface* mod) return m_Proxied->removeMod(mod); } -MOBase::IModInterface* ModListProxy::renameMod(MOBase::IModInterface* mod, const QString& name) +MOBase::IModInterface* ModListProxy::renameMod(MOBase::IModInterface* mod, + const QString& name) { return m_Proxied->renameMod(mod, name); } @@ -95,7 +101,8 @@ bool ModListProxy::onModRemoved(const std::function& func) return m_ModRemoved.connect(func).connected(); } -bool ModListProxy::onModStateChanged(const std::function&)>& func) +bool ModListProxy::onModStateChanged( + const std::function&)>& func) { return m_ModStateChanged.connect(func).connected(); } diff --git a/src/modlistproxy.h b/src/modlistproxy.h index d886d8b5b..dc6fff9b6 100644 --- a/src/modlistproxy.h +++ b/src/modlistproxy.h @@ -1,8 +1,8 @@ #ifndef MODLISTPROXY_H #define MODLISTPROXY_H -#include #include "modlist.h" +#include class OrganizerProxy; @@ -10,28 +10,29 @@ class ModListProxy : public MOBase::IModList { public: - ModListProxy(OrganizerProxy* oproxy, ModList* modlist); virtual ~ModListProxy(); QString displayName(const QString& internalName) const override; QStringList allMods() const override; - QStringList allModsByProfilePriority(MOBase::IProfile* profile = nullptr) const override; + QStringList + allModsByProfilePriority(MOBase::IProfile* profile = nullptr) const override; MOBase::IModInterface* getMod(const QString& name) const override; bool removeMod(MOBase::IModInterface* mod) override; - MOBase::IModInterface* renameMod(MOBase::IModInterface* mod, const QString& name) override; + MOBase::IModInterface* renameMod(MOBase::IModInterface* mod, + const QString& name) override; ModStates state(const QString& name) const override; bool setActive(const QString& name, bool active) override; int setActive(const QStringList& names, bool active) override; int priority(const QString& name) const override; bool setPriority(const QString& name, int newPriority) override; - bool onModInstalled(const std::function& func) override; + bool onModInstalled(const std::function& func) override; bool onModRemoved(const std::function& func) override; - bool onModStateChanged(const std::function&)>& func) override; + bool onModStateChanged( + const std::function&)>& func) override; bool onModMoved(const std::function& func) override; private: - friend class OrganizerProxy; // See OrganizerProxy::connectSignals(). @@ -49,4 +50,4 @@ class ModListProxy : public MOBase::IModList std::vector m_Connections; }; -#endif // ORGANIZERPROXY_H +#endif // ORGANIZERPROXY_H diff --git a/src/modlistsortproxy.cpp b/src/modlistsortproxy.cpp index 4a4800cd7..e61f94947 100644 --- a/src/modlistsortproxy.cpp +++ b/src/modlistsortproxy.cpp @@ -1,705 +1,683 @@ -/* -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 "modlistsortproxy.h" -#include "modlistbypriorityproxy.h" -#include "modlistdropinfo.h" -#include "modinfo.h" -#include "profile.h" -#include "messagedialog.h" -#include "qtgroupingproxy.h" -#include "organizercore.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace MOBase; - -ModListSortProxy::ModListSortProxy(Profile* profile, OrganizerCore * organizer) - : QSortFilterProxyModel(organizer) - , m_Organizer(organizer) - , m_Profile(profile) - , m_FilterActive(false) - , m_FilterMode(FilterAnd) - , m_FilterSeparators(SeparatorFilter) -{ - setDynamicSortFilter(true); // this seems to work without dynamicsortfilter - // but I don't know why. This should be necessary -} - -void ModListSortProxy::setProfile(Profile *profile) -{ - m_Profile = profile; -} - -void ModListSortProxy::updateFilterActive() -{ - m_FilterActive = (!m_Criteria.empty() || !m_Filter.isEmpty()); - emit filterActive(m_FilterActive); -} - -void ModListSortProxy::setCriteria(const std::vector& criteria) -{ - // avoid refreshing the filter unless we are checking all mods for update. - const bool changed = (criteria != m_Criteria); - const bool isForUpdates = ( - !criteria.empty() && - criteria[0].id == CategoryFactory::UpdateAvailable); - - if (changed || isForUpdates) { - m_Criteria = criteria; - updateFilterActive(); - invalidateFilter(); - emit filterInvalidated(); - } -} - -unsigned long ModListSortProxy::flagsId(const std::vector &flags) const -{ - unsigned long result = 0; - for (ModInfo::EFlag flag : flags) { - if ((flag != ModInfo::FLAG_FOREIGN) - && (flag != ModInfo::FLAG_OVERWRITE)) { - result += 1 << (int)flag; - } - } - return result; -} - -unsigned long ModListSortProxy::conflictFlagsId(const std::vector& flags) const -{ - unsigned long result = 0; - for (ModInfo::EConflictFlag flag : flags) { - if ((flag != ModInfo::FLAG_OVERWRITE_CONFLICT)) { - result += 1 << (int)flag; - } - } - return result; -} - -bool ModListSortProxy::lessThan(const QModelIndex &left, - const QModelIndex &right) const -{ - if (sourceModel()->hasChildren(left) || sourceModel()->hasChildren(right)) { - // when sorting by priority, we do not want to use the parent lessThan because - // it uses the display role which can be inconsistent (e.g. for backups) - if (sortColumn() != ModList::COL_PRIORITY) { - return QSortFilterProxyModel::lessThan(left, right); - } - else if (qobject_cast(sourceModel())) { - // if the underlying proxy is a QtGroupingProxy we need to rely on - // Qt::DisplayRole because the other roles are not correctly handled - // by that kind of proxy - return left.data(Qt::DisplayRole).toInt() < right.data(Qt::DisplayRole).toInt(); - } - } - - bool lOk, rOk; - int leftIndex = left.data(ModList::IndexRole).toInt(&lOk); - int rightIndex = right.data(ModList::IndexRole).toInt(&rOk); - - if (!lOk || !rOk) { - return false; - } - - ModInfo::Ptr leftMod = ModInfo::getByIndex(leftIndex); - ModInfo::Ptr rightMod = ModInfo::getByIndex(rightIndex); - - bool lt = left.data(ModList::PriorityRole).toInt() < right.data(ModList::PriorityRole).toInt(); - - switch (left.column()) { - case ModList::COL_FLAGS: { - std::vector leftFlags = leftMod->getFlags(); - std::vector rightFlags = rightMod->getFlags(); - if (leftFlags.size() != rightFlags.size()) { - lt = leftFlags.size() < rightFlags.size(); - } else { - lt = flagsId(leftFlags) < flagsId(rightFlags); - } - } break; - case ModList::COL_CONFLICTFLAGS: { - std::vector leftFlags = leftMod->getConflictFlags(); - std::vector rightFlags = rightMod->getConflictFlags(); - if (leftFlags.size() != rightFlags.size()) { - lt = leftFlags.size() < rightFlags.size(); - } - else { - lt = conflictFlagsId(leftFlags) < conflictFlagsId(rightFlags); - } - } break; - case ModList::COL_CONTENT: { - const auto& lContents = leftMod->getContents(); - const auto& rContents = rightMod->getContents(); - unsigned int lValue = 0; - unsigned int rValue = 0; - m_Organizer->modDataContents().forEachContentIn(lContents, [&lValue](auto const& content) { - lValue += 2U << static_cast(content.id()); - }); - m_Organizer->modDataContents().forEachContentIn(rContents, [&rValue](auto const& content) { - rValue += 2U << static_cast(content.id()); - }); - lt = lValue < rValue; - } break; - case ModList::COL_NAME: { - int comp = QString::compare(leftMod->name(), rightMod->name(), Qt::CaseInsensitive); - if (comp != 0) - lt = comp < 0; - } break; - case ModList::COL_CATEGORY: { - if (leftMod->primaryCategory() != rightMod->primaryCategory()) { - if (leftMod->primaryCategory() < 0) lt = false; - else if (rightMod->primaryCategory() < 0) lt = true; - else { - try { - CategoryFactory *categories = CategoryFactory::instance(); - QString leftCatName = categories->getCategoryName(categories->getCategoryIndex(leftMod->primaryCategory())); - QString rightCatName = categories->getCategoryName(categories->getCategoryIndex(rightMod->primaryCategory())); - lt = leftCatName < rightCatName; - } catch (const std::exception &e) { - log::error("failed to compare categories: {}", e.what()); - } - } - } - } break; - case ModList::COL_MODID: { - if (leftMod->nexusId() != rightMod->nexusId()) - lt = leftMod->nexusId() < rightMod->nexusId(); - } break; - case ModList::COL_VERSION: { - if (leftMod->version() != rightMod->version()) - lt = leftMod->version() < rightMod->version(); - } break; - case ModList::COL_INSTALLTIME: { - QDateTime leftTime = left.data().toDateTime(); - QDateTime rightTime = right.data().toDateTime(); - if (leftTime != rightTime) - return leftTime < rightTime; - } break; - case ModList::COL_GAME: { - if (leftMod->gameName() != rightMod->gameName()) { - lt = leftMod->gameName() < rightMod->gameName(); - } - else { - int comp = QString::compare(leftMod->name(), rightMod->name(), Qt::CaseInsensitive); - if (comp != 0) - lt = comp < 0; - } - } break; - case ModList::COL_NOTES: { - QString leftComments = leftMod->comments(); - QString rightComments = rightMod->comments(); - if (leftComments != rightComments) { - if (leftComments.isEmpty()) { - lt = sortOrder() == Qt::DescendingOrder; - } else if (rightComments.isEmpty()) { - lt = sortOrder() == Qt::AscendingOrder; - } else { - lt = leftComments < rightComments; - } - } - } break; - case ModList::COL_PRIORITY: { - if (leftMod->isBackup() != rightMod->isBackup()) { - lt = leftMod->isBackup(); - } - else if (leftMod->isOverwrite() != rightMod->isOverwrite()) { - lt = rightMod->isOverwrite(); - } - } break; - default: { - log::warn("Sorting is not defined for column {}", left.column()); - } break; - } - return lt; -} - -void ModListSortProxy::updateFilter(const QString& filter) -{ - m_Filter = filter; - updateFilterActive(); - invalidateFilter(); - emit filterInvalidated(); -} - -bool ModListSortProxy::hasConflictFlag(const std::vector &flags) const -{ - for (ModInfo::EConflictFlag flag : flags) { - if ((flag == ModInfo::FLAG_CONFLICT_MIXED) || - (flag == ModInfo::FLAG_CONFLICT_OVERWRITE) || - (flag == ModInfo::FLAG_CONFLICT_OVERWRITTEN) || - (flag == ModInfo::FLAG_CONFLICT_REDUNDANT) || - (flag == ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITE) || - (flag == ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITTEN) || - (flag == ModInfo::FLAG_ARCHIVE_CONFLICT_MIXED) || - (flag == ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITE) || - (flag == ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITTEN)) { - return true; - } - } - - return false; -} - -bool ModListSortProxy::filterMatchesModAnd(ModInfo::Ptr info, bool enabled) const -{ - for (auto&& c : m_Criteria) { - if (!criteriaMatchMod(info, enabled, c)) { - return false; - } - } - - return true; -} - -bool ModListSortProxy::filterMatchesModOr(ModInfo::Ptr info, bool enabled) const -{ - for (auto&& c : m_Criteria) { - if (criteriaMatchMod(info, enabled, c)) { - return true; - } - } - - if (!m_Criteria.empty()) { - // nothing matched - return false; - } - - return true; -} - -bool ModListSortProxy::optionsMatchMod(ModInfo::Ptr info, bool) const -{ - return true; -} - -bool ModListSortProxy::criteriaMatchMod( - ModInfo::Ptr info, bool enabled, const Criteria& c) const -{ - bool b = false; - - switch (c.type) - { - case TypeSpecial: // fall-through - case TypeCategory: - { - b = categoryMatchesMod(info, enabled, c.id); - break; - } - - case TypeContent: - { - b = contentMatchesMod(info, enabled, c.id); - break; - } - - default: - { - log::error("bad criteria type {}", c.type); - break; - } - } - - if (c.inverse) { - b = !b; - } - - return b; -} - -bool ModListSortProxy::categoryMatchesMod( - ModInfo::Ptr info, bool enabled, int category) const -{ - bool b = false; - - switch (category) - { - case CategoryFactory::Checked: - { - b = (enabled || info->alwaysEnabled()); - break; - } - - case CategoryFactory::UpdateAvailable: - { - b = (info->updateAvailable() || info->downgradeAvailable()); - break; - } - - case CategoryFactory::HasCategory: - { - b = !info->getCategories().empty(); - break; - } - - case CategoryFactory::Conflict: - { - b = (hasConflictFlag(info->getConflictFlags())); - break; - } - - case CategoryFactory::HasHiddenFiles: - { - b = (info->hasFlag(ModInfo::FLAG_HIDDEN_FILES)); - break; - } - - case CategoryFactory::Endorsed: - { - b = (info->endorsedState() == EndorsedState::ENDORSED_TRUE); - break; - } - - case CategoryFactory::Backup: - { - b = (info->hasFlag(ModInfo::FLAG_BACKUP)); - break; - } - - case CategoryFactory::Managed: - { - b = (!info->hasFlag(ModInfo::FLAG_FOREIGN)); - break; - } - - case CategoryFactory::HasGameData: - { - b = !info->hasFlag(ModInfo::FLAG_INVALID); - break; - } - - case CategoryFactory::HasNexusID: - { - // never show these - if ( - info->hasFlag(ModInfo::FLAG_FOREIGN) || - info->hasFlag(ModInfo::FLAG_BACKUP) || - info->hasFlag(ModInfo::FLAG_OVERWRITE)) - { - return false; - } - - b = (info->nexusId() > 0); - break; - } - - case CategoryFactory::Tracked: - { - b = (info->trackedState() == TrackedState::TRACKED_TRUE); - break; - } - - default: - { - b = (info->categorySet(category)); - break; - } - } - - return b; -} - -bool ModListSortProxy::contentMatchesMod(ModInfo::Ptr info, bool enabled, int content) const -{ - return info->hasContent(content); -} - -bool ModListSortProxy::filterMatchesMod(ModInfo::Ptr info, bool enabled) const -{ - // don't check if there are no filters selected - if (!m_FilterActive) { - return true; - } - - - // special case for separators - if (info->hasFlag(ModInfo::FLAG_SEPARATOR)) { - switch (m_FilterSeparators) - { - case SeparatorFilter: - { - // filter normally - break; - } - - case SeparatorShow: - { - // force visible - return true; - } - - case SeparatorHide: - { - // force hide - return false; - } - } - } - - - if (!m_Filter.isEmpty()) { - bool display = false; - QString filterCopy = QString(m_Filter); - filterCopy.replace("||", ";").replace("OR", ";").replace("|", ";"); - QStringList ORList = filterCopy.split(";", Qt::SkipEmptyParts); - - bool segmentGood = true; - - //split in ORSegments that internally use AND logic - for (auto& ORSegment : ORList) { - QStringList ANDKeywords = ORSegment.split(" ", Qt::SkipEmptyParts); - segmentGood = true; - bool foundKeyword = false; - - //check each word in the segment for match, each word needs to be matched but it doesn't matter where. - for (auto& currentKeyword : ANDKeywords) { - foundKeyword = false; - - //search keyword in name - if (m_EnabledColumns[ModList::COL_NAME] && - info->name().contains(currentKeyword, Qt::CaseInsensitive)) { - foundKeyword = true; - } - - // Search by notes - if (!foundKeyword && - m_EnabledColumns[ModList::COL_NOTES] && - info->comments().contains(currentKeyword, Qt::CaseInsensitive)) { - foundKeyword = true; - } - - // Search by categories - if (!foundKeyword && - m_EnabledColumns[ModList::COL_CATEGORY]) { - for (auto category : info->categories()) { - if (category.contains(currentKeyword, Qt::CaseInsensitive)) { - foundKeyword = true; - break; - } - } - } - - // Search by Nexus ID - if (!foundKeyword && - m_EnabledColumns[ModList::COL_MODID]) { - bool ok; - int filterID = currentKeyword.toInt(&ok); - if (ok) { - int modID = info->nexusId(); - while (modID > 0) { - if (modID == filterID) { - foundKeyword = true; - break; - } - modID = (int)(modID / 10); - } - } - } - - if (!foundKeyword) { - //currentKeword is missing from everything, AND fails and we need to check next ORsegment - segmentGood = false; - break; - } - - }//for ANDKeywords loop - - if (segmentGood) { - //the last AND loop didn't break so the ORSegments is true so mod matches filter - display = true; - break; - } - - }//for ORList loop - - if (!display) { - return false; - } - }//if (!m_CurrentFilter.isEmpty()) - - - if (m_FilterMode == FilterAnd) { - return filterMatchesModAnd(info, enabled); - } - else { - return filterMatchesModOr(info, enabled); - } -} - -void ModListSortProxy::setColumnVisible(int column, bool visible) -{ - m_EnabledColumns[column] = visible; -} - -void ModListSortProxy::setOptions( - ModListSortProxy::FilterMode mode, SeparatorsMode separators) -{ - if (m_FilterMode != mode || separators != m_FilterSeparators) { - m_FilterMode = mode; - m_FilterSeparators = separators; - invalidateFilter(); - emit filterInvalidated(); - } -} - -bool ModListSortProxy::filterAcceptsRow(int source_row, const QModelIndex &parent) const -{ - if (m_Profile == nullptr) { - return false; - } - - if (source_row >= static_cast(m_Profile->numMods())) { - log::warn("invalid row index: {}", source_row); - return false; - } - - QModelIndex idx = sourceModel()->index(source_row, 0, parent); - if (!idx.isValid()) { - log::debug("invalid mod index"); - return false; - } - - unsigned int index = ULONG_MAX; - { - bool ok = false; - index = idx.data(ModList::IndexRole).toInt(&ok); - if (!ok) { - index = ULONG_MAX; - } - } - - if (sourceModel()->hasChildren(idx)) { - // we need to check the separator itself first - if (index < ModInfo::getNumMods() && ModInfo::getByIndex(index)->isSeparator()) { - if (filterMatchesMod(ModInfo::getByIndex(index), false)) { - return true; - } - } - for (int i = 0; i < sourceModel()->rowCount(idx); ++i) { - if (filterAcceptsRow(i, idx)) { - return true; - } - } - - return false; - } else { - bool modEnabled = idx.sibling(source_row, 0).data(Qt::CheckStateRole).toInt() == Qt::Checked; - return filterMatchesMod(ModInfo::getByIndex(index), modEnabled); - } -} - -bool ModListSortProxy::sourceIsByPriorityProxy() const -{ - return dynamic_cast(sourceModel()) != nullptr; -} - -bool ModListSortProxy::canDropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) const -{ - ModListDropInfo dropInfo(data, *m_Organizer); - - if (!dropInfo.isLocalFileDrop() && sortColumn() != ModList::COL_PRIORITY) { - return false; - } - - // disable drop install with group proxy, except the one for collapsible separator - // - it would be nice to be able to "install to category" or something like that but - // it's a bit more complicated since the drop position is based on the category, so - // just disabling for now - if (dropInfo.isDownloadDrop()) { - // maybe there is a cleaner way? - if (qobject_cast(sourceModel())) { - return false; - } - } - - // see dropMimeData for details - if (sortOrder() == Qt::DescendingOrder && row != -1 && !sourceIsByPriorityProxy()) { - --row; - } - - return QSortFilterProxyModel::canDropMimeData(data, action, row, column, parent); -} - -bool ModListSortProxy::dropMimeData(const QMimeData *data, Qt::DropAction action, - int row, int column, const QModelIndex &parent) -{ - ModListDropInfo dropInfo(data, *m_Organizer); - - if (!dropInfo.isLocalFileDrop() && sortColumn() != ModList::COL_PRIORITY) { - QWidget *wid = qApp->activeWindow()->findChild("modList"); - MessageDialog::showMessage(tr("Drag&Drop is only supported when sorting by priority"), wid); - return false; - } - - if (row == -1 && column == -1) { - return sourceModel()->dropMimeData(data, action, -1, -1, mapToSource(parent)); - } - - // in the regular model, when dropping between rows, the row-value passed to - // the sourceModel is inconsistent between ascending and descending ordering - // - // we want to fix that, but we cannot do it for the by-priority proxy because - // it messes up with non top-level items, so we simply forward the row and the - // by-priority proxy will fix the row for us - if (sortOrder() == Qt::DescendingOrder && row != -1 && !sourceIsByPriorityProxy()) { - --row; - } - - return QSortFilterProxyModel::dropMimeData(data, action, row, column, parent); -} - -void ModListSortProxy::setSourceModel(QAbstractItemModel *sourceModel) -{ - QSortFilterProxyModel::setSourceModel(sourceModel); - QAbstractProxyModel *proxy = qobject_cast(sourceModel); - if (proxy != nullptr) { - sourceModel = proxy->sourceModel(); - } - if (sourceModel) { - connect(sourceModel, SIGNAL(aboutToChangeData()), this, SLOT(aboutToChangeData()), Qt::UniqueConnection); - connect(sourceModel, SIGNAL(postDataChanged()), this, SLOT(postDataChanged()), Qt::UniqueConnection); - } -} - -void ModListSortProxy::aboutToChangeData() -{ - // having a filter active when dataChanged is called caused a crash - // (at least with some Qt versions) - // this may be related to the fact that the item being edited may disappear from the view as a - // result of the edit - m_PreChangeCriteria = m_Criteria; - setCriteria({}); -} - -void ModListSortProxy::postDataChanged() -{ - // if the filter is re-activated right away the editor can't be deleted but becomes invisible - // or at least the view continues to think it's being edited. As a result no new editor can be - // opened - QTimer::singleShot(10, [this] () { - setCriteria(m_PreChangeCriteria); - m_PreChangeCriteria.clear(); - }); -} - +/* +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 "modlistsortproxy.h" +#include "messagedialog.h" +#include "modinfo.h" +#include "modlistbypriorityproxy.h" +#include "modlistdropinfo.h" +#include "organizercore.h" +#include "profile.h" +#include "qtgroupingproxy.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace MOBase; + +ModListSortProxy::ModListSortProxy(Profile* profile, OrganizerCore* organizer) + : QSortFilterProxyModel(organizer), m_Organizer(organizer), m_Profile(profile), + m_FilterActive(false), m_FilterMode(FilterAnd), + m_FilterSeparators(SeparatorFilter) +{ + setDynamicSortFilter(true); // this seems to work without dynamicsortfilter + // but I don't know why. This should be necessary +} + +void ModListSortProxy::setProfile(Profile* profile) +{ + m_Profile = profile; +} + +void ModListSortProxy::updateFilterActive() +{ + m_FilterActive = (!m_Criteria.empty() || !m_Filter.isEmpty()); + emit filterActive(m_FilterActive); +} + +void ModListSortProxy::setCriteria(const std::vector& criteria) +{ + // avoid refreshing the filter unless we are checking all mods for update. + const bool changed = (criteria != m_Criteria); + const bool isForUpdates = + (!criteria.empty() && criteria[0].id == CategoryFactory::UpdateAvailable); + + if (changed || isForUpdates) { + m_Criteria = criteria; + updateFilterActive(); + invalidateFilter(); + emit filterInvalidated(); + } +} + +unsigned long ModListSortProxy::flagsId(const std::vector& flags) const +{ + unsigned long result = 0; + for (ModInfo::EFlag flag : flags) { + if ((flag != ModInfo::FLAG_FOREIGN) && (flag != ModInfo::FLAG_OVERWRITE)) { + result += 1 << (int)flag; + } + } + return result; +} + +unsigned long ModListSortProxy::conflictFlagsId( + const std::vector& flags) const +{ + unsigned long result = 0; + for (ModInfo::EConflictFlag flag : flags) { + if ((flag != ModInfo::FLAG_OVERWRITE_CONFLICT)) { + result += 1 << (int)flag; + } + } + return result; +} + +bool ModListSortProxy::lessThan(const QModelIndex& left, const QModelIndex& right) const +{ + if (sourceModel()->hasChildren(left) || sourceModel()->hasChildren(right)) { + // when sorting by priority, we do not want to use the parent lessThan because + // it uses the display role which can be inconsistent (e.g. for backups) + if (sortColumn() != ModList::COL_PRIORITY) { + return QSortFilterProxyModel::lessThan(left, right); + } else if (qobject_cast(sourceModel())) { + // if the underlying proxy is a QtGroupingProxy we need to rely on + // Qt::DisplayRole because the other roles are not correctly handled + // by that kind of proxy + return left.data(Qt::DisplayRole).toInt() < right.data(Qt::DisplayRole).toInt(); + } + } + + bool lOk, rOk; + int leftIndex = left.data(ModList::IndexRole).toInt(&lOk); + int rightIndex = right.data(ModList::IndexRole).toInt(&rOk); + + if (!lOk || !rOk) { + return false; + } + + ModInfo::Ptr leftMod = ModInfo::getByIndex(leftIndex); + ModInfo::Ptr rightMod = ModInfo::getByIndex(rightIndex); + + bool lt = left.data(ModList::PriorityRole).toInt() < + right.data(ModList::PriorityRole).toInt(); + + switch (left.column()) { + case ModList::COL_FLAGS: { + std::vector leftFlags = leftMod->getFlags(); + std::vector rightFlags = rightMod->getFlags(); + if (leftFlags.size() != rightFlags.size()) { + lt = leftFlags.size() < rightFlags.size(); + } else { + lt = flagsId(leftFlags) < flagsId(rightFlags); + } + } break; + case ModList::COL_CONFLICTFLAGS: { + std::vector leftFlags = leftMod->getConflictFlags(); + std::vector rightFlags = rightMod->getConflictFlags(); + if (leftFlags.size() != rightFlags.size()) { + lt = leftFlags.size() < rightFlags.size(); + } else { + lt = conflictFlagsId(leftFlags) < conflictFlagsId(rightFlags); + } + } break; + case ModList::COL_CONTENT: { + const auto& lContents = leftMod->getContents(); + const auto& rContents = rightMod->getContents(); + unsigned int lValue = 0; + unsigned int rValue = 0; + m_Organizer->modDataContents().forEachContentIn( + lContents, [&lValue](auto const& content) { + lValue += 2U << static_cast(content.id()); + }); + m_Organizer->modDataContents().forEachContentIn( + rContents, [&rValue](auto const& content) { + rValue += 2U << static_cast(content.id()); + }); + lt = lValue < rValue; + } break; + case ModList::COL_NAME: { + int comp = QString::compare(leftMod->name(), rightMod->name(), Qt::CaseInsensitive); + if (comp != 0) + lt = comp < 0; + } break; + case ModList::COL_CATEGORY: { + if (leftMod->primaryCategory() != rightMod->primaryCategory()) { + if (leftMod->primaryCategory() < 0) + lt = false; + else if (rightMod->primaryCategory() < 0) + lt = true; + else { + try { + CategoryFactory* categories = CategoryFactory::instance(); + QString leftCatName = categories->getCategoryName( + categories->getCategoryIndex(leftMod->primaryCategory())); + QString rightCatName = categories->getCategoryName( + categories->getCategoryIndex(rightMod->primaryCategory())); + lt = leftCatName < rightCatName; + } catch (const std::exception& e) { + log::error("failed to compare categories: {}", e.what()); + } + } + } + } break; + case ModList::COL_MODID: { + if (leftMod->nexusId() != rightMod->nexusId()) + lt = leftMod->nexusId() < rightMod->nexusId(); + } break; + case ModList::COL_VERSION: { + if (leftMod->version() != rightMod->version()) + lt = leftMod->version() < rightMod->version(); + } break; + case ModList::COL_INSTALLTIME: { + QDateTime leftTime = left.data().toDateTime(); + QDateTime rightTime = right.data().toDateTime(); + if (leftTime != rightTime) + return leftTime < rightTime; + } break; + case ModList::COL_GAME: { + if (leftMod->gameName() != rightMod->gameName()) { + lt = leftMod->gameName() < rightMod->gameName(); + } else { + int comp = + QString::compare(leftMod->name(), rightMod->name(), Qt::CaseInsensitive); + if (comp != 0) + lt = comp < 0; + } + } break; + case ModList::COL_NOTES: { + QString leftComments = leftMod->comments(); + QString rightComments = rightMod->comments(); + if (leftComments != rightComments) { + if (leftComments.isEmpty()) { + lt = sortOrder() == Qt::DescendingOrder; + } else if (rightComments.isEmpty()) { + lt = sortOrder() == Qt::AscendingOrder; + } else { + lt = leftComments < rightComments; + } + } + } break; + case ModList::COL_PRIORITY: { + if (leftMod->isBackup() != rightMod->isBackup()) { + lt = leftMod->isBackup(); + } else if (leftMod->isOverwrite() != rightMod->isOverwrite()) { + lt = rightMod->isOverwrite(); + } + } break; + default: { + log::warn("Sorting is not defined for column {}", left.column()); + } break; + } + return lt; +} + +void ModListSortProxy::updateFilter(const QString& filter) +{ + m_Filter = filter; + updateFilterActive(); + invalidateFilter(); + emit filterInvalidated(); +} + +bool ModListSortProxy::hasConflictFlag( + const std::vector& flags) const +{ + for (ModInfo::EConflictFlag flag : flags) { + if ((flag == ModInfo::FLAG_CONFLICT_MIXED) || + (flag == ModInfo::FLAG_CONFLICT_OVERWRITE) || + (flag == ModInfo::FLAG_CONFLICT_OVERWRITTEN) || + (flag == ModInfo::FLAG_CONFLICT_REDUNDANT) || + (flag == ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITE) || + (flag == ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITTEN) || + (flag == ModInfo::FLAG_ARCHIVE_CONFLICT_MIXED) || + (flag == ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITE) || + (flag == ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITTEN)) { + return true; + } + } + + return false; +} + +bool ModListSortProxy::filterMatchesModAnd(ModInfo::Ptr info, bool enabled) const +{ + for (auto&& c : m_Criteria) { + if (!criteriaMatchMod(info, enabled, c)) { + return false; + } + } + + return true; +} + +bool ModListSortProxy::filterMatchesModOr(ModInfo::Ptr info, bool enabled) const +{ + for (auto&& c : m_Criteria) { + if (criteriaMatchMod(info, enabled, c)) { + return true; + } + } + + if (!m_Criteria.empty()) { + // nothing matched + return false; + } + + return true; +} + +bool ModListSortProxy::optionsMatchMod(ModInfo::Ptr info, bool) const +{ + return true; +} + +bool ModListSortProxy::criteriaMatchMod(ModInfo::Ptr info, bool enabled, + const Criteria& c) const +{ + bool b = false; + + switch (c.type) { + case TypeSpecial: // fall-through + case TypeCategory: { + b = categoryMatchesMod(info, enabled, c.id); + break; + } + + case TypeContent: { + b = contentMatchesMod(info, enabled, c.id); + break; + } + + default: { + log::error("bad criteria type {}", c.type); + break; + } + } + + if (c.inverse) { + b = !b; + } + + return b; +} + +bool ModListSortProxy::categoryMatchesMod(ModInfo::Ptr info, bool enabled, + int category) const +{ + bool b = false; + + switch (category) { + case CategoryFactory::Checked: { + b = (enabled || info->alwaysEnabled()); + break; + } + + case CategoryFactory::UpdateAvailable: { + b = (info->updateAvailable() || info->downgradeAvailable()); + break; + } + + case CategoryFactory::HasCategory: { + b = !info->getCategories().empty(); + break; + } + + case CategoryFactory::Conflict: { + b = (hasConflictFlag(info->getConflictFlags())); + break; + } + + case CategoryFactory::HasHiddenFiles: { + b = (info->hasFlag(ModInfo::FLAG_HIDDEN_FILES)); + break; + } + + case CategoryFactory::Endorsed: { + b = (info->endorsedState() == EndorsedState::ENDORSED_TRUE); + break; + } + + case CategoryFactory::Backup: { + b = (info->hasFlag(ModInfo::FLAG_BACKUP)); + break; + } + + case CategoryFactory::Managed: { + b = (!info->hasFlag(ModInfo::FLAG_FOREIGN)); + break; + } + + case CategoryFactory::HasGameData: { + b = !info->hasFlag(ModInfo::FLAG_INVALID); + break; + } + + case CategoryFactory::HasNexusID: { + // never show these + if (info->hasFlag(ModInfo::FLAG_FOREIGN) || info->hasFlag(ModInfo::FLAG_BACKUP) || + info->hasFlag(ModInfo::FLAG_OVERWRITE)) { + return false; + } + + b = (info->nexusId() > 0); + break; + } + + case CategoryFactory::Tracked: { + b = (info->trackedState() == TrackedState::TRACKED_TRUE); + break; + } + + default: { + b = (info->categorySet(category)); + break; + } + } + + return b; +} + +bool ModListSortProxy::contentMatchesMod(ModInfo::Ptr info, bool enabled, + int content) const +{ + return info->hasContent(content); +} + +bool ModListSortProxy::filterMatchesMod(ModInfo::Ptr info, bool enabled) const +{ + // don't check if there are no filters selected + if (!m_FilterActive) { + return true; + } + + // special case for separators + if (info->hasFlag(ModInfo::FLAG_SEPARATOR)) { + switch (m_FilterSeparators) { + case SeparatorFilter: { + // filter normally + break; + } + + case SeparatorShow: { + // force visible + return true; + } + + case SeparatorHide: { + // force hide + return false; + } + } + } + + if (!m_Filter.isEmpty()) { + bool display = false; + QString filterCopy = QString(m_Filter); + filterCopy.replace("||", ";").replace("OR", ";").replace("|", ";"); + QStringList ORList = filterCopy.split(";", Qt::SkipEmptyParts); + + bool segmentGood = true; + + // split in ORSegments that internally use AND logic + for (auto& ORSegment : ORList) { + QStringList ANDKeywords = ORSegment.split(" ", Qt::SkipEmptyParts); + segmentGood = true; + bool foundKeyword = false; + + // check each word in the segment for match, each word needs to be matched but it + // doesn't matter where. + for (auto& currentKeyword : ANDKeywords) { + foundKeyword = false; + + // search keyword in name + if (m_EnabledColumns[ModList::COL_NAME] && + info->name().contains(currentKeyword, Qt::CaseInsensitive)) { + foundKeyword = true; + } + + // Search by notes + if (!foundKeyword && m_EnabledColumns[ModList::COL_NOTES] && + info->comments().contains(currentKeyword, Qt::CaseInsensitive)) { + foundKeyword = true; + } + + // Search by categories + if (!foundKeyword && m_EnabledColumns[ModList::COL_CATEGORY]) { + for (auto category : info->categories()) { + if (category.contains(currentKeyword, Qt::CaseInsensitive)) { + foundKeyword = true; + break; + } + } + } + + // Search by Nexus ID + if (!foundKeyword && m_EnabledColumns[ModList::COL_MODID]) { + bool ok; + int filterID = currentKeyword.toInt(&ok); + if (ok) { + int modID = info->nexusId(); + while (modID > 0) { + if (modID == filterID) { + foundKeyword = true; + break; + } + modID = (int)(modID / 10); + } + } + } + + if (!foundKeyword) { + // currentKeword is missing from everything, AND fails and we need to check + // next ORsegment + segmentGood = false; + break; + } + + } // for ANDKeywords loop + + if (segmentGood) { + // the last AND loop didn't break so the ORSegments is true so mod matches + // filter + display = true; + break; + } + + } // for ORList loop + + if (!display) { + return false; + } + } // if (!m_CurrentFilter.isEmpty()) + + if (m_FilterMode == FilterAnd) { + return filterMatchesModAnd(info, enabled); + } else { + return filterMatchesModOr(info, enabled); + } +} + +void ModListSortProxy::setColumnVisible(int column, bool visible) +{ + m_EnabledColumns[column] = visible; +} + +void ModListSortProxy::setOptions(ModListSortProxy::FilterMode mode, + SeparatorsMode separators) +{ + if (m_FilterMode != mode || separators != m_FilterSeparators) { + m_FilterMode = mode; + m_FilterSeparators = separators; + invalidateFilter(); + emit filterInvalidated(); + } +} + +bool ModListSortProxy::filterAcceptsRow(int source_row, const QModelIndex& parent) const +{ + if (m_Profile == nullptr) { + return false; + } + + if (source_row >= static_cast(m_Profile->numMods())) { + log::warn("invalid row index: {}", source_row); + return false; + } + + QModelIndex idx = sourceModel()->index(source_row, 0, parent); + if (!idx.isValid()) { + log::debug("invalid mod index"); + return false; + } + + unsigned int index = ULONG_MAX; + { + bool ok = false; + index = idx.data(ModList::IndexRole).toInt(&ok); + if (!ok) { + index = ULONG_MAX; + } + } + + if (sourceModel()->hasChildren(idx)) { + // we need to check the separator itself first + if (index < ModInfo::getNumMods() && ModInfo::getByIndex(index)->isSeparator()) { + if (filterMatchesMod(ModInfo::getByIndex(index), false)) { + return true; + } + } + for (int i = 0; i < sourceModel()->rowCount(idx); ++i) { + if (filterAcceptsRow(i, idx)) { + return true; + } + } + + return false; + } else { + bool modEnabled = + idx.sibling(source_row, 0).data(Qt::CheckStateRole).toInt() == Qt::Checked; + return filterMatchesMod(ModInfo::getByIndex(index), modEnabled); + } +} + +bool ModListSortProxy::sourceIsByPriorityProxy() const +{ + return dynamic_cast(sourceModel()) != nullptr; +} + +bool ModListSortProxy::canDropMimeData(const QMimeData* data, Qt::DropAction action, + int row, int column, + const QModelIndex& parent) const +{ + ModListDropInfo dropInfo(data, *m_Organizer); + + if (!dropInfo.isLocalFileDrop() && sortColumn() != ModList::COL_PRIORITY) { + return false; + } + + // disable drop install with group proxy, except the one for collapsible separator + // - it would be nice to be able to "install to category" or something like that but + // it's a bit more complicated since the drop position is based on the category, so + // just disabling for now + if (dropInfo.isDownloadDrop()) { + // maybe there is a cleaner way? + if (qobject_cast(sourceModel())) { + return false; + } + } + + // see dropMimeData for details + if (sortOrder() == Qt::DescendingOrder && row != -1 && !sourceIsByPriorityProxy()) { + --row; + } + + return QSortFilterProxyModel::canDropMimeData(data, action, row, column, parent); +} + +bool ModListSortProxy::dropMimeData(const QMimeData* data, Qt::DropAction action, + int row, int column, const QModelIndex& parent) +{ + ModListDropInfo dropInfo(data, *m_Organizer); + + if (!dropInfo.isLocalFileDrop() && sortColumn() != ModList::COL_PRIORITY) { + QWidget* wid = qApp->activeWindow()->findChild("modList"); + MessageDialog::showMessage( + tr("Drag&Drop is only supported when sorting by priority"), wid); + return false; + } + + if (row == -1 && column == -1) { + return sourceModel()->dropMimeData(data, action, -1, -1, mapToSource(parent)); + } + + // in the regular model, when dropping between rows, the row-value passed to + // the sourceModel is inconsistent between ascending and descending ordering + // + // we want to fix that, but we cannot do it for the by-priority proxy because + // it messes up with non top-level items, so we simply forward the row and the + // by-priority proxy will fix the row for us + if (sortOrder() == Qt::DescendingOrder && row != -1 && !sourceIsByPriorityProxy()) { + --row; + } + + return QSortFilterProxyModel::dropMimeData(data, action, row, column, parent); +} + +void ModListSortProxy::setSourceModel(QAbstractItemModel* sourceModel) +{ + QSortFilterProxyModel::setSourceModel(sourceModel); + QAbstractProxyModel* proxy = qobject_cast(sourceModel); + if (proxy != nullptr) { + sourceModel = proxy->sourceModel(); + } + if (sourceModel) { + connect(sourceModel, SIGNAL(aboutToChangeData()), this, SLOT(aboutToChangeData()), + Qt::UniqueConnection); + connect(sourceModel, SIGNAL(postDataChanged()), this, SLOT(postDataChanged()), + Qt::UniqueConnection); + } +} + +void ModListSortProxy::aboutToChangeData() +{ + // having a filter active when dataChanged is called caused a crash + // (at least with some Qt versions) + // this may be related to the fact that the item being edited may disappear from the + // view as a result of the edit + m_PreChangeCriteria = m_Criteria; + setCriteria({}); +} + +void ModListSortProxy::postDataChanged() +{ + // if the filter is re-activated right away the editor can't be deleted but becomes + // invisible or at least the view continues to think it's being edited. As a result no + // new editor can be opened + QTimer::singleShot(10, [this]() { + setCriteria(m_PreChangeCriteria); + m_PreChangeCriteria.clear(); + }); +} diff --git a/src/modlistsortproxy.h b/src/modlistsortproxy.h index 23be77ede..2d7fbea37 100644 --- a/src/modlistsortproxy.h +++ b/src/modlistsortproxy.h @@ -1,172 +1,167 @@ -/* -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 MODLISTSORTPROXY_H -#define MODLISTSORTPROXY_H - -#include -#include -#include "modlist.h" - -class Profile; -class OrganizerCore; - -class ModListSortProxy : public QSortFilterProxyModel -{ - Q_OBJECT - -public: - enum FilterMode - { - FilterAnd, - FilterOr - }; - - enum CriteriaType { - TypeSpecial, - TypeCategory, - TypeContent - }; - - enum SeparatorsMode - { - SeparatorFilter, - SeparatorShow, - SeparatorHide - }; - - struct Criteria - { - CriteriaType type; - int id; - bool inverse; - - bool operator==(const Criteria& other) const - { - return - (type == other.type) && - (id == other.id) && - (inverse == other.inverse); - } - - bool operator!=(const Criteria& other) const - { - return !(*this == other); - } - }; - -public: - - explicit ModListSortProxy(Profile *profile, OrganizerCore *organizer); - - void setProfile(Profile *profile); - - bool canDropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) const override; - bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override; - - virtual void setSourceModel(QAbstractItemModel *sourceModel) override; - - /** - * @brief tests if a filtere matches for a mod - * @param info mod information - * @param enabled true if the mod is currently active - * @return true if current active filters match for the specified mod - */ - bool filterMatchesMod(ModInfo::Ptr info, bool enabled) const; - - /** - * @return true if a filter is currently active - */ - bool isFilterActive() const { return m_FilterActive; } - - void setCriteria(const std::vector& criteria); - void setOptions(FilterMode mode, SeparatorsMode separators); - - auto filterMode() const { return m_FilterMode; } - auto separatorsMode() const { return m_FilterSeparators; } - - /** - * @brief tests if the specified index has child nodes - * @param parent the node to test - * @return true if there are child nodes - */ - virtual bool hasChildren ( const QModelIndex & parent = QModelIndex() ) const { - return rowCount(parent) > 0; - } - - /** - * @brief sets whether a column is visible - * @param column the index of the column - * @param visible the visibility of the column - */ - void setColumnVisible(int column, bool visible); - -public slots: - - void updateFilter(const QString &filter); - -signals: - - void filterActive(bool active); - void filterInvalidated(); - -protected: - - virtual bool lessThan(const QModelIndex &left, const QModelIndex &right) const; - virtual bool filterAcceptsRow(int row, const QModelIndex &parent) const; - -private: - - unsigned long flagsId(const std::vector &flags) const; - unsigned long conflictFlagsId(const std::vector& flags) const; - bool hasConflictFlag(const std::vector &flags) const; - void updateFilterActive(); - bool filterMatchesModAnd(ModInfo::Ptr info, bool enabled) const; - bool filterMatchesModOr(ModInfo::Ptr info, bool enabled) const; - - // check if the source model is the by-priority proxy - // - bool sourceIsByPriorityProxy() const; - -private slots: - - void aboutToChangeData(); - void postDataChanged(); - -private: - OrganizerCore* m_Organizer; - - Profile* m_Profile; - std::vector m_Criteria; - QString m_Filter; - std::bitset m_EnabledColumns; - - bool m_FilterActive; - FilterMode m_FilterMode; - SeparatorsMode m_FilterSeparators; - - std::vector m_PreChangeCriteria; - - bool optionsMatchMod(ModInfo::Ptr info, bool enabled) const; - bool criteriaMatchMod(ModInfo::Ptr info, bool enabled, const Criteria& c) const; - bool categoryMatchesMod(ModInfo::Ptr info, bool enabled, int category) const; - bool contentMatchesMod(ModInfo::Ptr info, bool enabled, int content) const; -}; - -#endif // MODLISTSORTPROXY_H +/* +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 MODLISTSORTPROXY_H +#define MODLISTSORTPROXY_H + +#include "modlist.h" +#include +#include + +class Profile; +class OrganizerCore; + +class ModListSortProxy : public QSortFilterProxyModel +{ + Q_OBJECT + +public: + enum FilterMode + { + FilterAnd, + FilterOr + }; + + enum CriteriaType + { + TypeSpecial, + TypeCategory, + TypeContent + }; + + enum SeparatorsMode + { + SeparatorFilter, + SeparatorShow, + SeparatorHide + }; + + struct Criteria + { + CriteriaType type; + int id; + bool inverse; + + bool operator==(const Criteria& other) const + { + return (type == other.type) && (id == other.id) && (inverse == other.inverse); + } + + bool operator!=(const Criteria& other) const { return !(*this == other); } + }; + +public: + explicit ModListSortProxy(Profile* profile, OrganizerCore* organizer); + + void setProfile(Profile* profile); + + bool canDropMimeData(const QMimeData* data, Qt::DropAction action, int row, + int column, const QModelIndex& parent) const override; + bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, + const QModelIndex& parent) override; + + virtual void setSourceModel(QAbstractItemModel* sourceModel) override; + + /** + * @brief tests if a filtere matches for a mod + * @param info mod information + * @param enabled true if the mod is currently active + * @return true if current active filters match for the specified mod + */ + bool filterMatchesMod(ModInfo::Ptr info, bool enabled) const; + + /** + * @return true if a filter is currently active + */ + bool isFilterActive() const { return m_FilterActive; } + + void setCriteria(const std::vector& criteria); + void setOptions(FilterMode mode, SeparatorsMode separators); + + auto filterMode() const { return m_FilterMode; } + auto separatorsMode() const { return m_FilterSeparators; } + + /** + * @brief tests if the specified index has child nodes + * @param parent the node to test + * @return true if there are child nodes + */ + virtual bool hasChildren(const QModelIndex& parent = QModelIndex()) const + { + return rowCount(parent) > 0; + } + + /** + * @brief sets whether a column is visible + * @param column the index of the column + * @param visible the visibility of the column + */ + void setColumnVisible(int column, bool visible); + +public slots: + + void updateFilter(const QString& filter); + +signals: + + void filterActive(bool active); + void filterInvalidated(); + +protected: + virtual bool lessThan(const QModelIndex& left, const QModelIndex& right) const; + virtual bool filterAcceptsRow(int row, const QModelIndex& parent) const; + +private: + unsigned long flagsId(const std::vector& flags) const; + unsigned long conflictFlagsId(const std::vector& flags) const; + bool hasConflictFlag(const std::vector& flags) const; + void updateFilterActive(); + bool filterMatchesModAnd(ModInfo::Ptr info, bool enabled) const; + bool filterMatchesModOr(ModInfo::Ptr info, bool enabled) const; + + // check if the source model is the by-priority proxy + // + bool sourceIsByPriorityProxy() const; + +private slots: + + void aboutToChangeData(); + void postDataChanged(); + +private: + OrganizerCore* m_Organizer; + + Profile* m_Profile; + std::vector m_Criteria; + QString m_Filter; + std::bitset m_EnabledColumns; + + bool m_FilterActive; + FilterMode m_FilterMode; + SeparatorsMode m_FilterSeparators; + + std::vector m_PreChangeCriteria; + + bool optionsMatchMod(ModInfo::Ptr info, bool enabled) const; + bool criteriaMatchMod(ModInfo::Ptr info, bool enabled, const Criteria& c) const; + bool categoryMatchesMod(ModInfo::Ptr info, bool enabled, int category) const; + bool contentMatchesMod(ModInfo::Ptr info, bool enabled, int content) const; +}; + +#endif // MODLISTSORTPROXY_H diff --git a/src/modlistversiondelegate.cpp b/src/modlistversiondelegate.cpp index 5ce574a57..fbb4f03e6 100644 --- a/src/modlistversiondelegate.cpp +++ b/src/modlistversiondelegate.cpp @@ -1,41 +1,43 @@ #include "modlistversiondelegate.h" -#include "settings.h" -#include "modlistview.h" #include "log.h" +#include "modlistview.h" +#include "settings.h" -ModListVersionDelegate::ModListVersionDelegate(ModListView* view, Settings& settings) : - QItemDelegate(view), m_view(view), m_settings(settings) -{ -} - +ModListVersionDelegate::ModListVersionDelegate(ModListView* view, Settings& settings) + : QItemDelegate(view), m_view(view), m_settings(settings) +{} -void ModListVersionDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const +void ModListVersionDelegate::paint(QPainter* painter, + const QStyleOptionViewItem& option, + const QModelIndex& index) const { m_view->itemDelegate()->paint(painter, option, index); - if (m_view->hasCollapsibleSeparators() - && m_view->model()->hasChildren(index) - && m_settings.interface().collapsibleSeparatorsIcons(ModList::COL_VERSION) - && !m_view->isExpanded(index.sibling(index.row(), 0))) { + if (m_view->hasCollapsibleSeparators() && m_view->model()->hasChildren(index) && + m_settings.interface().collapsibleSeparatorsIcons(ModList::COL_VERSION) && + !m_view->isExpanded(index.sibling(index.row(), 0))) { auto* model = m_view->model(); bool downgrade = false, upgrade = false; for (int i = 0; i < model->rowCount(index); ++i) { - const auto mIndex = model->index(i, index.column(), index).data(ModList::IndexRole); + const auto mIndex = + model->index(i, index.column(), index).data(ModList::IndexRole); if (mIndex.isValid()) { auto info = ModInfo::getByIndex(mIndex.toInt()); downgrade = downgrade || info->downgradeAvailable(); - upgrade = upgrade || info->updateAvailable(); + upgrade = upgrade || info->updateAvailable(); } } - const int margin = m_view->style()->pixelMetric(QStyle::PM_FocusFrameHMargin, nullptr, m_view) + 1; + const int margin = + m_view->style()->pixelMetric(QStyle::PM_FocusFrameHMargin, nullptr, m_view) + 1; QStyleOptionViewItem opt(option); - const int sz = m_view->style()->pixelMetric(QStyle::PM_SmallIconSize, nullptr, m_view); - opt.decorationSize = QSize(sz, sz); + const int sz = + m_view->style()->pixelMetric(QStyle::PM_SmallIconSize, nullptr, m_view); + opt.decorationSize = QSize(sz, sz); opt.decorationAlignment = Qt::AlignCenter; if (upgrade) { @@ -60,7 +62,6 @@ void ModListVersionDelegate::paint(QPainter* painter, const QStyleOptionViewItem opt.rect.setRect(opt.rect.x(), opt.rect.y(), pm.width(), opt.rect.height()); drawDecoration(painter, opt, opt.rect, pixmap); - } } } diff --git a/src/modlistversiondelegate.h b/src/modlistversiondelegate.h index d3d0ad3b3..53a6e8ba4 100644 --- a/src/modlistversiondelegate.h +++ b/src/modlistversiondelegate.h @@ -11,8 +11,8 @@ class ModListVersionDelegate : public QItemDelegate public: ModListVersionDelegate(ModListView* view, Settings& settings); - void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override; - + void paint(QPainter* painter, const QStyleOptionViewItem& option, + const QModelIndex& index) const override; private: ModListView* m_view; diff --git a/src/modlistview.cpp b/src/modlistview.cpp index a37a52612..944825fac 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -1,1465 +1,1546 @@ -#include "modlistview.h" -#include -#include -#include - -#include - -#include "filesystemutilities.h" -#include - -#include "ui_mainwindow.h" - -#include "filterlist.h" -#include "organizercore.h" -#include "modlist.h" -#include "modlistsortproxy.h" -#include "modlistbypriorityproxy.h" -#include "log.h" -#include "modflagicondelegate.h" -#include "modconflicticondelegate.h" -#include "modcontenticondelegate.h" -#include "modlistversiondelegate.h" -#include "modlistviewactions.h" -#include "modlistdropinfo.h" -#include "modlistcontextmenu.h" -#include "genericicondelegate.h" -#include "copyeventfilter.h" -#include "shared/fileentry.h" -#include "shared/directoryentry.h" -#include "shared/filesorigin.h" -#include "mainwindow.h" -#include "modelutils.h" - -using namespace MOBase; -using namespace MOShared; - -// delegate to remove indentation for mods when using collapsible -// separator -// -// the delegate works by removing the indentation of the child items -// before drawing, but unfortunately this normally breaks event -// handling (e.g. checkbox, edit, etc.), so we also need to override -// the visualRect() function from the mod list view. -// -class ModListStyledItemDelegate : public QStyledItemDelegate -{ - ModListView* m_view; - -public: - - ModListStyledItemDelegate(ModListView* view) : - QStyledItemDelegate(view), m_view(view) { } - - void initStyleOption(QStyleOptionViewItem* option, const QModelIndex& index) const override - { - // the parent version always overwrite the background brush, so - // we need to save it and restore it - auto backgroundColor = option->backgroundBrush.color(); - QStyledItemDelegate::initStyleOption(option, index); - - if (backgroundColor.isValid()) { - option->backgroundBrush = backgroundColor; - } - } - - void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override - { - QStyleOptionViewItem opt(option); - - // remove items indentaiton when using collapsible separators - if (index.column() == 0 && m_view->hasCollapsibleSeparators()) { - if (!index.model()->hasChildren(index) && index.parent().isValid()) { - auto parentIndex = index.parent().data(ModList::IndexRole).toInt(); - if (ModInfo::getByIndex(parentIndex)->isSeparator()) { - opt.rect.adjust(-m_view->indentation(), 0, 0, 0); - } - } - } - - // compute required color from children, otherwise fallback to the - // color from the model, and draw the background here - auto color = m_view->markerColor(index); - if (!color.isValid()) { - color = index.data(Qt::BackgroundRole).value(); - } - opt.backgroundBrush = color; - - // we need to find the background color to compute the ideal text color - // but the mod list view uses alternate color so we need to find the - // right color - auto bg = opt.palette.base().color(); - if (opt.features & QStyleOptionViewItem::Alternate) { - bg = opt.palette.alternateBase().color(); - } - - // compute ideal foreground color for some rows - if (color.isValid()) { - if (((index.column() == ModList::COL_NAME || index.column() == ModList::COL_PRIORITY) - && ModInfo::getByIndex(index.data(ModList::IndexRole).toInt())->isSeparator()) - || index.column() == ModList::COL_NOTES) { - - // combine the color with the background and then find the "ideal" text color - const auto a = color.alpha() / 255.; - int r = (1 - a) * bg.red() + a * color.red(), - g = (1 - a) * bg.green() + a * color.green(), - b = (1 - a) * bg.blue() + a * color.blue(); - opt.palette.setBrush(QPalette::Text, ColorSettings::idealTextColor(QColor(r, g, b))); - } - } - - QStyledItemDelegate::paint(painter, opt, index); - } -}; - -class ModListViewMarkingScrollBar : public ViewMarkingScrollBar { - ModListView* m_view; -public: - ModListViewMarkingScrollBar(ModListView* view) : - ViewMarkingScrollBar(view, ModList::ScrollMarkRole), m_view(view) { } - - - QColor color(const QModelIndex& index) const override - { - auto color = m_view->markerColor(index); - if (!color.isValid()) { - color = ViewMarkingScrollBar::color(index); - } - return color; - } - -}; - -ModListView::ModListView(QWidget* parent) - : QTreeView(parent) - , m_core(nullptr) - , m_sortProxy(nullptr) - , m_byPriorityProxy(nullptr) - , m_byCategoryProxy(nullptr) - , m_byNexusIdProxy(nullptr) - , m_markers{ {}, {}, {}, {}, {}, {} } - , m_scrollbar(new ModListViewMarkingScrollBar(this)) -{ - setVerticalScrollBar(m_scrollbar); - MOBase::setCustomizableColumns(this); - setAutoExpandDelay(750); - - setItemDelegate(new ModListStyledItemDelegate(this)); - - connect(this, &ModListView::doubleClicked, this, &ModListView::onDoubleClicked); - connect(this, &ModListView::customContextMenuRequested, this, &ModListView::onCustomContextMenuRequested); - - // the timeout is pretty small because its main purpose is to avoid - // refreshing multiple times when calling expandAll() or collapseAll() - // which emit a lots of expanded/collapsed signals in a very small - // time window - m_refreshMarkersTimer.setInterval(50); - m_refreshMarkersTimer.setSingleShot(true); - connect(&m_refreshMarkersTimer, &QTimer::timeout, [=] { refreshMarkersAndPlugins(); }); - - installEventFilter(new CopyEventFilter(this, [=](auto& index) { - QVariant mIndex = index.data(ModList::IndexRole); - QString name = index.data(Qt::DisplayRole).toString(); - if (mIndex.isValid() && hasCollapsibleSeparators()) { - ModInfo::Ptr info = ModInfo::getByIndex(mIndex.toInt()); - if (info->isSeparator()) { - name = "[" + name + "]"; - } - } - else if (model()->hasChildren(index)) { - name = "[" + name + "]"; - } - return name; - })); -} - -void ModListView::refresh() -{ - updateGroupByProxy(); -} - -void ModListView::onProfileChanged(Profile* oldProfile, Profile* newProfile) -{ - const auto perProfileSeparators = - m_core->settings().interface().collapsibleSeparatorsPerProfile(); - - // save expanded/collapsed state of separators - if (oldProfile && perProfileSeparators) { - auto& collapsed = m_collapsed[m_byPriorityProxy]; - oldProfile->storeSetting("UserInterface", "collapsed_separators", QStringList(collapsed.begin(), collapsed.end())); - } - - m_sortProxy->setProfile(newProfile); - m_byPriorityProxy->setProfile(newProfile); - - if (newProfile && perProfileSeparators) { - auto collapsed = newProfile->setting("UserInterface", "collapsed_separators", QStringList()).toStringList(); - m_collapsed[m_byPriorityProxy] = { collapsed.begin(), collapsed.end() }; - } -} - -bool ModListView::hasCollapsibleSeparators() const -{ - return groupByMode() == GroupByMode::SEPARATOR; -} - -int ModListView::sortColumn() const -{ - return m_sortProxy ? m_sortProxy->sortColumn() : -1; -} - -Qt::SortOrder ModListView::sortOrder() const -{ - return m_sortProxy ? m_sortProxy->sortOrder() : Qt::AscendingOrder; -} - -bool ModListView::isFilterActive() const -{ - return m_sortProxy && m_sortProxy->isFilterActive(); -} - -ModListView::GroupByMode ModListView::groupByMode() const -{ - if (m_sortProxy == nullptr) { - return GroupByMode::NONE; - } - else if (m_sortProxy->sourceModel() == m_byPriorityProxy) { - return GroupByMode::SEPARATOR; - } - else if (m_sortProxy->sourceModel() == m_byCategoryProxy) { - return GroupByMode::CATEGORY; - } - else if (m_sortProxy->sourceModel() == m_byNexusIdProxy) { - return GroupByMode::NEXUS_ID; - } - else { - return GroupByMode::NONE; - } -} - -ModListViewActions& ModListView::actions() const -{ - return *m_actions; -} - -std::optional ModListView::nextMod(unsigned int modIndex) const -{ - const QModelIndex start = indexModelToView(m_core->modList()->index(modIndex, 0)); - - auto index = start; - - for (;;) { - index = nextIndex(index); - - if (index == start || !index.isValid()) { - // wrapped around, give up - break; - } - - modIndex = index.data(ModList::IndexRole).toInt(); - - ModInfo::Ptr mod = ModInfo::getByIndex(modIndex); - - // skip overwrite, backups and separators - if (mod->isOverwrite() || mod->isBackup() || mod->isSeparator()) { - continue; - } - - return modIndex; - } - - return {}; -} - -std::optional ModListView::prevMod(unsigned int modIndex) const -{ - const QModelIndex start = indexModelToView(m_core->modList()->index(modIndex, 0)); - - auto index = start; - - for (;;) { - index = prevIndex(index); - - if (index == start || !index.isValid()) { - // wrapped around, give up - break; - } - - modIndex = index.data(ModList::IndexRole).toInt(); - - // skip overwrite, backups and separators - ModInfo::Ptr mod = ModInfo::getByIndex(modIndex); - if (mod->isOverwrite() || mod->isBackup() || mod->isSeparator()) { - continue; - } - - return modIndex; - } - - return {}; -} - -void ModListView::invalidateFilter() -{ - m_sortProxy->invalidate(); -} - -void ModListView::setFilterCriteria(const std::vector& criteria) -{ - m_sortProxy->setCriteria(criteria); -} - -void ModListView::setFilterOptions(ModListSortProxy::FilterMode mode, ModListSortProxy::SeparatorsMode sep) -{ - m_sortProxy->setOptions(mode, sep); -} - -bool ModListView::isModVisible(unsigned int index) const -{ - return m_sortProxy->filterMatchesMod(ModInfo::getByIndex(index), m_core->currentProfile()->modEnabled(index)); -} - -bool ModListView::isModVisible(ModInfo::Ptr mod) const -{ - return m_sortProxy->filterMatchesMod(mod, m_core->currentProfile()->modEnabled(ModInfo::getIndex(mod->name()))); -} - -QModelIndex ModListView::indexModelToView(const QModelIndex& index) const -{ - return ::indexModelToView(index, this); -} - -QModelIndexList ModListView::indexModelToView(const QModelIndexList& index) const -{ - return ::indexModelToView(index, this); -} - -QModelIndex ModListView::indexViewToModel(const QModelIndex& index) const -{ - return ::indexViewToModel(index, m_core->modList()); -} - -QModelIndexList ModListView::indexViewToModel(const QModelIndexList& index) const -{ - return ::indexViewToModel(index, m_core->modList()); -} - -QModelIndex ModListView::nextIndex(const QModelIndex& index) const -{ - auto* model = index.model(); - if (!model) { - return {}; - } - - if (model->rowCount(index) > 0) { - return model->index(0, index.column(), index); - } - - if (index.parent().isValid()) { - if (index.row() + 1 < model->rowCount(index.parent())) { - return index.model()->index(index.row() + 1, index.column(), index.parent()); - } - else { - return index.model()->index((index.parent().row() + 1) % model->rowCount(index.parent().parent()), index.column(), index.parent().parent());; - } - } - else { - return index.model()->index((index.row() + 1) % model->rowCount(index.parent()), index.column(), index.parent()); - } -} - -QModelIndex ModListView::prevIndex(const QModelIndex& index) const -{ - if (index.row() == 0 && index.parent().isValid()) { - return index.parent(); - } - - auto* model = index.model(); - if (!model) { - return {}; - } - - auto prev = model->index((index.row() - 1) % model->rowCount(index.parent()), index.column(), index.parent()); - - if (model->rowCount(prev) > 0) { - return model->index(model->rowCount(prev) - 1, index.column(), prev); - } - - return prev; -} - -std::pair ModListView::selected() const -{ - return { indexViewToModel(currentIndex()), indexViewToModel(selectionModel()->selectedRows()) }; -} - -void ModListView::setSelected(const QModelIndex& current, const QModelIndexList& selected) -{ - setCurrentIndex(indexModelToView(current)); - for (auto idx : selected) { - selectionModel()->select(indexModelToView(idx), QItemSelectionModel::Select | QItemSelectionModel::Rows); - } -} - -void ModListView::scrollToAndSelect(const QModelIndex& index) -{ - scrollToAndSelect(QModelIndexList{index}); -} - -void ModListView::scrollToAndSelect(const QModelIndexList& indexes, const QModelIndex& current) -{ - // focus, scroll to and select - if (!current.isValid() && indexes.isEmpty()) { - return; - } - scrollTo(current.isValid() ? current : indexes.first()); - setCurrentIndex(current.isValid() ? current : indexes.first()); - QItemSelection selection; - for (auto& idx : indexes) { - selection.select(idx, idx); - } - selectionModel()->select(selection, QItemSelectionModel::Select | QItemSelectionModel::Rows); - QTimer::singleShot(50, [=] { setFocus(); }); -} - -void ModListView::refreshExpandedItems() -{ - auto* model = m_sortProxy->sourceModel(); - for (auto i = 0; i < model->rowCount(); ++i) { - auto idx = model->index(i, 0); - if (!m_collapsed[model].contains(idx.data(Qt::DisplayRole).toString())) { - setExpanded(m_sortProxy->mapFromSource(idx), true); - } - } -} - -void ModListView::onModPrioritiesChanged(const QModelIndexList& indices) -{ - // expand separator whose priority has changed and parents - for (auto index : indices) { - auto idx = indexModelToView(index); - if (hasCollapsibleSeparators() && model()->hasChildren(idx)) { - setExpanded(idx, true); - } - if (idx.parent().isValid()) { - setExpanded(idx.parent(), true); - } - } - - setOverwriteMarkers(selectionModel()->selectedRows()); -} - -void ModListView::onModInstalled(const QString& modName) -{ - unsigned int index = ModInfo::getIndex(modName); - - if (index == UINT_MAX) { - return; - } - - QModelIndex qIndex = indexModelToView(m_core->modList()->index(index, 0)); - - if (hasCollapsibleSeparators() && qIndex.parent().isValid()) { - setExpanded(qIndex.parent(), true); - } - - scrollToAndSelect(qIndex); -} - -void ModListView::onModFilterActive(bool filterActive) -{ - ui.clearFilters->setVisible(filterActive); - if (filterActive) { - setStyleSheet("QTreeView { border: 2px ridge #f00; }"); - ui.counter->setStyleSheet("QLCDNumber { border: 2px ridge #f00; }"); - } - else if (ui.groupBy->currentIndex() != GroupBy::NONE) { - setStyleSheet("QTreeView { border: 2px ridge #337733; }"); - ui.counter->setStyleSheet(""); - } - else { - setStyleSheet(""); - ui.counter->setStyleSheet(""); - } -} - -ModListView::ModCounters ModListView::counters() const -{ - ModCounters c; - - auto hasFlag = [](std::vector flags, ModInfo::EFlag filter) { - return std::find(flags.begin(), flags.end(), filter) != flags.end(); - }; - - - for (unsigned int index = 0; index < ModInfo::getNumMods(); ++index) { - auto info = ModInfo::getByIndex(index); - const auto flags = info->getFlags(); - - const bool enabled = m_core->currentProfile()->modEnabled(index); - const bool visible = m_sortProxy->filterMatchesMod(info, enabled); - - if (info->isBackup()) { - c.backup++; - if (visible) c.visible.backup++; - } - else if (info->isForeign()) { - c.foreign++; - if (visible) c.visible.foreign++; - } - else if (info->isSeparator()) { - c.separator++; - if (visible) c.visible.separator++; - } - else if (!info->isOverwrite()) { - c.regular++; - if (visible) c.visible.regular++; - if (enabled) { - c.active++; - if (visible) c.visible.active++; - } - } - } - - return c; -} - -void ModListView::updateModCount() -{ - const auto c = counters(); - - ui.counter->display(c.visible.active); - ui.counter->setToolTip(tr("" - "" - "" - "" - "" - "" - "
      TypeAllVisible
      Enabled mods: %1 / %2%3 / %4
      Unmanaged/DLCs: %5%6
      Mod backups: %7%8
      Separators: %9%10
      ") - .arg(c.active) - .arg(c.regular) - .arg(c.visible.active) - .arg(c.visible.regular) - .arg(c.foreign) - .arg(c.visible.foreign) - .arg(c.backup) - .arg(c.visible.backup) - .arg(c.separator) - .arg(c.visible.separator) - ); -} - -void ModListView::refreshFilters() -{ - auto [current, sourceRows] = selected(); - - setCurrentIndex(QModelIndex()); - m_filters->refresh(); - - setSelected(current, sourceRows); -} - -void ModListView::onExternalFolderDropped(const QUrl& url, int priority) -{ - setWindowState(Qt::WindowActive); - - QFileInfo fileInfo(url.toLocalFile()); - - GuessedValue name; - name.setFilter(&fixDirectoryName); - name.update(fileInfo.fileName(), GUESS_PRESET); - - do { - bool ok; - name.update(QInputDialog::getText(this, tr("Copy Folder..."), - tr("This will copy the content of %1 to a new mod.\n" - "Please enter the name:").arg(fileInfo.fileName()), QLineEdit::Normal, name, &ok), - GUESS_USER); - if (!ok) { - return; - } - } while (name->isEmpty()); - - if (m_core->modList()->getMod(name) != nullptr) { - reportError(tr("A mod with this name already exists.")); - return; - } - - IModInterface* newMod = m_core->createMod(name); - if (!newMod) { - return; - } - - // TODO: this is currently a silent copy, which can take some time, but there is - // no clean method to do this in uibase - if (!copyDir(fileInfo.absoluteFilePath(), newMod->absolutePath(), true)) { - return; - } - - m_core->refresh(); - - const auto index = ModInfo::getIndex(name); - if (priority != -1) { - m_core->modList()->changeModPriority(index, priority); - } - - scrollToAndSelect(indexModelToView(m_core->modList()->index(index, 0))); -} - -bool ModListView::moveSelection(int key) -{ - auto rows = selectionModel()->selectedRows(); - const QPersistentModelIndex current(key == Qt::Key_Up ? rows.first() : rows.last()); - - int offset = key == Qt::Key_Up ? -1 : 1; - if (m_sortProxy->sortOrder() == Qt::DescendingOrder) { - offset = -offset; - } - - m_core->modList()->shiftModsPriority(indexViewToModel(rows), offset); - selectionModel()->setCurrentIndex(current, QItemSelectionModel::NoUpdate); - scrollTo(current); - - return true; -} - -bool ModListView::removeSelection() -{ - m_actions->removeMods(indexViewToModel(selectionModel()->selectedRows())); - return true; -} - -bool ModListView::toggleSelectionState() -{ - if (!selectionModel()->hasSelection()) { - return true; - } - return m_core->modList()->toggleState(indexViewToModel(selectionModel()->selectedRows())); -} - -void ModListView::updateGroupByProxy() -{ - int groupIndex = ui.groupBy->currentIndex(); - auto* previousModel = m_sortProxy->sourceModel(); - - QAbstractItemModel* nextModel = m_core->modList(); - if (groupIndex == GroupBy::CATEGORY) { - nextModel = m_byCategoryProxy; - } - else if (groupIndex == GroupBy::NEXUS_ID) { - nextModel = m_byNexusIdProxy; - } - else if (m_core->settings().interface().collapsibleSeparators(m_sortProxy->sortOrder()) - && m_sortProxy->sortColumn() == ModList::COL_PRIORITY) { - m_byPriorityProxy->setSortOrder(m_sortProxy->sortOrder()); - nextModel = m_byPriorityProxy; - } - - if (nextModel != previousModel) { - - if (auto* proxy = dynamic_cast(nextModel)) { - proxy->setSourceModel(m_core->modList()); - } - m_sortProxy->setSourceModel(nextModel); - - // reset the source model of the old proxy because we do not want to - // react to signals - // - if (auto* proxy = qobject_cast(previousModel)) { - proxy->setSourceModel(nullptr); - } - - // expand items previously expanded - refreshExpandedItems(); - - if (hasCollapsibleSeparators()) { - ui.filterSeparators->setCurrentIndex(ModListSortProxy::SeparatorFilter); - ui.filterSeparators->setEnabled(false); - } - else { - ui.filterSeparators->setEnabled(true); - } - - } -} - -void ModListView::setup(OrganizerCore& core, CategoryFactory& factory, MainWindow* mw, Ui::MainWindow* mwui) -{ - // attributes - m_core = &core; - m_filters.reset(new FilterList(mwui, core, factory)); - m_categories = &factory; - m_actions = new ModListViewActions(core, *m_filters, factory, this, mwui->espList, mw); - ui = { - mwui->groupCombo, mwui->activeModsCounter, mwui->modFilterEdit, - mwui->currentCategoryLabel, mwui->clearFiltersButton, mwui->filtersSeparators, - mwui->espList - }; - - - connect(m_core, &OrganizerCore::modInstalled, [=](auto&& name) { onModInstalled(name); }); - connect(m_core, &OrganizerCore::profileChanged, this, &ModListView::onProfileChanged); - connect(core.modList(), &ModList::modPrioritiesChanged, [=](auto&& indices) { onModPrioritiesChanged(indices); }); - connect(core.modList(), &ModList::clearOverwrite, [=] { m_actions->clearOverwrite(); }); - connect(core.modList(), &ModList::modStatesChanged, [=] { - updateModCount(); - setOverwriteMarkers(selectionModel()->selectedRows()); - }); - connect(core.modList(), &ModList::modelReset, [=] { clearOverwriteMarkers(); }); - - // proxy for various group by - m_byPriorityProxy = new ModListByPriorityProxy(core.currentProfile(), core, this); - m_byCategoryProxy = new QtGroupingProxy(QModelIndex(), ModList::COL_CATEGORY, ModList::GroupingRole, 0, ModList::AggrRole); - m_byNexusIdProxy = new QtGroupingProxy(QModelIndex(), ModList::COL_MODID, ModList::GroupingRole, - QtGroupingProxy::FLAG_NOGROUPNAME | QtGroupingProxy::FLAG_NOSINGLE, ModList::AggrRole); - - // we need to store the expanded/collapsed state of all items and restore them 1) when - // switching proxies, 2) when filtering and 3) when reseting the mod list. - connect(this, &QTreeView::expanded, [=](const QModelIndex& index) { - auto it = m_collapsed[m_sortProxy->sourceModel()].find(index.data(Qt::DisplayRole).toString()); - if (it != m_collapsed[m_sortProxy->sourceModel()].end()) { - m_collapsed[m_sortProxy->sourceModel()].erase(it); - } - }); - connect(this, &QTreeView::collapsed, [=](const QModelIndex& index) { - m_collapsed[m_sortProxy->sourceModel()].insert(index.data(Qt::DisplayRole).toString()); - }); - - // the top-level proxy - m_sortProxy = new ModListSortProxy(core.currentProfile(), &core); - setModel(m_sortProxy); - connect(m_sortProxy, &ModList::modelReset, [=] { refreshExpandedItems(); }); - - // update the proxy when changing the sort column/direction and the group - connect(m_sortProxy, &QAbstractItemModel::layoutAboutToBeChanged, [this](auto&& parents, auto&& hint) { - if (hint == QAbstractItemModel::VerticalSortHint) { - updateGroupByProxy(); - } - }); - connect(ui.groupBy, QOverload::of(&QComboBox::currentIndexChanged), [=](int index) { - updateGroupByProxy(); - onModFilterActive(m_sortProxy->isFilterActive()); - }); - sortByColumn(ModList::COL_PRIORITY, Qt::AscendingOrder); - - // inform the mod list about the type of item being dropped at the beginning of a drag - // and the position of the drop indicator at the end (only for by-priority) - connect(this, &ModListView::dragEntered, core.modList(), &ModList::onDragEnter); - connect(this, &ModListView::dropEntered, m_byPriorityProxy, &ModListByPriorityProxy::onDropEnter); - - connect(m_sortProxy, &ModListSortProxy::filterInvalidated, this, &ModListView::updateModCount); - - connect(header(), &QHeaderView::sortIndicatorChanged, [=](int, Qt::SortOrder) { verticalScrollBar()->repaint(); }); - connect(header(), &QHeaderView::sectionResized, [=](int logicalIndex, int oldSize, int newSize) { - m_sortProxy->setColumnVisible(logicalIndex, newSize != 0); }); - - setItemDelegateForColumn(ModList::COL_FLAGS, new ModFlagIconDelegate(this, ModList::COL_FLAGS, 120)); - setItemDelegateForColumn(ModList::COL_CONFLICTFLAGS, new ModConflictIconDelegate(this, ModList::COL_CONFLICTFLAGS, 80)); - setItemDelegateForColumn(ModList::COL_CONTENT, new ModContentIconDelegate(this, ModList::COL_CONTENT, 150)); - setItemDelegateForColumn(ModList::COL_VERSION, new ModListVersionDelegate(this, core.settings())); - - if (m_core->settings().geometry().restoreState(header())) { - // hack: force the resize-signal to be triggered because restoreState doesn't seem to do that - for (int column = 0; column <= ModList::COL_LASTCOLUMN; ++column) { - int sectionSize = header()->sectionSize(column); - header()->resizeSection(column, sectionSize + 1); - header()->resizeSection(column, sectionSize); - } - } - else { - // hide these columns by default - header()->setSectionHidden(ModList::COL_CONTENT, true); - header()->setSectionHidden(ModList::COL_MODID, true); - header()->setSectionHidden(ModList::COL_GAME, true); - header()->setSectionHidden(ModList::COL_INSTALLTIME, true); - header()->setSectionHidden(ModList::COL_NOTES, true); - - // resize mod list to fit content - for (int i = 0; i < header()->count(); ++i) { - header()->setSectionResizeMode(i, QHeaderView::ResizeToContents); - } - - header()->setSectionResizeMode(ModList::COL_NAME, QHeaderView::Stretch); - } - - // prevent the name-column from being hidden - header()->setSectionHidden(ModList::COL_NAME, false); - - // we need QueuedConnection for the download/archive dropped otherwise the - // installation starts within the drop-event and it's not possible to drag&drop - // in the manual installer - connect(m_core->modList(), &ModList::downloadArchiveDropped, this, [=](int row, int priority) { - m_core->installDownload(row, priority); - }, Qt::QueuedConnection); - connect(m_core->modList(), &ModList::externalArchiveDropped, this, [=](const QUrl& url, int priority) { - setWindowState(Qt::WindowActive); - m_core->installArchive(url.toLocalFile(), priority, false, nullptr); - }, Qt::QueuedConnection); - connect(m_core->modList(), &ModList::externalFolderDropped, this, &ModListView::onExternalFolderDropped); - - connect(selectionModel(), &QItemSelectionModel::selectionChanged, [=] { m_refreshMarkersTimer.start(); }); - connect(this, &QTreeView::collapsed, [=] { m_refreshMarkersTimer.start(); }); - connect(this, &QTreeView::expanded, [=] { m_refreshMarkersTimer.start(); }); - - // filters - connect(m_sortProxy, &ModListSortProxy::filterActive, this, &ModListView::onModFilterActive); - connect(m_filters.get(), &FilterList::criteriaChanged, [=](auto&& v) { onFiltersCriteria(v); }); - connect(m_filters.get(), &FilterList::optionsChanged, [=](auto&& mode, auto&& sep) { setFilterOptions(mode, sep); }); - connect(ui.filter, &QLineEdit::textChanged, m_sortProxy, &ModListSortProxy::updateFilter); - connect(ui.clearFilters, &QPushButton::clicked, [=]() { - ui.filter->clear(); - m_filters->clearSelection(); - }); - connect(m_sortProxy, &ModListSortProxy::filterInvalidated, [=]() { - if (hasCollapsibleSeparators()) { - refreshExpandedItems(); - } - }); -} - -void ModListView::restoreState(const Settings& s) -{ - s.geometry().restoreState(header()); - - s.widgets().restoreIndex(ui.groupBy); - s.widgets().restoreTreeExpandState(this); - - m_filters->restoreState(s); -} - -void ModListView::saveState(Settings& s) const -{ - s.geometry().saveState(header()); - - s.widgets().saveIndex(ui.groupBy); - s.widgets().saveTreeExpandState(this); - - m_filters->saveState(s); -} - -QRect ModListView::visualRect(const QModelIndex& index) const -{ - // this shift the visualRect() from QTreeView to match the new actual - // zone after removing indentation (see the ModListStyledItemDelegate) - QRect rect = QTreeView::visualRect(index); - if (hasCollapsibleSeparators() - && index.column() == 0 && index.isValid() - && index.parent().isValid()) { - rect.adjust(-indentation(), 0, 0, 0); - } - return rect; -} - -void ModListView::drawBranches(QPainter* painter, const QRect& rect, const QModelIndex& index) const -{ - // the branches are the small indicator left to the row (there are none in the default style, and - // the VS dark style only has background for these) - // - // the branches are not shifted left with the visualRect() change and since MO2 uses stylesheet, - // it is not possible to shift those in the proxy style so we have to shift it here. - // - QRect r(rect); - if (hasCollapsibleSeparators() && index.parent().isValid()) { - r.adjust(-indentation(), 0, 0 -indentation(), 0); - } - QTreeView::drawBranches(painter, r, index); -} - -void ModListView::commitData(QWidget* editor) -{ - // maintain the selection when changing priority - if (currentIndex().column() == ModList::COL_PRIORITY) { - auto [current, selected] = this->selected(); - QTreeView::commitData(editor); - setSelected(current, selected); - } - else { - QTreeView::commitData(editor); - } -} - -QModelIndexList ModListView::selectedIndexes() const -{ - // during drag&drop events, we fake the return value of selectedIndexes() - // to allow drag&drop of a parent into its children - // - // this is only "active" during the actual dragXXXEvent and dropEvent method, - // not during the whole drag&drop event - // - // selectedIndexes() is a protected method from QTreeView which is little - // used so this should not break anything - // - return m_inDragMoveEvent ? QModelIndexList() : QTreeView::selectedIndexes(); -} - -void ModListView::onCustomContextMenuRequested(const QPoint& pos) -{ - try { - QModelIndex contextIdx = indexViewToModel(indexAt(pos)); - - if (!contextIdx.isValid()) { - // no selection - ModListGlobalContextMenu(*m_core, this).exec(viewport()->mapToGlobal(pos)); - } - else { - ModListContextMenu(contextIdx, *m_core, m_categories, this).exec(viewport()->mapToGlobal(pos)); - } - } - catch (const std::exception& e) { - reportError(tr("Exception: ").arg(e.what())); - } - catch (...) { - reportError(tr("Unknown exception")); - } -} - -void ModListView::onDoubleClicked(const QModelIndex& index) -{ - if (!index.isValid()) { - return; - } - - if (m_core->modList()->timeElapsedSinceLastChecked() <= QApplication::doubleClickInterval()) { - // don't interpret double click if we only just checked a mod - return; - } - - bool indexOk = false; - int modIndex = index.data(ModList::IndexRole).toInt(&indexOk); - - if (!indexOk || modIndex < 0 || modIndex >= ModInfo::getNumMods()) { - return; - } - - ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); - - const auto modifiers = QApplication::queryKeyboardModifiers(); - if (modifiers.testFlag(Qt::ControlModifier)) { - try { - shell::Explore(modInfo->absolutePath()); - } - catch (const std::exception& e) { - reportError(e.what()); - } - } - else if (modifiers.testFlag(Qt::ShiftModifier)) { - try { - actions().visitNexusOrWebPage({ indexViewToModel(index) }); - } - catch (const std::exception& e) { - reportError(e.what()); - } - } - else if (hasCollapsibleSeparators() && modInfo->isSeparator()) { - setExpanded(index, !isExpanded(index)); - } - else { - try { - auto tab = ModInfoTabIDs::None; - - switch (index.column()) { - case ModList::COL_NOTES: tab = ModInfoTabIDs::Notes; break; - case ModList::COL_VERSION: tab = ModInfoTabIDs::Nexus; break; - case ModList::COL_MODID: tab = ModInfoTabIDs::Nexus; break; - case ModList::COL_GAME: tab = ModInfoTabIDs::Nexus; break; - case ModList::COL_CATEGORY: tab = ModInfoTabIDs::Categories; break; - case ModList::COL_CONFLICTFLAGS: tab = ModInfoTabIDs::Conflicts; break; - } - - actions().displayModInformation(modIndex, tab); - } - catch (const std::exception& e) { - reportError(e.what()); - } - } - - // workaround to cancel the editor that might have opened because of - // selection-click - closePersistentEditor(index); -} - -void ModListView::clearOverwriteMarkers() -{ - m_markers.overwrite.clear(); - m_markers.overwritten.clear(); - m_markers.archiveOverwrite.clear(); - m_markers.archiveOverwritten.clear(); - m_markers.archiveLooseOverwrite.clear(); - m_markers.archiveLooseOverwritten.clear(); -} - -void ModListView::setOverwriteMarkers(const QModelIndexList& indexes) -{ - const auto insert = [](auto& dest, const auto& from) { - dest.insert(from.begin(), from.end()); - }; - clearOverwriteMarkers(); - for (auto& idx : indexes) { - auto mIndex = idx.data(ModList::IndexRole); - if (mIndex.isValid()) { - auto info = ModInfo::getByIndex(mIndex.toInt()); - insert(m_markers.overwrite, info->getModOverwrite()); - insert(m_markers.overwritten, info->getModOverwritten()); - insert(m_markers.archiveOverwrite, info->getModArchiveOverwrite()); - insert(m_markers.archiveOverwritten, info->getModArchiveOverwritten()); - insert(m_markers.archiveLooseOverwrite, info->getModArchiveLooseOverwrite()); - insert(m_markers.archiveLooseOverwritten, info->getModArchiveLooseOverwritten()); - } - } - dataChanged(model()->index(0, 0), model()->index(model()->rowCount(), model()->columnCount())); - verticalScrollBar()->repaint(); -} - -void ModListView::refreshMarkersAndPlugins() -{ - QModelIndexList indexes = selectionModel()->selectedRows(); - - if (m_core->settings().interface().collapsibleSeparatorsHighlightFrom()) { - for (auto& idx : selectionModel()->selectedRows()) { - if (hasCollapsibleSeparators() - && model()->hasChildren(idx) - && !isExpanded(idx)) { - for (int i = 0; i < model()->rowCount(idx); ++i) { - indexes.append(model()->index(i, idx.column(), idx)); - } - } - } - } - - setOverwriteMarkers(indexes); - - // highligth plugins - std::vector modIndices; - for (auto& idx : indexes) { - modIndices.push_back(idx.data(ModList::IndexRole).toInt()); - } - m_core->pluginList()->highlightPlugins(modIndices, *m_core->directoryStructure()); - ui.pluginList->verticalScrollBar()->repaint(); -} - - -void ModListView::setHighlightedMods(const std::vector& pluginIndices) -{ - m_markers.highlight.clear(); - auto& directoryEntry = *m_core->directoryStructure(); - for (auto idx : pluginIndices) { - QString pluginName = m_core->pluginList()->getName(idx); - - const MOShared::FileEntryPtr fileEntry = directoryEntry.findFile(pluginName.toStdWString()); - if (fileEntry.get() != nullptr) { - QString originName = QString::fromStdWString(directoryEntry.getOriginByID(fileEntry->getOrigin()).getName()); - const auto index = ModInfo::getIndex(originName); - if (index != UINT_MAX) { - m_markers.highlight.insert(index); - } - } - } - dataChanged(model()->index(0, 0), model()->index(model()->rowCount(), model()->columnCount())); - verticalScrollBar()->repaint(); -} - -QColor ModListView::markerColor(const QModelIndex& index) const -{ - unsigned int modIndex = index.data(ModList::IndexRole).toInt(); - bool highligth = m_markers.highlight.find(modIndex) != m_markers.highlight.end(); - bool overwrite = m_markers.overwrite.find(modIndex) != m_markers.overwrite.end(); - bool archiveOverwrite = m_markers.archiveOverwrite.find(modIndex) != m_markers.archiveOverwrite.end(); - bool archiveLooseOverwrite = m_markers.archiveLooseOverwrite.find(modIndex) != m_markers.archiveLooseOverwrite.end(); - bool overwritten = m_markers.overwritten.find(modIndex) != m_markers.overwritten.end(); - bool archiveOverwritten = m_markers.archiveOverwritten.find(modIndex) != m_markers.archiveOverwritten.end(); - bool archiveLooseOverwritten = m_markers.archiveLooseOverwritten.find(modIndex) != m_markers.archiveLooseOverwritten.end(); - - if (highligth) { - return Settings::instance().colors().modlistContainsPlugin(); - } - else if (overwritten || archiveLooseOverwritten) { - return Settings::instance().colors().modlistOverwritingLoose(); - } - else if (overwrite || archiveLooseOverwrite) { - return Settings::instance().colors().modlistOverwrittenLoose(); - } - else if (archiveOverwritten) { - return Settings::instance().colors().modlistOverwritingArchive(); - } - else if (archiveOverwrite) { - return Settings::instance().colors().modlistOverwrittenArchive(); - } - - // collapsed separator - auto rowIndex = index.sibling(index.row(), 0); - if (hasCollapsibleSeparators() - && m_core->settings().interface().collapsibleSeparatorsHighlightTo() - && model()->hasChildren(rowIndex) && !isExpanded(rowIndex)) { - - std::vector colors; - for (int i = 0; i < model()->rowCount(rowIndex); ++i) { - auto childColor = markerColor(model()->index(i, index.column(), rowIndex)); - if (childColor.isValid()) { - colors.push_back(childColor); - } - } - - if (colors.empty()) { - return QColor(); - } - - int r = 0, g = 0, b = 0, a = 0; - for (auto& color : colors) { - r += color.red(); - g += color.green(); - b += color.blue(); - a += color.alpha(); - } - - return QColor(r / colors.size(), g / colors.size(), b / colors.size(), a / colors.size()); - } - - return QColor(); -} - -std::vector ModListView::modFlags(const QModelIndex& index, bool* forceCompact) const -{ - ModInfo::Ptr info = ModInfo::getByIndex(index.data(ModList::IndexRole).toInt()); - - auto flags = info->getFlags(); - bool compact = false; - if (info->isSeparator() - && hasCollapsibleSeparators() - && m_core->settings().interface().collapsibleSeparatorsIcons(ModList::COL_FLAGS) - && !isExpanded(index.sibling(index.row(), 0))) { - - // combine the child conflicts - std::set eFlags(flags.begin(), flags.end()); - for (int i = 0; i < model()->rowCount(index); ++i) { - auto cIndex = model()->index(i, index.column(), index).data(ModList::IndexRole).toInt(); - auto cFlags = ModInfo::getByIndex(cIndex)->getFlags(); - eFlags.insert(cFlags.begin(), cFlags.end()); - } - flags = { eFlags.begin(), eFlags.end() }; - - // force compact because there can be a lots of flags here - compact = true; - } - - if (forceCompact) { - *forceCompact = true; - } - - return flags; -} - -std::vector ModListView::conflictFlags(const QModelIndex& index, bool* forceCompact) const -{ - ModInfo::Ptr info = ModInfo::getByIndex(index.data(ModList::IndexRole).toInt()); - - auto flags = info->getConflictFlags(); - bool compact = false; - if (info->isSeparator() - && hasCollapsibleSeparators() - && m_core->settings().interface().collapsibleSeparatorsIcons(ModList::COL_CONFLICTFLAGS) - && !isExpanded(index.sibling(index.row(), 0))) { - - // combine the child conflicts - std::set eFlags(flags.begin(), flags.end()); - for (int i = 0; i < model()->rowCount(index); ++i) { - auto cIndex = model()->index(i, index.column(), index).data(ModList::IndexRole).toInt(); - auto cFlags = ModInfo::getByIndex(cIndex)->getConflictFlags(); - eFlags.insert(cFlags.begin(), cFlags.end()); - } - flags = { eFlags.begin(), eFlags.end() }; - - // force compact because there can be a lots of flags here - compact = true; - } - - if (forceCompact) { - *forceCompact = true; - } - - return flags; -} - -std::set ModListView::contents(const QModelIndex& index, bool* includeChildren) const -{ - auto modIndex = index.data(ModList::IndexRole); - if (!modIndex.isValid()) { - return {}; - } - ModInfo::Ptr info = ModInfo::getByIndex(index.data(ModList::IndexRole).toInt()); - auto contents = info->getContents(); - bool children = false; - - if (info->isSeparator() - && hasCollapsibleSeparators() - && m_core->settings().interface().collapsibleSeparatorsIcons(ModList::COL_CONTENT) - && !isExpanded(index.sibling(index.row(), 0))) { - - // combine the child contents - std::set eContents(contents.begin(), contents.end()); - for (int i = 0; i < model()->rowCount(index); ++i) { - auto cIndex = model()->index(i, index.column(), index).data(ModList::IndexRole).toInt(); - auto cContents = ModInfo::getByIndex(cIndex)->getContents(); - eContents.insert(cContents.begin(), cContents.end()); - } - contents = { eContents.begin(), eContents.end() }; - children = true; - } - - if (includeChildren) { - *includeChildren = children; - } - - return contents; -} - -QList ModListView::contentsIcons(const QModelIndex& index, bool* forceCompact) const -{ - auto contents = this->contents(index, forceCompact); - QList result; - m_core->modDataContents().forEachContentInOrOut( - contents, - [&result](auto const& content) { result.append(content.icon()); }, - [&result](auto const&) { result.append(QString()); }); - return result; -} - -QString ModListView::contentsTooltip(const QModelIndex& index) const -{ - auto contents = this->contents(index, nullptr); - if (contents.empty()) { - return {}; - } - QString result(""); - m_core->modDataContents().forEachContentIn(contents, [&result](auto const& content) { - result.append(QString("" - "") - .arg(content.icon()).arg(content.name())); - }); - result.append("
      %2
      "); - return result; -} - -void ModListView::onFiltersCriteria(const std::vector& criteria) -{ - setFilterCriteria(criteria); - - QString label = "?"; - - if (criteria.empty()) { - label = ""; - } - else if (criteria.size() == 1) { - const auto& c = criteria[0]; - - if (c.type == ModListSortProxy::TypeContent) { - const auto* content = m_core->modDataContents().findById(c.id); - label = content ? content->name() : QString(); - } - else { - label = m_categories->getCategoryNameByID(c.id); - } - - if (label.isEmpty()) { - log::error("category {}:{} not found", c.type, c.id); - } - } - else { - label = tr(""); - } - - ui.currentCategory->setText(label); -} - -void ModListView::dragEnterEvent(QDragEnterEvent* event) -{ - // this event is used by the modlist to check if we are draggin - // to a mod (local files) or to a priority (mods, downloads, external - // files) - emit dragEntered(event->mimeData()); - QTreeView::dragEnterEvent(event); - - // there is no drop event for invalid data since canDropMimeData - // returns false, so we notify user on drag enter - ModListDropInfo dropInfo(event->mimeData(), *m_core); - - if (dropInfo.isValid() && !dropInfo.isLocalFileDrop() - && sortColumn() != ModList::COL_PRIORITY) { - log::warn("Drag&Drop is only supported when sorting by priority."); - } -} - -void ModListView::dragMoveEvent(QDragMoveEvent* event) -{ - // this replace the openTimer from QTreeView to prevent - // auto-collapse of items - if (autoExpandDelay() >= 0) { - m_openTimer.start(autoExpandDelay(), this); - } - - // see selectedIndexes() - m_inDragMoveEvent = true; - QAbstractItemView::dragMoveEvent(event); - m_inDragMoveEvent = false; -} - -void ModListView::dropEvent(QDropEvent* event) -{ - // from Qt source - QModelIndex index; - if (viewport()->rect().contains(event->pos())) { - index = indexAt(event->pos()); - if (!index.isValid() || !visualRect(index).contains(event->pos())) - index = QModelIndex(); - } - - // this event is used by the byPriorityProxy to know if allow - // dropping mod between a separator and its first mod (there - // is no way to deduce this except using dropIndicatorPosition()) - emit dropEntered(event->mimeData(), isExpanded(index), static_cast(dropIndicatorPosition())); - - // see selectedIndexes() - m_inDragMoveEvent = true; - QTreeView::dropEvent(event); - m_inDragMoveEvent = false; -} - -void ModListView::timerEvent(QTimerEvent* event) -{ - // prevent auto-collapse, see dragMoveEvent() - if (event->timerId() == m_openTimer.timerId()) { - QPoint pos = viewport()->mapFromGlobal(QCursor::pos()); - if (state() == QAbstractItemView::DraggingState - && viewport()->rect().contains(pos)) { - QModelIndex index = indexAt(pos); - setExpanded(index, !m_core->settings().interface().autoCollapseOnHover() || !isExpanded(index)); - } - m_openTimer.stop(); - } - else { - QTreeView::timerEvent(event); - } -} - -void ModListView::mousePressEvent(QMouseEvent* event) -{ - // allow alt+click to select all mods inside a separator - // when using collapsible separators - // - // similar code is also present in mouseReleaseEvent to - // avoid missing events - - // disable edit if Alt is pressed - auto triggers = editTriggers(); - if (event->modifiers() & Qt::AltModifier) { - setEditTriggers(NoEditTriggers); - } - - // we call the parent class first so that we can use the actual - // selection state of the item after - QTreeView::mousePressEvent(event); - - // restore triggers - setEditTriggers(triggers); - - const auto index = indexAt(event->pos()); - - if (event->isAccepted() - && hasCollapsibleSeparators() - && index.isValid() && model()->hasChildren(indexAt(event->pos())) - && (event->modifiers() & Qt::AltModifier)) { - - const auto flag = selectionModel()->isSelected(index) ? - QItemSelectionModel::Select : QItemSelectionModel::Deselect; - const QItemSelection selection( - model()->index(0, index.column(), index), - model()->index(model()->rowCount(index) - 1, index.column(), index)); - selectionModel()->select(selection, flag | QItemSelectionModel::Rows); - } -} - -void ModListView::mouseReleaseEvent(QMouseEvent* event) -{ - // this is a duplicate of mousePressEvent because for some reason - // the selection is not always triggered in mousePressEvent and only - // doing it here create a small lag between the selection of the - // separator and the children - - // disable edit if Alt is pressed - auto triggers = editTriggers(); - if (event->modifiers() & Qt::AltModifier) { - setEditTriggers(NoEditTriggers); - } - - // we call the parent class first so that we can use the actual - // selection state of the item after - QTreeView::mouseReleaseEvent(event); - - const auto index = indexAt(event->pos()); - - // restore triggers - setEditTriggers(triggers); - - if (event->isAccepted() - && hasCollapsibleSeparators() - && index.isValid() && model()->hasChildren(indexAt(event->pos())) - && (event->modifiers() & Qt::AltModifier)) { - - const auto flag = selectionModel()->isSelected(index) ? - QItemSelectionModel::Select : QItemSelectionModel::Deselect; - const QItemSelection selection( - model()->index(0, index.column(), index), - model()->index(model()->rowCount(index) - 1, index.column(), index)); - selectionModel()->select(selection, flag | QItemSelectionModel::Rows); - } -} - -bool ModListView::event(QEvent* event) -{ - if (event->type() == QEvent::KeyPress - && m_core->currentProfile() - && selectionModel()->hasSelection()) { - QKeyEvent* keyEvent = static_cast(event); - - auto index = selectionModel()->currentIndex(); - - if (keyEvent->modifiers() == Qt::ControlModifier) { - // ctrl+enter open explorer - if (keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Enter) { - if (selectionModel()->selectedRows().count() == 1) { - m_actions->openExplorer({ indexViewToModel(index) }); - return true; - } - } - // ctrl+up/down move selection - else if (sortColumn() == ModList::COL_PRIORITY - && (keyEvent->key() == Qt::Key_Up || keyEvent->key() == Qt::Key_Down)) { - return moveSelection(keyEvent->key()); - } - } - else if (keyEvent->modifiers() == Qt::ShiftModifier) { - // shift+enter expand - if ((keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Enter) - && selectionModel()->selectedRows().count() == 1) { - if (model()->hasChildren(index)) { - setExpanded(index, !isExpanded(index)); - } - else if (index.parent().isValid()) { - setExpanded(index.parent(), false); - selectionModel()->select(index.parent(), - QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); - setCurrentIndex(index.parent()); - } - } - } - else { - if (keyEvent->key() == Qt::Key_Delete) { - return removeSelection(); - } - else if (keyEvent->key() == Qt::Key_Space) { - return toggleSelectionState(); - } - } - return QTreeView::event(event); - } - return QTreeView::event(event); -} +#include "modlistview.h" +#include +#include +#include + +#include + +#include "filesystemutilities.h" +#include + +#include "ui_mainwindow.h" + +#include "copyeventfilter.h" +#include "filterlist.h" +#include "genericicondelegate.h" +#include "log.h" +#include "mainwindow.h" +#include "modconflicticondelegate.h" +#include "modcontenticondelegate.h" +#include "modelutils.h" +#include "modflagicondelegate.h" +#include "modlist.h" +#include "modlistbypriorityproxy.h" +#include "modlistcontextmenu.h" +#include "modlistdropinfo.h" +#include "modlistsortproxy.h" +#include "modlistversiondelegate.h" +#include "modlistviewactions.h" +#include "organizercore.h" +#include "shared/directoryentry.h" +#include "shared/fileentry.h" +#include "shared/filesorigin.h" + +using namespace MOBase; +using namespace MOShared; + +// delegate to remove indentation for mods when using collapsible +// separator +// +// the delegate works by removing the indentation of the child items +// before drawing, but unfortunately this normally breaks event +// handling (e.g. checkbox, edit, etc.), so we also need to override +// the visualRect() function from the mod list view. +// +class ModListStyledItemDelegate : public QStyledItemDelegate +{ + ModListView* m_view; + +public: + ModListStyledItemDelegate(ModListView* view) : QStyledItemDelegate(view), m_view(view) + {} + + void initStyleOption(QStyleOptionViewItem* option, + const QModelIndex& index) const override + { + // the parent version always overwrite the background brush, so + // we need to save it and restore it + auto backgroundColor = option->backgroundBrush.color(); + QStyledItemDelegate::initStyleOption(option, index); + + if (backgroundColor.isValid()) { + option->backgroundBrush = backgroundColor; + } + } + + void paint(QPainter* painter, const QStyleOptionViewItem& option, + const QModelIndex& index) const override + { + QStyleOptionViewItem opt(option); + + // remove items indentaiton when using collapsible separators + if (index.column() == 0 && m_view->hasCollapsibleSeparators()) { + if (!index.model()->hasChildren(index) && index.parent().isValid()) { + auto parentIndex = index.parent().data(ModList::IndexRole).toInt(); + if (ModInfo::getByIndex(parentIndex)->isSeparator()) { + opt.rect.adjust(-m_view->indentation(), 0, 0, 0); + } + } + } + + // compute required color from children, otherwise fallback to the + // color from the model, and draw the background here + auto color = m_view->markerColor(index); + if (!color.isValid()) { + color = index.data(Qt::BackgroundRole).value(); + } + opt.backgroundBrush = color; + + // we need to find the background color to compute the ideal text color + // but the mod list view uses alternate color so we need to find the + // right color + auto bg = opt.palette.base().color(); + if (opt.features & QStyleOptionViewItem::Alternate) { + bg = opt.palette.alternateBase().color(); + } + + // compute ideal foreground color for some rows + if (color.isValid()) { + if (((index.column() == ModList::COL_NAME || + index.column() == ModList::COL_PRIORITY) && + ModInfo::getByIndex(index.data(ModList::IndexRole).toInt()) + ->isSeparator()) || + index.column() == ModList::COL_NOTES) { + + // combine the color with the background and then find the "ideal" text color + const auto a = color.alpha() / 255.; + int r = (1 - a) * bg.red() + a * color.red(), + g = (1 - a) * bg.green() + a * color.green(), + b = (1 - a) * bg.blue() + a * color.blue(); + opt.palette.setBrush(QPalette::Text, + ColorSettings::idealTextColor(QColor(r, g, b))); + } + } + + QStyledItemDelegate::paint(painter, opt, index); + } +}; + +class ModListViewMarkingScrollBar : public ViewMarkingScrollBar +{ + ModListView* m_view; + +public: + ModListViewMarkingScrollBar(ModListView* view) + : ViewMarkingScrollBar(view, ModList::ScrollMarkRole), m_view(view) + {} + + QColor color(const QModelIndex& index) const override + { + auto color = m_view->markerColor(index); + if (!color.isValid()) { + color = ViewMarkingScrollBar::color(index); + } + return color; + } +}; + +ModListView::ModListView(QWidget* parent) + : QTreeView(parent), m_core(nullptr), m_sortProxy(nullptr), + m_byPriorityProxy(nullptr), m_byCategoryProxy(nullptr), + m_byNexusIdProxy(nullptr), m_markers{{}, {}, {}, {}, {}, {}}, + m_scrollbar(new ModListViewMarkingScrollBar(this)) +{ + setVerticalScrollBar(m_scrollbar); + MOBase::setCustomizableColumns(this); + setAutoExpandDelay(750); + + setItemDelegate(new ModListStyledItemDelegate(this)); + + connect(this, &ModListView::doubleClicked, this, &ModListView::onDoubleClicked); + connect(this, &ModListView::customContextMenuRequested, this, + &ModListView::onCustomContextMenuRequested); + + // the timeout is pretty small because its main purpose is to avoid + // refreshing multiple times when calling expandAll() or collapseAll() + // which emit a lots of expanded/collapsed signals in a very small + // time window + m_refreshMarkersTimer.setInterval(50); + m_refreshMarkersTimer.setSingleShot(true); + connect(&m_refreshMarkersTimer, &QTimer::timeout, [=] { + refreshMarkersAndPlugins(); + }); + + installEventFilter(new CopyEventFilter(this, [=](auto& index) { + QVariant mIndex = index.data(ModList::IndexRole); + QString name = index.data(Qt::DisplayRole).toString(); + if (mIndex.isValid() && hasCollapsibleSeparators()) { + ModInfo::Ptr info = ModInfo::getByIndex(mIndex.toInt()); + if (info->isSeparator()) { + name = "[" + name + "]"; + } + } else if (model()->hasChildren(index)) { + name = "[" + name + "]"; + } + return name; + })); +} + +void ModListView::refresh() +{ + updateGroupByProxy(); +} + +void ModListView::onProfileChanged(Profile* oldProfile, Profile* newProfile) +{ + const auto perProfileSeparators = + m_core->settings().interface().collapsibleSeparatorsPerProfile(); + + // save expanded/collapsed state of separators + if (oldProfile && perProfileSeparators) { + auto& collapsed = m_collapsed[m_byPriorityProxy]; + oldProfile->storeSetting("UserInterface", "collapsed_separators", + QStringList(collapsed.begin(), collapsed.end())); + } + + m_sortProxy->setProfile(newProfile); + m_byPriorityProxy->setProfile(newProfile); + + if (newProfile && perProfileSeparators) { + auto collapsed = + newProfile->setting("UserInterface", "collapsed_separators", QStringList()) + .toStringList(); + m_collapsed[m_byPriorityProxy] = {collapsed.begin(), collapsed.end()}; + } +} + +bool ModListView::hasCollapsibleSeparators() const +{ + return groupByMode() == GroupByMode::SEPARATOR; +} + +int ModListView::sortColumn() const +{ + return m_sortProxy ? m_sortProxy->sortColumn() : -1; +} + +Qt::SortOrder ModListView::sortOrder() const +{ + return m_sortProxy ? m_sortProxy->sortOrder() : Qt::AscendingOrder; +} + +bool ModListView::isFilterActive() const +{ + return m_sortProxy && m_sortProxy->isFilterActive(); +} + +ModListView::GroupByMode ModListView::groupByMode() const +{ + if (m_sortProxy == nullptr) { + return GroupByMode::NONE; + } else if (m_sortProxy->sourceModel() == m_byPriorityProxy) { + return GroupByMode::SEPARATOR; + } else if (m_sortProxy->sourceModel() == m_byCategoryProxy) { + return GroupByMode::CATEGORY; + } else if (m_sortProxy->sourceModel() == m_byNexusIdProxy) { + return GroupByMode::NEXUS_ID; + } else { + return GroupByMode::NONE; + } +} + +ModListViewActions& ModListView::actions() const +{ + return *m_actions; +} + +std::optional ModListView::nextMod(unsigned int modIndex) const +{ + const QModelIndex start = indexModelToView(m_core->modList()->index(modIndex, 0)); + + auto index = start; + + for (;;) { + index = nextIndex(index); + + if (index == start || !index.isValid()) { + // wrapped around, give up + break; + } + + modIndex = index.data(ModList::IndexRole).toInt(); + + ModInfo::Ptr mod = ModInfo::getByIndex(modIndex); + + // skip overwrite, backups and separators + if (mod->isOverwrite() || mod->isBackup() || mod->isSeparator()) { + continue; + } + + return modIndex; + } + + return {}; +} + +std::optional ModListView::prevMod(unsigned int modIndex) const +{ + const QModelIndex start = indexModelToView(m_core->modList()->index(modIndex, 0)); + + auto index = start; + + for (;;) { + index = prevIndex(index); + + if (index == start || !index.isValid()) { + // wrapped around, give up + break; + } + + modIndex = index.data(ModList::IndexRole).toInt(); + + // skip overwrite, backups and separators + ModInfo::Ptr mod = ModInfo::getByIndex(modIndex); + if (mod->isOverwrite() || mod->isBackup() || mod->isSeparator()) { + continue; + } + + return modIndex; + } + + return {}; +} + +void ModListView::invalidateFilter() +{ + m_sortProxy->invalidate(); +} + +void ModListView::setFilterCriteria( + const std::vector& criteria) +{ + m_sortProxy->setCriteria(criteria); +} + +void ModListView::setFilterOptions(ModListSortProxy::FilterMode mode, + ModListSortProxy::SeparatorsMode sep) +{ + m_sortProxy->setOptions(mode, sep); +} + +bool ModListView::isModVisible(unsigned int index) const +{ + return m_sortProxy->filterMatchesMod(ModInfo::getByIndex(index), + m_core->currentProfile()->modEnabled(index)); +} + +bool ModListView::isModVisible(ModInfo::Ptr mod) const +{ + return m_sortProxy->filterMatchesMod( + mod, m_core->currentProfile()->modEnabled(ModInfo::getIndex(mod->name()))); +} + +QModelIndex ModListView::indexModelToView(const QModelIndex& index) const +{ + return ::indexModelToView(index, this); +} + +QModelIndexList ModListView::indexModelToView(const QModelIndexList& index) const +{ + return ::indexModelToView(index, this); +} + +QModelIndex ModListView::indexViewToModel(const QModelIndex& index) const +{ + return ::indexViewToModel(index, m_core->modList()); +} + +QModelIndexList ModListView::indexViewToModel(const QModelIndexList& index) const +{ + return ::indexViewToModel(index, m_core->modList()); +} + +QModelIndex ModListView::nextIndex(const QModelIndex& index) const +{ + auto* model = index.model(); + if (!model) { + return {}; + } + + if (model->rowCount(index) > 0) { + return model->index(0, index.column(), index); + } + + if (index.parent().isValid()) { + if (index.row() + 1 < model->rowCount(index.parent())) { + return index.model()->index(index.row() + 1, index.column(), index.parent()); + } else { + return index.model()->index((index.parent().row() + 1) % + model->rowCount(index.parent().parent()), + index.column(), index.parent().parent()); + ; + } + } else { + return index.model()->index((index.row() + 1) % model->rowCount(index.parent()), + index.column(), index.parent()); + } +} + +QModelIndex ModListView::prevIndex(const QModelIndex& index) const +{ + if (index.row() == 0 && index.parent().isValid()) { + return index.parent(); + } + + auto* model = index.model(); + if (!model) { + return {}; + } + + auto prev = model->index((index.row() - 1) % model->rowCount(index.parent()), + index.column(), index.parent()); + + if (model->rowCount(prev) > 0) { + return model->index(model->rowCount(prev) - 1, index.column(), prev); + } + + return prev; +} + +std::pair ModListView::selected() const +{ + return {indexViewToModel(currentIndex()), + indexViewToModel(selectionModel()->selectedRows())}; +} + +void ModListView::setSelected(const QModelIndex& current, + const QModelIndexList& selected) +{ + setCurrentIndex(indexModelToView(current)); + for (auto idx : selected) { + selectionModel()->select(indexModelToView(idx), + QItemSelectionModel::Select | QItemSelectionModel::Rows); + } +} + +void ModListView::scrollToAndSelect(const QModelIndex& index) +{ + scrollToAndSelect(QModelIndexList{index}); +} + +void ModListView::scrollToAndSelect(const QModelIndexList& indexes, + const QModelIndex& current) +{ + // focus, scroll to and select + if (!current.isValid() && indexes.isEmpty()) { + return; + } + scrollTo(current.isValid() ? current : indexes.first()); + setCurrentIndex(current.isValid() ? current : indexes.first()); + QItemSelection selection; + for (auto& idx : indexes) { + selection.select(idx, idx); + } + selectionModel()->select(selection, + QItemSelectionModel::Select | QItemSelectionModel::Rows); + QTimer::singleShot(50, [=] { + setFocus(); + }); +} + +void ModListView::refreshExpandedItems() +{ + auto* model = m_sortProxy->sourceModel(); + for (auto i = 0; i < model->rowCount(); ++i) { + auto idx = model->index(i, 0); + if (!m_collapsed[model].contains(idx.data(Qt::DisplayRole).toString())) { + setExpanded(m_sortProxy->mapFromSource(idx), true); + } + } +} + +void ModListView::onModPrioritiesChanged(const QModelIndexList& indices) +{ + // expand separator whose priority has changed and parents + for (auto index : indices) { + auto idx = indexModelToView(index); + if (hasCollapsibleSeparators() && model()->hasChildren(idx)) { + setExpanded(idx, true); + } + if (idx.parent().isValid()) { + setExpanded(idx.parent(), true); + } + } + + setOverwriteMarkers(selectionModel()->selectedRows()); +} + +void ModListView::onModInstalled(const QString& modName) +{ + unsigned int index = ModInfo::getIndex(modName); + + if (index == UINT_MAX) { + return; + } + + QModelIndex qIndex = indexModelToView(m_core->modList()->index(index, 0)); + + if (hasCollapsibleSeparators() && qIndex.parent().isValid()) { + setExpanded(qIndex.parent(), true); + } + + scrollToAndSelect(qIndex); +} + +void ModListView::onModFilterActive(bool filterActive) +{ + ui.clearFilters->setVisible(filterActive); + if (filterActive) { + setStyleSheet("QTreeView { border: 2px ridge #f00; }"); + ui.counter->setStyleSheet("QLCDNumber { border: 2px ridge #f00; }"); + } else if (ui.groupBy->currentIndex() != GroupBy::NONE) { + setStyleSheet("QTreeView { border: 2px ridge #337733; }"); + ui.counter->setStyleSheet(""); + } else { + setStyleSheet(""); + ui.counter->setStyleSheet(""); + } +} + +ModListView::ModCounters ModListView::counters() const +{ + ModCounters c; + + auto hasFlag = [](std::vector flags, ModInfo::EFlag filter) { + return std::find(flags.begin(), flags.end(), filter) != flags.end(); + }; + + for (unsigned int index = 0; index < ModInfo::getNumMods(); ++index) { + auto info = ModInfo::getByIndex(index); + const auto flags = info->getFlags(); + + const bool enabled = m_core->currentProfile()->modEnabled(index); + const bool visible = m_sortProxy->filterMatchesMod(info, enabled); + + if (info->isBackup()) { + c.backup++; + if (visible) + c.visible.backup++; + } else if (info->isForeign()) { + c.foreign++; + if (visible) + c.visible.foreign++; + } else if (info->isSeparator()) { + c.separator++; + if (visible) + c.visible.separator++; + } else if (!info->isOverwrite()) { + c.regular++; + if (visible) + c.visible.regular++; + if (enabled) { + c.active++; + if (visible) + c.visible.active++; + } + } + } + + return c; +} + +void ModListView::updateModCount() +{ + const auto c = counters(); + + ui.counter->display(c.visible.active); + ui.counter->setToolTip(tr("" + "" + "" + "" + "" + "" + "
      TypeAllVisible
      Enabled mods: %1 / " + "%2%3 / %4
      Unmanaged/DLCs: %5%6
      Mod backups: %7%8
      Separators: %9%10
      ") + .arg(c.active) + .arg(c.regular) + .arg(c.visible.active) + .arg(c.visible.regular) + .arg(c.foreign) + .arg(c.visible.foreign) + .arg(c.backup) + .arg(c.visible.backup) + .arg(c.separator) + .arg(c.visible.separator)); +} + +void ModListView::refreshFilters() +{ + auto [current, sourceRows] = selected(); + + setCurrentIndex(QModelIndex()); + m_filters->refresh(); + + setSelected(current, sourceRows); +} + +void ModListView::onExternalFolderDropped(const QUrl& url, int priority) +{ + setWindowState(Qt::WindowActive); + + QFileInfo fileInfo(url.toLocalFile()); + + GuessedValue name; + name.setFilter(&fixDirectoryName); + name.update(fileInfo.fileName(), GUESS_PRESET); + + do { + bool ok; + name.update( + QInputDialog::getText(this, tr("Copy Folder..."), + tr("This will copy the content of %1 to a new mod.\n" + "Please enter the name:") + .arg(fileInfo.fileName()), + QLineEdit::Normal, name, &ok), + GUESS_USER); + if (!ok) { + return; + } + } while (name->isEmpty()); + + if (m_core->modList()->getMod(name) != nullptr) { + reportError(tr("A mod with this name already exists.")); + return; + } + + IModInterface* newMod = m_core->createMod(name); + if (!newMod) { + return; + } + + // TODO: this is currently a silent copy, which can take some time, but there is + // no clean method to do this in uibase + if (!copyDir(fileInfo.absoluteFilePath(), newMod->absolutePath(), true)) { + return; + } + + m_core->refresh(); + + const auto index = ModInfo::getIndex(name); + if (priority != -1) { + m_core->modList()->changeModPriority(index, priority); + } + + scrollToAndSelect(indexModelToView(m_core->modList()->index(index, 0))); +} + +bool ModListView::moveSelection(int key) +{ + auto rows = selectionModel()->selectedRows(); + const QPersistentModelIndex current(key == Qt::Key_Up ? rows.first() : rows.last()); + + int offset = key == Qt::Key_Up ? -1 : 1; + if (m_sortProxy->sortOrder() == Qt::DescendingOrder) { + offset = -offset; + } + + m_core->modList()->shiftModsPriority(indexViewToModel(rows), offset); + selectionModel()->setCurrentIndex(current, QItemSelectionModel::NoUpdate); + scrollTo(current); + + return true; +} + +bool ModListView::removeSelection() +{ + m_actions->removeMods(indexViewToModel(selectionModel()->selectedRows())); + return true; +} + +bool ModListView::toggleSelectionState() +{ + if (!selectionModel()->hasSelection()) { + return true; + } + return m_core->modList()->toggleState( + indexViewToModel(selectionModel()->selectedRows())); +} + +void ModListView::updateGroupByProxy() +{ + int groupIndex = ui.groupBy->currentIndex(); + auto* previousModel = m_sortProxy->sourceModel(); + + QAbstractItemModel* nextModel = m_core->modList(); + if (groupIndex == GroupBy::CATEGORY) { + nextModel = m_byCategoryProxy; + } else if (groupIndex == GroupBy::NEXUS_ID) { + nextModel = m_byNexusIdProxy; + } else if (m_core->settings().interface().collapsibleSeparators( + m_sortProxy->sortOrder()) && + m_sortProxy->sortColumn() == ModList::COL_PRIORITY) { + m_byPriorityProxy->setSortOrder(m_sortProxy->sortOrder()); + nextModel = m_byPriorityProxy; + } + + if (nextModel != previousModel) { + + if (auto* proxy = dynamic_cast(nextModel)) { + proxy->setSourceModel(m_core->modList()); + } + m_sortProxy->setSourceModel(nextModel); + + // reset the source model of the old proxy because we do not want to + // react to signals + // + if (auto* proxy = qobject_cast(previousModel)) { + proxy->setSourceModel(nullptr); + } + + // expand items previously expanded + refreshExpandedItems(); + + if (hasCollapsibleSeparators()) { + ui.filterSeparators->setCurrentIndex(ModListSortProxy::SeparatorFilter); + ui.filterSeparators->setEnabled(false); + } else { + ui.filterSeparators->setEnabled(true); + } + } +} + +void ModListView::setup(OrganizerCore& core, CategoryFactory& factory, MainWindow* mw, + Ui::MainWindow* mwui) +{ + // attributes + m_core = &core; + m_filters.reset(new FilterList(mwui, core, factory)); + m_categories = &factory; + m_actions = + new ModListViewActions(core, *m_filters, factory, this, mwui->espList, mw); + ui = {mwui->groupCombo, + mwui->activeModsCounter, + mwui->modFilterEdit, + mwui->currentCategoryLabel, + mwui->clearFiltersButton, + mwui->filtersSeparators, + mwui->espList}; + + connect(m_core, &OrganizerCore::modInstalled, [=](auto&& name) { + onModInstalled(name); + }); + connect(m_core, &OrganizerCore::profileChanged, this, &ModListView::onProfileChanged); + connect(core.modList(), &ModList::modPrioritiesChanged, [=](auto&& indices) { + onModPrioritiesChanged(indices); + }); + connect(core.modList(), &ModList::clearOverwrite, [=] { + m_actions->clearOverwrite(); + }); + connect(core.modList(), &ModList::modStatesChanged, [=] { + updateModCount(); + setOverwriteMarkers(selectionModel()->selectedRows()); + }); + connect(core.modList(), &ModList::modelReset, [=] { + clearOverwriteMarkers(); + }); + + // proxy for various group by + m_byPriorityProxy = new ModListByPriorityProxy(core.currentProfile(), core, this); + m_byCategoryProxy = new QtGroupingProxy(QModelIndex(), ModList::COL_CATEGORY, + ModList::GroupingRole, 0, ModList::AggrRole); + m_byNexusIdProxy = new QtGroupingProxy( + QModelIndex(), ModList::COL_MODID, ModList::GroupingRole, + QtGroupingProxy::FLAG_NOGROUPNAME | QtGroupingProxy::FLAG_NOSINGLE, + ModList::AggrRole); + + // we need to store the expanded/collapsed state of all items and restore them 1) when + // switching proxies, 2) when filtering and 3) when reseting the mod list. + connect(this, &QTreeView::expanded, [=](const QModelIndex& index) { + auto it = m_collapsed[m_sortProxy->sourceModel()].find( + index.data(Qt::DisplayRole).toString()); + if (it != m_collapsed[m_sortProxy->sourceModel()].end()) { + m_collapsed[m_sortProxy->sourceModel()].erase(it); + } + }); + connect(this, &QTreeView::collapsed, [=](const QModelIndex& index) { + m_collapsed[m_sortProxy->sourceModel()].insert( + index.data(Qt::DisplayRole).toString()); + }); + + // the top-level proxy + m_sortProxy = new ModListSortProxy(core.currentProfile(), &core); + setModel(m_sortProxy); + connect(m_sortProxy, &ModList::modelReset, [=] { + refreshExpandedItems(); + }); + + // update the proxy when changing the sort column/direction and the group + connect(m_sortProxy, &QAbstractItemModel::layoutAboutToBeChanged, + [this](auto&& parents, auto&& hint) { + if (hint == QAbstractItemModel::VerticalSortHint) { + updateGroupByProxy(); + } + }); + connect(ui.groupBy, QOverload::of(&QComboBox::currentIndexChanged), + [=](int index) { + updateGroupByProxy(); + onModFilterActive(m_sortProxy->isFilterActive()); + }); + sortByColumn(ModList::COL_PRIORITY, Qt::AscendingOrder); + + // inform the mod list about the type of item being dropped at the beginning of a drag + // and the position of the drop indicator at the end (only for by-priority) + connect(this, &ModListView::dragEntered, core.modList(), &ModList::onDragEnter); + connect(this, &ModListView::dropEntered, m_byPriorityProxy, + &ModListByPriorityProxy::onDropEnter); + + connect(m_sortProxy, &ModListSortProxy::filterInvalidated, this, + &ModListView::updateModCount); + + connect(header(), &QHeaderView::sortIndicatorChanged, [=](int, Qt::SortOrder) { + verticalScrollBar()->repaint(); + }); + connect(header(), &QHeaderView::sectionResized, + [=](int logicalIndex, int oldSize, int newSize) { + m_sortProxy->setColumnVisible(logicalIndex, newSize != 0); + }); + + setItemDelegateForColumn(ModList::COL_FLAGS, + new ModFlagIconDelegate(this, ModList::COL_FLAGS, 120)); + setItemDelegateForColumn( + ModList::COL_CONFLICTFLAGS, + new ModConflictIconDelegate(this, ModList::COL_CONFLICTFLAGS, 80)); + setItemDelegateForColumn(ModList::COL_CONTENT, + new ModContentIconDelegate(this, ModList::COL_CONTENT, 150)); + setItemDelegateForColumn(ModList::COL_VERSION, + new ModListVersionDelegate(this, core.settings())); + + if (m_core->settings().geometry().restoreState(header())) { + // hack: force the resize-signal to be triggered because restoreState doesn't seem + // to do that + for (int column = 0; column <= ModList::COL_LASTCOLUMN; ++column) { + int sectionSize = header()->sectionSize(column); + header()->resizeSection(column, sectionSize + 1); + header()->resizeSection(column, sectionSize); + } + } else { + // hide these columns by default + header()->setSectionHidden(ModList::COL_CONTENT, true); + header()->setSectionHidden(ModList::COL_MODID, true); + header()->setSectionHidden(ModList::COL_GAME, true); + header()->setSectionHidden(ModList::COL_INSTALLTIME, true); + header()->setSectionHidden(ModList::COL_NOTES, true); + + // resize mod list to fit content + for (int i = 0; i < header()->count(); ++i) { + header()->setSectionResizeMode(i, QHeaderView::ResizeToContents); + } + + header()->setSectionResizeMode(ModList::COL_NAME, QHeaderView::Stretch); + } + + // prevent the name-column from being hidden + header()->setSectionHidden(ModList::COL_NAME, false); + + // we need QueuedConnection for the download/archive dropped otherwise the + // installation starts within the drop-event and it's not possible to drag&drop + // in the manual installer + connect( + m_core->modList(), &ModList::downloadArchiveDropped, this, + [=](int row, int priority) { + m_core->installDownload(row, priority); + }, + Qt::QueuedConnection); + connect( + m_core->modList(), &ModList::externalArchiveDropped, this, + [=](const QUrl& url, int priority) { + setWindowState(Qt::WindowActive); + m_core->installArchive(url.toLocalFile(), priority, false, nullptr); + }, + Qt::QueuedConnection); + connect(m_core->modList(), &ModList::externalFolderDropped, this, + &ModListView::onExternalFolderDropped); + + connect(selectionModel(), &QItemSelectionModel::selectionChanged, [=] { + m_refreshMarkersTimer.start(); + }); + connect(this, &QTreeView::collapsed, [=] { + m_refreshMarkersTimer.start(); + }); + connect(this, &QTreeView::expanded, [=] { + m_refreshMarkersTimer.start(); + }); + + // filters + connect(m_sortProxy, &ModListSortProxy::filterActive, this, + &ModListView::onModFilterActive); + connect(m_filters.get(), &FilterList::criteriaChanged, [=](auto&& v) { + onFiltersCriteria(v); + }); + connect(m_filters.get(), &FilterList::optionsChanged, [=](auto&& mode, auto&& sep) { + setFilterOptions(mode, sep); + }); + connect(ui.filter, &QLineEdit::textChanged, m_sortProxy, + &ModListSortProxy::updateFilter); + connect(ui.clearFilters, &QPushButton::clicked, [=]() { + ui.filter->clear(); + m_filters->clearSelection(); + }); + connect(m_sortProxy, &ModListSortProxy::filterInvalidated, [=]() { + if (hasCollapsibleSeparators()) { + refreshExpandedItems(); + } + }); +} + +void ModListView::restoreState(const Settings& s) +{ + s.geometry().restoreState(header()); + + s.widgets().restoreIndex(ui.groupBy); + s.widgets().restoreTreeExpandState(this); + + m_filters->restoreState(s); +} + +void ModListView::saveState(Settings& s) const +{ + s.geometry().saveState(header()); + + s.widgets().saveIndex(ui.groupBy); + s.widgets().saveTreeExpandState(this); + + m_filters->saveState(s); +} + +QRect ModListView::visualRect(const QModelIndex& index) const +{ + // this shift the visualRect() from QTreeView to match the new actual + // zone after removing indentation (see the ModListStyledItemDelegate) + QRect rect = QTreeView::visualRect(index); + if (hasCollapsibleSeparators() && index.column() == 0 && index.isValid() && + index.parent().isValid()) { + rect.adjust(-indentation(), 0, 0, 0); + } + return rect; +} + +void ModListView::drawBranches(QPainter* painter, const QRect& rect, + const QModelIndex& index) const +{ + // the branches are the small indicator left to the row (there are none in the default + // style, and the VS dark style only has background for these) + // + // the branches are not shifted left with the visualRect() change and since MO2 uses + // stylesheet, it is not possible to shift those in the proxy style so we have to + // shift it here. + // + QRect r(rect); + if (hasCollapsibleSeparators() && index.parent().isValid()) { + r.adjust(-indentation(), 0, 0 - indentation(), 0); + } + QTreeView::drawBranches(painter, r, index); +} + +void ModListView::commitData(QWidget* editor) +{ + // maintain the selection when changing priority + if (currentIndex().column() == ModList::COL_PRIORITY) { + auto [current, selected] = this->selected(); + QTreeView::commitData(editor); + setSelected(current, selected); + } else { + QTreeView::commitData(editor); + } +} + +QModelIndexList ModListView::selectedIndexes() const +{ + // during drag&drop events, we fake the return value of selectedIndexes() + // to allow drag&drop of a parent into its children + // + // this is only "active" during the actual dragXXXEvent and dropEvent method, + // not during the whole drag&drop event + // + // selectedIndexes() is a protected method from QTreeView which is little + // used so this should not break anything + // + return m_inDragMoveEvent ? QModelIndexList() : QTreeView::selectedIndexes(); +} + +void ModListView::onCustomContextMenuRequested(const QPoint& pos) +{ + try { + QModelIndex contextIdx = indexViewToModel(indexAt(pos)); + + if (!contextIdx.isValid()) { + // no selection + ModListGlobalContextMenu(*m_core, this).exec(viewport()->mapToGlobal(pos)); + } else { + ModListContextMenu(contextIdx, *m_core, m_categories, this) + .exec(viewport()->mapToGlobal(pos)); + } + } catch (const std::exception& e) { + reportError(tr("Exception: ").arg(e.what())); + } catch (...) { + reportError(tr("Unknown exception")); + } +} + +void ModListView::onDoubleClicked(const QModelIndex& index) +{ + if (!index.isValid()) { + return; + } + + if (m_core->modList()->timeElapsedSinceLastChecked() <= + QApplication::doubleClickInterval()) { + // don't interpret double click if we only just checked a mod + return; + } + + bool indexOk = false; + int modIndex = index.data(ModList::IndexRole).toInt(&indexOk); + + if (!indexOk || modIndex < 0 || modIndex >= ModInfo::getNumMods()) { + return; + } + + ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); + + const auto modifiers = QApplication::queryKeyboardModifiers(); + if (modifiers.testFlag(Qt::ControlModifier)) { + try { + shell::Explore(modInfo->absolutePath()); + } catch (const std::exception& e) { + reportError(e.what()); + } + } else if (modifiers.testFlag(Qt::ShiftModifier)) { + try { + actions().visitNexusOrWebPage({indexViewToModel(index)}); + } catch (const std::exception& e) { + reportError(e.what()); + } + } else if (hasCollapsibleSeparators() && modInfo->isSeparator()) { + setExpanded(index, !isExpanded(index)); + } else { + try { + auto tab = ModInfoTabIDs::None; + + switch (index.column()) { + case ModList::COL_NOTES: + tab = ModInfoTabIDs::Notes; + break; + case ModList::COL_VERSION: + tab = ModInfoTabIDs::Nexus; + break; + case ModList::COL_MODID: + tab = ModInfoTabIDs::Nexus; + break; + case ModList::COL_GAME: + tab = ModInfoTabIDs::Nexus; + break; + case ModList::COL_CATEGORY: + tab = ModInfoTabIDs::Categories; + break; + case ModList::COL_CONFLICTFLAGS: + tab = ModInfoTabIDs::Conflicts; + break; + } + + actions().displayModInformation(modIndex, tab); + } catch (const std::exception& e) { + reportError(e.what()); + } + } + + // workaround to cancel the editor that might have opened because of + // selection-click + closePersistentEditor(index); +} + +void ModListView::clearOverwriteMarkers() +{ + m_markers.overwrite.clear(); + m_markers.overwritten.clear(); + m_markers.archiveOverwrite.clear(); + m_markers.archiveOverwritten.clear(); + m_markers.archiveLooseOverwrite.clear(); + m_markers.archiveLooseOverwritten.clear(); +} + +void ModListView::setOverwriteMarkers(const QModelIndexList& indexes) +{ + const auto insert = [](auto& dest, const auto& from) { + dest.insert(from.begin(), from.end()); + }; + clearOverwriteMarkers(); + for (auto& idx : indexes) { + auto mIndex = idx.data(ModList::IndexRole); + if (mIndex.isValid()) { + auto info = ModInfo::getByIndex(mIndex.toInt()); + insert(m_markers.overwrite, info->getModOverwrite()); + insert(m_markers.overwritten, info->getModOverwritten()); + insert(m_markers.archiveOverwrite, info->getModArchiveOverwrite()); + insert(m_markers.archiveOverwritten, info->getModArchiveOverwritten()); + insert(m_markers.archiveLooseOverwrite, info->getModArchiveLooseOverwrite()); + insert(m_markers.archiveLooseOverwritten, info->getModArchiveLooseOverwritten()); + } + } + dataChanged(model()->index(0, 0), + model()->index(model()->rowCount(), model()->columnCount())); + verticalScrollBar()->repaint(); +} + +void ModListView::refreshMarkersAndPlugins() +{ + QModelIndexList indexes = selectionModel()->selectedRows(); + + if (m_core->settings().interface().collapsibleSeparatorsHighlightFrom()) { + for (auto& idx : selectionModel()->selectedRows()) { + if (hasCollapsibleSeparators() && model()->hasChildren(idx) && !isExpanded(idx)) { + for (int i = 0; i < model()->rowCount(idx); ++i) { + indexes.append(model()->index(i, idx.column(), idx)); + } + } + } + } + + setOverwriteMarkers(indexes); + + // highligth plugins + std::vector modIndices; + for (auto& idx : indexes) { + modIndices.push_back(idx.data(ModList::IndexRole).toInt()); + } + m_core->pluginList()->highlightPlugins(modIndices, *m_core->directoryStructure()); + ui.pluginList->verticalScrollBar()->repaint(); +} + +void ModListView::setHighlightedMods(const std::vector& pluginIndices) +{ + m_markers.highlight.clear(); + auto& directoryEntry = *m_core->directoryStructure(); + for (auto idx : pluginIndices) { + QString pluginName = m_core->pluginList()->getName(idx); + + const MOShared::FileEntryPtr fileEntry = + directoryEntry.findFile(pluginName.toStdWString()); + if (fileEntry.get() != nullptr) { + QString originName = QString::fromStdWString( + directoryEntry.getOriginByID(fileEntry->getOrigin()).getName()); + const auto index = ModInfo::getIndex(originName); + if (index != UINT_MAX) { + m_markers.highlight.insert(index); + } + } + } + dataChanged(model()->index(0, 0), + model()->index(model()->rowCount(), model()->columnCount())); + verticalScrollBar()->repaint(); +} + +QColor ModListView::markerColor(const QModelIndex& index) const +{ + unsigned int modIndex = index.data(ModList::IndexRole).toInt(); + bool highligth = m_markers.highlight.find(modIndex) != m_markers.highlight.end(); + bool overwrite = m_markers.overwrite.find(modIndex) != m_markers.overwrite.end(); + bool archiveOverwrite = + m_markers.archiveOverwrite.find(modIndex) != m_markers.archiveOverwrite.end(); + bool archiveLooseOverwrite = m_markers.archiveLooseOverwrite.find(modIndex) != + m_markers.archiveLooseOverwrite.end(); + bool overwritten = + m_markers.overwritten.find(modIndex) != m_markers.overwritten.end(); + bool archiveOverwritten = + m_markers.archiveOverwritten.find(modIndex) != m_markers.archiveOverwritten.end(); + bool archiveLooseOverwritten = m_markers.archiveLooseOverwritten.find(modIndex) != + m_markers.archiveLooseOverwritten.end(); + + if (highligth) { + return Settings::instance().colors().modlistContainsPlugin(); + } else if (overwritten || archiveLooseOverwritten) { + return Settings::instance().colors().modlistOverwritingLoose(); + } else if (overwrite || archiveLooseOverwrite) { + return Settings::instance().colors().modlistOverwrittenLoose(); + } else if (archiveOverwritten) { + return Settings::instance().colors().modlistOverwritingArchive(); + } else if (archiveOverwrite) { + return Settings::instance().colors().modlistOverwrittenArchive(); + } + + // collapsed separator + auto rowIndex = index.sibling(index.row(), 0); + if (hasCollapsibleSeparators() && + m_core->settings().interface().collapsibleSeparatorsHighlightTo() && + model()->hasChildren(rowIndex) && !isExpanded(rowIndex)) { + + std::vector colors; + for (int i = 0; i < model()->rowCount(rowIndex); ++i) { + auto childColor = markerColor(model()->index(i, index.column(), rowIndex)); + if (childColor.isValid()) { + colors.push_back(childColor); + } + } + + if (colors.empty()) { + return QColor(); + } + + int r = 0, g = 0, b = 0, a = 0; + for (auto& color : colors) { + r += color.red(); + g += color.green(); + b += color.blue(); + a += color.alpha(); + } + + return QColor(r / colors.size(), g / colors.size(), b / colors.size(), + a / colors.size()); + } + + return QColor(); +} + +std::vector ModListView::modFlags(const QModelIndex& index, + bool* forceCompact) const +{ + ModInfo::Ptr info = ModInfo::getByIndex(index.data(ModList::IndexRole).toInt()); + + auto flags = info->getFlags(); + bool compact = false; + if (info->isSeparator() && hasCollapsibleSeparators() && + m_core->settings().interface().collapsibleSeparatorsIcons(ModList::COL_FLAGS) && + !isExpanded(index.sibling(index.row(), 0))) { + + // combine the child conflicts + std::set eFlags(flags.begin(), flags.end()); + for (int i = 0; i < model()->rowCount(index); ++i) { + auto cIndex = + model()->index(i, index.column(), index).data(ModList::IndexRole).toInt(); + auto cFlags = ModInfo::getByIndex(cIndex)->getFlags(); + eFlags.insert(cFlags.begin(), cFlags.end()); + } + flags = {eFlags.begin(), eFlags.end()}; + + // force compact because there can be a lots of flags here + compact = true; + } + + if (forceCompact) { + *forceCompact = true; + } + + return flags; +} + +std::vector ModListView::conflictFlags(const QModelIndex& index, + bool* forceCompact) const +{ + ModInfo::Ptr info = ModInfo::getByIndex(index.data(ModList::IndexRole).toInt()); + + auto flags = info->getConflictFlags(); + bool compact = false; + if (info->isSeparator() && hasCollapsibleSeparators() && + m_core->settings().interface().collapsibleSeparatorsIcons( + ModList::COL_CONFLICTFLAGS) && + !isExpanded(index.sibling(index.row(), 0))) { + + // combine the child conflicts + std::set eFlags(flags.begin(), flags.end()); + for (int i = 0; i < model()->rowCount(index); ++i) { + auto cIndex = + model()->index(i, index.column(), index).data(ModList::IndexRole).toInt(); + auto cFlags = ModInfo::getByIndex(cIndex)->getConflictFlags(); + eFlags.insert(cFlags.begin(), cFlags.end()); + } + flags = {eFlags.begin(), eFlags.end()}; + + // force compact because there can be a lots of flags here + compact = true; + } + + if (forceCompact) { + *forceCompact = true; + } + + return flags; +} + +std::set ModListView::contents(const QModelIndex& index, + bool* includeChildren) const +{ + auto modIndex = index.data(ModList::IndexRole); + if (!modIndex.isValid()) { + return {}; + } + ModInfo::Ptr info = ModInfo::getByIndex(index.data(ModList::IndexRole).toInt()); + auto contents = info->getContents(); + bool children = false; + + if (info->isSeparator() && hasCollapsibleSeparators() && + m_core->settings().interface().collapsibleSeparatorsIcons(ModList::COL_CONTENT) && + !isExpanded(index.sibling(index.row(), 0))) { + + // combine the child contents + std::set eContents(contents.begin(), contents.end()); + for (int i = 0; i < model()->rowCount(index); ++i) { + auto cIndex = + model()->index(i, index.column(), index).data(ModList::IndexRole).toInt(); + auto cContents = ModInfo::getByIndex(cIndex)->getContents(); + eContents.insert(cContents.begin(), cContents.end()); + } + contents = {eContents.begin(), eContents.end()}; + children = true; + } + + if (includeChildren) { + *includeChildren = children; + } + + return contents; +} + +QList ModListView::contentsIcons(const QModelIndex& index, + bool* forceCompact) const +{ + auto contents = this->contents(index, forceCompact); + QList result; + m_core->modDataContents().forEachContentInOrOut( + contents, + [&result](auto const& content) { + result.append(content.icon()); + }, + [&result](auto const&) { + result.append(QString()); + }); + return result; +} + +QString ModListView::contentsTooltip(const QModelIndex& index) const +{ + auto contents = this->contents(index, nullptr); + if (contents.empty()) { + return {}; + } + QString result(""); + m_core->modDataContents().forEachContentIn(contents, [&result](auto const& content) { + result.append(QString("" + "") + .arg(content.icon()) + .arg(content.name())); + }); + result.append("
      %2
      "); + return result; +} + +void ModListView::onFiltersCriteria( + const std::vector& criteria) +{ + setFilterCriteria(criteria); + + QString label = "?"; + + if (criteria.empty()) { + label = ""; + } else if (criteria.size() == 1) { + const auto& c = criteria[0]; + + if (c.type == ModListSortProxy::TypeContent) { + const auto* content = m_core->modDataContents().findById(c.id); + label = content ? content->name() : QString(); + } else { + label = m_categories->getCategoryNameByID(c.id); + } + + if (label.isEmpty()) { + log::error("category {}:{} not found", c.type, c.id); + } + } else { + label = tr(""); + } + + ui.currentCategory->setText(label); +} + +void ModListView::dragEnterEvent(QDragEnterEvent* event) +{ + // this event is used by the modlist to check if we are draggin + // to a mod (local files) or to a priority (mods, downloads, external + // files) + emit dragEntered(event->mimeData()); + QTreeView::dragEnterEvent(event); + + // there is no drop event for invalid data since canDropMimeData + // returns false, so we notify user on drag enter + ModListDropInfo dropInfo(event->mimeData(), *m_core); + + if (dropInfo.isValid() && !dropInfo.isLocalFileDrop() && + sortColumn() != ModList::COL_PRIORITY) { + log::warn("Drag&Drop is only supported when sorting by priority."); + } +} + +void ModListView::dragMoveEvent(QDragMoveEvent* event) +{ + // this replace the openTimer from QTreeView to prevent + // auto-collapse of items + if (autoExpandDelay() >= 0) { + m_openTimer.start(autoExpandDelay(), this); + } + + // see selectedIndexes() + m_inDragMoveEvent = true; + QAbstractItemView::dragMoveEvent(event); + m_inDragMoveEvent = false; +} + +void ModListView::dropEvent(QDropEvent* event) +{ + // from Qt source + QModelIndex index; + if (viewport()->rect().contains(event->pos())) { + index = indexAt(event->pos()); + if (!index.isValid() || !visualRect(index).contains(event->pos())) + index = QModelIndex(); + } + + // this event is used by the byPriorityProxy to know if allow + // dropping mod between a separator and its first mod (there + // is no way to deduce this except using dropIndicatorPosition()) + emit dropEntered(event->mimeData(), isExpanded(index), + static_cast(dropIndicatorPosition())); + + // see selectedIndexes() + m_inDragMoveEvent = true; + QTreeView::dropEvent(event); + m_inDragMoveEvent = false; +} + +void ModListView::timerEvent(QTimerEvent* event) +{ + // prevent auto-collapse, see dragMoveEvent() + if (event->timerId() == m_openTimer.timerId()) { + QPoint pos = viewport()->mapFromGlobal(QCursor::pos()); + if (state() == QAbstractItemView::DraggingState && + viewport()->rect().contains(pos)) { + QModelIndex index = indexAt(pos); + setExpanded(index, !m_core->settings().interface().autoCollapseOnHover() || + !isExpanded(index)); + } + m_openTimer.stop(); + } else { + QTreeView::timerEvent(event); + } +} + +void ModListView::mousePressEvent(QMouseEvent* event) +{ + // allow alt+click to select all mods inside a separator + // when using collapsible separators + // + // similar code is also present in mouseReleaseEvent to + // avoid missing events + + // disable edit if Alt is pressed + auto triggers = editTriggers(); + if (event->modifiers() & Qt::AltModifier) { + setEditTriggers(NoEditTriggers); + } + + // we call the parent class first so that we can use the actual + // selection state of the item after + QTreeView::mousePressEvent(event); + + // restore triggers + setEditTriggers(triggers); + + const auto index = indexAt(event->pos()); + + if (event->isAccepted() && hasCollapsibleSeparators() && index.isValid() && + model()->hasChildren(indexAt(event->pos())) && + (event->modifiers() & Qt::AltModifier)) { + + const auto flag = selectionModel()->isSelected(index) + ? QItemSelectionModel::Select + : QItemSelectionModel::Deselect; + const QItemSelection selection( + model()->index(0, index.column(), index), + model()->index(model()->rowCount(index) - 1, index.column(), index)); + selectionModel()->select(selection, flag | QItemSelectionModel::Rows); + } +} + +void ModListView::mouseReleaseEvent(QMouseEvent* event) +{ + // this is a duplicate of mousePressEvent because for some reason + // the selection is not always triggered in mousePressEvent and only + // doing it here create a small lag between the selection of the + // separator and the children + + // disable edit if Alt is pressed + auto triggers = editTriggers(); + if (event->modifiers() & Qt::AltModifier) { + setEditTriggers(NoEditTriggers); + } + + // we call the parent class first so that we can use the actual + // selection state of the item after + QTreeView::mouseReleaseEvent(event); + + const auto index = indexAt(event->pos()); + + // restore triggers + setEditTriggers(triggers); + + if (event->isAccepted() && hasCollapsibleSeparators() && index.isValid() && + model()->hasChildren(indexAt(event->pos())) && + (event->modifiers() & Qt::AltModifier)) { + + const auto flag = selectionModel()->isSelected(index) + ? QItemSelectionModel::Select + : QItemSelectionModel::Deselect; + const QItemSelection selection( + model()->index(0, index.column(), index), + model()->index(model()->rowCount(index) - 1, index.column(), index)); + selectionModel()->select(selection, flag | QItemSelectionModel::Rows); + } +} + +bool ModListView::event(QEvent* event) +{ + if (event->type() == QEvent::KeyPress && m_core->currentProfile() && + selectionModel()->hasSelection()) { + QKeyEvent* keyEvent = static_cast(event); + + auto index = selectionModel()->currentIndex(); + + if (keyEvent->modifiers() == Qt::ControlModifier) { + // ctrl+enter open explorer + if (keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Enter) { + if (selectionModel()->selectedRows().count() == 1) { + m_actions->openExplorer({indexViewToModel(index)}); + return true; + } + } + // ctrl+up/down move selection + else if (sortColumn() == ModList::COL_PRIORITY && + (keyEvent->key() == Qt::Key_Up || keyEvent->key() == Qt::Key_Down)) { + return moveSelection(keyEvent->key()); + } + } else if (keyEvent->modifiers() == Qt::ShiftModifier) { + // shift+enter expand + if ((keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Enter) && + selectionModel()->selectedRows().count() == 1) { + if (model()->hasChildren(index)) { + setExpanded(index, !isExpanded(index)); + } else if (index.parent().isValid()) { + setExpanded(index.parent(), false); + selectionModel()->select(index.parent(), QItemSelectionModel::SelectCurrent | + QItemSelectionModel::Rows); + setCurrentIndex(index.parent()); + } + } + } else { + if (keyEvent->key() == Qt::Key_Delete) { + return removeSelection(); + } else if (keyEvent->key() == Qt::Key_Space) { + return toggleSelectionState(); + } + } + return QTreeView::event(event); + } + return QTreeView::event(event); +} diff --git a/src/modlistview.h b/src/modlistview.h index a306c955a..f4b4e3988 100644 --- a/src/modlistview.h +++ b/src/modlistview.h @@ -1,344 +1,352 @@ -#ifndef MODLISTVIEW_H -#define MODLISTVIEW_H - -#include -#include -#include - -#include -#include -#include -#include - -#include "qtgroupingproxy.h" -#include "viewmarkingscrollbar.h" -#include "modlistsortproxy.h" - -namespace Ui { class MainWindow; } - -class CategoryFactory; -class FilterList; -class OrganizerCore; -class MainWindow; -class Profile; -class ModListByPriorityProxy; -class ModListViewActions; -class PluginListView; - -class ModListView : public QTreeView -{ - Q_OBJECT - -public: - - // this is a public version of DropIndicatorPosition - enum DropPosition { - OnItem = DropIndicatorPosition::OnItem, - AboveItem = DropIndicatorPosition::AboveItem, - BelowItem = DropIndicatorPosition::BelowItem, - OnViewport = DropIndicatorPosition::OnViewport - }; - - // indiucate the groupby mode - enum class GroupByMode { - NONE, - SEPARATOR, - CATEGORY, - NEXUS_ID - }; - -public: - explicit ModListView(QWidget* parent = 0); - - void setup(OrganizerCore& core, CategoryFactory& factory, MainWindow* mw, Ui::MainWindow* mwui); - - // restore/save the state between session - // - void restoreState(const Settings& s); - void saveState(Settings& s) const; - - // check if collapsible separators are currently used - // - bool hasCollapsibleSeparators() const; - - // the column/order by which the mod list is currently sorted - // - int sortColumn() const; - Qt::SortOrder sortOrder() const; - - // check if a filter is currently active - // - bool isFilterActive() const; - - // the current group mode - // - GroupByMode groupByMode() const; - - // retrieve the actions from the view - // - ModListViewActions& actions() const; - - // retrieve the next/previous mod in the current view, the given index - // should be a mod index (not a model row) - // - std::optional nextMod(unsigned int index) const; - std::optional prevMod(unsigned int index) const; - - // check if the given mod is visible, i.e. not filtered (returns true - // for collapsed mods) - // - bool isModVisible(unsigned int index) const; - bool isModVisible(ModInfo::Ptr mod) const; - - // focus the view, select the given index and scroll to it - // - void scrollToAndSelect(const QModelIndex& index); - void scrollToAndSelect(const QModelIndexList& indexes, const QModelIndex& current = QModelIndex()); - - // refresh the view (to call when settings have been changed) - // - void refresh(); - -signals: - - // emitted for dragEnter events - // - void dragEntered(const QMimeData* mimeData); - - // emitted for dropEnter events, the boolean indicates if the drop target - // is expanded and the position of the indicator - // - void dropEntered(const QMimeData* mimeData, bool dropExpanded, DropPosition position); - -public slots: - - // invalidate (refresh) the filter (similar to a layout changed event) - // - void invalidateFilter(); - - // set the filter criteria/options for mods - // - void setFilterCriteria(const std::vector& criteria); - void setFilterOptions(ModListSortProxy::FilterMode mode, ModListSortProxy::SeparatorsMode sep); - - // update the mod counter - // - void updateModCount(); - - // refresh the filters - // - void refreshFilters(); - - // set highligth markers - // - void setHighlightedMods(const std::vector& pluginIndices); - -protected: - - // map from/to the view indexes to the model - // - QModelIndex indexModelToView(const QModelIndex& index) const; - QModelIndexList indexModelToView(const QModelIndexList& index) const; - QModelIndex indexViewToModel(const QModelIndex& index) const; - QModelIndexList indexViewToModel(const QModelIndexList& index) const; - - // returns the next/previous index of the given index - // - QModelIndex nextIndex(const QModelIndex& index) const; - QModelIndex prevIndex(const QModelIndex& index) const; - - // re-implemented to fake the return value to allow drag-and-drop on - // itself for separators - // - QModelIndexList selectedIndexes() const; - - // drop from external folder - // - void onExternalFolderDropped(const QUrl& url, int priority); - - // method to react to various key events - // - bool moveSelection(int key); - bool removeSelection(); - bool toggleSelectionState(); - - // re-implemented to fix indentation with collapsible separators - // - QRect visualRect(const QModelIndex& index) const override; - void drawBranches(QPainter* painter, const QRect& rect, const QModelIndex& index) const override; - - void timerEvent(QTimerEvent* event) override; - void dragEnterEvent(QDragEnterEvent* event) override; - void dragMoveEvent(QDragMoveEvent* event) override; - void dropEvent(QDropEvent* event) override; - void mousePressEvent(QMouseEvent* event) override; - void mouseReleaseEvent(QMouseEvent* event) override; - bool event(QEvent* event) override; - -protected slots: - - void onCustomContextMenuRequested(const QPoint& pos); - void onDoubleClicked(const QModelIndex& index); - void onFiltersCriteria(const std::vector& filters); - void onProfileChanged(Profile* oldProfile, Profile* newProfile); - - void commitData(QWidget* editor) override; - -private: // friend classes - - friend class ModConflictIconDelegate; - friend class ModContentIconDelegate; - friend class ModFlagIconDelegate; - friend class ModListContextMenu; - friend class ModListStyledItemDelegate; - friend class ModListViewActions; - friend class ModListViewMarkingScrollBar; - -private: // private structures - - struct ModListViewUi { - // the group by combo box - QComboBox* groupBy; - - // the mod counter - QLCDNumber* counter; - - // filters related - QLineEdit* filter; - QLabel* currentCategory; - QPushButton* clearFilters; - QComboBox* filterSeparators; - - // the plugin list (for highligths) - PluginListView* pluginList; - }; - - struct MarkerInfos { - // conflicts - std::set overwrite; - std::set overwritten; - std::set archiveOverwrite; - std::set archiveOverwritten; - std::set archiveLooseOverwrite; - std::set archiveLooseOverwritten; - - // selected plugins - std::set highlight; - }; - - struct ModCounters { - int active = 0; - int backup = 0; - int foreign = 0; - int separator = 0; - int regular = 0; - - struct { - int active = 0; - int backup = 0; - int foreign = 0; - int separator = 0; - int regular = 0; - } visible; - }; - - // index in the groupby combo - // - enum GroupBy { - NONE = 0, - CATEGORY = 1, - NEXUS_ID = 2 - }; - -private: // private functions - - void onModPrioritiesChanged(const QModelIndexList& indices); - void onModInstalled(const QString& modName); - void onModFilterActive(bool filterActive); - - // refresh the overwrite markers and the highligthed plugins from - // the current selection - // - void refreshMarkersAndPlugins(); - - // clear overwrite markers (without repainting) - // - void clearOverwriteMarkers(); - - // set overwrite markers from the mod in the given list and repaint (if the list - // is empty, clear overwrite and repaint) - // - void setOverwriteMarkers(const QModelIndexList& indexes); - - // retrieve the marker color for the given index - // - QColor markerColor(const QModelIndex& index) const; - - // retrieve the mod flags for the given index - // - std::vector modFlags( - const QModelIndex& index, bool* forceCompact = nullptr) const; - - // retrieve the conflicts flags for the given index - // - std::vector conflictFlags( - const QModelIndex& index, bool* forceCompact = nullptr) const; - - // retrieve the content icons and tooltip for the given index - // - std::set contents(const QModelIndex& index, bool* includeChildren) const; - QList contentsIcons(const QModelIndex& index, bool* forceCompact = nullptr) const; - QString contentsTooltip(const QModelIndex& index) const; - - // compute the counters for mods according to the current filter - // - ModCounters counters() const; - - // get/set the selected items on the view, this method return/take indices - // from the mod list model, not the view, so it's safe to restore - // - std::pair selected() const; - void setSelected(const QModelIndex& current, const QModelIndexList& selected); - - // refresh stored expanded items for the current intermediate proxy - // - void refreshExpandedItems(); - - // refresh the group-by proxy, if the index is -1 will refresh the - // current one (e.g. when changing the sort column) - // - void updateGroupByProxy(); - -public: // member variables - - OrganizerCore* m_core; - std::unique_ptr m_filters; - CategoryFactory* m_categories; - ModListViewUi ui; - ModListViewActions* m_actions; - - ModListSortProxy* m_sortProxy; - ModListByPriorityProxy* m_byPriorityProxy; - QtGroupingProxy* m_byCategoryProxy; - QtGroupingProxy* m_byNexusIdProxy; - - // marker used to avoid calling refreshing markers to many - // time in a row - QTimer m_refreshMarkersTimer; - - // maintain collapsed items for each proxy to avoid - // losing them on model reset - std::map> m_collapsed; - - MarkerInfos m_markers; - ViewMarkingScrollBar* m_scrollbar; - - bool m_inDragMoveEvent = false; - - // replace the auto-expand timer from QTreeView to avoid - // auto-collapsing - QBasicTimer m_openTimer; - -}; - -#endif // MODLISTVIEW_H +#ifndef MODLISTVIEW_H +#define MODLISTVIEW_H + +#include +#include +#include + +#include +#include +#include +#include + +#include "modlistsortproxy.h" +#include "qtgroupingproxy.h" +#include "viewmarkingscrollbar.h" + +namespace Ui +{ +class MainWindow; +} + +class CategoryFactory; +class FilterList; +class OrganizerCore; +class MainWindow; +class Profile; +class ModListByPriorityProxy; +class ModListViewActions; +class PluginListView; + +class ModListView : public QTreeView +{ + Q_OBJECT + +public: + // this is a public version of DropIndicatorPosition + enum DropPosition + { + OnItem = DropIndicatorPosition::OnItem, + AboveItem = DropIndicatorPosition::AboveItem, + BelowItem = DropIndicatorPosition::BelowItem, + OnViewport = DropIndicatorPosition::OnViewport + }; + + // indiucate the groupby mode + enum class GroupByMode + { + NONE, + SEPARATOR, + CATEGORY, + NEXUS_ID + }; + +public: + explicit ModListView(QWidget* parent = 0); + + void setup(OrganizerCore& core, CategoryFactory& factory, MainWindow* mw, + Ui::MainWindow* mwui); + + // restore/save the state between session + // + void restoreState(const Settings& s); + void saveState(Settings& s) const; + + // check if collapsible separators are currently used + // + bool hasCollapsibleSeparators() const; + + // the column/order by which the mod list is currently sorted + // + int sortColumn() const; + Qt::SortOrder sortOrder() const; + + // check if a filter is currently active + // + bool isFilterActive() const; + + // the current group mode + // + GroupByMode groupByMode() const; + + // retrieve the actions from the view + // + ModListViewActions& actions() const; + + // retrieve the next/previous mod in the current view, the given index + // should be a mod index (not a model row) + // + std::optional nextMod(unsigned int index) const; + std::optional prevMod(unsigned int index) const; + + // check if the given mod is visible, i.e. not filtered (returns true + // for collapsed mods) + // + bool isModVisible(unsigned int index) const; + bool isModVisible(ModInfo::Ptr mod) const; + + // focus the view, select the given index and scroll to it + // + void scrollToAndSelect(const QModelIndex& index); + void scrollToAndSelect(const QModelIndexList& indexes, + const QModelIndex& current = QModelIndex()); + + // refresh the view (to call when settings have been changed) + // + void refresh(); + +signals: + + // emitted for dragEnter events + // + void dragEntered(const QMimeData* mimeData); + + // emitted for dropEnter events, the boolean indicates if the drop target + // is expanded and the position of the indicator + // + void dropEntered(const QMimeData* mimeData, bool dropExpanded, DropPosition position); + +public slots: + + // invalidate (refresh) the filter (similar to a layout changed event) + // + void invalidateFilter(); + + // set the filter criteria/options for mods + // + void setFilterCriteria(const std::vector& criteria); + void setFilterOptions(ModListSortProxy::FilterMode mode, + ModListSortProxy::SeparatorsMode sep); + + // update the mod counter + // + void updateModCount(); + + // refresh the filters + // + void refreshFilters(); + + // set highligth markers + // + void setHighlightedMods(const std::vector& pluginIndices); + +protected: + // map from/to the view indexes to the model + // + QModelIndex indexModelToView(const QModelIndex& index) const; + QModelIndexList indexModelToView(const QModelIndexList& index) const; + QModelIndex indexViewToModel(const QModelIndex& index) const; + QModelIndexList indexViewToModel(const QModelIndexList& index) const; + + // returns the next/previous index of the given index + // + QModelIndex nextIndex(const QModelIndex& index) const; + QModelIndex prevIndex(const QModelIndex& index) const; + + // re-implemented to fake the return value to allow drag-and-drop on + // itself for separators + // + QModelIndexList selectedIndexes() const; + + // drop from external folder + // + void onExternalFolderDropped(const QUrl& url, int priority); + + // method to react to various key events + // + bool moveSelection(int key); + bool removeSelection(); + bool toggleSelectionState(); + + // re-implemented to fix indentation with collapsible separators + // + QRect visualRect(const QModelIndex& index) const override; + void drawBranches(QPainter* painter, const QRect& rect, + const QModelIndex& index) const override; + + void timerEvent(QTimerEvent* event) override; + void dragEnterEvent(QDragEnterEvent* event) override; + void dragMoveEvent(QDragMoveEvent* event) override; + void dropEvent(QDropEvent* event) override; + void mousePressEvent(QMouseEvent* event) override; + void mouseReleaseEvent(QMouseEvent* event) override; + bool event(QEvent* event) override; + +protected slots: + + void onCustomContextMenuRequested(const QPoint& pos); + void onDoubleClicked(const QModelIndex& index); + void onFiltersCriteria(const std::vector& filters); + void onProfileChanged(Profile* oldProfile, Profile* newProfile); + + void commitData(QWidget* editor) override; + +private: // friend classes + friend class ModConflictIconDelegate; + friend class ModContentIconDelegate; + friend class ModFlagIconDelegate; + friend class ModListContextMenu; + friend class ModListStyledItemDelegate; + friend class ModListViewActions; + friend class ModListViewMarkingScrollBar; + +private: // private structures + struct ModListViewUi + { + // the group by combo box + QComboBox* groupBy; + + // the mod counter + QLCDNumber* counter; + + // filters related + QLineEdit* filter; + QLabel* currentCategory; + QPushButton* clearFilters; + QComboBox* filterSeparators; + + // the plugin list (for highligths) + PluginListView* pluginList; + }; + + struct MarkerInfos + { + // conflicts + std::set overwrite; + std::set overwritten; + std::set archiveOverwrite; + std::set archiveOverwritten; + std::set archiveLooseOverwrite; + std::set archiveLooseOverwritten; + + // selected plugins + std::set highlight; + }; + + struct ModCounters + { + int active = 0; + int backup = 0; + int foreign = 0; + int separator = 0; + int regular = 0; + + struct + { + int active = 0; + int backup = 0; + int foreign = 0; + int separator = 0; + int regular = 0; + } visible; + }; + + // index in the groupby combo + // + enum GroupBy + { + NONE = 0, + CATEGORY = 1, + NEXUS_ID = 2 + }; + +private: // private functions + void onModPrioritiesChanged(const QModelIndexList& indices); + void onModInstalled(const QString& modName); + void onModFilterActive(bool filterActive); + + // refresh the overwrite markers and the highligthed plugins from + // the current selection + // + void refreshMarkersAndPlugins(); + + // clear overwrite markers (without repainting) + // + void clearOverwriteMarkers(); + + // set overwrite markers from the mod in the given list and repaint (if the list + // is empty, clear overwrite and repaint) + // + void setOverwriteMarkers(const QModelIndexList& indexes); + + // retrieve the marker color for the given index + // + QColor markerColor(const QModelIndex& index) const; + + // retrieve the mod flags for the given index + // + std::vector modFlags(const QModelIndex& index, + bool* forceCompact = nullptr) const; + + // retrieve the conflicts flags for the given index + // + std::vector conflictFlags(const QModelIndex& index, + bool* forceCompact = nullptr) const; + + // retrieve the content icons and tooltip for the given index + // + std::set contents(const QModelIndex& index, bool* includeChildren) const; + QList contentsIcons(const QModelIndex& index, + bool* forceCompact = nullptr) const; + QString contentsTooltip(const QModelIndex& index) const; + + // compute the counters for mods according to the current filter + // + ModCounters counters() const; + + // get/set the selected items on the view, this method return/take indices + // from the mod list model, not the view, so it's safe to restore + // + std::pair selected() const; + void setSelected(const QModelIndex& current, const QModelIndexList& selected); + + // refresh stored expanded items for the current intermediate proxy + // + void refreshExpandedItems(); + + // refresh the group-by proxy, if the index is -1 will refresh the + // current one (e.g. when changing the sort column) + // + void updateGroupByProxy(); + +public: // member variables + OrganizerCore* m_core; + std::unique_ptr m_filters; + CategoryFactory* m_categories; + ModListViewUi ui; + ModListViewActions* m_actions; + + ModListSortProxy* m_sortProxy; + ModListByPriorityProxy* m_byPriorityProxy; + QtGroupingProxy* m_byCategoryProxy; + QtGroupingProxy* m_byNexusIdProxy; + + // marker used to avoid calling refreshing markers to many + // time in a row + QTimer m_refreshMarkersTimer; + + // maintain collapsed items for each proxy to avoid + // losing them on model reset + std::map> m_collapsed; + + MarkerInfos m_markers; + ViewMarkingScrollBar* m_scrollbar; + + bool m_inDragMoveEvent = false; + + // replace the auto-expand timer from QTreeView to avoid + // auto-collapsing + QBasicTimer m_openTimer; +}; + +#endif // MODLISTVIEW_H diff --git a/src/modlistviewactions.cpp b/src/modlistviewactions.cpp index 6adb32c7d..abc64b60c 100644 --- a/src/modlistviewactions.cpp +++ b/src/modlistviewactions.cpp @@ -5,58 +5,52 @@ #include #include +#include "filesystemutilities.h" #include #include -#include "filesystemutilities.h" #include "categories.h" +#include "csvbuilder.h" +#include "directoryrefresher.h" #include "filedialogmemory.h" #include "filterlist.h" #include "listdialog.h" +#include "messagedialog.h" +#include "modelutils.h" #include "modinfodialog.h" #include "modlist.h" #include "modlistview.h" -#include "modelutils.h" -#include "messagedialog.h" #include "nexusinterface.h" #include "nxmaccessmanager.h" -#include "savetextasdialog.h" #include "organizercore.h" #include "overwriteinfodialog.h" -#include "csvbuilder.h" #include "pluginlistview.h" -#include "shared/filesorigin.h" +#include "savetextasdialog.h" #include "shared/directoryentry.h" #include "shared/fileregister.h" +#include "shared/filesorigin.h" #include "directoryrefresher.h" #include "downloadmanager.h" using namespace MOBase; using namespace MOShared; - -ModListViewActions::ModListViewActions( - OrganizerCore& core, FilterList& filters, CategoryFactory& categoryFactory, - ModListView* view, PluginListView* pluginView, QObject* nxmReceiver) : - QObject(view) - , m_core(core) - , m_filters(filters) - , m_categories(categoryFactory) - , m_view(view) - , m_pluginView(pluginView) - , m_parent(view->topLevelWidget()) - , m_receiver(nxmReceiver) -{ - -} +ModListViewActions::ModListViewActions(OrganizerCore& core, FilterList& filters, + CategoryFactory& categoryFactory, + ModListView* view, PluginListView* pluginView, + QObject* nxmReceiver) + : QObject(view), m_core(core), m_filters(filters), m_categories(categoryFactory), + m_view(view), m_pluginView(pluginView), m_parent(view->topLevelWidget()), + m_receiver(nxmReceiver) +{} int ModListViewActions::findInstallPriority(const QModelIndex& index) const { int newPriority = -1; - if (index.isValid() && index.data(ModList::IndexRole).isValid() - && m_view->sortColumn() == ModList::COL_PRIORITY) { + if (index.isValid() && index.data(ModList::IndexRole).isValid() && + m_view->sortColumn() == ModList::COL_PRIORITY) { auto mIndex = index.data(ModList::IndexRole).toInt(); - auto info = ModInfo::getByIndex(mIndex); + auto info = ModInfo::getByIndex(mIndex); newPriority = m_core.currentProfile()->getModPriority(mIndex); if (info->isSeparator()) { @@ -72,17 +66,15 @@ int ModListViewActions::findInstallPriority(const QModelIndex& index) const auto it = std::find_if(ibp.find(newPriority + 1), ibp.end(), isSeparator); if (it != ibp.end()) { newPriority = it->first; - } - else { + } else { newPriority = -1; } - } - else { - auto it = std::find_if(std::reverse_iterator{ ibp.find(newPriority - 1) }, ibp.rend(), isSeparator); + } else { + auto it = std::find_if(std::reverse_iterator{ibp.find(newPriority - 1)}, + ibp.rend(), isSeparator); if (it != ibp.rend()) { newPriority = it->first + 1; - } - else { + } else { // create "before" priority 0, i.e. at the end in descending priority. newPriority = 0; } @@ -93,7 +85,8 @@ int ModListViewActions::findInstallPriority(const QModelIndex& index) const return newPriority; } -void ModListViewActions::installMod(const QString& archivePath, const QModelIndex& index) const +void ModListViewActions::installMod(const QString& archivePath, + const QModelIndex& index) const { try { QString path = archivePath; @@ -103,18 +96,17 @@ void ModListViewActions::installMod(const QString& archivePath, const QModelInde *iter = "*." + *iter; } - path = FileDialogMemory::getOpenFileName("installMod", m_parent, tr("Choose Mod"), QString(), - tr("Mod Archive").append(QString(" (%1)").arg(extensions.join(" ")))); + path = FileDialogMemory::getOpenFileName( + "installMod", m_parent, tr("Choose Mod"), QString(), + tr("Mod Archive").append(QString(" (%1)").arg(extensions.join(" ")))); } if (path.isEmpty()) { return; - } - else { + } else { m_core.installMod(path, findInstallPriority(index), false, nullptr, QString()); } - } - catch (const std::exception& e) { + } catch (const std::exception& e) { reportError(e.what()); } } @@ -127,9 +119,10 @@ void ModListViewActions::createEmptyMod(const QModelIndex& index) const while (name->isEmpty()) { bool ok; name.update(QInputDialog::getText(m_parent, tr("Create Mod..."), - tr("This will create an empty mod.\n" - "Please enter a name:"), QLineEdit::Normal, "", &ok), - GUESS_USER); + tr("This will create an empty mod.\n" + "Please enter a name:"), + QLineEdit::Normal, "", &ok), + GUESS_USER); if (!ok) { return; } @@ -140,7 +133,6 @@ void ModListViewActions::createEmptyMod(const QModelIndex& index) const return; } - if (m_core.createMod(name) == nullptr) { return; } @@ -154,7 +146,8 @@ void ModListViewActions::createEmptyMod(const QModelIndex& index) const m_core.modList()->changeModPriority(mIndex, newPriority); } - m_view->scrollToAndSelect(m_view->indexModelToView(m_core.modList()->index(mIndex, 0))); + m_view->scrollToAndSelect( + m_view->indexModelToView(m_core.modList()->index(mIndex, 0))); } void ModListViewActions::createSeparator(const QModelIndex& index) const @@ -164,10 +157,13 @@ void ModListViewActions::createSeparator(const QModelIndex& index) const while (name->isEmpty()) { bool ok; name.update(QInputDialog::getText(m_parent, tr("Create Separator..."), - tr("This will create a new separator.\n" - "Please enter a name:"), QLineEdit::Normal, "", &ok), - GUESS_USER); - if (!ok) { return; } + tr("This will create a new separator.\n" + "Please enter a name:"), + QLineEdit::Normal, "", &ok), + GUESS_USER); + if (!ok) { + return; + } } if (m_core.modList()->getMod(name) != nullptr) { reportError(tr("A separator with this name already exists")); @@ -180,7 +176,8 @@ void ModListViewActions::createSeparator(const QModelIndex& index) const int newPriority = -1; if (index.isValid() && m_view->sortColumn() == ModList::COL_PRIORITY) { - newPriority = m_core.currentProfile()->getModPriority(index.data(ModList::IndexRole).toInt()); + newPriority = + m_core.currentProfile()->getModPriority(index.data(ModList::IndexRole).toInt()); // descending order, we need to fix the priority if (m_view->sortOrder() == Qt::DescendingOrder) { @@ -203,22 +200,23 @@ void ModListViewActions::createSeparator(const QModelIndex& index) const ModInfo::getByIndex(mIndex)->setColor(*c); } - m_view->scrollToAndSelect(m_view->indexModelToView(m_core.modList()->index(mIndex, 0))); + m_view->scrollToAndSelect( + m_view->indexModelToView(m_core.modList()->index(mIndex, 0))); } void ModListViewActions::setAllMatchingModsEnabled(bool enabled) const { // number of mods to enable / disable const auto counters = m_view->counters(); - const auto count = enabled ? - counters.visible.regular - counters.visible.active : counters.visible.active; + const auto count = enabled ? counters.visible.regular - counters.visible.active + : counters.visible.active; // retrieve visible mods from the model view const auto allIndex = m_view->indexViewToModel(flatIndex(m_view->model())); - const QString message = enabled ? - tr("Really enable %1 mod(s)?") : tr("Really disable %1 mod(s)?"); + const QString message = + enabled ? tr("Really enable %1 mod(s)?") : tr("Really disable %1 mod(s)?"); if (QMessageBox::question(m_parent, tr("Confirm"), message.arg(count), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { m_core.modList()->setActive(allIndex, enabled); } } @@ -227,16 +225,21 @@ void ModListViewActions::checkModsForUpdates() const { bool checkingModsForUpdate = false; if (NexusInterface::instance().getAccessManager()->validated()) { - checkingModsForUpdate = ModInfo::checkAllForUpdate(&m_core.pluginContainer(), m_receiver); - NexusInterface::instance().requestEndorsementInfo(m_receiver, QVariant(), QString()); + checkingModsForUpdate = + ModInfo::checkAllForUpdate(&m_core.pluginContainer(), m_receiver); + NexusInterface::instance().requestEndorsementInfo(m_receiver, QVariant(), + QString()); NexusInterface::instance().requestTrackingInfo(m_receiver, QVariant(), QString()); } else { QString apiKey; if (GlobalSettings::nexusApiKey(apiKey)) { - m_core.doAfterLogin([=] () { checkModsForUpdates(); }); + m_core.doAfterLogin([=]() { + checkModsForUpdates(); + }); NexusInterface::instance().getAccessManager()->apiCheck(apiKey); } else { - log::warn("{}", tr("You are not currently authenticated with Nexus. Please do so under Settings -> Nexus.")); + log::warn("{}", tr("You are not currently authenticated with Nexus. Please do so " + "under Settings -> Nexus.")); } } @@ -250,17 +253,11 @@ void ModListViewActions::checkModsForUpdates() const } if (updatesAvailable || checkingModsForUpdate) { - m_view->setFilterCriteria({{ - ModListSortProxy::TypeSpecial, - CategoryFactory::UpdateAvailable, - false} - }); + m_view->setFilterCriteria( + {{ModListSortProxy::TypeSpecial, CategoryFactory::UpdateAvailable, false}}); - m_filters.setSelection({{ - ModListSortProxy::TypeSpecial, - CategoryFactory::UpdateAvailable, - false - }}); + m_filters.setSelection( + {{ModListSortProxy::TypeSpecial, CategoryFactory::UpdateAvailable, false}}); } } @@ -283,7 +280,8 @@ void ModListViewActions::assignCategories() const } } -void ModListViewActions::checkModsForUpdates(std::multimap const& IDs) const +void ModListViewActions::checkModsForUpdates( + std::multimap const& IDs) const { if (m_core.settings().network().offlineMode()) { return; @@ -291,15 +289,16 @@ void ModListViewActions::checkModsForUpdates(std::multimap const& if (NexusInterface::instance().getAccessManager()->validated()) { ModInfo::manualUpdateCheck(m_receiver, IDs); - } - else { + } else { QString apiKey; if (GlobalSettings::nexusApiKey(apiKey)) { - m_core.doAfterLogin([=]() { checkModsForUpdates(IDs); }); + m_core.doAfterLogin([=]() { + checkModsForUpdates(IDs); + }); NexusInterface::instance().getAccessManager()->apiCheck(apiKey); - } - else - log::warn("{}", tr("You are not currently authenticated with Nexus. Please do so under Settings -> Nexus.")); + } else + log::warn("{}", tr("You are not currently authenticated with Nexus. Please do so " + "under Settings -> Nexus.")); } } @@ -320,13 +319,18 @@ void ModListViewActions::exportModListCSV() const selection.setWindowTitle(tr("Export to csv")); QLabel* csvDescription = new QLabel(); - csvDescription->setText(tr("CSV (Comma Separated Values) is a format that can be imported in programs like Excel to create a spreadsheet.\nYou can also use online editors and converters instead.")); + csvDescription->setText( + tr("CSV (Comma Separated Values) is a format that can be imported in programs " + "like Excel to create a spreadsheet.\nYou can also use online editors and " + "converters instead.")); grid->addWidget(csvDescription); QGroupBox* groupBoxRows = new QGroupBox(tr("Select what mods you want export:")); - QRadioButton* all = new QRadioButton(tr("All installed mods")); - QRadioButton* active = new QRadioButton(tr("Only active (checked) mods from your current profile")); - QRadioButton* visible = new QRadioButton(tr("All currently visible mods in the mod list")); + QRadioButton* all = new QRadioButton(tr("All installed mods")); + QRadioButton* active = + new QRadioButton(tr("Only active (checked) mods from your current profile")); + QRadioButton* visible = + new QRadioButton(tr("All currently visible mods in the mod list")); QVBoxLayout* vbox = new QVBoxLayout; vbox->addWidget(all); @@ -350,14 +354,14 @@ void ModListViewActions::exportModListCSV() const mod_Priority->setChecked(true); QCheckBox* mod_Name = new QCheckBox(tr("Mod_Name")); mod_Name->setChecked(true); - QCheckBox* mod_Note = new QCheckBox(tr("Notes_column")); + QCheckBox* mod_Note = new QCheckBox(tr("Notes_column")); QCheckBox* mod_Status = new QCheckBox(tr("Mod_Status")); mod_Status->setChecked(true); - QCheckBox* primary_Category = new QCheckBox(tr("Primary_Category")); - QCheckBox* nexus_ID = new QCheckBox(tr("Nexus_ID")); - QCheckBox* mod_Nexus_URL = new QCheckBox(tr("Mod_Nexus_URL")); - QCheckBox* mod_Version = new QCheckBox(tr("Mod_Version")); - QCheckBox* install_Date = new QCheckBox(tr("Install_Date")); + QCheckBox* primary_Category = new QCheckBox(tr("Primary_Category")); + QCheckBox* nexus_ID = new QCheckBox(tr("Nexus_ID")); + QCheckBox* mod_Nexus_URL = new QCheckBox(tr("Mod_Nexus_URL")); + QCheckBox* mod_Version = new QCheckBox(tr("Mod_Version")); + QCheckBox* install_Date = new QCheckBox(tr("Install_Date")); QCheckBox* download_File_Name = new QCheckBox(tr("Download_File_Name")); QVBoxLayout* vbox1 = new QVBoxLayout; @@ -375,9 +379,10 @@ void ModListViewActions::exportModListCSV() const grid->addWidget(groupBoxColumns); - QPushButton* ok = new QPushButton("Ok"); + QPushButton* ok = new QPushButton("Ok"); QPushButton* cancel = new QPushButton("Cancel"); - QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + QDialogButtonBox* buttons = + new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); connect(buttons, SIGNAL(accepted()), &selection, SLOT(accept())); connect(buttons, SIGNAL(rejected()), &selection, SLOT(reject())); @@ -386,38 +391,45 @@ void ModListViewActions::exportModListCSV() const selection.setLayout(grid); - if (selection.exec() == QDialog::Accepted) { unsigned int numMods = ModInfo::getNumMods(); - int selectedRowID = buttonGroupRows->checkedId(); + int selectedRowID = buttonGroupRows->checkedId(); try { QBuffer buffer; buffer.open(QIODevice::ReadWrite); CSVBuilder builder(&buffer); builder.setEscapeMode(CSVBuilder::TYPE_STRING, CSVBuilder::QUOTE_ALWAYS); - std::vector > fields; + std::vector> fields; if (mod_Priority->isChecked()) - fields.push_back(std::make_pair(QString("#Mod_Priority"), CSVBuilder::TYPE_STRING)); + fields.push_back( + std::make_pair(QString("#Mod_Priority"), CSVBuilder::TYPE_STRING)); if (mod_Status->isChecked()) - fields.push_back(std::make_pair(QString("#Mod_Status"), CSVBuilder::TYPE_STRING)); + fields.push_back( + std::make_pair(QString("#Mod_Status"), CSVBuilder::TYPE_STRING)); if (mod_Name->isChecked()) fields.push_back(std::make_pair(QString("#Mod_Name"), CSVBuilder::TYPE_STRING)); if (mod_Note->isChecked()) fields.push_back(std::make_pair(QString("#Note"), CSVBuilder::TYPE_STRING)); if (primary_Category->isChecked()) - fields.push_back(std::make_pair(QString("#Primary_Category"), CSVBuilder::TYPE_STRING)); + fields.push_back( + std::make_pair(QString("#Primary_Category"), CSVBuilder::TYPE_STRING)); if (nexus_ID->isChecked()) - fields.push_back(std::make_pair(QString("#Nexus_ID"), CSVBuilder::TYPE_INTEGER)); + fields.push_back( + std::make_pair(QString("#Nexus_ID"), CSVBuilder::TYPE_INTEGER)); if (mod_Nexus_URL->isChecked()) - fields.push_back(std::make_pair(QString("#Mod_Nexus_URL"), CSVBuilder::TYPE_STRING)); + fields.push_back( + std::make_pair(QString("#Mod_Nexus_URL"), CSVBuilder::TYPE_STRING)); if (mod_Version->isChecked()) - fields.push_back(std::make_pair(QString("#Mod_Version"), CSVBuilder::TYPE_STRING)); + fields.push_back( + std::make_pair(QString("#Mod_Version"), CSVBuilder::TYPE_STRING)); if (install_Date->isChecked()) - fields.push_back(std::make_pair(QString("#Install_Date"), CSVBuilder::TYPE_STRING)); + fields.push_back( + std::make_pair(QString("#Install_Date"), CSVBuilder::TYPE_STRING)); if (download_File_Name->isChecked()) - fields.push_back(std::make_pair(QString("#Download_File_Name"), CSVBuilder::TYPE_STRING)); + fields.push_back( + std::make_pair(QString("#Download_File_Name"), CSVBuilder::TYPE_STRING)); builder.setFields(fields); @@ -426,34 +438,46 @@ void ModListViewActions::exportModListCSV() const auto indexesByPriority = m_core.currentProfile()->getAllIndexesByPriority(); for (auto& iter : indexesByPriority) { ModInfo::Ptr info = ModInfo::getByIndex(iter.second); - bool enabled = m_core.currentProfile()->modEnabled(iter.second); + bool enabled = m_core.currentProfile()->modEnabled(iter.second); if ((selectedRowID == 1) && !enabled) { continue; - } - else if ((selectedRowID == 2) && !m_view->isModVisible(iter.second)) { + } else if ((selectedRowID == 2) && !m_view->isModVisible(iter.second)) { continue; } std::vector flags = info->getFlags(); - if ((std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) == flags.end()) && - (std::find(flags.begin(), flags.end(), ModInfo::FLAG_BACKUP) == flags.end())) { + if ((std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) == + flags.end()) && + (std::find(flags.begin(), flags.end(), ModInfo::FLAG_BACKUP) == + flags.end())) { if (mod_Priority->isChecked()) - builder.setRowField("#Mod_Priority", QString("%1").arg(iter.first, 4, 10, QChar('0'))); + builder.setRowField("#Mod_Priority", + QString("%1").arg(iter.first, 4, 10, QChar('0'))); if (mod_Status->isChecked()) builder.setRowField("#Mod_Status", (enabled) ? "+" : "-"); if (mod_Name->isChecked()) builder.setRowField("#Mod_Name", info->name()); if (mod_Note->isChecked()) - builder.setRowField("#Note", QString("%1").arg(info->comments().remove(','))); + builder.setRowField("#Note", + QString("%1").arg(info->comments().remove(','))); if (primary_Category->isChecked()) - builder.setRowField("#Primary_Category", (m_categories.categoryExists(info->primaryCategory())) ? m_categories.getCategoryNameByID(info->primaryCategory()) : ""); + builder.setRowField( + "#Primary_Category", + (m_categories.categoryExists(info->primaryCategory())) + ? m_categories.getCategoryNameByID(info->primaryCategory()) + : ""); if (nexus_ID->isChecked()) builder.setRowField("#Nexus_ID", info->nexusId()); if (mod_Nexus_URL->isChecked()) - builder.setRowField("#Mod_Nexus_URL", (info->nexusId() > 0) ? NexusInterface::instance().getModURL(info->nexusId(), info->gameName()) : ""); + builder.setRowField("#Mod_Nexus_URL", + (info->nexusId() > 0) + ? NexusInterface::instance().getModURL( + info->nexusId(), info->gameName()) + : ""); if (mod_Version->isChecked()) builder.setRowField("#Mod_Version", info->version().canonicalString()); if (install_Date->isChecked()) - builder.setRowField("#Install_Date", info->creationTime().toString("yyyy/MM/dd HH:mm:ss")); + builder.setRowField("#Install_Date", + info->creationTime().toString("yyyy/MM/dd HH:mm:ss")); if (download_File_Name->isChecked()) builder.setRowField("#Download_File_Name", info->installationFile()); @@ -464,14 +488,14 @@ void ModListViewActions::exportModListCSV() const SaveTextAsDialog saveDialog(m_parent); saveDialog.setText(buffer.data()); saveDialog.exec(); - } - catch (const std::exception& e) { + } catch (const std::exception& e) { reportError(tr("export failed: %1").arg(e.what())); } } } -void ModListViewActions::displayModInformation(const QString& modName, ModInfoTabIDs tab) const +void ModListViewActions::displayModInformation(const QString& modName, + ModInfoTabIDs tab) const { unsigned int index = ModInfo::getIndex(modName); if (index == UINT_MAX) { @@ -483,16 +507,20 @@ void ModListViewActions::displayModInformation(const QString& modName, ModInfoTa displayModInformation(modInfo, index, tab); } -void ModListViewActions::displayModInformation(unsigned int index, ModInfoTabIDs tab) const +void ModListViewActions::displayModInformation(unsigned int index, + ModInfoTabIDs tab) const { ModInfo::Ptr modInfo = ModInfo::getByIndex(index); displayModInformation(modInfo, index, tab); } -void ModListViewActions::displayModInformation(ModInfo::Ptr modInfo, unsigned int modIndex, ModInfoTabIDs tab) const +void ModListViewActions::displayModInformation(ModInfo::Ptr modInfo, + unsigned int modIndex, + ModInfoTabIDs tab) const { if (!m_core.modList()->modInfoAboutToChange(modInfo)) { - log::debug("a different mod information dialog is open. If this is incorrect, please restart MO"); + log::debug("a different mod information dialog is open. If this is incorrect, " + "please restart MO"); return; } std::vector flags = modInfo->getFlags(); @@ -502,8 +530,7 @@ void ModListViewActions::displayModInformation(ModInfo::Ptr modInfo, unsigned in if (dialog == nullptr) { dialog = new OverwriteInfoDialog(modInfo, m_parent); dialog->setObjectName("__overwriteDialog"); - } - else { + } else { qobject_cast(dialog)->setModInfo(modInfo); } @@ -515,23 +542,23 @@ void ModListViewActions::displayModInformation(ModInfo::Ptr modInfo, unsigned in dialog->deleteLater(); m_core.refreshDirectoryStructure(); }); - } - catch (const std::exception& e) { + } catch (const std::exception& e) { reportError(tr("Failed to display overwrite dialog: %1").arg(e.what())); } - } - else { + } else { modInfo->saveMeta(); ModInfoDialog dialog(m_core, m_core.pluginContainer(), modInfo, m_view, m_parent); - connect(&dialog, &ModInfoDialog::originModified, this, &ModListViewActions::originModified); + connect(&dialog, &ModInfoDialog::originModified, this, + &ModListViewActions::originModified); connect(&dialog, &ModInfoDialog::modChanged, [=](unsigned int index) { auto idx = m_view->indexModelToView(m_core.modList()->index(index, 0)); - m_view->selectionModel()->select(idx, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); + m_view->selectionModel()->select(idx, QItemSelectionModel::ClearAndSelect | + QItemSelectionModel::Rows); m_view->scrollTo(idx); }); - //Open the tab first if we want to use the standard indexes of the tabs. + // Open the tab first if we want to use the standard indexes of the tabs. if (tab != ModInfoTabIDs::None) { dialog.selectTab(tab); } @@ -544,19 +571,19 @@ void ModListViewActions::displayModInformation(ModInfo::Ptr modInfo, unsigned in } if (m_core.currentProfile()->modEnabled(modIndex) && !modInfo->isForeign()) { - FilesOrigin& origin = m_core.directoryStructure()->getOriginByName(ToWString(modInfo->name())); + FilesOrigin& origin = + m_core.directoryStructure()->getOriginByName(ToWString(modInfo->name())); origin.enable(false); if (m_core.directoryStructure()->originExists(ToWString(modInfo->name()))) { - FilesOrigin& origin = m_core.directoryStructure()->getOriginByName(ToWString(modInfo->name())); + FilesOrigin& origin = + m_core.directoryStructure()->getOriginByName(ToWString(modInfo->name())); origin.enable(false); - m_core.directoryRefresher()->addModToStructure(m_core.directoryStructure() - , modInfo->name() - , m_core.currentProfile()->getModPriority(modIndex) - , modInfo->absolutePath() - , modInfo->stealFiles() - , modInfo->archives()); + m_core.directoryRefresher()->addModToStructure( + m_core.directoryStructure(), modInfo->name(), + m_core.currentProfile()->getModPriority(modIndex), modInfo->absolutePath(), + modInfo->stealFiles(), modInfo->archives()); DirectoryRefresher::cleanStructure(m_core.directoryStructure()); m_core.directoryStructure()->getFileRegister()->sortOrigins(); m_core.refreshLists(); @@ -577,10 +604,11 @@ void ModListViewActions::sendModsToBottom(const QModelIndexList& indexes) const void ModListViewActions::sendModsToPriority(const QModelIndexList& indexes) const { bool ok; - int priority = QInputDialog::getInt(m_parent, - tr("Set Priority"), tr("Set the priority of the selected mods"), - 0, 0, std::numeric_limits::max(), 1, &ok); - if (!ok) return; + int priority = QInputDialog::getInt(m_parent, tr("Set Priority"), + tr("Set the priority of the selected mods"), 0, 0, + std::numeric_limits::max(), 1, &ok); + if (!ok) + return; m_core.modList()->changeModsPriority(indexes, priority); } @@ -593,7 +621,8 @@ void ModListViewActions::sendModsToSeparator(const QModelIndexList& indexes) con if (index < ModInfo::getNumMods()) { ModInfo::Ptr modInfo = ModInfo::getByIndex(index); if (modInfo->isSeparator()) { - separators << modInfo->name().chopped(10); // chops the "_separator" away from the name + separators << modInfo->name().chopped( + 10); // chops the "_separator" away from the name } } } @@ -616,14 +645,13 @@ void ModListViewActions::sendModsToSeparator(const QModelIndexList& indexes) con return; } - const auto sepPriority = m_core.currentProfile()->getModPriority( - ModInfo::getIndex(result + "_separator")); + const auto sepPriority = + m_core.currentProfile()->getModPriority(ModInfo::getIndex(result + "_separator")); auto isSeparator = [](const auto& p) { return ModInfo::getByIndex(p.second)->isSeparator(); }; - // start right after/before the current priority and look for the next // separator int priority = -1; @@ -631,17 +659,15 @@ void ModListViewActions::sendModsToSeparator(const QModelIndexList& indexes) con auto it = std::find_if(ibp.find(sepPriority + 1), ibp.end(), isSeparator); if (it != ibp.end()) { priority = it->first; - } - else { + } else { priority = Profile::MaximumPriority; } - } - else { - auto it = std::find_if(--std::reverse_iterator{ ibp.find(sepPriority - 1) }, ibp.rend(), isSeparator); + } else { + auto it = std::find_if(--std::reverse_iterator{ibp.find(sepPriority - 1)}, + ibp.rend(), isSeparator); if (it != ibp.rend()) { priority = it->first + 1; - } - else { + } else { // create "before" priority 0, i.e. at the end in descending priority. priority = Profile::MinimumPriority; } @@ -649,7 +675,8 @@ void ModListViewActions::sendModsToSeparator(const QModelIndexList& indexes) con // when the priority of a single mod is incremented, we need to shift the // target priority, otherwise we will miss the target by one - if (indexes.size() == 1 && indexes[0].data(ModList::PriorityRole).toInt() < sepPriority) { + if (indexes.size() == 1 && + indexes[0].data(ModList::PriorityRole).toInt() < sepPriority) { priority--; } @@ -669,9 +696,10 @@ void ModListViewActions::sendModsToFirstConflict(const QModelIndexList& indexes) } std::set priorities; - std::transform(conflicts.begin(), conflicts.end(), std::inserter(priorities, priorities.end()), [=](auto index) { - return m_core.currentProfile()->getModPriority(index); - }); + std::transform(conflicts.begin(), conflicts.end(), + std::inserter(priorities, priorities.end()), [=](auto index) { + return m_core.currentProfile()->getModPriority(index); + }); if (!priorities.empty()) { m_core.modList()->changeModsPriority(indexes, *priorities.begin()); @@ -687,13 +715,15 @@ void ModListViewActions::sendModsToLastConflict(const QModelIndexList& indexes) continue; } auto info = ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt()); - conflicts.insert(info->getModOverwritten().begin(), info->getModOverwritten().end()); + conflicts.insert(info->getModOverwritten().begin(), + info->getModOverwritten().end()); } std::set priorities; - std::transform(conflicts.begin(), conflicts.end(), std::inserter(priorities, priorities.end()), [=](auto index) { - return m_core.currentProfile()->getModPriority(index); - }); + std::transform(conflicts.begin(), conflicts.end(), + std::inserter(priorities, priorities.end()), [=](auto index) { + return m_core.currentProfile()->getModPriority(index); + }); if (!priorities.empty()) { m_core.modList()->changeModsPriority(indexes, *priorities.rbegin()); @@ -704,8 +734,7 @@ void ModListViewActions::renameMod(const QModelIndex& index) const { try { m_view->edit(m_view->indexModelToView(index)); - } - catch (const std::exception& e) { + } catch (const std::exception& e) { reportError(tr("failed to rename mod: %1").arg(e.what())); } } @@ -732,32 +761,33 @@ void ModListViewActions::removeMods(const QModelIndexList& indices) const if (i < max_items) { mods += "
    • " + name + "
    • "; - } - else if (i == max_items) { + } else if (i == max_items) { mods += "
    • ...
    • "; } - modNames.append(ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt())->name()); + modNames.append( + ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt())->name()); ++i; } - if (QMessageBox::question(m_parent, tr("Confirm"), - tr("Remove the following mods?
        %1
      ").arg(mods), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - // use mod names instead of indexes because those become invalid during the removal + if (QMessageBox::question( + m_parent, tr("Confirm"), + tr("Remove the following mods?
        %1
      ").arg(mods), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + // use mod names instead of indexes because those become invalid during the + // removal DownloadManager::startDisableDirWatcher(); for (QString name : modNames) { m_core.modList()->removeRowForce(ModInfo::getIndex(name), QModelIndex()); } DownloadManager::endDisableDirWatcher(); } - } - else if (!indices.isEmpty()) { - m_core.modList()->removeRow(indices[0].data(ModList::IndexRole).toInt(), QModelIndex()); + } else if (!indices.isEmpty()) { + m_core.modList()->removeRow(indices[0].data(ModList::IndexRole).toInt(), + QModelIndex()); } m_view->updateModCount(); m_pluginView->updatePluginCount(); - } - catch (const std::exception& e) { + } catch (const std::exception& e) { reportError(tr("failed to remove mod: %1").arg(e.what())); } } @@ -765,36 +795,44 @@ void ModListViewActions::removeMods(const QModelIndexList& indices) const void ModListViewActions::ignoreMissingData(const QModelIndexList& indices) const { for (auto& idx : indices) { - int row_idx = idx.data(ModList::IndexRole).toInt(); + int row_idx = idx.data(ModList::IndexRole).toInt(); ModInfo::Ptr info = ModInfo::getByIndex(row_idx); info->markValidated(true); m_core.modList()->notifyChange(row_idx); } } -void ModListViewActions::setIgnoreUpdate(const QModelIndexList& indices, bool ignore) const +void ModListViewActions::setIgnoreUpdate(const QModelIndexList& indices, + bool ignore) const { for (auto& idx : indices) { - int modIdx = idx.data(ModList::IndexRole).toInt(); + int modIdx = idx.data(ModList::IndexRole).toInt(); ModInfo::Ptr info = ModInfo::getByIndex(modIdx); info->ignoreUpdate(ignore); m_core.modList()->notifyChange(modIdx); } } -void ModListViewActions::changeVersioningScheme(const QModelIndex& index) const { - if (QMessageBox::question(m_parent, tr("Continue?"), - 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) { +void ModListViewActions::changeVersioningScheme(const QModelIndex& index) const +{ + if (QMessageBox::question( + m_parent, tr("Continue?"), + 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(index.data(ModList::IndexRole).toInt()); bool success = false; - static VersionInfo::VersionScheme schemes[] = { VersionInfo::SCHEME_REGULAR, VersionInfo::SCHEME_DECIMALMARK, VersionInfo::SCHEME_NUMBERSANDLETTERS }; + 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) { + for (int i = 0; + i < sizeof(schemes) / sizeof(VersionInfo::VersionScheme) && !success; ++i) { VersionInfo verOld(info->version().canonicalString(), schemes[i]); VersionInfo verNew(info->newestVersion().canonicalString(), schemes[i]); if (verOld < verNew) { @@ -804,9 +842,12 @@ void ModListViewActions::changeVersioningScheme(const QModelIndex& index) const } } if (!success) { - QMessageBox::information(m_parent, tr("Sorry"), - tr("I don't know a versioning scheme where %1 is newer than %2.").arg(info->newestVersion().canonicalString()).arg(info->version().canonicalString()), - QMessageBox::Ok); + QMessageBox::information( + m_parent, tr("Sorry"), + tr("I don't know a versioning scheme where %1 is newer than %2.") + .arg(info->newestVersion().canonicalString()) + .arg(info->version().canonicalString()), + QMessageBox::Ok); } } } @@ -814,7 +855,7 @@ void ModListViewActions::changeVersioningScheme(const QModelIndex& index) const void ModListViewActions::markConverted(const QModelIndexList& indices) const { for (auto& idx : indices) { - int modIdx = idx.data(ModList::IndexRole).toInt(); + int modIdx = idx.data(ModList::IndexRole).toInt(); ModInfo::Ptr info = ModInfo::getByIndex(modIdx); info->markConverted(true); m_core.modList()->notifyChange(modIdx); @@ -825,20 +866,21 @@ void ModListViewActions::visitOnNexus(const QModelIndexList& indices) const { if (indices.size() > 10) { if (QMessageBox::question(m_parent, tr("Opening Nexus Links"), - tr("You are trying to open %1 links to Nexus Mods. Are you sure you want to do this?").arg(indices.size()), - QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { + tr("You are trying to open %1 links to Nexus Mods. Are " + "you sure you want to do this?") + .arg(indices.size()), + QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { return; } } for (auto& idx : indices) { ModInfo::Ptr info = ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt()); - int modID = info->nexusId(); - QString gameName = info->gameName(); + int modID = info->nexusId(); + QString gameName = info->gameName(); if (modID > 0) { shell::Open(QUrl(NexusInterface::instance().getModURL(modID, gameName))); - } - else { + } else { log::error("mod '{}' has no nexus id", info->name()); } } @@ -848,8 +890,10 @@ void ModListViewActions::visitWebPage(const QModelIndexList& indices) const { if (indices.size() > 10) { if (QMessageBox::question(m_parent, tr("Opening Web Pages"), - tr("You are trying to open %1 Web Pages. Are you sure you want to do this?").arg(indices.size()), - QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { + tr("You are trying to open %1 Web Pages. Are you sure " + "you want to do this?") + .arg(indices.size()), + QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { return; } } @@ -868,8 +912,10 @@ void ModListViewActions::visitNexusOrWebPage(const QModelIndexList& indices) con { if (indices.size() > 10) { if (QMessageBox::question(m_parent, tr("Opening Web Pages"), - tr("You are trying to open %1 Web Pages. Are you sure you want to do this?").arg(indices.size()), - QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { + tr("You are trying to open %1 Web Pages. Are you sure " + "you want to do this?") + .arg(indices.size()), + QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { return; } } @@ -881,17 +927,15 @@ void ModListViewActions::visitNexusOrWebPage(const QModelIndexList& indices) con continue; } - int modID = info->nexusId(); + int modID = info->nexusId(); QString gameName = info->gameName(); - const auto url = info->parseCustomURL(); + const auto url = info->parseCustomURL(); if (modID > 0) { shell::Open(QUrl(NexusInterface::instance().getModURL(modID, gameName))); - } - else if (url.isValid()) { + } else if (url.isValid()) { shell::Open(url); - } - else { + } else { log::error("mod '{}' has no valid link", info->name()); } } @@ -907,34 +951,34 @@ void ModListViewActions::reinstallMod(const QModelIndex& index) const if (fileInfo.isAbsolute()) { if (fileInfo.exists()) { fullInstallationFile = installationFile; + } else { + fullInstallationFile = + m_core.downloadManager()->getOutputDirectory() + "/" + fileInfo.fileName(); } - else { - fullInstallationFile = m_core.downloadManager()->getOutputDirectory() + "/" + fileInfo.fileName(); - } - } - else { - fullInstallationFile = m_core.downloadManager()->getOutputDirectory() + "/" + installationFile; + } else { + fullInstallationFile = + m_core.downloadManager()->getOutputDirectory() + "/" + installationFile; } if (QFile::exists(fullInstallationFile)) { m_core.installMod(fullInstallationFile, -1, true, modInfo, modInfo->name()); + } else { + QMessageBox::information(m_parent, tr("Failed"), + tr("Installation file no longer exists")); } - else { - QMessageBox::information(m_parent, tr("Failed"), tr("Installation file no longer exists")); - } - } - else { - QMessageBox::information(m_parent, tr("Failed"), - tr("Mods installed with old versions of MO can't be reinstalled in this way.")); + } else { + QMessageBox::information( + m_parent, tr("Failed"), + tr("Mods installed with old versions of MO can't be reinstalled in this way.")); } } void ModListViewActions::createBackup(const QModelIndex& index) const { ModInfo::Ptr modInfo = ModInfo::getByIndex(index.data(ModList::IndexRole).toInt()); - QString backupDirectory = m_core.installationManager()->generateBackupName(modInfo->absolutePath()); + QString backupDirectory = + m_core.installationManager()->generateBackupName(modInfo->absolutePath()); if (!copyDir(modInfo->absolutePath(), backupDirectory, false)) { - QMessageBox::information(m_parent, tr("Failed"), - tr("Failed to create backup.")); + QMessageBox::information(m_parent, tr("Failed"), tr("Failed to create backup.")); } m_core.refresh(); m_view->updateModCount(); @@ -958,10 +1002,11 @@ void ModListViewActions::restoreHiddenFiles(const QModelIndexList& indices) cons for (auto& idx : indices) { ModInfo::Ptr modInfo = ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt()); - const auto flags = modInfo->getFlags(); + const auto flags = modInfo->getFlags(); if (!modInfo->isRegular() || - std::find(flags.begin(), flags.end(), ModInfo::FLAG_HIDDEN_FILES) == flags.end()) { + std::find(flags.begin(), flags.end(), ModInfo::FLAG_HIDDEN_FILES) == + flags.end()) { continue; } @@ -973,16 +1018,20 @@ void ModListViewActions::restoreHiddenFiles(const QModelIndexList& indices) cons mods += "
    • ...
    • "; } - if (QMessageBox::question(m_parent, tr("Confirm"), - tr("Restore all hidden files in the following mods?
        %1
      ").arg(mods), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + if (QMessageBox::question( + m_parent, tr("Confirm"), + tr("Restore all hidden files in the following mods?
        %1
      ") + .arg(mods), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { for (auto& idx : indices) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt()); + ModInfo::Ptr modInfo = + ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt()); const auto flags = modInfo->getFlags(); - if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_HIDDEN_FILES) != flags.end()) { + if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_HIDDEN_FILES) != + flags.end()) { const QString modDir = modInfo->absolutePath(); auto partialResult = restoreHiddenFilesRecursive(renamer, modDir); @@ -992,30 +1041,33 @@ void ModListViewActions::restoreHiddenFiles(const QModelIndexList& indices) cons break; } emit originModified((m_core.directoryStructure()->getOriginByName( - ToWString(modInfo->internalName()))).getID()); + ToWString(modInfo->internalName()))) + .getID()); } } } } else if (!indices.isEmpty()) { - //single selection - ModInfo::Ptr modInfo = ModInfo::getByIndex(indices[0].data(ModList::IndexRole).toInt()); + // single selection + ModInfo::Ptr modInfo = + ModInfo::getByIndex(indices[0].data(ModList::IndexRole).toInt()); const QString modDir = modInfo->absolutePath(); - if (QMessageBox::question(m_parent, tr("Are you sure?"), - tr("About to restore all hidden files in:\n") + modInfo->name(), - QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok) { + if (QMessageBox::question( + m_parent, tr("Are you sure?"), + tr("About to restore all hidden files in:\n") + modInfo->name(), + QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok) { result = restoreHiddenFilesRecursive(renamer, modDir); emit originModified((m_core.directoryStructure()->getOriginByName( - ToWString(modInfo->internalName()))).getID()); + ToWString(modInfo->internalName()))) + .getID()); } } if (result == FileRenamer::RESULT_CANCEL) { log::debug("Restoring hidden files operation cancelled"); - } - else { + } else { log::debug("Finished restoring hidden files"); } } @@ -1029,11 +1081,13 @@ void ModListViewActions::setTracked(const QModelIndexList& indices, bool tracked }); } -void ModListViewActions::setEndorsed(const QModelIndexList& indices, bool endorsed) const +void ModListViewActions::setEndorsed(const QModelIndexList& indices, + bool endorsed) const { m_core.loggedInAction(m_parent, [=] { if (indices.size() > 1) { - MessageDialog::showMessage(tr("Endorsing multiple mods will take a while. Please wait..."), m_parent); + MessageDialog::showMessage( + tr("Endorsing multiple mods will take a while. Please wait..."), m_parent); } for (auto& idx : indices) { @@ -1064,9 +1118,10 @@ void ModListViewActions::remapCategory(const QModelIndexList& indices) const } } -void ModListViewActions::setColor(const QModelIndexList& indices, const QModelIndex& refIndex) const +void ModListViewActions::setColor(const QModelIndexList& indices, + const QModelIndex& refIndex) const { - auto& settings = m_core.settings(); + auto& settings = m_core.settings(); ModInfo::Ptr modInfo = ModInfo::getByIndex(refIndex.data(ModList::IndexRole).toInt()); QColorDialog dialog(m_parent); @@ -1075,8 +1130,7 @@ void ModListViewActions::setColor(const QModelIndexList& indices, const QModelIn QColor currentColor = modInfo->color(); if (currentColor.isValid()) { dialog.setCurrentColor(currentColor); - } - else if (auto c = settings.colors().previousSeparatorColor()) { + } else if (auto c = settings.colors().previousSeparatorColor()) { dialog.setCurrentColor(*c); } @@ -1093,7 +1147,6 @@ void ModListViewActions::setColor(const QModelIndexList& indices, const QModelIn ModInfo::Ptr info = ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt()); info->setColor(currentColor); } - } void ModListViewActions::resetColor(const QModelIndexList& indices) const @@ -1105,14 +1158,17 @@ void ModListViewActions::resetColor(const QModelIndexList& indices) const m_core.settings().colors().removePreviousSeparatorColor(); } -void ModListViewActions::setCategories(ModInfo::Ptr mod, const std::vector>& categories) const +void ModListViewActions::setCategories( + ModInfo::Ptr mod, const std::vector>& categories) const { for (auto& [id, enabled] : categories) { mod->setCategory(id, enabled); } } -void ModListViewActions::setCategoriesIf(ModInfo::Ptr mod, ModInfo::Ptr ref, const std::vector>& categories) const +void ModListViewActions::setCategoriesIf( + ModInfo::Ptr mod, ModInfo::Ptr ref, + const std::vector>& categories) const { for (auto& [id, enabled] : categories) { if (ref->categorySet(id) != enabled) { @@ -1121,21 +1177,20 @@ void ModListViewActions::setCategoriesIf(ModInfo::Ptr mod, ModInfo::Ptr ref, con } } -void ModListViewActions::setCategories(const QModelIndexList& selected, const QModelIndex& ref, - const std::vector>& categories) const +void ModListViewActions::setCategories( + const QModelIndexList& selected, const QModelIndex& ref, + const std::vector>& categories) const { ModInfo::Ptr refMod = ModInfo::getByIndex(ref.data(ModList::IndexRole).toInt()); if (selected.size() > 1) { for (auto& idx : selected) { if (idx.row() != ref.row()) { - setCategoriesIf( - ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt()), - refMod, categories); + setCategoriesIf(ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt()), + refMod, categories); } } setCategories(refMod, categories); - } - else if (!selected.isEmpty()) { + } else if (!selected.isEmpty()) { // for single mod selections, just do a replace setCategories(refMod, categories); } @@ -1147,11 +1202,13 @@ void ModListViewActions::setCategories(const QModelIndexList& selected, const QM // reset the selection manually - still needed auto viewIndices = m_view->indexModelToView(selected); for (auto& idx : viewIndices) { - m_view->selectionModel()->select(idx, QItemSelectionModel::Select | QItemSelectionModel::Rows); + m_view->selectionModel()->select(idx, QItemSelectionModel::Select | + QItemSelectionModel::Rows); } } -void ModListViewActions::setPrimaryCategory(const QModelIndexList& selected, int category, bool force) +void ModListViewActions::setPrimaryCategory(const QModelIndexList& selected, + int category, bool force) { for (auto& idx : selected) { ModInfo::Ptr info = ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt()); @@ -1164,7 +1221,8 @@ void ModListViewActions::setPrimaryCategory(const QModelIndexList& selected, int // reset the selection manually - still needed auto viewIndices = m_view->indexModelToView(selected); for (auto& idx : viewIndices) { - m_view->selectionModel()->select(idx, QItemSelectionModel::Select | QItemSelectionModel::Rows); + m_view->selectionModel()->select(idx, QItemSelectionModel::Select | + QItemSelectionModel::Rows); } } @@ -1182,21 +1240,26 @@ void ModListViewActions::restoreBackup(const QModelIndex& index) const { QRegularExpression backupRegEx("(.*)_backup[0-9]*$"); ModInfo::Ptr modInfo = ModInfo::getByIndex(index.data(ModList::IndexRole).toInt()); - auto match = backupRegEx.match(modInfo->name()); + auto match = backupRegEx.match(modInfo->name()); if (match.hasMatch()) { QString regName = match.captured(1); QDir modDir(QDir::fromNativeSeparators(m_core.settings().paths().mods())); if (!modDir.exists(regName) || - (QMessageBox::question(m_parent, tr("Overwrite?"), - tr("This will replace the existing mod \"%1\". Continue?").arg(regName), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes)) { - if (modDir.exists(regName) && !shellDelete(QStringList(modDir.absoluteFilePath(regName)))) { + (QMessageBox::question( + m_parent, tr("Overwrite?"), + tr("This will replace the existing mod \"%1\". Continue?").arg(regName), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes)) { + if (modDir.exists(regName) && + !shellDelete(QStringList(modDir.absoluteFilePath(regName)))) { reportError(tr("failed to remove mod \"%1\"").arg(regName)); - } - else { - QString destinationPath = QDir::fromNativeSeparators(m_core.settings().paths().mods()) + "/" + regName; + } else { + QString destinationPath = + QDir::fromNativeSeparators(m_core.settings().paths().mods()) + "/" + + regName; if (!modDir.rename(modInfo->absolutePath(), destinationPath)) { - reportError(tr("failed to rename \"%1\" to \"%2\"").arg(modInfo->absolutePath()).arg(destinationPath)); + reportError(tr("failed to rename \"%1\" to \"%2\"") + .arg(modInfo->absolutePath()) + .arg(destinationPath)); } m_core.refresh(); m_view->updateModCount(); @@ -1208,13 +1271,13 @@ void ModListViewActions::restoreBackup(const QModelIndex& index) const void ModListViewActions::moveOverwriteContentsTo(const QString& absolutePath) const { ModInfo::Ptr overwriteInfo = ModInfo::getOverwrite(); - bool successful = shellMove((QDir::toNativeSeparators(overwriteInfo->absolutePath()) + "\\*"), - (QDir::toNativeSeparators(absolutePath)), false, m_parent); + bool successful = + shellMove((QDir::toNativeSeparators(overwriteInfo->absolutePath()) + "\\*"), + (QDir::toNativeSeparators(absolutePath)), false, m_parent); if (successful) { MessageDialog::showMessage(tr("Move successful."), m_parent); - } - else { + } else { const auto e = GetLastError(); log::error("Move operation failed: {}", formatSystemMessage(e)); } @@ -1229,10 +1292,13 @@ void ModListViewActions::createModFromOverwrite() const while (name->isEmpty()) { bool ok; - name.update(QInputDialog::getText(m_parent, tr("Create Mod..."), - tr("This will move all files from overwrite into a new, regular mod.\n" - "Please enter a name:"), QLineEdit::Normal, "", &ok), - GUESS_USER); + name.update( + QInputDialog::getText( + m_parent, tr("Create Mod..."), + tr("This will move all files from overwrite into a new, regular mod.\n" + "Please enter a name:"), + QLineEdit::Normal, "", &ok), + GUESS_USER); if (!ok) { return; } @@ -1277,7 +1343,7 @@ void ModListViewActions::moveOverwriteContentToExistingMod() const for (const auto& mod : m_core.modList()->allModsByProfilePriority()) { if (result.compare(mod) == 0) { ModInfo::Ptr modInfo = ModInfo::getByIndex(ModInfo::getIndex(mod)); - modAbsolutePath = modInfo->absolutePath(); + modAbsolutePath = modInfo->absolutePath(); break; } } @@ -1295,21 +1361,20 @@ void ModListViewActions::moveOverwriteContentToExistingMod() const void ModListViewActions::clearOverwrite() const { ModInfo::Ptr modInfo = ModInfo::getOverwrite(); - if (modInfo) - { + if (modInfo) { QDir overwriteDir(modInfo->absolutePath()); - if (QMessageBox::question(m_parent, tr("Are you sure?"), - tr("About to recursively delete:\n") + overwriteDir.absolutePath(), - QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok) - { + if (QMessageBox::question( + m_parent, tr("Are you sure?"), + tr("About to recursively delete:\n") + overwriteDir.absolutePath(), + QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok) { QStringList delList; - for (auto f : overwriteDir.entryList(QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot)) + for (auto f : + overwriteDir.entryList(QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot)) delList.push_back(overwriteDir.absoluteFilePath(f)); if (shellDelete(delList, true)) { emit overwriteCleared(); m_core.refresh(); - } - else { + } else { const auto e = GetLastError(); log::error("Delete operation failed: {}", formatSystemMessage(e)); } diff --git a/src/modlistviewactions.h b/src/modlistviewactions.h index 5a2d842a8..ad20e7840 100644 --- a/src/modlistviewactions.h +++ b/src/modlistviewactions.h @@ -20,17 +20,12 @@ class ModListViewActions : public QObject Q_OBJECT public: - // currently passing the main window itself because a lots of stuff needs it but // it would be nice to avoid passing it at some point // - ModListViewActions( - OrganizerCore& core, - FilterList& filters, - CategoryFactory& categoryFactory, - ModListView* view, - PluginListView* pluginView, - QObject* nxmReceiver); + ModListViewActions(OrganizerCore& core, FilterList& filters, + CategoryFactory& categoryFactory, ModListView* view, + PluginListView* pluginView, QObject* nxmReceiver); // install mod, create an empty mod or a separator with at priority based on the // index and the current sorting of the view @@ -45,7 +40,8 @@ class ModListViewActions : public QObject // if the archivePath is empty for installMod, ask the user for the archive // to install // - void installMod(const QString& archivePath = "", const QModelIndex& index = QModelIndex()) const; + void installMod(const QString& archivePath = "", + const QModelIndex& index = QModelIndex()) const; void createEmptyMod(const QModelIndex& index = QModelIndex()) const; void createSeparator(const QModelIndex& index = QModelIndex()) const; @@ -68,11 +64,15 @@ class ModListViewActions : public QObject // display mod information // - void displayModInformation(const QString& modName, ModInfoTabIDs tabID = ModInfoTabIDs::None) const; - void displayModInformation(unsigned int index, ModInfoTabIDs tab = ModInfoTabIDs::None) const; - void displayModInformation(ModInfo::Ptr modInfo, unsigned int modIndex, ModInfoTabIDs tabID = ModInfoTabIDs::None) const; + void displayModInformation(const QString& modName, + ModInfoTabIDs tabID = ModInfoTabIDs::None) const; + void displayModInformation(unsigned int index, + ModInfoTabIDs tab = ModInfoTabIDs::None) const; + void displayModInformation(ModInfo::Ptr modInfo, unsigned int modIndex, + ModInfoTabIDs tabID = ModInfoTabIDs::None) const; - // move mods to top/bottom, start the "Send to priority" and "Send to separator" dialog + // move mods to top/bottom, start the "Send to priority" and "Send to separator" + // dialog // void sendModsToTop(const QModelIndexList& index) const; void sendModsToBottom(const QModelIndexList& index) const; @@ -112,13 +112,14 @@ class ModListViewActions : public QObject // on the reference is different // void setCategories(const QModelIndexList& selected, const QModelIndex& ref, - const std::vector>& categories) const; + const std::vector>& categories) const; // set the primary category of the mods in the given list, adding the appropriate // category to the mods unless force is false, in which case the primary category // is set only on mods that have this category // - void setPrimaryCategory(const QModelIndexList& selected, int category, bool force = true); + void setPrimaryCategory(const QModelIndexList& selected, int category, + bool force = true); // open the Windows explorer for the specified mods // @@ -149,7 +150,6 @@ class ModListViewActions : public QObject void modInfoDisplayed() const; private: - // find the priority where to install or create a mod for the // given index (e.g. above, below, inside separator or at the end) // @@ -161,19 +161,20 @@ class ModListViewActions : public QObject // set the category of the given mod based on the given array // - void setCategories(ModInfo::Ptr mod, const std::vector>& categories) const; + void setCategories(ModInfo::Ptr mod, + const std::vector>& categories) const; - // set the category of the given mod if the category from the reference mod does not match - // the one in the array of categories + // set the category of the given mod if the category from the reference mod does not + // match the one in the array of categories // - void setCategoriesIf(ModInfo::Ptr mod, ModInfo::Ptr ref, const std::vector>& categories) const; + void setCategoriesIf(ModInfo::Ptr mod, ModInfo::Ptr ref, + const std::vector>& categories) const; // check the given mods from update, the map should map game names to nexus ID // void checkModsForUpdates(std::multimap const& IDs) const; private: - OrganizerCore& m_core; FilterList& m_filters; CategoryFactory& m_categories; diff --git a/src/moshortcut.cpp b/src/moshortcut.cpp index 0f5ae74e9..76caf0f95 100644 --- a/src/moshortcut.cpp +++ b/src/moshortcut.cpp @@ -1,22 +1,20 @@ #include "moshortcut.h" MOShortcut::MOShortcut(const QString& link) - : m_valid(link.startsWith("moshortcut://")) - , m_hasInstance(false) - , m_hasExecutable(false) + : m_valid(link.startsWith("moshortcut://")), m_hasInstance(false), + m_hasExecutable(false) { if (m_valid) { int start = (int)strlen("moshortcut://"); - int sep = link.indexOf(':', start); + int sep = link.indexOf(':', start); if (sep >= 0) { m_hasInstance = true; - m_instance = link.mid(start, sep - start); - m_executable = link.mid(sep + 1); - } - else + m_instance = link.mid(start, sep - start); + m_executable = link.mid(sep + 1); + } else m_executable = link.mid(start); - if(!(m_executable=="")) - m_hasExecutable=true; + if (!(m_executable == "")) + m_hasExecutable = true; } } diff --git a/src/moshortcut.h b/src/moshortcut.h index a6cf7fb52..2bfd1a4f6 100644 --- a/src/moshortcut.h +++ b/src/moshortcut.h @@ -1,13 +1,13 @@ #ifndef MODORGANIZER_MOSHORTCUT_INCLUDED #define MODORGANIZER_MOSHORTCUT_INCLUDED -#include #include "instancemanager.h" +#include class MOShortcut { public: - MOShortcut(const QString& link={}); + MOShortcut(const QString& link = {}); // true if initialized using a valid moshortcut link // diff --git a/src/motddialog.cpp b/src/motddialog.cpp index eee80205d..979649cb3 100644 --- a/src/motddialog.cpp +++ b/src/motddialog.cpp @@ -1,51 +1,51 @@ -/* -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 "motddialog.h" -#include "bbcode.h" -#include "utility.h" -#include "ui_motddialog.h" -#include "organizercore.h" -#include -#include - -using namespace MOBase; - -MotDDialog::MotDDialog(const QString &message, QWidget *parent) - : QDialog(parent), ui(new Ui::MotDDialog) -{ - ui->setupUi(this); - ui->motdView->setHtml(BBCode::convertToHTML(message)); - connect(ui->motdView, SIGNAL(anchorClicked(QUrl)), this, SLOT(linkClicked(QUrl))); -} - -MotDDialog::~MotDDialog() -{ - delete ui; -} - -void MotDDialog::on_okButton_clicked() -{ - this->close(); -} - -void MotDDialog::linkClicked(const QUrl &url) -{ - shell::Open(url); -} +/* +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 "motddialog.h" +#include "bbcode.h" +#include "organizercore.h" +#include "ui_motddialog.h" +#include "utility.h" +#include +#include + +using namespace MOBase; + +MotDDialog::MotDDialog(const QString& message, QWidget* parent) + : QDialog(parent), ui(new Ui::MotDDialog) +{ + ui->setupUi(this); + ui->motdView->setHtml(BBCode::convertToHTML(message)); + connect(ui->motdView, SIGNAL(anchorClicked(QUrl)), this, SLOT(linkClicked(QUrl))); +} + +MotDDialog::~MotDDialog() +{ + delete ui; +} + +void MotDDialog::on_okButton_clicked() +{ + this->close(); +} + +void MotDDialog::linkClicked(const QUrl& url) +{ + shell::Open(url); +} diff --git a/src/motddialog.h b/src/motddialog.h index 5791321f4..4e0175d3f 100644 --- a/src/motddialog.h +++ b/src/motddialog.h @@ -1,46 +1,47 @@ -/* -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 MOTDDIALOG_H -#define MOTDDIALOG_H - -#include -#include - -namespace Ui { -class MotDDialog; -} - -class MotDDialog : public QDialog -{ - Q_OBJECT - -public: - explicit MotDDialog(const QString &message, QWidget *parent = 0); - ~MotDDialog(); - -private slots: - void on_okButton_clicked(); - void linkClicked(const QUrl &url); - -private: - Ui::MotDDialog *ui; -}; - -#endif // MOTDDIALOG_H +/* +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 MOTDDIALOG_H +#define MOTDDIALOG_H + +#include +#include + +namespace Ui +{ +class MotDDialog; +} + +class MotDDialog : public QDialog +{ + Q_OBJECT + +public: + explicit MotDDialog(const QString& message, QWidget* parent = 0); + ~MotDDialog(); + +private slots: + void on_okButton_clicked(); + void linkClicked(const QUrl& url); + +private: + Ui::MotDDialog* ui; +}; + +#endif // MOTDDIALOG_H diff --git a/src/multiprocess.cpp b/src/multiprocess.cpp index 4ce587186..fb7f8d4d4 100644 --- a/src/multiprocess.cpp +++ b/src/multiprocess.cpp @@ -1,102 +1,104 @@ -#include "multiprocess.h" -#include "utility.h" -#include -#include -#include - -static const char s_Key[] = "mo-43d1a3ad-eeb0-4818-97c9-eda5216c29b5"; -static const int s_Timeout = 5000; - -using MOBase::reportError; - -MOMultiProcess::MOMultiProcess(bool allowMultiple, QObject *parent) : - QObject(parent), m_Ephemeral(false), m_OwnsSM(false) -{ - m_SharedMem.setKey(s_Key); - - if (!m_SharedMem.create(1)) { - if (m_SharedMem.error() == QSharedMemory::AlreadyExists) { - if (!allowMultiple) { - m_SharedMem.attach(); - m_Ephemeral = true; - } - } - - if ((m_SharedMem.error() != QSharedMemory::NoError) && - (m_SharedMem.error() != QSharedMemory::AlreadyExists)) { - throw MOBase::MyException(tr("SHM error: %1").arg(m_SharedMem.errorString())); - } - } else { - m_OwnsSM = true; - } - - if (m_OwnsSM) { - connect(&m_Server, SIGNAL(newConnection()), this, SLOT(receiveMessage()), Qt::QueuedConnection); - // has to be called before listen - m_Server.setSocketOptions(QLocalServer::WorldAccessOption); - m_Server.listen(s_Key); - } -} - - -void MOMultiProcess::sendMessage(const QString &message) -{ - if (m_OwnsSM) { - // nobody there to receive the message - return; - } - QLocalSocket socket(this); - - bool connected = false; - for(int i = 0; i < 2 && !connected; ++i) { - if (i > 0) { - Sleep(250); - } - - // other process may be just starting up - socket.connectToServer(s_Key, QIODevice::WriteOnly); - connected = socket.waitForConnected(s_Timeout); - } - - if (!connected) { - reportError(tr("failed to connect to running process: %1").arg(socket.errorString())); - return; - } - - socket.write(message.toUtf8()); - if (!socket.waitForBytesWritten(s_Timeout)) { - reportError(tr("failed to communicate with running process: %1").arg(socket.errorString())); - return; - } - - socket.disconnectFromServer(); - socket.waitForDisconnected(); -} - -void MOMultiProcess::receiveMessage() -{ - QLocalSocket *socket = m_Server.nextPendingConnection(); - if (!socket) { - return; - } - - if (!socket->waitForReadyRead(s_Timeout)) { - // check if there are bytes available; if so, it probably means the data was - // already received by the time waitForReadyRead() was called and the - // connection has been closed - const auto av = socket->bytesAvailable(); - - if (av <= 0) { - MOBase::log::error( - "failed to receive data from secondary process: {}", - socket->errorString()); - - reportError(tr("failed to receive data from secondary process: %1").arg(socket->errorString())); - return; - } - } - - QString message = QString::fromUtf8(socket->readAll().constData()); - emit messageSent(message); - socket->disconnectFromServer(); -} +#include "multiprocess.h" +#include "utility.h" +#include +#include +#include + +static const char s_Key[] = "mo-43d1a3ad-eeb0-4818-97c9-eda5216c29b5"; +static const int s_Timeout = 5000; + +using MOBase::reportError; + +MOMultiProcess::MOMultiProcess(bool allowMultiple, QObject* parent) + : QObject(parent), m_Ephemeral(false), m_OwnsSM(false) +{ + m_SharedMem.setKey(s_Key); + + if (!m_SharedMem.create(1)) { + if (m_SharedMem.error() == QSharedMemory::AlreadyExists) { + if (!allowMultiple) { + m_SharedMem.attach(); + m_Ephemeral = true; + } + } + + if ((m_SharedMem.error() != QSharedMemory::NoError) && + (m_SharedMem.error() != QSharedMemory::AlreadyExists)) { + throw MOBase::MyException(tr("SHM error: %1").arg(m_SharedMem.errorString())); + } + } else { + m_OwnsSM = true; + } + + if (m_OwnsSM) { + connect(&m_Server, SIGNAL(newConnection()), this, SLOT(receiveMessage()), + Qt::QueuedConnection); + // has to be called before listen + m_Server.setSocketOptions(QLocalServer::WorldAccessOption); + m_Server.listen(s_Key); + } +} + +void MOMultiProcess::sendMessage(const QString& message) +{ + if (m_OwnsSM) { + // nobody there to receive the message + return; + } + QLocalSocket socket(this); + + bool connected = false; + for (int i = 0; i < 2 && !connected; ++i) { + if (i > 0) { + Sleep(250); + } + + // other process may be just starting up + socket.connectToServer(s_Key, QIODevice::WriteOnly); + connected = socket.waitForConnected(s_Timeout); + } + + if (!connected) { + reportError( + tr("failed to connect to running process: %1").arg(socket.errorString())); + return; + } + + socket.write(message.toUtf8()); + if (!socket.waitForBytesWritten(s_Timeout)) { + reportError( + tr("failed to communicate with running process: %1").arg(socket.errorString())); + return; + } + + socket.disconnectFromServer(); + socket.waitForDisconnected(); +} + +void MOMultiProcess::receiveMessage() +{ + QLocalSocket* socket = m_Server.nextPendingConnection(); + if (!socket) { + return; + } + + if (!socket->waitForReadyRead(s_Timeout)) { + // check if there are bytes available; if so, it probably means the data was + // already received by the time waitForReadyRead() was called and the + // connection has been closed + const auto av = socket->bytesAvailable(); + + if (av <= 0) { + MOBase::log::error("failed to receive data from secondary process: {}", + socket->errorString()); + + reportError(tr("failed to receive data from secondary process: %1") + .arg(socket->errorString())); + return; + } + } + + QString message = QString::fromUtf8(socket->readAll().constData()); + emit messageSent(message); + socket->disconnectFromServer(); +} diff --git a/src/multiprocess.h b/src/multiprocess.h index 810e63d23..3c641faa9 100644 --- a/src/multiprocess.h +++ b/src/multiprocess.h @@ -1,70 +1,62 @@ -#ifndef MODORGANIZER_MOMULTIPROCESS_INCLUDED -#define MODORGANIZER_MOMULTIPROCESS_INCLUDED - -#include -#include -#include - - -/** - * used to ensure only a single process of Mod Organizer is started and to - * allow ephemeral processes to send messages to the primary (visible) one. - * This way, other processes can start a download in the primary one - **/ -class MOMultiProcess : public QObject -{ - Q_OBJECT - -public: - // `allowMultiple`: if another process is running, run this one - // disconnected from the shared memory - explicit MOMultiProcess(bool allowMultiple, QObject *parent = 0); - - /** - * @return true if this process's job is to forward data to the primary - * process through shared memory - **/ - bool ephemeral() const - { - return m_Ephemeral; - } - - // returns true if this is not the primary process, but was allowed because - // of the AllowMultiple flag - // - bool secondary() const - { - return !m_Ephemeral && !m_OwnsSM; - } - - /** - * send a message to the primary process. This can be used to transmit download urls - * - * @param message message to send - **/ - void sendMessage(const QString &message); - -signals: - - /** - * @brief emitted when an ephemeral process has sent a message (to us) - * - * @param message the message we received - **/ - void messageSent(const QString &message); - -public slots: - -private slots: - - void receiveMessage(); - -private: - bool m_Ephemeral; - bool m_OwnsSM; - QSharedMemory m_SharedMem; - QLocalServer m_Server; - -}; - -#endif // MODORGANIZER_MOMULTIPROCESS_INCLUDED +#ifndef MODORGANIZER_MOMULTIPROCESS_INCLUDED +#define MODORGANIZER_MOMULTIPROCESS_INCLUDED + +#include +#include +#include + +/** + * used to ensure only a single process of Mod Organizer is started and to + * allow ephemeral processes to send messages to the primary (visible) one. + * This way, other processes can start a download in the primary one + **/ +class MOMultiProcess : public QObject +{ + Q_OBJECT + +public: + // `allowMultiple`: if another process is running, run this one + // disconnected from the shared memory + explicit MOMultiProcess(bool allowMultiple, QObject* parent = 0); + + /** + * @return true if this process's job is to forward data to the primary + * process through shared memory + **/ + bool ephemeral() const { return m_Ephemeral; } + + // returns true if this is not the primary process, but was allowed because + // of the AllowMultiple flag + // + bool secondary() const { return !m_Ephemeral && !m_OwnsSM; } + + /** + * send a message to the primary process. This can be used to transmit download urls + * + * @param message message to send + **/ + void sendMessage(const QString& message); + +signals: + + /** + * @brief emitted when an ephemeral process has sent a message (to us) + * + * @param message the message we received + **/ + void messageSent(const QString& message); + +public slots: + +private slots: + + void receiveMessage(); + +private: + bool m_Ephemeral; + bool m_OwnsSM; + QSharedMemory m_SharedMem; + QLocalServer m_Server; +}; + +#endif // MODORGANIZER_MOMULTIPROCESS_INCLUDED diff --git a/src/nexusinterface.cpp b/src/nexusinterface.cpp index c87577489..45415afb6 100644 --- a/src/nexusinterface.cpp +++ b/src/nexusinterface.cpp @@ -19,72 +19,76 @@ along with Mod Organizer. If not, see . #include "nexusinterface.h" +#include "bbcode.h" #include "iplugingame.h" #include "nxmaccessmanager.h" #include "selectiondialog.h" -#include "bbcode.h" #include "settings.h" -#include #include "shared/util.h" #include #include +#include #include -#include #include +#include #include #include - using namespace MOBase; using namespace MOShared; - void throttledWarning(const APIUserAccount& user) { - log::error( - "You have fewer than {} requests remaining ({}). Only downloads and " - "login validation are being allowed.", - APIUserAccount::ThrottleThreshold, - user.remainingRequests()); + log::error("You have fewer than {} requests remaining ({}). Only downloads and " + "login validation are being allowed.", + APIUserAccount::ThrottleThreshold, user.remainingRequests()); } - -NexusBridge::NexusBridge(PluginContainer *pluginContainer, const QString &subModule) - : m_Interface(&NexusInterface::instance()) - , m_SubModule(subModule) -{ -} +NexusBridge::NexusBridge(PluginContainer* pluginContainer, const QString& subModule) + : m_Interface(&NexusInterface::instance()), m_SubModule(subModule) +{} void NexusBridge::requestDescription(QString gameName, int modID, QVariant userData) { - m_RequestIDs.insert(m_Interface->requestDescription(gameName, modID, this, userData, m_SubModule)); + m_RequestIDs.insert( + m_Interface->requestDescription(gameName, modID, this, userData, m_SubModule)); } void NexusBridge::requestFiles(QString gameName, int modID, QVariant userData) { - m_RequestIDs.insert(m_Interface->requestFiles(gameName, modID, this, userData, m_SubModule)); + m_RequestIDs.insert( + m_Interface->requestFiles(gameName, modID, this, userData, m_SubModule)); } -void NexusBridge::requestFileInfo(QString gameName, int modID, int fileID, QVariant userData) +void NexusBridge::requestFileInfo(QString gameName, int modID, int fileID, + QVariant userData) { - m_RequestIDs.insert(m_Interface->requestFileInfo(gameName, modID, fileID, this, userData, m_SubModule)); + m_RequestIDs.insert(m_Interface->requestFileInfo(gameName, modID, fileID, this, + userData, m_SubModule)); } -void NexusBridge::requestDownloadURL(QString gameName, int modID, int fileID, QVariant userData) +void NexusBridge::requestDownloadURL(QString gameName, int modID, int fileID, + QVariant userData) { - m_RequestIDs.insert(m_Interface->requestDownloadURL(gameName, modID, fileID, this, userData, m_SubModule)); + m_RequestIDs.insert(m_Interface->requestDownloadURL(gameName, modID, fileID, this, + userData, m_SubModule)); } -void NexusBridge::requestToggleEndorsement(QString gameName, int modID, QString modVersion, bool endorse, QVariant userData) +void NexusBridge::requestToggleEndorsement(QString gameName, int modID, + QString modVersion, bool endorse, + QVariant userData) { - m_RequestIDs.insert(m_Interface->requestToggleEndorsement(gameName, modID, modVersion, endorse, this, userData, m_SubModule)); + m_RequestIDs.insert(m_Interface->requestToggleEndorsement( + gameName, modID, modVersion, endorse, this, userData, m_SubModule)); } -void NexusBridge::requestToggleTracking(QString gameName, int modID, bool track, QVariant userData) +void NexusBridge::requestToggleTracking(QString gameName, int modID, bool track, + QVariant userData) { - m_RequestIDs.insert(m_Interface->requestToggleTracking(gameName, modID, track, this, userData, m_SubModule)); + m_RequestIDs.insert(m_Interface->requestToggleTracking(gameName, modID, track, this, + userData, m_SubModule)); } void NexusBridge::requestGameInfo(QString gameName, QVariant userData) @@ -92,7 +96,9 @@ void NexusBridge::requestGameInfo(QString gameName, QVariant userData) m_RequestIDs.insert(m_Interface->requestGameInfo(gameName, this, userData, m_SubModule)); } -void NexusBridge::nxmDescriptionAvailable(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID) +void NexusBridge::nxmDescriptionAvailable(QString gameName, int modID, + QVariant userData, QVariant resultData, + int requestID) { std::set::iterator iter = m_RequestIDs.find(requestID); if (iter != m_RequestIDs.end()) { @@ -102,7 +108,8 @@ void NexusBridge::nxmDescriptionAvailable(QString gameName, int modID, QVariant } } -void NexusBridge::nxmFilesAvailable(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID) +void NexusBridge::nxmFilesAvailable(QString gameName, int modID, QVariant userData, + QVariant resultData, int requestID) { std::set::iterator iter = m_RequestIDs.find(requestID); if (iter != m_RequestIDs.end()) { @@ -111,18 +118,18 @@ void NexusBridge::nxmFilesAvailable(QString gameName, int modID, QVariant userDa QList fileInfoList; QVariantMap resultInfo = resultData.toMap(); - QList resultList = resultInfo["files"].toList(); + QList resultList = resultInfo["files"].toList(); - for (const QVariant &file : resultList) { + for (const QVariant& file : resultList) { ModRepositoryFileInfo temp; QVariantMap fileInfo = file.toMap(); - temp.uri = fileInfo["file_name"].toString(); - temp.name = fileInfo["name"].toString(); - temp.description = BBCode::convertToHTML(fileInfo["description"].toString()); - temp.version = VersionInfo(fileInfo["version"].toString()); - temp.categoryID = fileInfo["category_id"].toInt(); - temp.fileID = fileInfo["file_id"].toInt(); - temp.fileSize = fileInfo["size"].toInt(); + temp.uri = fileInfo["file_name"].toString(); + temp.name = fileInfo["name"].toString(); + temp.description = BBCode::convertToHTML(fileInfo["description"].toString()); + temp.version = VersionInfo(fileInfo["version"].toString()); + temp.categoryID = fileInfo["category_id"].toInt(); + temp.fileID = fileInfo["file_id"].toInt(); + temp.fileSize = fileInfo["size"].toInt(); fileInfoList.append(&temp); } @@ -130,7 +137,9 @@ void NexusBridge::nxmFilesAvailable(QString gameName, int modID, QVariant userDa } } -void NexusBridge::nxmFileInfoAvailable(QString gameName, int modID, int fileID, QVariant userData, QVariant resultData, int requestID) +void NexusBridge::nxmFileInfoAvailable(QString gameName, int modID, int fileID, + QVariant userData, QVariant resultData, + int requestID) { std::set::iterator iter = m_RequestIDs.find(requestID); if (iter != m_RequestIDs.end()) { @@ -139,7 +148,9 @@ void NexusBridge::nxmFileInfoAvailable(QString gameName, int modID, int fileID, } } -void NexusBridge::nxmDownloadURLsAvailable(QString gameName, int modID, int fileID, QVariant userData, QVariant resultData, int requestID) +void NexusBridge::nxmDownloadURLsAvailable(QString gameName, int modID, int fileID, + QVariant userData, QVariant resultData, + int requestID) { std::set::iterator iter = m_RequestIDs.find(requestID); if (iter != m_RequestIDs.end()) { @@ -148,7 +159,8 @@ void NexusBridge::nxmDownloadURLsAvailable(QString gameName, int modID, int file } } -void NexusBridge::nxmEndorsementsAvailable(QVariant userData, QVariant resultData, int requestID) +void NexusBridge::nxmEndorsementsAvailable(QVariant userData, QVariant resultData, + int requestID) { std::set::iterator iter = m_RequestIDs.find(requestID); if (iter != m_RequestIDs.end()) { @@ -157,7 +169,8 @@ void NexusBridge::nxmEndorsementsAvailable(QVariant userData, QVariant resultDat } } -void NexusBridge::nxmEndorsementToggled(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID) +void NexusBridge::nxmEndorsementToggled(QString gameName, int modID, QVariant userData, + QVariant resultData, int requestID) { std::set::iterator iter = m_RequestIDs.find(requestID); if (iter != m_RequestIDs.end()) { @@ -166,7 +179,8 @@ void NexusBridge::nxmEndorsementToggled(QString gameName, int modID, QVariant us } } -void NexusBridge::nxmTrackedModsAvailable(QVariant userData, QVariant resultData, int requestID) +void NexusBridge::nxmTrackedModsAvailable(QVariant userData, QVariant resultData, + int requestID) { std::set::iterator iter = m_RequestIDs.find(requestID); if (iter != m_RequestIDs.end()) { @@ -175,7 +189,8 @@ void NexusBridge::nxmTrackedModsAvailable(QVariant userData, QVariant resultData } } -void NexusBridge::nxmTrackingToggled(QString gameName, int modID, QVariant userData, bool tracked, int requestID) +void NexusBridge::nxmTrackingToggled(QString gameName, int modID, QVariant userData, + bool tracked, int requestID) { std::set::iterator iter = m_RequestIDs.find(requestID); if (iter != m_RequestIDs.end()) { @@ -193,7 +208,9 @@ void NexusBridge::nxmGameInfoAvailable(QString gameName, QVariant userData, QVar } } -void NexusBridge::nxmRequestFailed(QString gameName, int modID, int fileID, QVariant userData, int requestID, int errorCode, const QString &errorMessage) +void NexusBridge::nxmRequestFailed(QString gameName, int modID, int fileID, + QVariant userData, int requestID, int errorCode, + const QString& errorMessage) { std::set::iterator iter = m_RequestIDs.find(requestID); if (iter != m_RequestIDs.end()) { @@ -202,21 +219,19 @@ void NexusBridge::nxmRequestFailed(QString gameName, int modID, int fileID, QVar } } - QAtomicInt NexusInterface::NXMRequestInfo::s_NextID(0); - APILimits NexusInterface::defaultAPILimits() { // https://app.swaggerhub.com/apis-docs/NexusMods/nexus-mods_public_api_params_in_form_data/1.0#/ - const int MaxDaily = 2500; + const int MaxDaily = 2500; const int MaxHourly = 100; APILimits limits; - limits.maxDailyRequests = MaxDaily; - limits.remainingDailyRequests = MaxDaily; - limits.maxHourlyRequests = MaxHourly; + limits.maxDailyRequests = MaxDaily; + limits.remainingDailyRequests = MaxDaily; + limits.maxHourlyRequests = MaxHourly; limits.remainingHourlyRequests = MaxHourly; return limits; @@ -227,8 +242,8 @@ APILimits NexusInterface::parseLimits(const QNetworkReply* reply) return parseLimits(reply->rawHeaderPairs()); } -APILimits NexusInterface::parseLimits( - const QList& headers) +APILimits +NexusInterface::parseLimits(const QList& headers) { APILimits limits; @@ -249,11 +264,9 @@ APILimits NexusInterface::parseLimits( return limits; } - static NexusInterface* g_instance = nullptr; -NexusInterface::NexusInterface(Settings* s) - : m_PluginContainer(nullptr) +NexusInterface::NexusInterface(Settings* s) : m_PluginContainer(nullptr) { MO_ASSERT(!g_instance); g_instance = this; @@ -261,12 +274,12 @@ NexusInterface::NexusInterface(Settings* s) m_User.limits(defaultAPILimits()); m_MOVersion = createVersionInfo(); - m_AccessManager = new NXMAccessManager( - this, s, m_MOVersion.displayString(3)); + m_AccessManager = new NXMAccessManager(this, s, m_MOVersion.displayString(3)); m_DiskCache = new QNetworkDiskCache(this); - connect(m_AccessManager, SIGNAL(requestNXMDownload(QString)), this, SLOT(downloadRequestedNXM(QString))); + connect(m_AccessManager, SIGNAL(requestNXMDownload(QString)), this, + SLOT(downloadRequestedNXM(QString))); } NexusInterface::~NexusInterface() @@ -277,7 +290,7 @@ NexusInterface::~NexusInterface() g_instance = nullptr; } -NXMAccessManager *NexusInterface::getAccessManager() +NXMAccessManager* NexusInterface::getAccessManager() { return m_AccessManager; } @@ -288,7 +301,7 @@ NexusInterface& NexusInterface::instance() return *g_instance; } -void NexusInterface::setCacheDirectory(const QString &directory) +void NexusInterface::setCacheDirectory(const QString& directory) { m_DiskCache->setCacheDirectory(directory); m_AccessManager->setCache(m_DiskCache); @@ -305,20 +318,22 @@ void NexusInterface::setUserAccount(const APIUserAccount& user) emit requestsChanged(getAPIStats(), m_User); } -void NexusInterface::interpretNexusFileName(const QString &fileName, QString &modName, int &modID, bool query) +void NexusInterface::interpretNexusFileName(const QString& fileName, QString& modName, + int& modID, bool query) { // guess the mod name from the file name - static const QRegularExpression complex(R"(^([a-zA-Z0-9_'"\-.() ]*?)([-_ ][VvRr]+[0-9]+(?:(?:[\.][0-9]+){0,2}|(?:[_][0-9]+){0,2}|(?:[-.][0-9]+){0,2})?[ab]?)??-([1-9][0-9]+)?-.*?\.(zip|rar|7z))"); - //complex regex explanation: - //group 1: modname. - //group 2: optional version, + static const QRegularExpression complex( + R"(^([a-zA-Z0-9_'"\-.() ]*?)([-_ ][VvRr]+[0-9]+(?:(?:[\.][0-9]+){0,2}|(?:[_][0-9]+){0,2}|(?:[-.][0-9]+){0,2})?[ab]?)??-([1-9][0-9]+)?-.*?\.(zip|rar|7z))"); + // complex regex explanation: + // group 1: modname. + // group 2: optional version, // assumed to start with v (empty most of the time). - //group 3: NexusId, + // group 3: NexusId, // assumed wrapped in "-", will miss single digit IDs for better accuracy. - //If no id is present the whole regex does not match. + // If no id is present the whole regex does not match. static const QRegularExpression simple(R"(^[^a-zA-Z]*([a-zA-Z_ ]+))"); auto complexMatch = complex.match(fileName); - auto simpleMatch = simple.match(fileName); + auto simpleMatch = simple.match(fileName); // assume not found modID = -1; @@ -328,20 +343,18 @@ void NexusInterface::interpretNexusFileName(const QString &fileName, QString &mo if (!query && !complexMatch.captured(3).isNull()) { modID = complexMatch.captured(3).toInt(); } - } - else if (simpleMatch.hasMatch()) { + } else if (simpleMatch.hasMatch()) { modName = simpleMatch.captured(0); - } - else { + } else { modName.clear(); } if (query) { SelectionDialog selection(tr("Please pick the mod ID for \"%1\"").arg(fileName)); - int index = 0; + int index = 0; auto splits = fileName.split(QRegularExpression("[^0-9]"), Qt::KeepEmptyParts); for (auto substr : splits) { - bool ok = false; + bool ok = false; int value = substr.toInt(&ok); if (ok) { QString highlight(fileName); @@ -359,22 +372,20 @@ void NexusInterface::interpretNexusFileName(const QString &fileName, QString &mo if (selection.numChoices() > 0) { if (selection.exec() == QDialog::Accepted) { auto choice = selection.getChoiceData().toStringList(); - modID = choice.at(0).toInt(); - modName = choice.at(1); - modName = modName.replace('_', ' ').trimmed(); + modID = choice.at(0).toInt(); + modName = choice.at(1); + modName = modName.replace('_', ' ').trimmed(); log::debug("user selected mod ID {} and mod name \"{}\"", modID, modName); - } - else { + } else { log::debug("user canceled mod ID selection"); } - } - else { + } else { log::debug("no possible mod IDs found in file name"); } } } -bool NexusInterface::isURLGameRelated(const QUrl &url) const +bool NexusInterface::isURLGameRelated(const QUrl& url) const { QString const name(url.toString()); return name.startsWith(getGameURL("") + "/") || @@ -383,13 +394,12 @@ bool NexusInterface::isURLGameRelated(const QUrl &url) const QString NexusInterface::getGameURL(QString gameName) const { - IPluginGame *game = getGame(gameName); + IPluginGame* game = getGame(gameName); if (game != nullptr) { QString gameNexusName = game->gameNexusName().toLower(); if (gameNexusName.isEmpty()) { return ""; - } - else { + } else { return "https://www.nexusmods.com/" + gameNexusName; } } else { @@ -400,7 +410,7 @@ QString NexusInterface::getGameURL(QString gameName) const QString NexusInterface::getOldModsURL(QString gameName) const { - IPluginGame *game = getGame(gameName); + IPluginGame* game = getGame(gameName); if (game != nullptr) { return "https://" + game->gameNexusName().toLower() + ".nexusmods.com/mods"; } else { @@ -409,20 +419,22 @@ QString NexusInterface::getOldModsURL(QString gameName) const } } - QString NexusInterface::getModURL(int modID, QString gameName = "") const { return QString("%1/mods/%2").arg(getGameURL(gameName)).arg(modID); } -std::vector> NexusInterface::getGameChoices(const MOBase::IPluginGame *game) +std::vector> +NexusInterface::getGameChoices(const MOBase::IPluginGame* game) { std::vector> choices; - choices.push_back(std::pair(game->gameShortName(), game->gameName())); + choices.push_back( + std::pair(game->gameShortName(), game->gameName())); for (QString gameName : game->validShortNames()) { for (auto gamePlugin : m_PluginContainer->plugins()) { if (gamePlugin->gameShortName().compare(gameName, Qt::CaseInsensitive) == 0) { - choices.push_back(std::pair(gamePlugin->gameShortName(), gamePlugin->gameName())); + choices.push_back(std::pair(gamePlugin->gameShortName(), + gamePlugin->gameName())); break; } } @@ -430,249 +442,308 @@ std::vector> NexusInterface::getGameChoices(const MO return choices; } -bool NexusInterface::isModURL(int modID, const QString &url) const +bool NexusInterface::isModURL(int modID, const QString& url) const { if (QUrl(url) == QUrl(getModURL(modID))) { return true; } - //Try the alternate (old style) mod name + // Try the alternate (old style) mod name QString alt = QString("%1/%2").arg(getOldModsURL("")).arg(modID); return QUrl(alt) == QUrl(url); } -void NexusInterface::setPluginContainer(PluginContainer *pluginContainer) +void NexusInterface::setPluginContainer(PluginContainer* pluginContainer) { m_PluginContainer = pluginContainer; } -int NexusInterface::requestDescription(QString gameName, int modID, QObject *receiver, QVariant userData, - const QString &subModule, MOBase::IPluginGame const *game) +int NexusInterface::requestDescription(QString gameName, int modID, QObject* receiver, + QVariant userData, const QString& subModule, + MOBase::IPluginGame const* game) { - NXMRequestInfo requestInfo(modID, NXMRequestInfo::TYPE_DESCRIPTION, userData, subModule, game); + NXMRequestInfo requestInfo(modID, NXMRequestInfo::TYPE_DESCRIPTION, userData, + subModule, game); m_RequestQueue.enqueue(requestInfo); connect(this, SIGNAL(nxmDescriptionAvailable(QString, int, QVariant, QVariant, int)), - receiver, SLOT(nxmDescriptionAvailable(QString, int, QVariant, QVariant, int)), Qt::UniqueConnection); + receiver, + SLOT(nxmDescriptionAvailable(QString, int, QVariant, QVariant, int)), + Qt::UniqueConnection); - connect(this, SIGNAL(nxmRequestFailed(QString, int, int, QVariant, int, int, QString)), - receiver, SLOT(nxmRequestFailed(QString, int, int, QVariant, int, int, QString)), Qt::UniqueConnection); + connect( + this, SIGNAL(nxmRequestFailed(QString, int, int, QVariant, int, int, QString)), + receiver, SLOT(nxmRequestFailed(QString, int, int, QVariant, int, int, QString)), + Qt::UniqueConnection); nextRequest(); return requestInfo.m_ID; } -int NexusInterface::requestModInfo(QString gameName, int modID, QObject *receiver, QVariant userData, - const QString &subModule, MOBase::IPluginGame const *game) +int NexusInterface::requestModInfo(QString gameName, int modID, QObject* receiver, + QVariant userData, const QString& subModule, + MOBase::IPluginGame const* game) { if (m_User.shouldThrottle()) { throttledWarning(m_User); return -1; } - NXMRequestInfo requestInfo(modID, NXMRequestInfo::TYPE_MODINFO, userData, subModule, game); + NXMRequestInfo requestInfo(modID, NXMRequestInfo::TYPE_MODINFO, userData, subModule, + game); m_RequestQueue.enqueue(requestInfo); connect(this, SIGNAL(nxmModInfoAvailable(QString, int, QVariant, QVariant, int)), - receiver, SLOT(nxmModInfoAvailable(QString, int, QVariant, QVariant, int)), Qt::UniqueConnection); + receiver, SLOT(nxmModInfoAvailable(QString, int, QVariant, QVariant, int)), + Qt::UniqueConnection); - connect(this, SIGNAL(nxmRequestFailed(QString, int, int, QVariant, int, int, QString)), - receiver, SLOT(nxmRequestFailed(QString, int, int, QVariant, int, int, QString)), Qt::UniqueConnection); + connect( + this, SIGNAL(nxmRequestFailed(QString, int, int, QVariant, int, int, QString)), + receiver, SLOT(nxmRequestFailed(QString, int, int, QVariant, int, int, QString)), + Qt::UniqueConnection); nextRequest(); return requestInfo.m_ID; } -int NexusInterface::requestUpdateInfo(QString gameName, NexusInterface::UpdatePeriod period, QObject *receiver, QVariant userData, - const QString &subModule, const MOBase::IPluginGame *game) +int NexusInterface::requestUpdateInfo(QString gameName, + NexusInterface::UpdatePeriod period, + QObject* receiver, QVariant userData, + const QString& subModule, + const MOBase::IPluginGame* game) { if (m_User.shouldThrottle()) { throttledWarning(m_User); return -1; } - NXMRequestInfo requestInfo(period, NXMRequestInfo::TYPE_CHECKUPDATES, userData, subModule, game); + NXMRequestInfo requestInfo(period, NXMRequestInfo::TYPE_CHECKUPDATES, userData, + subModule, game); m_RequestQueue.enqueue(requestInfo); connect(this, SIGNAL(nxmUpdateInfoAvailable(QString, QVariant, QVariant, int)), - receiver, SLOT(nxmUpdateInfoAvailable(QString, QVariant, QVariant, int)), Qt::UniqueConnection); + receiver, SLOT(nxmUpdateInfoAvailable(QString, QVariant, QVariant, int)), + Qt::UniqueConnection); - connect(this, SIGNAL(nxmRequestFailed(QString, int, int, QVariant, int, int, QString)), - receiver, SLOT(nxmRequestFailed(QString, int, int, QVariant, int, int, QString)), Qt::UniqueConnection); + connect( + this, SIGNAL(nxmRequestFailed(QString, int, int, QVariant, int, int, QString)), + receiver, SLOT(nxmRequestFailed(QString, int, int, QVariant, int, int, QString)), + Qt::UniqueConnection); nextRequest(); return requestInfo.m_ID; } -int NexusInterface::requestUpdates(const int &modID, QObject *receiver, QVariant userData, - QString gameName, const QString &subModule) +int NexusInterface::requestUpdates(const int& modID, QObject* receiver, + QVariant userData, QString gameName, + const QString& subModule) { if (m_User.shouldThrottle()) { throttledWarning(m_User); return -1; } - IPluginGame *game = getGame(gameName); + IPluginGame* game = getGame(gameName); if (game == nullptr) { log::error("requestUpdates can't find plugin for {}", gameName); return -1; } - NXMRequestInfo requestInfo(modID, NXMRequestInfo::TYPE_GETUPDATES, userData, subModule, game); + NXMRequestInfo requestInfo(modID, NXMRequestInfo::TYPE_GETUPDATES, userData, + subModule, game); m_RequestQueue.enqueue(requestInfo); connect(this, SIGNAL(nxmUpdatesAvailable(QString, int, QVariant, QVariant, int)), - receiver, SLOT(nxmUpdatesAvailable(QString, int, QVariant, QVariant, int)), Qt::UniqueConnection); + receiver, SLOT(nxmUpdatesAvailable(QString, int, QVariant, QVariant, int)), + Qt::UniqueConnection); - connect(this, SIGNAL(nxmRequestFailed(QString, int, int, QVariant, int, int, QString)), - receiver, SLOT(nxmRequestFailed(QString, int, int, QVariant, int, int, QString)), Qt::UniqueConnection); + connect( + this, SIGNAL(nxmRequestFailed(QString, int, int, QVariant, int, int, QString)), + receiver, SLOT(nxmRequestFailed(QString, int, int, QVariant, int, int, QString)), + Qt::UniqueConnection); nextRequest(); return requestInfo.m_ID; } - void NexusInterface::fakeFiles() { static int id = 42; QVariantList result; QVariantMap fileMap; - fileMap["uri"] = "fakeURI"; - fileMap["name"] = "fakeName"; + fileMap["uri"] = "fakeURI"; + fileMap["name"] = "fakeName"; fileMap["description"] = "fakeDescription"; - fileMap["version"] = "1.0.0"; + fileMap["version"] = "1.0.0"; fileMap["category_id"] = "1"; - fileMap["id"] = "1"; - fileMap["size"] = "512"; + fileMap["id"] = "1"; + fileMap["size"] = "512"; result.append(fileMap); emit nxmFilesAvailable("fakeGame", 1234, "fake", result, id++); } - -int NexusInterface::requestFiles(QString gameName, int modID, QObject *receiver, QVariant userData, - const QString &subModule, MOBase::IPluginGame const *game) +int NexusInterface::requestFiles(QString gameName, int modID, QObject* receiver, + QVariant userData, const QString& subModule, + MOBase::IPluginGame const* game) { - NXMRequestInfo requestInfo(modID, NXMRequestInfo::TYPE_FILES, userData, subModule, game); + NXMRequestInfo requestInfo(modID, NXMRequestInfo::TYPE_FILES, userData, subModule, + game); m_RequestQueue.enqueue(requestInfo); connect(this, SIGNAL(nxmFilesAvailable(QString, int, QVariant, QVariant, int)), - receiver, SLOT(nxmFilesAvailable(QString, int, QVariant, QVariant, int)), Qt::UniqueConnection); + receiver, SLOT(nxmFilesAvailable(QString, int, QVariant, QVariant, int)), + Qt::UniqueConnection); - connect(this, SIGNAL(nxmRequestFailed(QString, int, int, QVariant, int, int, QString)), - receiver, SLOT(nxmRequestFailed(QString, int, int, QVariant, int, int, QString)), Qt::UniqueConnection); + connect( + this, SIGNAL(nxmRequestFailed(QString, int, int, QVariant, int, int, QString)), + receiver, SLOT(nxmRequestFailed(QString, int, int, QVariant, int, int, QString)), + Qt::UniqueConnection); nextRequest(); return requestInfo.m_ID; } - -int NexusInterface::requestFileInfo(QString gameName, int modID, int fileID, QObject *receiver, QVariant userData, const QString &subModule) +int NexusInterface::requestFileInfo(QString gameName, int modID, int fileID, + QObject* receiver, QVariant userData, + const QString& subModule) { - IPluginGame *gamePlugin = getGame(gameName); + IPluginGame* gamePlugin = getGame(gameName); if (gamePlugin == nullptr) { log::error("requestFileInfo can't find plugin for {}", gameName); return -1; } - NXMRequestInfo requestInfo(modID, fileID, NXMRequestInfo::TYPE_FILEINFO, userData, subModule, gamePlugin); + NXMRequestInfo requestInfo(modID, fileID, NXMRequestInfo::TYPE_FILEINFO, userData, + subModule, gamePlugin); m_RequestQueue.enqueue(requestInfo); - connect(this, SIGNAL(nxmFileInfoAvailable(QString, int, int, QVariant, QVariant, int)), - receiver, SLOT(nxmFileInfoAvailable(QString, int, int, QVariant, QVariant, int)), Qt::UniqueConnection); + connect( + this, SIGNAL(nxmFileInfoAvailable(QString, int, int, QVariant, QVariant, int)), + receiver, SLOT(nxmFileInfoAvailable(QString, int, int, QVariant, QVariant, int)), + Qt::UniqueConnection); - connect(this, SIGNAL(nxmRequestFailed(QString, int, int, QVariant, int, int, QString)), - receiver, SLOT(nxmRequestFailed(QString, int, int, QVariant, int, int, QString)), Qt::UniqueConnection); + connect( + this, SIGNAL(nxmRequestFailed(QString, int, int, QVariant, int, int, QString)), + receiver, SLOT(nxmRequestFailed(QString, int, int, QVariant, int, int, QString)), + Qt::UniqueConnection); nextRequest(); return requestInfo.m_ID; } - -int NexusInterface::requestDownloadURL(QString gameName, int modID, int fileID, QObject *receiver, QVariant userData, - const QString &subModule, MOBase::IPluginGame const *game) +int NexusInterface::requestDownloadURL(QString gameName, int modID, int fileID, + QObject* receiver, QVariant userData, + const QString& subModule, + MOBase::IPluginGame const* game) { - NXMRequestInfo requestInfo(modID, fileID, NXMRequestInfo::TYPE_DOWNLOADURL, userData, subModule, game); + NXMRequestInfo requestInfo(modID, fileID, NXMRequestInfo::TYPE_DOWNLOADURL, userData, + subModule, game); m_RequestQueue.enqueue(requestInfo); - connect(this, SIGNAL(nxmDownloadURLsAvailable(QString,int,int,QVariant,QVariant,int)), - receiver, SLOT(nxmDownloadURLsAvailable(QString,int,int,QVariant,QVariant,int)), Qt::UniqueConnection); + connect(this, + SIGNAL(nxmDownloadURLsAvailable(QString, int, int, QVariant, QVariant, int)), + receiver, + SLOT(nxmDownloadURLsAvailable(QString, int, int, QVariant, QVariant, int)), + Qt::UniqueConnection); - connect(this, SIGNAL(nxmRequestFailed(QString,int,int,QVariant,int,int,QString)), - receiver, SLOT(nxmRequestFailed(QString,int,int,QVariant,int,int,QString)), Qt::UniqueConnection); + connect( + this, SIGNAL(nxmRequestFailed(QString, int, int, QVariant, int, int, QString)), + receiver, SLOT(nxmRequestFailed(QString, int, int, QVariant, int, int, QString)), + Qt::UniqueConnection); nextRequest(); return requestInfo.m_ID; } -int NexusInterface::requestEndorsementInfo(QObject *receiver, QVariant userData, const QString &subModule) +int NexusInterface::requestEndorsementInfo(QObject* receiver, QVariant userData, + const QString& subModule) { NXMRequestInfo requestInfo(NXMRequestInfo::TYPE_ENDORSEMENTS, userData, subModule); m_RequestQueue.enqueue(requestInfo); - connect(this, SIGNAL(nxmEndorsementsAvailable(QVariant, QVariant, int)), - receiver, SLOT(nxmEndorsementsAvailable(QVariant, QVariant, int)), Qt::UniqueConnection); + connect(this, SIGNAL(nxmEndorsementsAvailable(QVariant, QVariant, int)), receiver, + SLOT(nxmEndorsementsAvailable(QVariant, QVariant, int)), + Qt::UniqueConnection); - connect(this, SIGNAL(nxmRequestFailed(QString, int, int, QVariant, int, int, QString)), - receiver, SLOT(nxmRequestFailed(QString, int, int, QVariant, int, int, QString)), Qt::UniqueConnection); + connect( + this, SIGNAL(nxmRequestFailed(QString, int, int, QVariant, int, int, QString)), + receiver, SLOT(nxmRequestFailed(QString, int, int, QVariant, int, int, QString)), + Qt::UniqueConnection); nextRequest(); return requestInfo.m_ID; } -int NexusInterface::requestToggleEndorsement(QString gameName, int modID, QString modVersion, bool endorse, QObject *receiver, QVariant userData, - const QString &subModule, MOBase::IPluginGame const *game) +int NexusInterface::requestToggleEndorsement(QString gameName, int modID, + QString modVersion, bool endorse, + QObject* receiver, QVariant userData, + const QString& subModule, + MOBase::IPluginGame const* game) { if (m_User.shouldThrottle()) { throttledWarning(m_User); return -1; } - NXMRequestInfo requestInfo(modID, modVersion, NXMRequestInfo::TYPE_TOGGLEENDORSEMENT, userData, subModule, game); + NXMRequestInfo requestInfo(modID, modVersion, NXMRequestInfo::TYPE_TOGGLEENDORSEMENT, + userData, subModule, game); requestInfo.m_Endorse = endorse; m_RequestQueue.enqueue(requestInfo); connect(this, SIGNAL(nxmEndorsementToggled(QString, int, QVariant, QVariant, int)), - receiver, SLOT(nxmEndorsementToggled(QString, int, QVariant, QVariant, int)), Qt::UniqueConnection); + receiver, SLOT(nxmEndorsementToggled(QString, int, QVariant, QVariant, int)), + Qt::UniqueConnection); - connect(this, SIGNAL(nxmRequestFailed(QString, int, int, QVariant, int, int, QString)), - receiver, SLOT(nxmRequestFailed(QString, int, int, QVariant, int, int, QString)), Qt::UniqueConnection); + connect( + this, SIGNAL(nxmRequestFailed(QString, int, int, QVariant, int, int, QString)), + receiver, SLOT(nxmRequestFailed(QString, int, int, QVariant, int, int, QString)), + Qt::UniqueConnection); nextRequest(); return requestInfo.m_ID; } -int NexusInterface::requestTrackingInfo(QObject *receiver, QVariant userData, const QString &subModule) +int NexusInterface::requestTrackingInfo(QObject* receiver, QVariant userData, + const QString& subModule) { NXMRequestInfo requestInfo(NXMRequestInfo::TYPE_TRACKEDMODS, userData, subModule); m_RequestQueue.enqueue(requestInfo); - connect(this, SIGNAL(nxmTrackedModsAvailable(QVariant, QVariant, int)), - receiver, SLOT(nxmTrackedModsAvailable(QVariant, QVariant, int)), Qt::UniqueConnection); + connect(this, SIGNAL(nxmTrackedModsAvailable(QVariant, QVariant, int)), receiver, + SLOT(nxmTrackedModsAvailable(QVariant, QVariant, int)), Qt::UniqueConnection); - connect(this, SIGNAL(nxmRequestFailed(QString, int, int, QVariant, int, int, QString)), - receiver, SLOT(nxmRequestFailed(QString, int, int, QVariant, int, int, QString)), Qt::UniqueConnection); + connect( + this, SIGNAL(nxmRequestFailed(QString, int, int, QVariant, int, int, QString)), + receiver, SLOT(nxmRequestFailed(QString, int, int, QVariant, int, int, QString)), + Qt::UniqueConnection); nextRequest(); return requestInfo.m_ID; } -int NexusInterface::requestToggleTracking(QString gameName, int modID, bool track, QObject *receiver, QVariant userData, - const QString &subModule, MOBase::IPluginGame const *game) +int NexusInterface::requestToggleTracking(QString gameName, int modID, bool track, + QObject* receiver, QVariant userData, + const QString& subModule, + MOBase::IPluginGame const* game) { if (m_User.shouldThrottle()) { throttledWarning(m_User); return -1; } - NXMRequestInfo requestInfo(modID, NXMRequestInfo::TYPE_TOGGLETRACKING, userData, subModule, game); + NXMRequestInfo requestInfo(modID, NXMRequestInfo::TYPE_TOGGLETRACKING, userData, + subModule, game); requestInfo.m_Track = track; m_RequestQueue.enqueue(requestInfo); - connect(this, SIGNAL(nxmTrackingToggled(QString, int, QVariant, bool, int)), - receiver, SLOT(nxmTrackingToggled(QString, int, QVariant, bool, int)), Qt::UniqueConnection); + connect(this, SIGNAL(nxmTrackingToggled(QString, int, QVariant, bool, int)), receiver, + SLOT(nxmTrackingToggled(QString, int, QVariant, bool, int)), + Qt::UniqueConnection); - connect(this, SIGNAL(nxmRequestFailed(QString, int, int, QVariant, int, int, QString)), - receiver, SLOT(nxmRequestFailed(QString, int, int, QVariant, int, int, QString)), Qt::UniqueConnection); + connect( + this, SIGNAL(nxmRequestFailed(QString, int, int, QVariant, int, int, QString)), + receiver, SLOT(nxmRequestFailed(QString, int, int, QVariant, int, int, QString)), + Qt::UniqueConnection); nextRequest(); return requestInfo.m_ID; @@ -698,20 +769,27 @@ int NexusInterface::requestGameInfo(QString gameName, QObject* receiver, QVarian return requestInfo.m_ID; } -int NexusInterface::requestInfoFromMd5(QString gameName, QByteArray &hash, QObject *receiver, QVariant userData, - const QString &subModule, MOBase::IPluginGame const *game) +int NexusInterface::requestInfoFromMd5(QString gameName, QByteArray& hash, + QObject* receiver, QVariant userData, + const QString& subModule, + MOBase::IPluginGame const* game) { - NXMRequestInfo requestInfo(hash, NXMRequestInfo::TYPE_FILEINFO_MD5, userData, subModule, game); + NXMRequestInfo requestInfo(hash, NXMRequestInfo::TYPE_FILEINFO_MD5, userData, + subModule, game); requestInfo.m_Hash = hash; - requestInfo.m_AllowedErrors[QNetworkReply::NetworkError::ContentNotFoundError].append(404); + requestInfo.m_AllowedErrors[QNetworkReply::NetworkError::ContentNotFoundError].append( + 404); requestInfo.m_IgnoreGenericErrorHandler = true; m_RequestQueue.enqueue(requestInfo); connect(this, SIGNAL(nxmFileInfoFromMd5Available(QString, QVariant, QVariant, int)), - receiver, SLOT(nxmFileInfoFromMd5Available(QString, QVariant, QVariant, int)), Qt::UniqueConnection); + receiver, SLOT(nxmFileInfoFromMd5Available(QString, QVariant, QVariant, int)), + Qt::UniqueConnection); - connect(this, SIGNAL(nxmRequestFailed(QString, int, int, QVariant, int, int, QString)), - receiver, SLOT(nxmRequestFailed(QString, int, int, QVariant, int, int, QString)), Qt::UniqueConnection); + connect( + this, SIGNAL(nxmRequestFailed(QString, int, int, QVariant, int, int, QString)), + receiver, SLOT(nxmRequestFailed(QString, int, int, QVariant, int, int, QString)), + Qt::UniqueConnection); nextRequest(); return requestInfo.m_ID; @@ -719,10 +797,11 @@ int NexusInterface::requestInfoFromMd5(QString gameName, QByteArray &hash, QObje IPluginGame* NexusInterface::getGame(QString gameName) const { - auto gamePlugins = m_PluginContainer->plugins(); - IPluginGame *gamePlugin = qApp->property("managed_game").value(); + auto gamePlugins = m_PluginContainer->plugins(); + IPluginGame* gamePlugin = qApp->property("managed_game").value(); for (auto plugin : gamePlugins) { - if (plugin != nullptr && plugin->gameShortName().compare(gameName, Qt::CaseInsensitive) == 0) { + if (plugin != nullptr && + plugin->gameShortName().compare(gameName, Qt::CaseInsensitive) == 0) { gamePlugin = plugin; break; } @@ -733,7 +812,7 @@ IPluginGame* NexusInterface::getGame(QString gameName) const void NexusInterface::cleanup() { m_AccessManager = nullptr; - m_DiskCache = nullptr; + m_DiskCache = nullptr; } void NexusInterface::clearCache() @@ -744,8 +823,7 @@ void NexusInterface::clearCache() void NexusInterface::nextRequest() { - if ((m_ActiveRequest.size() >= MAX_ACTIVE_DOWNLOADS) - || m_RequestQueue.isEmpty()) { + if ((m_ActiveRequest.size() >= MAX_ACTIVE_DOWNLOADS) || m_RequestQueue.isEmpty()) { return; } @@ -756,7 +834,9 @@ void NexusInterface::nextRequest() } else if (getAccessManager()->validateWaiting()) { return; } else { - log::error("{}", tr("You must authorize MO2 in Settings -> Nexus to use the Nexus API.")); + log::error( + "{}", + tr("You must authorize MO2 in Settings -> Nexus to use the Nexus API.")); } } @@ -765,18 +845,19 @@ void NexusInterface::nextRequest() QTime time = QTime::currentTime(); QTime targetTime; targetTime.setHMS((time.hour() + 1) % 23, 5, 0); - QString warning = tr( - "You've exceeded the Nexus API rate limit and requests are now being throttled. " - "Your next batch of requests will be available in approximately %1 minutes and %2 seconds.") - .arg(time.secsTo(targetTime) / 60) - .arg(time.secsTo(targetTime) % 60); + QString warning = tr("You've exceeded the Nexus API rate limit and requests are " + "now being throttled. " + "Your next batch of requests will be available in " + "approximately %1 minutes and %2 seconds.") + .arg(time.secsTo(targetTime) / 60) + .arg(time.secsTo(targetTime) % 60); log::warn("{}", warning); return; } NXMRequestInfo info = m_RequestQueue.dequeue(); - info.m_Timeout = new QTimer(this); + info.m_Timeout = new QTimer(this); info.m_Timeout->setInterval(60000); QJsonObject postObject; @@ -787,82 +868,123 @@ void NexusInterface::nextRequest() if (!info.m_Reroute) { bool hasParams = false; switch (info.m_Type) { - case NXMRequestInfo::TYPE_DESCRIPTION: - case NXMRequestInfo::TYPE_MODINFO: { - url = QString("%1/games/%2/mods/%3").arg(info.m_URL).arg(info.m_GameName).arg(info.m_ModID); - } break; - case NXMRequestInfo::TYPE_CHECKUPDATES: { - QString period; - switch (info.m_UpdatePeriod) { - case UpdatePeriod::DAY: - period = "1d"; - break; - case UpdatePeriod::WEEK: - period = "1w"; - break; - case UpdatePeriod::MONTH: - period = "1m"; - break; - } - url = QString("%1/games/%2/mods/updated?period=%3").arg(info.m_URL).arg(info.m_GameName).arg(period); - } break; - case NXMRequestInfo::TYPE_FILES: - case NXMRequestInfo::TYPE_GETUPDATES: { - url = QString("%1/games/%2/mods/%3/files").arg(info.m_URL).arg(info.m_GameName).arg(info.m_ModID); - } break; - case NXMRequestInfo::TYPE_FILEINFO: { - url = QString("%1/games/%2/mods/%3/files/%4").arg(info.m_URL).arg(info.m_GameName).arg(info.m_ModID).arg(info.m_FileID); - } break; - case NXMRequestInfo::TYPE_DOWNLOADURL: { - ModRepositoryFileInfo *fileInfo = qobject_cast(qvariant_cast(info.m_UserData)); - if (m_User.type() == APIUserAccountTypes::Premium) { - url = QString("%1/games/%2/mods/%3/files/%4/download_link").arg(info.m_URL).arg(info.m_GameName).arg(info.m_ModID).arg(info.m_FileID); - } else if (!fileInfo->nexusKey.isEmpty() && fileInfo->nexusExpires && fileInfo->nexusDownloadUser == m_User.id().toInt()) { - url = QString("%1/games/%2/mods/%3/files/%4/download_link?key=%5&expires=%6") - .arg(info.m_URL).arg(info.m_GameName).arg(info.m_ModID).arg(info.m_FileID).arg(fileInfo->nexusKey).arg(fileInfo->nexusExpires); - } else { - log::warn("{}", tr("Aborting download: Either you clicked on a premium-only link and your account is not premium, " - "or the download link was generated by a different account than the one stored in Mod Organizer.")); - return; - } - } break; - case NXMRequestInfo::TYPE_ENDORSEMENTS: { - url = QString("%1/user/endorsements").arg(info.m_URL); - } break; - case NXMRequestInfo::TYPE_TOGGLEENDORSEMENT: { - QString endorse = info.m_Endorse ? "endorse" : "abstain"; - url = QString("%1/games/%2/mods/%3/%4").arg(info.m_URL).arg(info.m_GameName).arg(info.m_ModID).arg(endorse); - postObject.insert("Version", info.m_ModVersion); - postData.setObject(postObject); - } break; - case NXMRequestInfo::TYPE_TOGGLETRACKING: { - url = QStringLiteral("%1/user/tracked_mods?domain_name=%2").arg(info.m_URL).arg(info.m_GameName); - postObject.insert("mod_id", info.m_ModID); - postData.setObject(postObject); - requestIsDelete = !info.m_Track; - } break; - case NXMRequestInfo::TYPE_TRACKEDMODS: { - url = QStringLiteral("%1/user/tracked_mods").arg(info.m_URL); - } break; - case NXMRequestInfo::TYPE_FILEINFO_MD5: { - url = QStringLiteral("%1/games/%2/mods/md5_search/%3").arg(info.m_URL).arg(info.m_GameName).arg(QString(info.m_Hash.toHex())); - } break; - case NXMRequestInfo::TYPE_GAMEINFO: { - url = QStringLiteral("%1/games/%2").arg(info.m_URL).arg(info.m_GameName); - } break; + case NXMRequestInfo::TYPE_DESCRIPTION: + case NXMRequestInfo::TYPE_MODINFO: { + url = QString("%1/games/%2/mods/%3") + .arg(info.m_URL) + .arg(info.m_GameName) + .arg(info.m_ModID); + } break; + case NXMRequestInfo::TYPE_CHECKUPDATES: { + QString period; + switch (info.m_UpdatePeriod) { + case UpdatePeriod::DAY: + period = "1d"; + break; + case UpdatePeriod::WEEK: + period = "1w"; + break; + case UpdatePeriod::MONTH: + period = "1m"; + break; + } + url = QString("%1/games/%2/mods/updated?period=%3") + .arg(info.m_URL) + .arg(info.m_GameName) + .arg(period); + } break; + case NXMRequestInfo::TYPE_FILES: + case NXMRequestInfo::TYPE_GETUPDATES: { + url = QString("%1/games/%2/mods/%3/files") + .arg(info.m_URL) + .arg(info.m_GameName) + .arg(info.m_ModID); + } break; + case NXMRequestInfo::TYPE_FILEINFO: { + url = QString("%1/games/%2/mods/%3/files/%4") + .arg(info.m_URL) + .arg(info.m_GameName) + .arg(info.m_ModID) + .arg(info.m_FileID); + } break; + case NXMRequestInfo::TYPE_DOWNLOADURL: { + ModRepositoryFileInfo* fileInfo = qobject_cast( + qvariant_cast(info.m_UserData)); + if (m_User.type() == APIUserAccountTypes::Premium) { + url = QString("%1/games/%2/mods/%3/files/%4/download_link") + .arg(info.m_URL) + .arg(info.m_GameName) + .arg(info.m_ModID) + .arg(info.m_FileID); + } else if (!fileInfo->nexusKey.isEmpty() && fileInfo->nexusExpires && + fileInfo->nexusDownloadUser == m_User.id().toInt()) { + url = QString("%1/games/%2/mods/%3/files/%4/download_link?key=%5&expires=%6") + .arg(info.m_URL) + .arg(info.m_GameName) + .arg(info.m_ModID) + .arg(info.m_FileID) + .arg(fileInfo->nexusKey) + .arg(fileInfo->nexusExpires); + } else { + log::warn("{}", tr("Aborting download: Either you clicked on a premium-only " + "link and your account is not premium, " + "or the download link was generated by a different account " + "than the one stored in Mod Organizer.")); + return; + } + } break; + case NXMRequestInfo::TYPE_ENDORSEMENTS: { + url = QString("%1/user/endorsements").arg(info.m_URL); + } break; + case NXMRequestInfo::TYPE_TOGGLEENDORSEMENT: { + QString endorse = info.m_Endorse ? "endorse" : "abstain"; + url = QString("%1/games/%2/mods/%3/%4") + .arg(info.m_URL) + .arg(info.m_GameName) + .arg(info.m_ModID) + .arg(endorse); + postObject.insert("Version", info.m_ModVersion); + postData.setObject(postObject); + } break; + case NXMRequestInfo::TYPE_TOGGLETRACKING: { + url = QStringLiteral("%1/user/tracked_mods?domain_name=%2") + .arg(info.m_URL) + .arg(info.m_GameName); + postObject.insert("mod_id", info.m_ModID); + postData.setObject(postObject); + requestIsDelete = !info.m_Track; + } break; + case NXMRequestInfo::TYPE_TRACKEDMODS: { + url = QStringLiteral("%1/user/tracked_mods").arg(info.m_URL); + } break; + case NXMRequestInfo::TYPE_FILEINFO_MD5: { + url = QStringLiteral("%1/games/%2/mods/md5_search/%3") + .arg(info.m_URL) + .arg(info.m_GameName) + .arg(QString(info.m_Hash.toHex())); + } break; + case NXMRequestInfo::TYPE_GAMEINFO: { + url = QStringLiteral("%1/games/%2") + .arg(info.m_URL) + .arg(info.m_GameName); + } } } else { url = info.m_URL; } QNetworkRequest request(url); request.setAttribute(QNetworkRequest::CacheSaveControlAttribute, false); - request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork); + request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, + QNetworkRequest::AlwaysNetwork); request.setRawHeader("APIKEY", m_User.apiKey().toUtf8()); - request.setHeader(QNetworkRequest::KnownHeaders::UserAgentHeader, m_AccessManager->userAgent(info.m_SubModule)); - request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json"); + request.setHeader(QNetworkRequest::KnownHeaders::UserAgentHeader, + m_AccessManager->userAgent(info.m_SubModule)); + request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, + "application/json"); request.setRawHeader("Protocol-Version", "1.0.0"); request.setRawHeader("Application-Name", "MO2"); - request.setRawHeader("Application-Version", QApplication::applicationVersion().toUtf8()); + request.setRawHeader("Application-Version", + QApplication::applicationVersion().toUtf8()); if (postData.object().isEmpty()) { if (!requestIsDelete) { @@ -873,42 +995,46 @@ void NexusInterface::nextRequest() } else if (!requestIsDelete) { info.m_Reply = m_AccessManager->post(request, postData.toJson()); } else { - // Qt doesn't support DELETE with a payload as that's technically against the HTTP standard... - info.m_Reply = m_AccessManager->sendCustomRequest(request, "DELETE", postData.toJson()); + // Qt doesn't support DELETE with a payload as that's technically against the HTTP + // standard... + info.m_Reply = + m_AccessManager->sendCustomRequest(request, "DELETE", postData.toJson()); } connect(info.m_Reply, SIGNAL(finished()), this, SLOT(requestFinished())); if (!info.m_IgnoreGenericErrorHandler) - connect(info.m_Reply, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(requestError(QNetworkReply::NetworkError))); + connect(info.m_Reply, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, + SLOT(requestError(QNetworkReply::NetworkError))); connect(info.m_Timeout, SIGNAL(timeout()), this, SLOT(requestTimeout())); info.m_Timeout->start(); m_ActiveRequest.push_back(info); } - -void NexusInterface::downloadRequestedNXM(const QString &url) +void NexusInterface::downloadRequestedNXM(const QString& url) { emit requestNXMDownload(url); } void NexusInterface::requestFinished(std::list::iterator iter) { - QNetworkReply *reply = iter->m_Reply; + QNetworkReply* reply = iter->m_Reply; auto error = reply->error(); if (error != QNetworkReply::NoError) { int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); QString errorMsg = reply->errorString(); - if (iter->m_AllowedErrors.contains(error) && iter->m_AllowedErrors[error].contains(statusCode)) { - // These errors are allows to silently happen. They should be handled in nxmRequestFailed below. + if (iter->m_AllowedErrors.contains(error) && + iter->m_AllowedErrors[error].contains(statusCode)) { + // These errors are allows to silently happen. They should be handled in + // nxmRequestFailed below. } else if (statusCode == 429) { m_User.limits(parseLimits(reply)); if (!m_User.exhausted()) { - log::warn("You appear to be making requests to the Nexus API too quickly and are being throttled. Please inform the MO2 team."); - } - else { + log::warn("You appear to be making requests to the Nexus API too quickly and " + "are being throttled. Please inform the MO2 team."); + } else { log::warn("All API requests have been consumed and are now being denied."); } @@ -920,21 +1046,23 @@ void NexusInterface::requestFinished(std::list::iterator iter) QJsonDocument responseDoc = QJsonDocument::fromJson(data); if (!responseDoc.isNull()) { auto result = responseDoc.toVariant().toMap(); - auto error = result.find("error"); + auto error = result.find("error"); if (error != result.end()) errorMsg = result.value("error").toString(); } } } - emit nxmRequestFailed(iter->m_GameName, iter->m_ModID, iter->m_FileID, iter->m_UserData, iter->m_ID, statusCode, errorMsg); + emit nxmRequestFailed(iter->m_GameName, iter->m_ModID, iter->m_FileID, + iter->m_UserData, iter->m_ID, statusCode, errorMsg); } else { int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); if (statusCode == 301) { // redirect request, return request to queue - iter->m_URL = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toString(); + iter->m_URL = + reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toString(); iter->m_Reroute = true; m_RequestQueue.enqueue(*iter); - //nextRequest(); + // nextRequest(); return; } QByteArray data = reply->readAll(); @@ -944,75 +1072,94 @@ void NexusInterface::requestFinished(std::list::iterator iter) nexusError = tr("empty response"); } log::debug("nexus error: {}", nexusError); - emit nxmRequestFailed(iter->m_GameName, iter->m_ModID, iter->m_FileID, iter->m_UserData, iter->m_ID, reply->error(), nexusError); + emit nxmRequestFailed(iter->m_GameName, iter->m_ModID, iter->m_FileID, + iter->m_UserData, iter->m_ID, reply->error(), nexusError); } else { QJsonDocument responseDoc = QJsonDocument::fromJson(data); if (!responseDoc.isNull()) { QVariant result = responseDoc.toVariant(); switch (iter->m_Type) { - case NXMRequestInfo::TYPE_DESCRIPTION: { - emit nxmDescriptionAvailable(iter->m_GameName, iter->m_ModID, iter->m_UserData, result, iter->m_ID); - } break; - case NXMRequestInfo::TYPE_MODINFO: { - emit nxmModInfoAvailable(iter->m_GameName, iter->m_ModID, iter->m_UserData, result, iter->m_ID); - } break; - case NXMRequestInfo::TYPE_CHECKUPDATES: { - emit nxmUpdateInfoAvailable(iter->m_GameName, iter->m_UserData, result, iter->m_ID); - } break; - case NXMRequestInfo::TYPE_FILES: { - emit nxmFilesAvailable(iter->m_GameName, iter->m_ModID, iter->m_UserData, result, iter->m_ID); - } break; - case NXMRequestInfo::TYPE_GETUPDATES: { - emit nxmUpdatesAvailable(iter->m_GameName, iter->m_ModID, iter->m_UserData, result, iter->m_ID); - } break; - case NXMRequestInfo::TYPE_FILEINFO: { - emit nxmFileInfoAvailable(iter->m_GameName, iter->m_ModID, iter->m_FileID, iter->m_UserData, result, iter->m_ID); - } break; - case NXMRequestInfo::TYPE_DOWNLOADURL: { - emit nxmDownloadURLsAvailable(iter->m_GameName, iter->m_ModID, iter->m_FileID, iter->m_UserData, result, iter->m_ID); - } break; - case NXMRequestInfo::TYPE_ENDORSEMENTS: { - emit nxmEndorsementsAvailable(iter->m_UserData, result, iter->m_ID); - } break; - case NXMRequestInfo::TYPE_TOGGLEENDORSEMENT: { - emit nxmEndorsementToggled(iter->m_GameName, iter->m_ModID, iter->m_UserData, result, iter->m_ID); - } break; - case NXMRequestInfo::TYPE_TOGGLETRACKING: { - auto results = result.toMap(); - auto message = results["message"].toString(); - if (message.contains(QRegularExpression("User [0-9]+ is already Tracking Mod: [0-9]+")) || - message.contains(QRegularExpression("User [0-9]+ is now Tracking Mod: [0-9]+"))) { - emit nxmTrackingToggled(iter->m_GameName, iter->m_ModID, iter->m_UserData, true, iter->m_ID); - } else if (message.contains(QRegularExpression("User [0-9]+ is no longer tracking [0-9]+")) || - message.contains(QRegularExpression("Users is not tracking mod. Unable to untrack."))) { - emit nxmTrackingToggled(iter->m_GameName, iter->m_ModID, iter->m_UserData, false, iter->m_ID); - } - } break; - case NXMRequestInfo::TYPE_TRACKEDMODS: { - emit nxmTrackedModsAvailable(iter->m_UserData, result, iter->m_ID); - } break; - case NXMRequestInfo::TYPE_FILEINFO_MD5: { - emit nxmFileInfoFromMd5Available(iter->m_GameName, iter->m_UserData, result, iter->m_ID); - } break; - case NXMRequestInfo::TYPE_GAMEINFO: { - emit nxmGameInfoAvailable(iter->m_GameName, iter->m_UserData, result, iter->m_ID); - } break; + case NXMRequestInfo::TYPE_DESCRIPTION: { + emit nxmDescriptionAvailable(iter->m_GameName, iter->m_ModID, + iter->m_UserData, result, iter->m_ID); + } break; + case NXMRequestInfo::TYPE_MODINFO: { + emit nxmModInfoAvailable(iter->m_GameName, iter->m_ModID, iter->m_UserData, + result, iter->m_ID); + } break; + case NXMRequestInfo::TYPE_CHECKUPDATES: { + emit nxmUpdateInfoAvailable(iter->m_GameName, iter->m_UserData, result, + iter->m_ID); + } break; + case NXMRequestInfo::TYPE_FILES: { + emit nxmFilesAvailable(iter->m_GameName, iter->m_ModID, iter->m_UserData, + result, iter->m_ID); + } break; + case NXMRequestInfo::TYPE_GETUPDATES: { + emit nxmUpdatesAvailable(iter->m_GameName, iter->m_ModID, iter->m_UserData, + result, iter->m_ID); + } break; + case NXMRequestInfo::TYPE_FILEINFO: { + emit nxmFileInfoAvailable(iter->m_GameName, iter->m_ModID, iter->m_FileID, + iter->m_UserData, result, iter->m_ID); + } break; + case NXMRequestInfo::TYPE_DOWNLOADURL: { + emit nxmDownloadURLsAvailable(iter->m_GameName, iter->m_ModID, iter->m_FileID, + iter->m_UserData, result, iter->m_ID); + } break; + case NXMRequestInfo::TYPE_ENDORSEMENTS: { + emit nxmEndorsementsAvailable(iter->m_UserData, result, iter->m_ID); + } break; + case NXMRequestInfo::TYPE_TOGGLEENDORSEMENT: { + emit nxmEndorsementToggled(iter->m_GameName, iter->m_ModID, iter->m_UserData, + result, iter->m_ID); + } break; + case NXMRequestInfo::TYPE_TOGGLETRACKING: { + auto results = result.toMap(); + auto message = results["message"].toString(); + if (message.contains( + QRegularExpression("User [0-9]+ is already Tracking Mod: [0-9]+")) || + message.contains( + QRegularExpression("User [0-9]+ is now Tracking Mod: [0-9]+"))) { + emit nxmTrackingToggled(iter->m_GameName, iter->m_ModID, iter->m_UserData, + true, iter->m_ID); + } else if (message.contains(QRegularExpression( + "User [0-9]+ is no longer tracking [0-9]+")) || + message.contains(QRegularExpression( + "Users is not tracking mod. Unable to untrack."))) { + emit nxmTrackingToggled(iter->m_GameName, iter->m_ModID, iter->m_UserData, + false, iter->m_ID); + } + } break; + case NXMRequestInfo::TYPE_TRACKEDMODS: { + emit nxmTrackedModsAvailable(iter->m_UserData, result, iter->m_ID); + } break; + case NXMRequestInfo::TYPE_FILEINFO_MD5: { + emit nxmFileInfoFromMd5Available(iter->m_GameName, iter->m_UserData, result, + iter->m_ID); + } break; + case NXMRequestInfo::TYPE_GAMEINFO: { + emit nxmGameInfoAvailable(iter->m_GameName, iter->m_UserData, result, + iter->m_ID); + } break; } m_User.limits(parseLimits(reply)); emit requestsChanged(getAPIStats(), m_User); } else { - emit nxmRequestFailed(iter->m_GameName, iter->m_ModID, iter->m_FileID, iter->m_UserData, iter->m_ID, reply->error(), tr("invalid response")); + emit nxmRequestFailed(iter->m_GameName, iter->m_ModID, iter->m_FileID, + iter->m_UserData, iter->m_ID, reply->error(), + tr("invalid response")); } } } } - void NexusInterface::requestFinished() { - QNetworkReply *reply = static_cast(sender()); - for (std::list::iterator iter = m_ActiveRequest.begin(); iter != m_ActiveRequest.end(); ++iter) { + QNetworkReply* reply = static_cast(sender()); + for (std::list::iterator iter = m_ActiveRequest.begin(); + iter != m_ActiveRequest.end(); ++iter) { if (iter->m_Reply == reply) { iter->m_Timeout->stop(); iter->m_Timeout->deleteLater(); @@ -1025,29 +1172,27 @@ void NexusInterface::requestFinished() } } - void NexusInterface::requestError(QNetworkReply::NetworkError) { - QNetworkReply *reply = qobject_cast(sender()); + QNetworkReply* reply = qobject_cast(sender()); if (reply == nullptr) { log::warn("invalid sender type"); return; } - log::error( - "request ({}) error: {} ({})", - reply->url().toString(), reply->errorString(), reply->error()); + log::error("request ({}) error: {} ({})", reply->url().toString(), + reply->errorString(), reply->error()); } - void NexusInterface::requestTimeout() { - QTimer *timer = qobject_cast(sender()); + QTimer* timer = qobject_cast(sender()); if (timer == nullptr) { log::warn("invalid sender type"); return; } - for (std::list::iterator iter = m_ActiveRequest.begin(); iter != m_ActiveRequest.end(); ++iter) { + for (std::list::iterator iter = m_ActiveRequest.begin(); + iter != m_ActiveRequest.end(); ++iter) { if (iter->m_Timeout == timer) { // this abort causes a "request failed" which cleans up the rest iter->m_Reply->abort(); @@ -1069,90 +1214,34 @@ APIStats NexusInterface::getAPIStats() const return stats; } - -namespace { - QString get_management_url() - { - return "https://api.nexusmods.com/v1"; - } -} - -NexusInterface::NXMRequestInfo::NXMRequestInfo(int modID - , NexusInterface::NXMRequestInfo::Type type - , QVariant userData - , const QString &subModule - , MOBase::IPluginGame const *game - ) - : m_ModID(modID) - , m_ModVersion("0") - , m_FileID(0) - , m_Reply(nullptr) - , m_Type(type) - , m_UpdatePeriod(UpdatePeriod::NONE) - , m_UserData(userData) - , m_Timeout(nullptr) - , m_Reroute(false) - , m_ID(s_NextID.fetchAndAddAcquire(1)) - , m_URL(get_management_url()) - , m_SubModule(subModule) - , m_NexusGameID(game->nexusGameID()) - , m_GameName(game->gameNexusName()) - , m_Endorse(false) - , m_Track(false) - , m_Hash(QByteArray()) +namespace { -} - -NexusInterface::NXMRequestInfo::NXMRequestInfo(int modID - , QString modVersion - , NexusInterface::NXMRequestInfo::Type type - , QVariant userData - , const QString &subModule - , MOBase::IPluginGame const *game -) - : m_ModID(modID) - , m_ModVersion(modVersion) - , m_FileID(0) - , m_Reply(nullptr) - , m_Type(type) - , m_UpdatePeriod(UpdatePeriod::NONE) - , m_UserData(userData) - , m_Timeout(nullptr) - , m_Reroute(false) - , m_ID(s_NextID.fetchAndAddAcquire(1)) - , m_URL(get_management_url()) - , m_SubModule(subModule) - , m_NexusGameID(game->nexusGameID()) - , m_GameName(game->gameNexusName()) - , m_Endorse(false) - , m_Track(false) - , m_Hash(QByteArray()) +QString get_management_url() +{ + return "https://api.nexusmods.com/v1"; +} +} // namespace + +NexusInterface::NXMRequestInfo::NXMRequestInfo( + int modID, NexusInterface::NXMRequestInfo::Type type, QVariant userData, + const QString& subModule, MOBase::IPluginGame const* game) + : m_ModID(modID), m_ModVersion("0"), m_FileID(0), m_Reply(nullptr), m_Type(type), + m_UpdatePeriod(UpdatePeriod::NONE), m_UserData(userData), m_Timeout(nullptr), + m_Reroute(false), m_ID(s_NextID.fetchAndAddAcquire(1)), + m_URL(get_management_url()), m_SubModule(subModule), + m_NexusGameID(game->nexusGameID()), m_GameName(game->gameNexusName()), + m_Endorse(false), m_Track(false), m_Hash(QByteArray()) {} -NexusInterface::NXMRequestInfo::NXMRequestInfo(int modID - , int fileID - , NexusInterface::NXMRequestInfo::Type type - , QVariant userData - , const QString &subModule - , MOBase::IPluginGame const *game -) - : m_ModID(modID) - , m_ModVersion("0") - , m_FileID(fileID) - , m_Reply(nullptr) - , m_Type(type) - , m_UpdatePeriod(UpdatePeriod::NONE) - , m_UserData(userData) - , m_Timeout(nullptr) - , m_Reroute(false) - , m_ID(s_NextID.fetchAndAddAcquire(1)) - , m_URL(get_management_url()) - , m_SubModule(subModule) - , m_NexusGameID(game->nexusGameID()) - , m_GameName(game->gameNexusName()) - , m_Endorse(false) - , m_Track(false) - , m_Hash(QByteArray()) +NexusInterface::NXMRequestInfo::NXMRequestInfo( + int modID, QString modVersion, NexusInterface::NXMRequestInfo::Type type, + QVariant userData, const QString& subModule, MOBase::IPluginGame const* game) + : m_ModID(modID), m_ModVersion(modVersion), m_FileID(0), m_Reply(nullptr), + m_Type(type), m_UpdatePeriod(UpdatePeriod::NONE), m_UserData(userData), + m_Timeout(nullptr), m_Reroute(false), m_ID(s_NextID.fetchAndAddAcquire(1)), + m_URL(get_management_url()), m_SubModule(subModule), + m_NexusGameID(game->nexusGameID()), m_GameName(game->gameNexusName()), + m_Endorse(false), m_Track(false), m_Hash(QByteArray()) {} NexusInterface::NXMRequestInfo::NXMRequestInfo(Type type @@ -1179,77 +1268,44 @@ NexusInterface::NXMRequestInfo::NXMRequestInfo(Type type , m_Hash(QByteArray()) {} -NexusInterface::NXMRequestInfo::NXMRequestInfo(Type type - , QVariant userData - , const QString &subModule -) - : m_ModID(0) - , m_ModVersion("0") - , m_FileID(0) - , m_Reply(nullptr) - , m_Type(type) - , m_UpdatePeriod(UpdatePeriod::NONE) - , m_UserData(userData) - , m_Timeout(nullptr) - , m_Reroute(false) - , m_ID(s_NextID.fetchAndAddAcquire(1)) - , m_URL(get_management_url()) - , m_SubModule(subModule) - , m_NexusGameID(0) - , m_GameName("") - , m_Endorse(false) - , m_Track(false) - , m_Hash(QByteArray()) +NexusInterface::NXMRequestInfo::NXMRequestInfo( + int modID, int fileID, NexusInterface::NXMRequestInfo::Type type, QVariant userData, + const QString& subModule, MOBase::IPluginGame const* game) + : m_ModID(modID), m_ModVersion("0"), m_FileID(fileID), m_Reply(nullptr), + m_Type(type), m_UpdatePeriod(UpdatePeriod::NONE), m_UserData(userData), + m_Timeout(nullptr), m_Reroute(false), m_ID(s_NextID.fetchAndAddAcquire(1)), + m_URL(get_management_url()), m_SubModule(subModule), + m_NexusGameID(game->nexusGameID()), m_GameName(game->gameNexusName()), + m_Endorse(false), m_Track(false), m_Hash(QByteArray()) {} - -NexusInterface::NXMRequestInfo::NXMRequestInfo(UpdatePeriod period - , NexusInterface::NXMRequestInfo::Type type - , QVariant userData - , const QString &subModule - , MOBase::IPluginGame const *game -) - : m_ModID(0) - , m_ModVersion("0") - , m_FileID(0) - , m_Reply(nullptr) - , m_Type(type) - , m_UpdatePeriod(period) - , m_UserData(userData) - , m_Timeout(nullptr) - , m_Reroute(false) - , m_ID(s_NextID.fetchAndAddAcquire(1)) - , m_URL(get_management_url()) - , m_SubModule(subModule) - , m_NexusGameID(game->nexusGameID()) - , m_GameName(game->gameNexusName()) - , m_Endorse(false) - , m_Track(false) - , m_Hash(QByteArray()) +NexusInterface::NXMRequestInfo::NXMRequestInfo(Type type, QVariant userData, + const QString& subModule) + : m_ModID(0), m_ModVersion("0"), m_FileID(0), m_Reply(nullptr), m_Type(type), + m_UpdatePeriod(UpdatePeriod::NONE), m_UserData(userData), m_Timeout(nullptr), + m_Reroute(false), m_ID(s_NextID.fetchAndAddAcquire(1)), + m_URL(get_management_url()), m_SubModule(subModule), m_NexusGameID(0), + m_GameName(""), m_Endorse(false), m_Track(false), m_Hash(QByteArray()) {} +NexusInterface::NXMRequestInfo::NXMRequestInfo( + UpdatePeriod period, NexusInterface::NXMRequestInfo::Type type, QVariant userData, + const QString& subModule, MOBase::IPluginGame const* game) + : m_ModID(0), m_ModVersion("0"), m_FileID(0), m_Reply(nullptr), m_Type(type), + m_UpdatePeriod(period), m_UserData(userData), m_Timeout(nullptr), + m_Reroute(false), m_ID(s_NextID.fetchAndAddAcquire(1)), + m_URL(get_management_url()), m_SubModule(subModule), + m_NexusGameID(game->nexusGameID()), m_GameName(game->gameNexusName()), + m_Endorse(false), m_Track(false), m_Hash(QByteArray()) +{} -NexusInterface::NXMRequestInfo::NXMRequestInfo(QByteArray &hash - , NexusInterface::NXMRequestInfo::Type type - , QVariant userData - , const QString &subModule - , MOBase::IPluginGame const *game -) - : m_ModID(0) - , m_ModVersion("0") - , m_FileID(0) - , m_Reply(nullptr) - , m_Type(type) - , m_UpdatePeriod(UpdatePeriod::NONE) - , m_UserData(userData) - , m_Timeout(nullptr) - , m_Reroute(false) - , m_ID(s_NextID.fetchAndAddAcquire(1)) - , m_URL(get_management_url()) - , m_SubModule(subModule) - , m_NexusGameID(game->nexusGameID()) - , m_GameName(game->gameNexusName()) - , m_Endorse(false) - , m_Track(false) - , m_Hash(hash) +NexusInterface::NXMRequestInfo::NXMRequestInfo( + QByteArray& hash, NexusInterface::NXMRequestInfo::Type type, QVariant userData, + const QString& subModule, MOBase::IPluginGame const* game) + : m_ModID(0), m_ModVersion("0"), m_FileID(0), m_Reply(nullptr), m_Type(type), + m_UpdatePeriod(UpdatePeriod::NONE), m_UserData(userData), m_Timeout(nullptr), + m_Reroute(false), m_ID(s_NextID.fetchAndAddAcquire(1)), + m_URL(get_management_url()), m_SubModule(subModule), + m_NexusGameID(game->nexusGameID()), m_GameName(game->gameNexusName()), + m_Endorse(false), m_Track(false), m_Hash(hash) {} diff --git a/src/nexusinterface.h b/src/nexusinterface.h index 3bbc6676c..a1a3a0d96 100644 --- a/src/nexusinterface.h +++ b/src/nexusinterface.h @@ -23,20 +23,23 @@ along with Mod Organizer. If not, see . #include "apiuseraccount.h" #include "plugincontainer.h" +#include #include #include -#include -#include #include +#include #include -#include #include +#include #include #include -namespace MOBase { class IPluginGame; } +namespace MOBase +{ +class IPluginGame; +} class NexusInterface; class NXMAccessManager; @@ -45,10 +48,9 @@ class Settings; /** * @brief convenience class to make nxm requests easier * usually, all objects that started a nxm request will be signaled if one finished. - * Therefore, the objects need to store the id of the requests they started and then filter - * the result. - * NexusBridge does this automatically. Users connect to the signals of NexusBridge they intend - * to handle and only receive the signals the caused + * Therefore, the objects need to store the id of the requests they started and then + *filter the result. NexusBridge does this automatically. Users connect to the signals + *of NexusBridge they intend to handle and only receive the signals the caused **/ class NexusBridge : public MOBase::IModRepositoryBridge { @@ -56,7 +58,7 @@ class NexusBridge : public MOBase::IModRepositoryBridge Q_OBJECT public: - NexusBridge(PluginContainer *pluginContainer, const QString &subModule = ""); + NexusBridge(PluginContainer* pluginContainer, const QString& subModule = ""); /** * @brief request description for a mod @@ -82,7 +84,8 @@ class NexusBridge : public MOBase::IModRepositoryBridge * @param fileID id of the file the caller is interested in * @param userData user data to be returned with the result **/ - virtual void requestFileInfo(QString gameName, int modID, int fileID, QVariant userData); + virtual void requestFileInfo(QString gameName, int modID, int fileID, + QVariant userData); /** * @brief request the download url of a file @@ -91,21 +94,24 @@ class NexusBridge : public MOBase::IModRepositoryBridge * @param fileID id of the file the caller is interested in * @param userData user data to be returned with the result **/ - virtual void requestDownloadURL(QString gameName, int modID, int fileID, QVariant userData); + virtual void requestDownloadURL(QString gameName, int modID, int fileID, + QVariant userData); /** * @brief requestToggleEndorsement * @param modID id of the mod caller is interested in * @param userData user data to be returned with the result */ - virtual void requestToggleEndorsement(QString gameName, int modID, QString modVersion, bool endorse, QVariant userData); + virtual void requestToggleEndorsement(QString gameName, int modID, QString modVersion, + bool endorse, QVariant userData); /** * @brief requestToggleTracking * @param modID id of the mod caller is interested in * @param userData user data to be returned with the result */ - virtual void requestToggleTracking(QString gameName, int modID, bool track, QVariant userData); + virtual void requestToggleTracking(QString gameName, int modID, bool track, + QVariant userData); /** * @brief requestGameInfo @@ -115,56 +121,66 @@ class NexusBridge : public MOBase::IModRepositoryBridge public slots: - void nxmDescriptionAvailable(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID); - void nxmFilesAvailable(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID); - void nxmFileInfoAvailable(QString gameName, int modID, int fileID, QVariant userData, QVariant resultData, int requestID); - void nxmDownloadURLsAvailable(QString gameName, int modID, int fileID, QVariant userData, QVariant resultData, int requestID); + void nxmDescriptionAvailable(QString gameName, int modID, QVariant userData, + QVariant resultData, int requestID); + void nxmFilesAvailable(QString gameName, int modID, QVariant userData, + QVariant resultData, int requestID); + void nxmFileInfoAvailable(QString gameName, int modID, int fileID, QVariant userData, + QVariant resultData, int requestID); + void nxmDownloadURLsAvailable(QString gameName, int modID, int fileID, + QVariant userData, QVariant resultData, int requestID); void nxmEndorsementsAvailable(QVariant userData, QVariant resultData, int requestID); - void nxmEndorsementToggled(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID); + void nxmEndorsementToggled(QString gameName, int modID, QVariant userData, + QVariant resultData, int requestID); void nxmTrackedModsAvailable(QVariant userData, QVariant resultData, int requestID); - void nxmTrackingToggled(QString gameName, int modID, QVariant userData, bool tracked, int requestID); - void nxmGameInfoAvailable(QString gameName, QVariant userData, QVariant resultData, int requestID); - void nxmRequestFailed(QString gameName, int modID, int fileID, QVariant userData, int requestID, int errorCode, const QString &errorMessage); + void nxmTrackingToggled(QString gameName, int modID, QVariant userData, bool tracked, + int requestID); + void nxmGameInfoAvailable(QString gameName, QVariant userData, QVariant resultData, + int requestID); + void nxmRequestFailed(QString gameName, int modID, int fileID, QVariant userData, + int requestID, int errorCode, const QString& errorMessage); private: - - NexusInterface *m_Interface; + NexusInterface* m_Interface; QString m_SubModule; std::set m_RequestIDs; - }; - /** * @brief Makes asynchronous requests to the nexus API * * This class can be used to make asynchronous requests to the Nexus API. - * Currently, responses are sent to all receivers that have sent a request of the relevant type, so the - * recipient has to filter the response by the id returned when making the request + * Currently, responses are sent to all receivers that have sent a request of the + *relevant type, so the recipient has to filter the response by the id returned when + *making the request **/ class NexusInterface : public QObject { Q_OBJECT public: - - enum UpdatePeriod { + enum UpdatePeriod + { NONE, DAY, WEEK, MONTH }; - // Nexus file category IDs (MAIN, OLD etc), not to be confused with mod categories (Armors, Texture etc). - enum FileStatus { - MAIN = 1, - UPDATE = 2, - OPTIONAL_FILE = 3, // actual string version is "OPTIONAL", but that is already defined as a macro in minwindef.h - OLD_VERSION = 4, - MISCELLANEOUS = 5, - REMOVED = 6, - ARCHIVED = 7, - ARCHIVED_HIDDEN = 1000 // Archived files can be hidden by authors so if they aren't listed we can assume they were hidden. + // Nexus file category IDs (MAIN, OLD etc), not to be confused with mod categories + // (Armors, Texture etc). + enum FileStatus + { + MAIN = 1, + UPDATE = 2, + OPTIONAL_FILE = 3, // actual string version is "OPTIONAL", but that is already + // defined as a macro in minwindef.h + OLD_VERSION = 4, + MISCELLANEOUS = 5, + REMOVED = 6, + ARCHIVED = 7, + ARCHIVED_HIDDEN = 1000 // Archived files can be hidden by authors so if they aren't + // listed we can assume they were hidden. }; public: @@ -180,10 +196,11 @@ class NexusInterface : public QObject /** * @return the access manager object used to connect to nexus **/ - NXMAccessManager *getAccessManager(); + NXMAccessManager* getAccessManager(); /** - * @brief cleanup this interface. this is destructive, afterwards it can't be used again + * @brief cleanup this interface. this is destructive, afterwards it can't be used + * again */ void cleanup(); @@ -196,14 +213,18 @@ class NexusInterface : public QObject * @brief request description for a mod * * @param gameName the game short name to support multiple game sources - * @param modID id of the mod caller is interested in (assumed to be for the current game) - * @param receiver the object to receive the result asynchronously via a signal (nxmDescriptionAvailable) + * @param modID id of the mod caller is interested in (assumed to be for the current + *game) + * @param receiver the object to receive the result asynchronously via a signal + *(nxmDescriptionAvailable) * @param userData user data to be returned with the result * @return int an id to identify the request **/ - int requestDescription(QString gameName, int modID, QObject *receiver, QVariant userData, const QString &subModule) + int requestDescription(QString gameName, int modID, QObject* receiver, + QVariant userData, const QString& subModule) { - return requestDescription(gameName, modID, receiver, userData, subModule, getGame(gameName)); + return requestDescription(gameName, modID, receiver, userData, subModule, + getGame(gameName)); } /** @@ -211,26 +232,32 @@ class NexusInterface : public QObject * * @param gameName the game short name to support multiple game sources * @param modID id of the mod caller is interested in - * @param receiver the object to receive the result asynchronously via a signal (nxmDescriptionAvailable) + * @param receiver the object to receive the result asynchronously via a signal + *(nxmDescriptionAvailable) * @param userData user data to be returned with the result * @param game Game with which the mod is associated * @return int an id to identify the request **/ - int requestDescription(QString gameName, int modID, QObject *receiver, QVariant userData, const QString &subModule, - MOBase::IPluginGame const *game); + int requestDescription(QString gameName, int modID, QObject* receiver, + QVariant userData, const QString& subModule, + MOBase::IPluginGame const* game); /** * @brief request description for a mod * * @param gameName the game short name to support multiple game sources - * @param modID id of the mod caller is interested in (assumed to be for the current game) - * @param receiver the object to receive the result asynchronously via a signal (nxmModInfoAvailable) + * @param modID id of the mod caller is interested in (assumed to be for the current + *game) + * @param receiver the object to receive the result asynchronously via a signal + *(nxmModInfoAvailable) * @param userData user data to be returned with the result * @return int an id to identify the request **/ - int requestModInfo(QString gameName, int modID, QObject *receiver, QVariant userData, const QString &subModule) + int requestModInfo(QString gameName, int modID, QObject* receiver, QVariant userData, + const QString& subModule) { - return requestModInfo(gameName, modID, receiver, userData, subModule, getGame(gameName)); + return requestModInfo(gameName, modID, receiver, userData, subModule, + getGame(gameName)); } /** @@ -238,86 +265,102 @@ class NexusInterface : public QObject * * @param gameName the game short name to support multiple game sources * @param modID id of the mod caller is interested in - * @param receiver the object to receive the result asynchronously via a signal (nxmModInfoAvailable) + * @param receiver the object to receive the result asynchronously via a signal + *(nxmModInfoAvailable) * @param userData user data to be returned with the result * @param game Game with which the mod is associated * @return int an id to identify the request **/ - int requestModInfo(QString gameName, int modID, QObject *receiver, QVariant userData, const QString &subModule, - MOBase::IPluginGame const *game); + int requestModInfo(QString gameName, int modID, QObject* receiver, QVariant userData, + const QString& subModule, MOBase::IPluginGame const* game); - int requestUpdateInfo(QString gameName, NexusInterface::UpdatePeriod period, QObject *receiver, QVariant userData, - const QString &subModule) + int requestUpdateInfo(QString gameName, NexusInterface::UpdatePeriod period, + QObject* receiver, QVariant userData, const QString& subModule) { - return requestUpdateInfo(gameName, period, receiver, userData, subModule, getGame(gameName)); + return requestUpdateInfo(gameName, period, receiver, userData, subModule, + getGame(gameName)); } - int requestUpdateInfo(QString gameName, NexusInterface::UpdatePeriod period, QObject *receiver, QVariant userData, - const QString &subModule, const MOBase::IPluginGame *game); + int requestUpdateInfo(QString gameName, NexusInterface::UpdatePeriod period, + QObject* receiver, QVariant userData, const QString& subModule, + const MOBase::IPluginGame* game); /** * @brief request nexus descriptions for multiple mods at once * @param modID id of the mod the caller is interested in - * @param receiver the object to receive the result asynchronously via a signal (nxmDescriptionAvailable) + * @param receiver the object to receive the result asynchronously via a signal + * (nxmDescriptionAvailable) * @param userData user data to be returned with the result * @param gameName the game with which the mods are associated * @return int an id to identify the request */ - int requestUpdates(const int &modID, QObject *receiver, QVariant userData, QString gameName, const QString &subModule); + int requestUpdates(const int& modID, QObject* receiver, QVariant userData, + QString gameName, const QString& subModule); /** * @brief request a list of the files belonging to a mod * * @param gameName the game short name to support multiple game sources - * @param modID id of the mod caller is interested in (assumed to be for the current game) - * @param receiver the object to receive the result asynchronously via a signal (nxmFilesAvailable) + * @param modID id of the mod caller is interested in (assumed to be for the current + *game) + * @param receiver the object to receive the result asynchronously via a signal + *(nxmFilesAvailable) * @param userData user data to be returned with the result * @return int an id to identify the request **/ - int requestFiles(QString gameName, int modID, QObject *receiver, QVariant userData, const QString &subModule) + int requestFiles(QString gameName, int modID, QObject* receiver, QVariant userData, + const QString& subModule) { - return requestFiles(gameName, modID, receiver, userData, subModule, getGame(gameName)); + return requestFiles(gameName, modID, receiver, userData, subModule, + getGame(gameName)); } - /** * @brief request a list of the files belonging to a mod * * @param gameName the game short name to support multiple game sources * @param modID id of the mod caller is interested in - * @param receiver the object to receive the result asynchronously via a signal (nxmFilesAvailable) + * @param receiver the object to receive the result asynchronously via a signal + *(nxmFilesAvailable) * @param userData user data to be returned with the result * @param game the game with which the mods are associated * @return int an id to identify the request **/ - int requestFiles(QString gameName, int modID, QObject *receiver, QVariant userData, const QString &subModule, - MOBase::IPluginGame const *game); + int requestFiles(QString gameName, int modID, QObject* receiver, QVariant userData, + const QString& subModule, MOBase::IPluginGame const* game); /** * @brief request info about a single file of a mod * * @param gameName name of the game short name to request the download from - * @param modID id of the mod caller is interested in (assumed to be for the current game) + * @param modID id of the mod caller is interested in (assumed to be for the current + *game) * @param fileID id of the file the caller is interested in - * @param receiver the object to receive the result asynchronously via a signal (nxmFilesAvailable) + * @param receiver the object to receive the result asynchronously via a signal + *(nxmFilesAvailable) * @param userData user data to be returned with the result * @return int an id to identify the request **/ - int requestFileInfo(QString gameName, int modID, int fileID, QObject *receiver, QVariant userData, const QString &subModule); + int requestFileInfo(QString gameName, int modID, int fileID, QObject* receiver, + QVariant userData, const QString& subModule); /** * @brief request the download url of a file * * @param gameName the game short name to support multiple game sources - * @param modID id of the mod caller is interested in (assumed to be for the current game) + * @param modID id of the mod caller is interested in (assumed to be for the current + *game) * @param fileID id of the file the caller is interested in - * @param receiver the object to receive the result asynchronously via a signal (nxmFilesAvailable) + * @param receiver the object to receive the result asynchronously via a signal + *(nxmFilesAvailable) * @param userData user data to be returned with the result * @return int an id to identify the request **/ - int requestDownloadURL(QString gameName, int modID, int fileID, QObject *receiver, QVariant userData, const QString &subModule) + int requestDownloadURL(QString gameName, int modID, int fileID, QObject* receiver, + QVariant userData, const QString& subModule) { - return requestDownloadURL(gameName, modID, fileID, receiver, userData, subModule, getGame(gameName)); + return requestDownloadURL(gameName, modID, fileID, receiver, userData, subModule, + getGame(gameName)); } /** @@ -326,29 +369,35 @@ class NexusInterface : public QObject * @param gameName the game short name to support multiple game sources * @param modID id of the mod caller is interested in * @param fileID id of the file the caller is interested in - * @param receiver the object to receive the result asynchronously via a signal (nxmFilesAvailable) + * @param receiver the object to receive the result asynchronously via a signal + *(nxmFilesAvailable) * @param userData user data to be returned with the result * @param game the game with which the mods are associated * @return int an id to identify the request **/ - int requestDownloadURL(QString gameName, int modID, int fileID, QObject *receiver, QVariant userData, const QString &subModule, MOBase::IPluginGame const *game); - - - int requestEndorsementInfo(QObject *receiver, QVariant userData, const QString &subModule); + int requestDownloadURL(QString gameName, int modID, int fileID, QObject* receiver, + QVariant userData, const QString& subModule, + MOBase::IPluginGame const* game); + int requestEndorsementInfo(QObject* receiver, QVariant userData, + const QString& subModule); /** * @param gameName the game short name to support multiple game sources * @brief toggle endorsement state of the mod * @param modID id of the mod (assumed to be for the current game) * @param endorse true if the mod should be endorsed, false for un-endorse - * @param receiver the object to receive the result asynchronously via a signal (nxmFilesAvailable) + * @param receiver the object to receive the result asynchronously via a signal + * (nxmFilesAvailable) * @param userData user data to be returned with the result * @return int an id to identify the request */ - int requestToggleEndorsement(QString gameName, int modID, QString modVersion, bool endorse, QObject *receiver, QVariant userData, const QString &subModule) + int requestToggleEndorsement(QString gameName, int modID, QString modVersion, + bool endorse, QObject* receiver, QVariant userData, + const QString& subModule) { - return requestToggleEndorsement(gameName, modID, modVersion, endorse, receiver, userData, subModule, getGame(gameName)); + return requestToggleEndorsement(gameName, modID, modVersion, endorse, receiver, + userData, subModule, getGame(gameName)); } /** @@ -356,122 +405,140 @@ class NexusInterface : public QObject * @brief toggle endorsement state of the mod * @param modID id of the mod * @param endorse true if the mod should be endorsed, false for un-endorse - * @param receiver the object to receive the result asynchronously via a signal (nxmFilesAvailable) + * @param receiver the object to receive the result asynchronously via a signal + * (nxmFilesAvailable) + * @param userData user data to be returned with the result + * @param game the game with which the mods are associated + * @return int an id to identify the request + */ + int requestToggleEndorsement(QString gameName, int modID, QString modVersion, + bool endorse, QObject* receiver, QVariant userData, + const QString& subModule, + MOBase::IPluginGame const* game); + + int requestTrackingInfo(QObject* receiver, QVariant userData, + const QString& subModule); + + /** + * @param gameName the game short name to support multiple game sources + * @brief toggle tracking state of the mod + * @param modID id of the mod + * @param track true if the mod should be tracked, false for not tracked + * @param receiver the object to receive the result asynchronously via a signal + * (nxmFilesAvailable) * @param userData user data to be returned with the result * @param game the game with which the mods are associated * @return int an id to identify the request */ - int requestToggleEndorsement(QString gameName, int modID, QString modVersion, bool endorse, QObject *receiver, QVariant userData, const QString &subModule, - MOBase::IPluginGame const *game); - - int requestTrackingInfo(QObject *receiver, QVariant userData, const QString &subModule); - - /** - * @param gameName the game short name to support multiple game sources - * @brief toggle tracking state of the mod - * @param modID id of the mod - * @param track true if the mod should be tracked, false for not tracked - * @param receiver the object to receive the result asynchronously via a signal (nxmFilesAvailable) - * @param userData user data to be returned with the result - * @param game the game with which the mods are associated - * @return int an id to identify the request - */ - int requestToggleTracking(QString gameName, int modID, bool track, QObject *receiver, QVariant userData, const QString &subModule) + int requestToggleTracking(QString gameName, int modID, bool track, QObject* receiver, + QVariant userData, const QString& subModule) { - return requestToggleTracking(gameName, modID, track, receiver, userData, subModule, getGame(gameName)); + return requestToggleTracking(gameName, modID, track, receiver, userData, subModule, + getGame(gameName)); } /** - * @param gameName the game short name to support multiple game sources - * @brief toggle tracking state of the mod - * @param modID id of the mod - * @param track true if the mod should be tracked, false for not tracked - * @param receiver the object to receive the result asynchronously via a signal (nxmFilesAvailable) - * @param userData user data to be returned with the result - * @param game the game with which the mods are associated - * @return int an id to identify the request - */ - int requestToggleTracking(QString gameName, int modID, bool track, QObject *receiver, QVariant userData, const QString &subModule, - MOBase::IPluginGame const *game); - - /** - * @param gameName the game short name to support multiple game sources - * @brief toggle tracking state of the mod - * @param modID id of the mod - * @param track true if the mod should be tracked, false for not tracked - * @param receiver the object to receive the result asynchronously via a signal (nxmFilesAvailable) - * @param userData user data to be returned with the result - * @param game the game with which the mods are associated - * @return int an id to identify the request - */ - int requestGameInfo(QString gameName, QObject* receiver, QVariant userData, const QString& subModule) + * @param gameName the game short name to support multiple game sources + * @brief toggle tracking state of the mod + * @param modID id of the mod + * @param track true if the mod should be tracked, false for not tracked + * @param receiver the object to receive the result asynchronously via a signal + * (nxmFilesAvailable) + * @param userData user data to be returned with the result + * @param game the game with which the mods are associated + * @return int an id to identify the request + */ + int requestToggleTracking(QString gameName, int modID, bool track, QObject* receiver, + QVariant userData, const QString& subModule, + MOBase::IPluginGame const* game); + + /** + * @param gameName the game short name to support multiple game sources + * @brief toggle tracking state of the mod + * @param modID id of the mod + * @param track true if the mod should be tracked, false for not tracked + * @param receiver the object to receive the result asynchronously via a signal + * (nxmFilesAvailable) + * @param userData user data to be returned with the result + * @param game the game with which the mods are associated + * @return int an id to identify the request + */ + int requestGameInfo(QString gameName, QObject* receiver, QVariant userData, + const QString& subModule) { return requestGameInfo(gameName, receiver, userData, subModule, getGame(gameName)); } /** - * @param gameName the game short name to support multiple game sources - * @brief toggle tracking state of the mod - * @param modID id of the mod - * @param track true if the mod should be tracked, false for not tracked - * @param receiver the object to receive the result asynchronously via a signal (nxmFilesAvailable) - * @param userData user data to be returned with the result - * @param game the game with which the mods are associated - * @return int an id to identify the request - */ - int requestGameInfo(QString gameName, QObject* receiver, QVariant userData, const QString& subModule, - MOBase::IPluginGame const* game); + * @param gameName the game short name to support multiple game sources + * @brief toggle tracking state of the mod + * @param modID id of the mod + * @param track true if the mod should be tracked, false for not tracked + * @param receiver the object to receive the result asynchronously via a signal + * (nxmFilesAvailable) + * @param userData user data to be returned with the result + * @param game the game with which the mods are associated + * @return int an id to identify the request + */ + int requestGameInfo(QString gameName, QObject* receiver, QVariant userData, + const QString& subModule, MOBase::IPluginGame const* game); /** - * - */ - int requestInfoFromMd5(QString gameName, QByteArray &hash, QObject *receiver, QVariant userData, const QString &subModule) + * + */ + int requestInfoFromMd5(QString gameName, QByteArray& hash, QObject* receiver, + QVariant userData, const QString& subModule) { - return requestInfoFromMd5(gameName, hash, receiver, userData, subModule, getGame(gameName)); + return requestInfoFromMd5(gameName, hash, receiver, userData, subModule, + getGame(gameName)); } /** * */ - int requestInfoFromMd5(QString gameName, QByteArray &hash, QObject *receiver, QVariant userData, const QString &subModule, - MOBase::IPluginGame const *game); + int requestInfoFromMd5(QString gameName, QByteArray& hash, QObject* receiver, + QVariant userData, const QString& subModule, + MOBase::IPluginGame const* game); /** * @param directory the directory to store cache files **/ - void setCacheDirectory(const QString &directory); + void setCacheDirectory(const QString& directory); /** - * @brief called when the log-in completes. This was, requests waiting for the log-in can be run + * @brief called when the log-in completes. This was, requests waiting for the log-in + * can be run */ void loginCompleted(); - std::vector> getGameChoices(const MOBase::IPluginGame *game); + std::vector> + getGameChoices(const MOBase::IPluginGame* game); APIUserAccount getAPIUserAccount() const; APIStats getAPIStats() const; public: - /** * @brief guess the mod id from a filename as delivered by Nexus * @param fileName name of the file * @return the guessed mod id - * @note this currently doesn't fit well with the remaining interface but this is the best place for the function + * @note this currently doesn't fit well with the remaining interface but this is the + * best place for the function */ - static void interpretNexusFileName(const QString &fileName, QString &modName, int &modID, bool query); + static void interpretNexusFileName(const QString& fileName, QString& modName, + int& modID, bool query); /** * @brief get the currently managed game */ - MOBase::IPluginGame const *managedGame() const; + MOBase::IPluginGame const* managedGame() const; /** * @brief see if the passed URL is related to the current game * * Arguably, this should optionally take a gameplugin pointer */ - bool isURLGameRelated(QUrl const &url) const; + bool isURLGameRelated(QUrl const& url) const; /** * @brief Get the nexus page for the current game @@ -492,30 +559,42 @@ class NexusInterface : public QObject * @param url * @return */ - bool isModURL(int modID, QString const &url) const; + bool isModURL(int modID, QString const& url) const; - void setPluginContainer(PluginContainer *pluginContainer); + void setPluginContainer(PluginContainer* pluginContainer); signals: - void requestNXMDownload(const QString &url); + void requestNXMDownload(const QString& url); void needLogin(); - void nxmDescriptionAvailable(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID); - void nxmModInfoAvailable(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID); - void nxmUpdateInfoAvailable(QString gameName, QVariant userData, QVariant resultData, int requestID); - void nxmUpdatesAvailable(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID); - void nxmFilesAvailable(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID); - void nxmFileInfoAvailable(QString gameName, int modID, int fileID, QVariant userData, QVariant resultData, int requestID); - void nxmFileInfoFromMd5Available(QString gameName, QVariant userData, QVariant resultData, int requestID); - void nxmDownloadURLsAvailable(QString gameName, int modID, int fileID, QVariant userData, QVariant resultData, int requestID); + void nxmDescriptionAvailable(QString gameName, int modID, QVariant userData, + QVariant resultData, int requestID); + void nxmModInfoAvailable(QString gameName, int modID, QVariant userData, + QVariant resultData, int requestID); + void nxmUpdateInfoAvailable(QString gameName, QVariant userData, QVariant resultData, + int requestID); + void nxmUpdatesAvailable(QString gameName, int modID, QVariant userData, + QVariant resultData, int requestID); + void nxmFilesAvailable(QString gameName, int modID, QVariant userData, + QVariant resultData, int requestID); + void nxmFileInfoAvailable(QString gameName, int modID, int fileID, QVariant userData, + QVariant resultData, int requestID); + void nxmFileInfoFromMd5Available(QString gameName, QVariant userData, + QVariant resultData, int requestID); + void nxmDownloadURLsAvailable(QString gameName, int modID, int fileID, + QVariant userData, QVariant resultData, int requestID); void nxmEndorsementsAvailable(QVariant userData, QVariant resultData, int requestID); - void nxmEndorsementToggled(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID); + void nxmEndorsementToggled(QString gameName, int modID, QVariant userData, + QVariant resultData, int requestID); void nxmTrackedModsAvailable(QVariant userData, QVariant resultData, int requestID); - void nxmTrackingToggled(QString gameName, int modID, QVariant userData, bool tracked, int requestID); - void nxmGameInfoAvailable(QString gameName, QVariant userData, QVariant resultData, int requestID); - void nxmRequestFailed(QString gameName, int modID, int fileID, QVariant userData, int requestID, int errorCode, const QString &errorString); + void nxmTrackingToggled(QString gameName, int modID, QVariant userData, bool tracked, + int requestID); + void nxmGameInfoAvailable(QString gameName, QVariant userData, QVariant resultData, + int requestID); + void nxmRequestFailed(QString gameName, int modID, int fileID, QVariant userData, + int requestID, int errorCode, const QString& errorString); void requestsChanged(const APIStats& stats, const APIUserAccount& user); public slots: @@ -528,19 +607,20 @@ private slots: void requestError(QNetworkReply::NetworkError error); void requestTimeout(); - void downloadRequestedNXM(const QString &url); + void downloadRequestedNXM(const QString& url); void fakeFiles(); private: - - struct NXMRequestInfo { + struct NXMRequestInfo + { int m_ModID; QString m_ModVersion; std::vector m_ModIDList; int m_FileID; - QNetworkReply *m_Reply; - enum Type { + QNetworkReply* m_Reply; + enum Type + { TYPE_DESCRIPTION, TYPE_MODINFO, TYPE_FILES, @@ -557,7 +637,7 @@ private slots: } m_Type; UpdatePeriod m_UpdatePeriod; QVariant m_UserData; - QTimer *m_Timeout; + QTimer* m_Timeout; QString m_URL; QString m_SubModule; QString m_GameName; @@ -567,16 +647,22 @@ private slots: int m_Endorse; int m_Track; QByteArray m_Hash; - QMap> m_AllowedErrors; + QMap> m_AllowedErrors; bool m_IgnoreGenericErrorHandler; - NXMRequestInfo(int modID, Type type, QVariant userData, const QString &subModule, MOBase::IPluginGame const *game); - NXMRequestInfo(int modID, QString modVersion, Type type, QVariant userData, const QString &subModule, MOBase::IPluginGame const *game); - NXMRequestInfo(int modID, int fileID, Type type, QVariant userData, const QString &subModule, MOBase::IPluginGame const *game); - NXMRequestInfo(Type type, QVariant userData, const QString &subModule, MOBase::IPluginGame const *game); + NXMRequestInfo(int modID, Type type, QVariant userData, const QString& subModule, + MOBase::IPluginGame const* game); + NXMRequestInfo(int modID, QString modVersion, Type type, QVariant userData, + const QString& subModule, MOBase::IPluginGame const* game); + NXMRequestInfo(int modID, int fileID, Type type, QVariant userData, + const QString& subModule, MOBase::IPluginGame const* game); + NXMRequestInfo(Type type, QVariant userData, const QString& subModule, + MOBase::IPluginGame const* game); NXMRequestInfo(Type type, QVariant userData, const QString &subModule); - NXMRequestInfo(UpdatePeriod period, Type type, QVariant userData, const QString &subModule, MOBase::IPluginGame const *game); - NXMRequestInfo(QByteArray &hash, Type type, QVariant userData, const QString &subModule, MOBase::IPluginGame const *game); + NXMRequestInfo(UpdatePeriod period, Type type, QVariant userData, + const QString& subModule, MOBase::IPluginGame const* game); + NXMRequestInfo(QByteArray& hash, Type type, QVariant userData, + const QString& subModule, MOBase::IPluginGame const* game); private: static QAtomicInt s_NextID; @@ -587,17 +673,17 @@ private slots: private: void nextRequest(); void requestFinished(std::list::iterator iter); - MOBase::IPluginGame *getGame(QString gameName) const; + MOBase::IPluginGame* getGame(QString gameName) const; QString getOldModsURL(QString gameName) const; private: - QNetworkDiskCache *m_DiskCache; - NXMAccessManager *m_AccessManager; + QNetworkDiskCache* m_DiskCache; + NXMAccessManager* m_AccessManager; std::list m_ActiveRequest; QQueue m_RequestQueue; MOBase::VersionInfo m_MOVersion; - PluginContainer *m_PluginContainer; + PluginContainer* m_PluginContainer; APIUserAccount m_User; }; -#endif // NEXUSINTERFACE_H +#endif // NEXUSINTERFACE_H diff --git a/src/noeditdelegate.cpp b/src/noeditdelegate.cpp index d5147b9f2..3e0ebcb02 100644 --- a/src/noeditdelegate.cpp +++ b/src/noeditdelegate.cpp @@ -1,10 +1,9 @@ -#include "noeditdelegate.h" - -NoEditDelegate::NoEditDelegate(QObject *parent) - : QStyledItemDelegate(parent) -{ -} - -QWidget *NoEditDelegate::createEditor(QWidget*, const QStyleOptionViewItem&, const QModelIndex&) const { - return nullptr; -} +#include "noeditdelegate.h" + +NoEditDelegate::NoEditDelegate(QObject* parent) : QStyledItemDelegate(parent) {} + +QWidget* NoEditDelegate::createEditor(QWidget*, const QStyleOptionViewItem&, + const QModelIndex&) const +{ + return nullptr; +} diff --git a/src/noeditdelegate.h b/src/noeditdelegate.h index 6fd5ba76e..88d48aa49 100644 --- a/src/noeditdelegate.h +++ b/src/noeditdelegate.h @@ -1,12 +1,14 @@ -#ifndef NOEDITDELEGATE_H -#define NOEDITDELEGATE_H - -#include - -class NoEditDelegate: public QStyledItemDelegate { -public: - NoEditDelegate(QObject *parent = nullptr); - virtual QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const; -}; - -#endif // NOEDITDELEGATE_H +#ifndef NOEDITDELEGATE_H +#define NOEDITDELEGATE_H + +#include + +class NoEditDelegate : public QStyledItemDelegate +{ +public: + NoEditDelegate(QObject* parent = nullptr); + virtual QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, + const QModelIndex& index) const; +}; + +#endif // NOEDITDELEGATE_H diff --git a/src/nxmaccessmanager.cpp b/src/nxmaccessmanager.cpp index f98f21714..3efdcda01 100644 --- a/src/nxmaccessmanager.cpp +++ b/src/nxmaccessmanager.cpp @@ -21,40 +21,44 @@ along with Mod Organizer. If not, see . #include "iplugingame.h" #include "nexusinterface.h" #include "nxmurl.h" +#include "persistentcookiejar.h" #include "report.h" -#include "utility.h" #include "selfupdater.h" -#include "persistentcookiejar.h" #include "settings.h" +#include "utility.h" +#include +#include +#include +#include #include -#include -#include -#include #include #include -#include -#include -#include +#include +#include +#include #include -#include -#include +#include using namespace MOBase; using namespace std::chrono_literals; const QString NexusBaseUrl("https://api.nexusmods.com/v1"); const QString NexusSSO("wss://sso.nexusmods.com"); -const QString NexusSSOPage("https://www.nexusmods.com/sso?id=%1&application=modorganizer2"); - +const QString + NexusSSOPage("https://www.nexusmods.com/sso?id=%1&application=modorganizer2"); ValidationProgressDialog::ValidationProgressDialog(Settings* s, NexusKeyValidator& v) - : m_settings(s), m_validator(v), m_updateTimer(nullptr), m_first(true) + : m_settings(s), m_validator(v), m_updateTimer(nullptr), m_first(true) { ui.reset(new Ui::ValidationProgressDialog); ui->setupUi(this); - connect(ui->hide, &QPushButton::clicked, [&]{ onHide(); }); - connect(ui->cancel, &QPushButton::clicked, [&]{ onCancel(); }); + connect(ui->hide, &QPushButton::clicked, [&] { + onHide(); + }); + connect(ui->cancel, &QPushButton::clicked, [&] { + onCancel(); + }); } void ValidationProgressDialog::setParentWidget(QWidget* w) @@ -75,7 +79,9 @@ void ValidationProgressDialog::start() { if (!m_updateTimer) { m_updateTimer = new QTimer(this); - connect(m_updateTimer, &QTimer::timeout, [&]{ onTimer(); }); + connect(m_updateTimer, &QTimer::timeout, [&] { + onTimer(); + }); m_updateTimer->setInterval(100ms); } @@ -138,7 +144,7 @@ void ValidationProgressDialog::updateProgress() ui->progress->setRange(0, 0); } - if (const auto* a=m_validator.lastAttempt()) { + if (const auto* a = m_validator.lastAttempt()) { ui->label->setText(a->message() + ". " + tr("Trying again...")); } else if (current) { ui->label->setText(tr("Connecting to Nexus...")); @@ -147,77 +153,74 @@ void ValidationProgressDialog::updateProgress() } } - -NexusSSOLogin::NexusSSOLogin() - : m_keyReceived(false), m_active(false) +NexusSSOLogin::NexusSSOLogin() : m_keyReceived(false), m_active(false) { m_timeout.setInterval(10s); m_timeout.setSingleShot(true); - QObject::connect( - &m_socket, &QWebSocket::connected, - [&]{ onConnected(); }); + QObject::connect(&m_socket, &QWebSocket::connected, [&] { + onConnected(); + }); - QObject::connect( - &m_socket, qOverload(&QWebSocket::error), - [&](auto&& e){ onError(e); }); + QObject::connect(&m_socket, + qOverload(&QWebSocket::error), + [&](auto&& e) { + onError(e); + }); - QObject::connect( - &m_socket, &QWebSocket::sslErrors, - [&](auto&& errors){ onSslErrors(errors); }); + QObject::connect(&m_socket, &QWebSocket::sslErrors, [&](auto&& errors) { + onSslErrors(errors); + }); - QObject::connect( - &m_socket, &QWebSocket::textMessageReceived, - [&](auto&& s){ onMessage(s); }); + QObject::connect(&m_socket, &QWebSocket::textMessageReceived, [&](auto&& s) { + onMessage(s); + }); - QObject::connect( - &m_socket, &QWebSocket::disconnected, - [&]{ onDisconnected(); }); + QObject::connect(&m_socket, &QWebSocket::disconnected, [&] { + onDisconnected(); + }); - QObject::connect(&m_timeout, &QTimer::timeout, [&]{ onTimeout(); }); + QObject::connect(&m_timeout, &QTimer::timeout, [&] { + onTimeout(); + }); } QString NexusSSOLogin::stateToString(States s, const QString& e) { - switch (s) - { - case ConnectingToSSO: - return QObject::tr("Connecting to Nexus..."); - - case WaitingForToken: - return QObject::tr("Waiting for Nexus..."); - - case WaitingForBrowser: - return - QObject::tr("Opened Nexus in browser.") + "\n" + - QObject::tr("Switch to your browser and accept the request."); - - case Finished: - return QObject::tr("Finished."); - - case Timeout: - return - QObject::tr("No answer from Nexus.") + "\n" + - QObject::tr("A firewall might be blocking Mod Organizer."); - - case ClosedByRemote: - return - QObject::tr("Nexus closed the connection.") + "\n" + - QObject::tr("A firewall might be blocking Mod Organizer."); - - case Cancelled: - return QObject::tr("Cancelled."); - - case Error: // fall-through - default: - { - if (e.isEmpty()) { - return QString("%1").arg(s); - } else { - return e; - } + switch (s) { + case ConnectingToSSO: + return QObject::tr("Connecting to Nexus..."); + + case WaitingForToken: + return QObject::tr("Waiting for Nexus..."); + + case WaitingForBrowser: + return QObject::tr("Opened Nexus in browser.") + "\n" + + QObject::tr("Switch to your browser and accept the request."); + + case Finished: + return QObject::tr("Finished."); + + case Timeout: + return QObject::tr("No answer from Nexus.") + "\n" + + QObject::tr("A firewall might be blocking Mod Organizer."); + + case ClosedByRemote: + return QObject::tr("Nexus closed the connection.") + "\n" + + QObject::tr("A firewall might be blocking Mod Organizer."); + + case Cancelled: + return QObject::tr("Cancelled."); + + case Error: // fall-through + default: { + if (e.isEmpty()) { + return QString("%1").arg(s); + } else { + return e; } } + } } void NexusSSOLogin::start() @@ -272,7 +275,7 @@ void NexusSSOLogin::onConnected() boost::uuids::random_generator generator; boost::uuids::uuid sessionId = generator(); - m_guid = boost::uuids::to_string(sessionId).c_str(); + m_guid = boost::uuids::to_string(sessionId).c_str(); QJsonObject data; data.insert(QString("id"), QJsonValue(m_guid)); @@ -285,13 +288,13 @@ void NexusSSOLogin::onConnected() void NexusSSOLogin::onMessage(const QString& s) { const QJsonDocument doc = QJsonDocument::fromJson(s.toUtf8()); - const QVariantMap root = doc.object().toVariantMap(); + const QVariantMap root = doc.object().toVariantMap(); if (!root["success"].toBool()) { close(); setState(Error, QString("There was a problem with SSO initialization: %1") - .arg(root["error"].toString())); + .arg(root["error"].toString())); return; } @@ -355,14 +358,15 @@ void NexusSSOLogin::onTimeout() setState(Timeout); } - ValidationAttempt::ValidationAttempt(std::chrono::seconds timeout) - : m_reply(nullptr), m_result(None) + : m_reply(nullptr), m_result(None) { m_timeout.setSingleShot(true); m_timeout.setInterval(timeout); - QObject::connect(&m_timeout, &QTimer::timeout, [&]{ onTimeout(); }); + QObject::connect(&m_timeout, &QTimer::timeout, [&] { + onTimeout(); + }); } void ValidationAttempt::start(NXMAccessManager& m, const QString& key) @@ -374,19 +378,19 @@ void ValidationAttempt::start(NXMAccessManager& m, const QString& key) m_elapsed.start(); m_timeout.start(); - log::debug( - "nexus: attempt started with timeout of {} seconds", timeout().count()); + log::debug("nexus: attempt started with timeout of {} seconds", timeout().count()); } -bool ValidationAttempt::sendRequest( - NXMAccessManager& m, const QString& key) +bool ValidationAttempt::sendRequest(NXMAccessManager& m, const QString& key) { const QString requestUrl(NexusBaseUrl + "/users/validate"); QNetworkRequest request(requestUrl); request.setRawHeader("APIKEY", key.toUtf8()); - request.setHeader(QNetworkRequest::KnownHeaders::UserAgentHeader, m.userAgent().toUtf8()); - request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json"); + request.setHeader(QNetworkRequest::KnownHeaders::UserAgentHeader, + m.userAgent().toUtf8()); + request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, + "application/json"); request.setRawHeader("Protocol-Version", "1.0.0"); request.setRawHeader("Application-Name", "MO2"); request.setRawHeader("Application-Version", m.MOVersion().toUtf8()); @@ -398,13 +402,13 @@ bool ValidationAttempt::sendRequest( return false; } - QObject::connect( - m_reply, &QNetworkReply::finished, - [&]{ onFinished(); }); + QObject::connect(m_reply, &QNetworkReply::finished, [&] { + onFinished(); + }); - QObject::connect( - m_reply, &QNetworkReply::sslErrors, - [&](auto&& errors){ onSslErrors(errors); }); + QObject::connect(m_reply, &QNetworkReply::sslErrors, [&](auto&& errors) { + onSslErrors(errors); + }); return true; } @@ -443,7 +447,7 @@ const QString& ValidationAttempt::message() const std::chrono::seconds ValidationAttempt::timeout() const { return std::chrono::duration_cast( - m_timeout.intervalAsDuration()); + m_timeout.intervalAsDuration()); } QElapsedTimer ValidationAttempt::elapsed() const @@ -466,8 +470,8 @@ void ValidationAttempt::onFinished() return; } - const auto code = m_reply->attribute( - QNetworkRequest::HttpStatusCodeAttribute).toInt(); + const auto code = + m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); if (code == 0) { // request wasn't even sent @@ -476,8 +480,8 @@ void ValidationAttempt::onFinished() return; } - const auto doc = QJsonDocument::fromJson(m_reply->readAll()); - const auto headers = m_reply->rawHeaderPairs(); + const auto doc = QJsonDocument::fromJson(m_reply->readAll()); + const auto headers = m_reply->rawHeaderPairs(); const auto httpError = m_reply->errorString(); const QJsonObject data = doc.object(); @@ -516,8 +520,8 @@ void ValidationAttempt::onFinished() return; } - const int id = data.value("user_id").toInt(); - const QString key = data.value("key").toString(); + const int id = data.value("user_id").toInt(); + const QString key = data.value("key").toString(); const QString name = data.value("name").toString(); const bool premium = data.value("is_premium").toBool(); @@ -526,12 +530,13 @@ void ValidationAttempt::onFinished() return; } - const auto user = APIUserAccount() - .apiKey(key) - .id(QString("%1").arg(id)) - .name(name) - .type(premium ? APIUserAccountTypes::Premium : APIUserAccountTypes::Regular) - .limits(NexusInterface::parseLimits(headers)); + const auto user = + APIUserAccount() + .apiKey(key) + .id(QString("%1").arg(id)) + .name(name) + .type(premium ? APIUserAccountTypes::Premium : APIUserAccountTypes::Regular) + .limits(NexusInterface::parseLimits(headers)); setSuccess(user); } @@ -561,7 +566,7 @@ void ValidationAttempt::setFailure(Result r, const QString& error) cleanup(); - m_result = r; + m_result = r; m_message = error; if (failure) { @@ -574,7 +579,7 @@ void ValidationAttempt::setSuccess(const APIUserAccount& user) log::debug("nexus connection successful"); cleanup(); - m_result = Success; + m_result = Success; m_message = ""; if (success) { @@ -593,11 +598,9 @@ void ValidationAttempt::cleanup() } } - NexusKeyValidator::NexusKeyValidator(Settings* s, NXMAccessManager& am) - : m_settings(s), m_manager(am) -{ -} + : m_settings(s), m_manager(am) +{} NexusKeyValidator::~NexusKeyValidator() { @@ -624,26 +627,23 @@ void NexusKeyValidator::start(const QString& key, Behaviour b) const auto timeouts = getTimeouts(); - switch (b) - { - case OneShot: - { - createAttempts({timeouts[0]}); - break; - } + switch (b) { + case OneShot: { + createAttempts({timeouts[0]}); + break; + } - case Retry: - { - createAttempts(timeouts); - break; - } + case Retry: { + createAttempts(timeouts); + break; + } } nextTry(); } void NexusKeyValidator::createAttempts( - const std::vector& timeouts) + const std::vector& timeouts) { m_attempts.clear(); @@ -702,8 +702,12 @@ bool NexusKeyValidator::nextTry() { for (auto&& a : m_attempts) { if (!a->done()) { - a->success = [&](auto&& user){ onAttemptSuccess(*a, user); }; - a->failure = [&]{ onAttemptFailure(*a); }; + a->success = [&](auto&& user) { + onAttemptSuccess(*a, user); + }; + a->failure = [&] { + onAttemptFailure(*a); + }; a->start(m_manager, m_key); return true; @@ -714,8 +718,8 @@ bool NexusKeyValidator::nextTry() return false; } -void NexusKeyValidator::onAttemptSuccess( - const ValidationAttempt& a, const APIUserAccount& u) +void NexusKeyValidator::onAttemptSuccess(const ValidationAttempt& a, + const APIUserAccount& u) { if (attemptFinished) { attemptFinished(a); @@ -730,48 +734,40 @@ void NexusKeyValidator::onAttemptFailure(const ValidationAttempt& a) attemptFinished(a); } - switch (a.result()) - { - case ValidationAttempt::SoftError: - { - if (!nextTry()) { - setFinished(a.result(), a.message(), {}); - } - - break; - } - - case ValidationAttempt::HardError: - { - cancel(); + switch (a.result()) { + case ValidationAttempt::SoftError: { + if (!nextTry()) { setFinished(a.result(), a.message(), {}); - break; } - case ValidationAttempt::Cancelled: - { - setFinished(ValidationAttempt::Cancelled, QObject::tr("Cancelled"), {}); - break; - } + break; + } + + case ValidationAttempt::HardError: { + cancel(); + setFinished(a.result(), a.message(), {}); + break; + } + + case ValidationAttempt::Cancelled: { + setFinished(ValidationAttempt::Cancelled, QObject::tr("Cancelled"), {}); + break; + } } } -void NexusKeyValidator::setFinished( - ValidationAttempt::Result r, const QString& message, - std::optional user) +void NexusKeyValidator::setFinished(ValidationAttempt::Result r, const QString& message, + std::optional user) { if (finished) { finished(r, message, user); } } - -NXMAccessManager::NXMAccessManager(QObject *parent, Settings* s, const QString &moVersion) - : QNetworkAccessManager(parent) - , m_Settings(s) - , m_MOVersion(moVersion) - , m_validator(s, *this) - , m_validationState(NotChecked) +NXMAccessManager::NXMAccessManager(QObject* parent, Settings* s, + const QString& moVersion) + : QNetworkAccessManager(parent), m_Settings(s), m_MOVersion(moVersion), + m_validator(s, *this), m_validationState(NotChecked) { m_validator.finished = [&](auto&& r, auto&& m, auto&& u) { onValidatorFinished(r, m, u); @@ -783,7 +779,7 @@ NXMAccessManager::NXMAccessManager(QObject *parent, Settings* s, const QString & if (m_Settings) { setCookieJar(new PersistentCookieJar(QDir::fromNativeSeparators( - m_Settings->paths().cache() + "/nexus_cookies.dat"))); + m_Settings->paths().cache() + "/nexus_cookies.dat"))); } } @@ -799,9 +795,9 @@ void NXMAccessManager::setTopLevelWidget(QWidget* w) } } -QNetworkReply *NXMAccessManager::createRequest( - QNetworkAccessManager::Operation operation, const QNetworkRequest &request, - QIODevice *device) +QNetworkReply* +NXMAccessManager::createRequest(QNetworkAccessManager::Operation operation, + const QNetworkRequest& request, QIODevice* device) { if (request.url().scheme() != "nxm") { return QNetworkAccessManager::createRequest(operation, request, device); @@ -813,7 +809,8 @@ QNetworkReply *NXMAccessManager::createRequest( return QNetworkAccessManager::createRequest(QNetworkAccessManager::GetOperation, QNetworkRequest(QUrl())); } else if (operation == PostOperation) { - return QNetworkAccessManager::createRequest(operation, request, device);; + return QNetworkAccessManager::createRequest(operation, request, device); + ; } else { return QNetworkAccessManager::createRequest(operation, request, device); } @@ -822,16 +819,15 @@ QNetworkReply *NXMAccessManager::createRequest( void NXMAccessManager::showCookies() const { QUrl url(NexusBaseUrl + "/"); - for (const QNetworkCookie &cookie : cookieJar()->cookiesForUrl(url)) { - log::debug("{} - {} (expires: {})", - cookie.name().constData(), cookie.value().constData(), - cookie.expirationDate().toString()); + for (const QNetworkCookie& cookie : cookieJar()->cookiesForUrl(url)) { + log::debug("{} - {} (expires: {})", cookie.name().constData(), + cookie.value().constData(), cookie.expirationDate().toString()); } } void NXMAccessManager::clearCookies() { - PersistentCookieJar *jar = qobject_cast(cookieJar()); + PersistentCookieJar* jar = qobject_cast(cookieJar()); if (jar != nullptr) { jar->clear(); } else { @@ -851,9 +847,9 @@ void NXMAccessManager::startValidationCheck(const QString& key) } } -void NXMAccessManager::onValidatorFinished( - ValidationAttempt::Result r, const QString& message, - std::optional user) +void NXMAccessManager::onValidatorFinished(ValidationAttempt::Result r, + const QString& message, + std::optional user) { stopProgress(); @@ -874,23 +870,20 @@ void NXMAccessManager::onValidatorFinished( void NXMAccessManager::onValidatorAttemptFinished(const ValidationAttempt& a) { if (!m_ProgressDialog) { - switch (a.result()) - { - case ValidationAttempt::SoftError: - case ValidationAttempt::HardError: - { - startProgress(); - break; - } + switch (a.result()) { + case ValidationAttempt::SoftError: + case ValidationAttempt::HardError: { + startProgress(); + break; + } - case ValidationAttempt::None: - case ValidationAttempt::Success: - case ValidationAttempt::Cancelled: - default: - { - // don't show the dialog - break; - } + case ValidationAttempt::None: + case ValidationAttempt::Success: + case ValidationAttempt::Cancelled: + default: { + // don't show the dialog + break; + } } } } @@ -923,7 +916,7 @@ bool NXMAccessManager::validateWaiting() const return m_validator.isActive(); } -void NXMAccessManager::apiCheck(const QString &apiKey, bool force) +void NXMAccessManager::apiCheck(const QString& apiKey, bool force) { if (m_validator.isActive()) { return; @@ -951,21 +944,24 @@ const QString& NXMAccessManager::MOVersion() const return m_MOVersion; } -QString NXMAccessManager::userAgent(const QString &subModule) const +QString NXMAccessManager::userAgent(const QString& subModule) const { QStringList comments; QString os; if (QSysInfo::productType() == "windows") - comments << ((QSysInfo::kernelType() == "winnt") ? "Windows_NT " : "Windows ") + QSysInfo::kernelVersion(); + comments << ((QSysInfo::kernelType() == "winnt") ? "Windows_NT " : "Windows ") + + QSysInfo::kernelVersion(); else comments << QSysInfo::kernelType().left(1).toUpper() + QSysInfo::kernelType().mid(1) - << QSysInfo::productType().left(1).toUpper() + QSysInfo::kernelType().mid(1) + " " + QSysInfo::productVersion(); + << QSysInfo::productType().left(1).toUpper() + + QSysInfo::kernelType().mid(1) + " " + QSysInfo::productVersion(); if (!subModule.isEmpty()) { comments << "module: " + subModule; } comments << ((QSysInfo::buildCpuArchitecture() == "x86_64") ? "x64" : "x86"); - return QString("Mod Organizer/%1 (%2) Qt/%3").arg(m_MOVersion, comments.join("; "), qVersion()); + return QString("Mod Organizer/%1 (%2) Qt/%3") + .arg(m_MOVersion, comments.join("; "), qVersion()); } void NXMAccessManager::clearApiKey() diff --git a/src/nxmaccessmanager.h b/src/nxmaccessmanager.h index 0812be408..79371a457 100644 --- a/src/nxmaccessmanager.h +++ b/src/nxmaccessmanager.h @@ -22,16 +22,19 @@ along with Mod Organizer. If not, see . #include "apiuseraccount.h" #include "ui_validationprogressdialog.h" +#include +#include #include -#include #include #include -#include -#include +#include #include #include -namespace MOBase { class IPluginGame; } +namespace MOBase +{ +class IPluginGame; +} class NXMAccessManager; class Settings; @@ -50,8 +53,8 @@ class NexusSSOLogin Error }; - std::function keyChanged; - std::function stateChanged; + std::function keyChanged; + std::function stateChanged; static QString stateToString(States s, const QString& e); @@ -69,7 +72,7 @@ class NexusSSOLogin bool m_active; QTimer m_timeout; - void setState(States s, const QString& error={}); + void setState(States s, const QString& error = {}); void close(); void abort(); @@ -82,7 +85,6 @@ class NexusSSOLogin void onTimeout(); }; - class ValidationAttempt { public: @@ -95,11 +97,11 @@ class ValidationAttempt Cancelled }; - std::function success; - std::function failure; + std::function success; + std::function failure; ValidationAttempt(std::chrono::seconds timeout); - ValidationAttempt(const ValidationAttempt&) = delete; + ValidationAttempt(const ValidationAttempt&) = delete; ValidationAttempt& operator=(const ValidationAttempt&) = delete; void start(NXMAccessManager& m, const QString& key); @@ -130,7 +132,6 @@ class ValidationAttempt void cleanup(); }; - class NexusKeyValidator { public: @@ -140,12 +141,11 @@ class NexusKeyValidator Retry }; - using FinishedCallback = void ( - ValidationAttempt::Result, const QString&, - std::optional); + using FinishedCallback = void(ValidationAttempt::Result, const QString&, + std::optional); std::function finished; - std::function attemptFinished; + std::function attemptFinished; NexusKeyValidator(Settings* s, NXMAccessManager& am); ~NexusKeyValidator(); @@ -170,12 +170,10 @@ class NexusKeyValidator void onAttemptSuccess(const ValidationAttempt& a, const APIUserAccount& u); void onAttemptFailure(const ValidationAttempt& a); - void setFinished( - ValidationAttempt::Result r, const QString& message, - std::optional user); + void setFinished(ValidationAttempt::Result r, const QString& message, + std::optional user); }; - class ValidationProgressDialog : public QDialog { Q_OBJECT; @@ -205,7 +203,6 @@ class ValidationProgressDialog : public QDialog void updateProgress(); }; - /** * @brief access manager extended to handle nxm links **/ @@ -213,7 +210,7 @@ class NXMAccessManager : public QNetworkAccessManager { Q_OBJECT public: - NXMAccessManager(QObject *parent, Settings* s, const QString &moVersion); + NXMAccessManager(QObject* parent, Settings* s, const QString& moVersion); void setTopLevelWidget(QWidget* w); @@ -222,13 +219,13 @@ class NXMAccessManager : public QNetworkAccessManager bool validateAttempted() const; bool validateWaiting() const; - void apiCheck(const QString &apiKey, bool force=false); + void apiCheck(const QString& apiKey, bool force = false); void showCookies() const; void clearCookies(); - QString userAgent(const QString &subModule = QString()) const; + QString userAgent(const QString& subModule = QString()) const; const QString& MOVersion() const; void clearApiKey(); @@ -242,22 +239,22 @@ class NXMAccessManager : public QNetworkAccessManager * * @param url the nxm-link **/ - void requestNXMDownload(const QString &url); + void requestNXMDownload(const QString& url); /** * @brief emitted after a successful login or if login was not necessary * - * @param necessary true if a login was necessary and succeeded, false if the user is still logged in + * @param necessary true if a login was necessary and succeeded, false if the user is + *still logged in **/ void validateSuccessful(bool necessary); - void validateFailed(const QString &message); + void validateFailed(const QString& message); void credentialsReceived(const APIUserAccount& user); protected: - - virtual QNetworkReply *createRequest( - QNetworkAccessManager::Operation operation, const QNetworkRequest &request, - QIODevice *device); + virtual QNetworkReply* createRequest(QNetworkAccessManager::Operation operation, + const QNetworkRequest& request, + QIODevice* device); private: enum States @@ -276,9 +273,8 @@ class NXMAccessManager : public QNetworkAccessManager void startValidationCheck(const QString& key); - void onValidatorFinished( - ValidationAttempt::Result r, const QString& message, - std::optional); + void onValidatorFinished(ValidationAttempt::Result r, const QString& message, + std::optional); void onValidatorAttemptFinished(const ValidationAttempt& a); @@ -286,4 +282,4 @@ class NXMAccessManager : public QNetworkAccessManager void stopProgress(); }; -#endif // NXMACCESSMANAGER_H +#endif // NXMACCESSMANAGER_H diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 3a959cb38..b92c6c6f4 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -1,42 +1,42 @@ #include "organizercore.h" +#include "credentialsdialog.h" #include "delayedfilewriter.h" +#include "directoryrefresher.h" +#include "env.h" +#include "envfs.h" +#include "envmodule.h" +#include "filedialogmemory.h" #include "guessedvalue.h" #include "imodinterface.h" #include "imoinfo.h" +#include "instancemanager.h" #include "iplugingame.h" #include "iuserinterface.h" #include "messagedialog.h" #include "modlistsortproxy.h" #include "modrepositoryfileinfo.h" #include "nexusinterface.h" +#include "nxmaccessmanager.h" #include "plugincontainer.h" +#include "previewdialog.h" #include "profile.h" -#include "credentialsdialog.h" -#include "filedialogmemory.h" +#include "shared/appconfig.h" +#include "shared/directoryentry.h" +#include "shared/fileentry.h" +#include "shared/filesorigin.h" +#include "shared/util.h" #include "spawn.h" #include "syncoverwritedialog.h" -#include "nxmaccessmanager.h" -#include +#include "virtualfiletree.h" #include +#include #include -#include -#include -#include -#include "shared/appconfig.h" -#include #include -#include "instancemanager.h" +#include +#include #include -#include "previewdialog.h" -#include "env.h" -#include "envmodule.h" -#include "envfs.h" -#include "directoryrefresher.h" -#include "virtualfiletree.h" -#include "shared/directoryentry.h" -#include "shared/filesorigin.h" -#include "shared/fileentry.h" -#include "shared/util.h" +#include +#include #include #include @@ -50,23 +50,23 @@ #include #include -#include // for qUtf8Printable, etc +#include // for qUtf8Printable, etc #include #include +#include // for _tcsicmp #include -#include // for _tcsicmp #include #include -#include // for memset, wcsrchr +#include // for memset, wcsrchr +#include #include #include -#include #include #include -#include //for wstring +#include //for wstring #include #include @@ -87,22 +87,18 @@ QStringList toStringList(InputIterator current, InputIterator end) return result; } - -OrganizerCore::OrganizerCore(Settings &settings) - : m_UserInterface(nullptr) - , m_PluginContainer(nullptr) - , m_CurrentProfile(nullptr) - , m_Settings(settings) - , m_Updater(&NexusInterface::instance()) - , m_ModList(m_PluginContainer, this) - , m_PluginList(*this) - , m_DirectoryRefresher(new DirectoryRefresher(settings.refreshThreadCount())) - , m_DirectoryStructure(new DirectoryEntry(L"data", nullptr, 0)) - , m_VirtualFileTree([this]() { return VirtualFileTree::makeTree(m_DirectoryStructure); }) - , m_DownloadManager(&NexusInterface::instance(), this) - , m_DirectoryUpdate(false) - , m_ArchivesInit(false) - , m_PluginListsWriter(std::bind(&OrganizerCore::savePluginList, this)) +OrganizerCore::OrganizerCore(Settings& settings) + : m_UserInterface(nullptr), m_PluginContainer(nullptr), m_CurrentProfile(nullptr), + m_Settings(settings), m_Updater(&NexusInterface::instance()), + m_ModList(m_PluginContainer, this), m_PluginList(*this), + m_DirectoryRefresher(new DirectoryRefresher(settings.refreshThreadCount())), + m_DirectoryStructure(new DirectoryEntry(L"data", nullptr, 0)), + m_VirtualFileTree([this]() { + return VirtualFileTree::makeTree(m_DirectoryStructure); + }), + m_DownloadManager(&NexusInterface::instance(), this), m_DirectoryUpdate(false), + m_ArchivesInit(false), + m_PluginListsWriter(std::bind(&OrganizerCore::savePluginList, this)) { env::setHandleCloserThreadCount(settings.refreshThreadCount()); m_DownloadManager.setOutputDirectory(m_Settings.paths().downloads(), false); @@ -117,10 +113,13 @@ OrganizerCore::OrganizerCore(Settings &settings) connect(m_DirectoryRefresher.get(), SIGNAL(refreshed()), this, SLOT(directory_refreshed())); - connect(&m_ModList, SIGNAL(removeOrigin(QString)), this, - SLOT(removeOrigin(QString))); - connect(&m_ModList, &ModList::modStatesChanged, [=] { currentProfile()->writeModlist(); }); - connect(&m_ModList, &ModList::modPrioritiesChanged, [this](auto&& indexes) { modPrioritiesChanged(indexes); }); + connect(&m_ModList, SIGNAL(removeOrigin(QString)), this, SLOT(removeOrigin(QString))); + connect(&m_ModList, &ModList::modStatesChanged, [=] { + currentProfile()->writeModlist(); + }); + connect(&m_ModList, &ModList::modPrioritiesChanged, [this](auto&& indexes) { + modPrioritiesChanged(indexes); + }); connect(NexusInterface::instance().getAccessManager(), SIGNAL(validateSuccessful(bool)), this, SLOT(loginSuccessful(bool))); @@ -128,23 +127,22 @@ OrganizerCore::OrganizerCore(Settings &settings) SIGNAL(validateFailed(QString)), this, SLOT(loginFailed(QString))); // This seems awfully imperative - connect(this, SIGNAL(managedGameChanged(MOBase::IPluginGame const *)), - &m_Settings, SLOT(managedGameChanged(MOBase::IPluginGame const *))); - connect(this, SIGNAL(managedGameChanged(MOBase::IPluginGame const *)), - &m_DownloadManager, - SLOT(managedGameChanged(MOBase::IPluginGame const *))); - connect(this, SIGNAL(managedGameChanged(MOBase::IPluginGame const *)), - &m_PluginList, SLOT(managedGameChanged(MOBase::IPluginGame const *))); - - connect(this, &OrganizerCore::managedGameChanged, [this](IPluginGame const* gamePlugin) { - ModDataContent* contentFeature = gamePlugin->feature(); - if (contentFeature) { - m_Contents = ModDataContentHolder(contentFeature->getAllContents()); - } - else { - m_Contents = ModDataContentHolder(); - } - }); + connect(this, SIGNAL(managedGameChanged(MOBase::IPluginGame const*)), &m_Settings, + SLOT(managedGameChanged(MOBase::IPluginGame const*))); + connect(this, SIGNAL(managedGameChanged(MOBase::IPluginGame const*)), + &m_DownloadManager, SLOT(managedGameChanged(MOBase::IPluginGame const*))); + connect(this, SIGNAL(managedGameChanged(MOBase::IPluginGame const*)), &m_PluginList, + SLOT(managedGameChanged(MOBase::IPluginGame const*))); + + connect(this, &OrganizerCore::managedGameChanged, + [this](IPluginGame const* gamePlugin) { + ModDataContent* contentFeature = gamePlugin->feature(); + if (contentFeature) { + m_Contents = ModDataContentHolder(contentFeature->getAllContents()); + } else { + m_Contents = ModDataContentHolder(); + } + }); connect(&m_PluginList, &PluginList::writePluginsList, &m_PluginListsWriter, &DelayedFileWriterBase::write); @@ -153,9 +151,10 @@ OrganizerCore::OrganizerCore(Settings &settings) m_RefresherThread.start(); m_DirectoryRefresher->moveToThread(&m_RefresherThread); - connect(&settings.plugins(), &PluginSettings::pluginSettingChanged, [this](auto const& ...args) { - m_PluginSettingChanged(args...); - }); + connect(&settings.plugins(), &PluginSettings::pluginSettingChanged, + [this](auto const&... args) { + m_PluginSettingChanged(args...); + }); } OrganizerCore::~OrganizerCore() @@ -203,9 +202,9 @@ void OrganizerCore::storeSettings() } QMessageBox::critical( - qApp->activeWindow(), tr("Failed to write settings"), - tr("An error occurred trying to write back MO settings to %1: %2") - .arg(m_Settings.filename(), reason)); + qApp->activeWindow(), tr("Failed to write settings"), + tr("An error occurred trying to write back MO settings to %1: %2") + .arg(m_Settings.filename(), reason)); } } @@ -219,11 +218,11 @@ void OrganizerCore::updateExecutablesList() m_ExecutablesList.load(managedGame(), m_Settings); } -void OrganizerCore::updateModInfoFromDisc() { - ModInfo::updateFromDisc( - m_Settings.paths().mods(), *this, - m_Settings.interface().displayForeign(), - m_Settings.refreshThreadCount()); +void OrganizerCore::updateModInfoFromDisc() +{ + ModInfo::updateFromDisc(m_Settings.paths().mods(), *this, + m_Settings.interface().displayForeign(), + m_Settings.refreshThreadCount()); } void OrganizerCore::setUserInterface(IUserInterface* ui) @@ -254,7 +253,7 @@ void OrganizerCore::checkForUpdates() } } -void OrganizerCore::connectPlugins(PluginContainer *container) +void OrganizerCore::connectPlugins(PluginContainer* container) { m_PluginContainer = container; m_Updater.setPluginContainer(m_PluginContainer); @@ -267,13 +266,15 @@ void OrganizerCore::connectPlugins(PluginContainer *container) emit managedGameChanged(m_GamePlugin); } - connect(m_PluginContainer, &PluginContainer::pluginEnabled, - [&](IPlugin* plugin) { m_PluginEnabled(plugin); }); - connect(m_PluginContainer, &PluginContainer::pluginDisabled, - [&](IPlugin* plugin) { m_PluginDisabled(plugin); }); + connect(m_PluginContainer, &PluginContainer::pluginEnabled, [&](IPlugin* plugin) { + m_PluginEnabled(plugin); + }); + connect(m_PluginContainer, &PluginContainer::pluginDisabled, [&](IPlugin* plugin) { + m_PluginDisabled(plugin); + }); } -void OrganizerCore::setManagedGame(MOBase::IPluginGame *game) +void OrganizerCore::setManagedGame(MOBase::IPluginGame* game) { m_GameName = game->gameName(); m_GamePlugin = game; @@ -281,7 +282,7 @@ void OrganizerCore::setManagedGame(MOBase::IPluginGame *game) emit managedGameChanged(m_GamePlugin); } -Settings &OrganizerCore::settings() +Settings& OrganizerCore::settings() { return m_Settings; } @@ -290,8 +291,7 @@ bool OrganizerCore::nexusApi(bool retry) { auto* accessManager = NexusInterface::instance().getAccessManager(); - if ((accessManager->validateAttempted() || accessManager->validated()) - && !retry) { + if ((accessManager->validateAttempted() || accessManager->validated()) && !retry) { // previous attempt, maybe even successful return false; } else { @@ -312,13 +312,15 @@ bool OrganizerCore::nexusApi(bool retry) void OrganizerCore::startMOUpdate() { if (nexusApi()) { - m_PostLoginTasks.append([&]() { m_Updater.startUpdate(); }); + m_PostLoginTasks.append([&]() { + m_Updater.startUpdate(); + }); } else { m_Updater.startUpdate(); } } -void OrganizerCore::downloadRequestedNXM(const QString &url) +void OrganizerCore::downloadRequestedNXM(const QString& url) { log::debug("download requested: {}", url); if (nexusApi()) { @@ -338,7 +340,8 @@ void OrganizerCore::profileCreated(MOBase::IProfile* profile) m_ProfileCreated(profile); } -void OrganizerCore::profileRenamed(MOBase::IProfile* profile, QString const& oldName, QString const& newName) +void OrganizerCore::profileRenamed(MOBase::IProfile* profile, QString const& oldName, + QString const& newName) { m_ProfileRenamed(profile, oldName, newName); } @@ -348,38 +351,39 @@ void OrganizerCore::profileRemoved(QString const& profileName) m_ProfileRemoved(profileName); } -void OrganizerCore::downloadRequested(QNetworkReply *reply, QString gameName, int modID, - const QString &fileName) +void OrganizerCore::downloadRequested(QNetworkReply* reply, QString gameName, int modID, + const QString& fileName) { try { - if (m_DownloadManager.addDownload(reply, QStringList(), fileName, gameName, modID, 0, - new ModRepositoryFileInfo(gameName, modID))) { + if (m_DownloadManager.addDownload(reply, QStringList(), fileName, gameName, modID, + 0, new ModRepositoryFileInfo(gameName, modID))) { MessageDialog::showMessage(tr("Download started"), qApp->activeWindow()); } - } catch (const std::exception &e) { + } catch (const std::exception& e) { MessageDialog::showMessage(tr("Download failed"), qApp->activeWindow()); log::error("exception starting download: {}", e.what()); } } -void OrganizerCore::removeOrigin(const QString &name) +void OrganizerCore::removeOrigin(const QString& name) { - FilesOrigin &origin = m_DirectoryStructure->getOriginByName(ToWString(name)); + FilesOrigin& origin = m_DirectoryStructure->getOriginByName(ToWString(name)); origin.enable(false); refreshLists(); } -void OrganizerCore::downloadSpeed(const QString &serverName, int bytesPerSecond) +void OrganizerCore::downloadSpeed(const QString& serverName, int bytesPerSecond) { m_Settings.network().setDownloadSpeed(serverName, bytesPerSecond); } -InstallationManager *OrganizerCore::installationManager() +InstallationManager* OrganizerCore::installationManager() { return &m_InstallationManager; } -bool OrganizerCore::createDirectory(const QString &path) { +bool OrganizerCore::createDirectory(const QString& path) +{ if (!QDir(path).exists() && !QDir().mkpath(path)) { QMessageBox::critical(nullptr, QObject::tr("Error"), QObject::tr("Failed to create \"%1\". Your user " @@ -391,17 +395,19 @@ bool OrganizerCore::createDirectory(const QString &path) { } } -bool OrganizerCore::checkPathSymlinks() { - const bool hasSymlink = ( - QFileInfo(m_Settings.paths().profiles()).isSymLink() || - QFileInfo(m_Settings.paths().mods()).isSymLink() || - QFileInfo(m_Settings.paths().overwrite()).isSymLink()); +bool OrganizerCore::checkPathSymlinks() +{ + const bool hasSymlink = (QFileInfo(m_Settings.paths().profiles()).isSymLink() || + QFileInfo(m_Settings.paths().mods()).isSymLink() || + QFileInfo(m_Settings.paths().overwrite()).isSymLink()); if (hasSymlink) { - log::warn("{}", QObject::tr( - "One of the configured MO2 directories (profiles, mods, or overwrite) " - "is on a path containing a symbolic (or other) link. This is likely to " - "be incompatible with MO2's virtual filesystem.")); + log::warn( + "{}", + QObject::tr( + "One of the configured MO2 directories (profiles, mods, or overwrite) " + "is on a path containing a symbolic (or other) link. This is likely to " + "be incompatible with MO2's virtual filesystem.")); return false; } @@ -411,13 +417,9 @@ bool OrganizerCore::checkPathSymlinks() { bool OrganizerCore::bootstrap() { - const auto dirs = { - m_Settings.paths().profiles(), - m_Settings.paths().mods(), - m_Settings.paths().downloads(), - m_Settings.paths().overwrite(), - QString::fromStdWString(getGlobalCoreDumpPath()) - }; + const auto dirs = {m_Settings.paths().profiles(), m_Settings.paths().mods(), + m_Settings.paths().downloads(), m_Settings.paths().overwrite(), + QString::fromStdWString(getGlobalCoreDumpPath())}; for (auto&& dir : dirs) { if (!createDirectory(dir)) { @@ -434,15 +436,13 @@ bool OrganizerCore::bootstrap() } // log if there are any dmp files - const auto hasCrashDumps = - !QDir(QString::fromStdWString(getGlobalCoreDumpPath())) - .entryList({"*.dmp"}, QDir::Files) - .empty(); + const auto hasCrashDumps = !QDir(QString::fromStdWString(getGlobalCoreDumpPath())) + .entryList({"*.dmp"}, QDir::Files) + .empty(); if (hasCrashDumps) { - log::debug( - "there are crash dumps in '{}'", - QString::fromStdWString(getGlobalCoreDumpPath())); + log::debug("there are crash dumps in '{}'", + QString::fromStdWString(getGlobalCoreDumpPath())); } return true; @@ -452,9 +452,8 @@ void OrganizerCore::createDefaultProfile() { QString profilesPath = settings().paths().profiles(); if (QDir(profilesPath).entryList(QDir::AllDirs | QDir::NoDotAndDotDot).size() == 0) { - Profile newProf( - QString::fromStdWString(AppConfig::defaultProfileName()), - managedGame(), false); + Profile newProf(QString::fromStdWString(AppConfig::defaultProfileName()), + managedGame(), false); m_ProfileCreated(&newProf); } @@ -465,15 +464,16 @@ void OrganizerCore::prepareVFS() m_USVFS.updateMapping(fileMapping(m_CurrentProfile->name(), QString())); } -void OrganizerCore::updateVFSParams( - log::Levels logLevel, env::CoreDumpTypes coreDumpType, - const QString& crashDumpsPath, - std::chrono::seconds spawnDelay, QString executableBlacklist) +void OrganizerCore::updateVFSParams(log::Levels logLevel, + env::CoreDumpTypes coreDumpType, + const QString& crashDumpsPath, + std::chrono::seconds spawnDelay, + QString executableBlacklist) { setGlobalCoreDumpType(coreDumpType); - m_USVFS.updateParams( - logLevel, coreDumpType, crashDumpsPath, spawnDelay, executableBlacklist); + m_USVFS.updateParams(logLevel, coreDumpType, crashDumpsPath, spawnDelay, + executableBlacklist); } void OrganizerCore::setLogLevel(log::Levels level) @@ -481,11 +481,9 @@ void OrganizerCore::setLogLevel(log::Levels level) m_Settings.diagnostics().setLogLevel(level); updateVFSParams( - m_Settings.diagnostics().logLevel(), - m_Settings.diagnostics().coreDumpType(), - QString::fromStdWString(getGlobalCoreDumpPath()), - m_Settings.diagnostics().spawnDelay(), - m_Settings.executablesBlacklist()); + m_Settings.diagnostics().logLevel(), m_Settings.diagnostics().coreDumpType(), + QString::fromStdWString(getGlobalCoreDumpPath()), + m_Settings.diagnostics().spawnDelay(), m_Settings.executablesBlacklist()); log::getDefault().setLevel(m_Settings.diagnostics().logLevel()); } @@ -493,10 +491,10 @@ void OrganizerCore::setLogLevel(log::Levels level) bool OrganizerCore::cycleDiagnostics() { const auto maxDumps = settings().diagnostics().maxCoreDumps(); - const auto path = QString::fromStdWString(getGlobalCoreDumpPath()); + const auto path = QString::fromStdWString(getGlobalCoreDumpPath()); if (maxDumps > 0) { - removeOldFiles(path, "*.dmp", maxDumps, QDir::Time|QDir::Reversed); + removeOldFiles(path, "*.dmp", maxDumps, QDir::Time | QDir::Reversed); } return true; @@ -524,10 +522,9 @@ std::wstring OrganizerCore::getGlobalCoreDumpPath() return {}; } -void OrganizerCore::setCurrentProfile(const QString &profileName) +void OrganizerCore::setCurrentProfile(const QString& profileName) { - if ((m_CurrentProfile != nullptr) - && (profileName == m_CurrentProfile->name())) { + if ((m_CurrentProfile != nullptr) && (profileName == m_CurrentProfile->name())) { return; } @@ -535,8 +532,7 @@ void OrganizerCore::setCurrentProfile(const QString &profileName) QDir profileBaseDir(settings().paths().profiles()); - const auto subdirs = profileBaseDir.entryList( - QDir::AllDirs | QDir::NoDotAndDotDot); + const auto subdirs = profileBaseDir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot); QString profileDir; @@ -563,9 +559,10 @@ void OrganizerCore::setCurrentProfile(const QString &profileName) log::error("picked profile '{}' instead", QDir(profileDir).dirName()); - reportError( - tr("The selected profile '%1' does not exist. The profile '%2' will be used instead") - .arg(profileName).arg(QDir(profileDir).dirName())); + reportError(tr("The selected profile '%1' does not exist. The profile '%2' will be " + "used instead") + .arg(profileName) + .arg(QDir(profileDir).dirName())); } // Keep the old profile to emit signal-changed: @@ -583,8 +580,14 @@ void OrganizerCore::setCurrentProfile(const QString &profileName) m_Settings.game().setSelectedProfileName(m_CurrentProfile->name()); - connect(m_CurrentProfile.get(), qOverload(&Profile::modStatusChanged), [this](auto&& index) { modStatusChanged(index); }); - connect(m_CurrentProfile.get(), qOverload>(&Profile::modStatusChanged), [this](auto&& indexes) { modStatusChanged(indexes); }); + connect(m_CurrentProfile.get(), qOverload(&Profile::modStatusChanged), + [this](auto&& index) { + modStatusChanged(index); + }); + connect(m_CurrentProfile.get(), qOverload>(&Profile::modStatusChanged), + [this](auto&& indexes) { + modStatusChanged(indexes); + }); refreshDirectoryStructure(); m_CurrentProfile->debugDump(); @@ -593,7 +596,7 @@ void OrganizerCore::setCurrentProfile(const QString &profileName) m_ProfileChanged(oldProfile.get(), m_CurrentProfile.get()); } -MOBase::IModRepositoryBridge *OrganizerCore::createNexusBridge() const +MOBase::IModRepositoryBridge* OrganizerCore::createNexusBridge() const { return new NexusBridge(m_PluginContainer); } @@ -641,16 +644,17 @@ MOBase::VersionInfo OrganizerCore::appVersion() const return m_Updater.getVersion(); } -MOBase::IPluginGame *OrganizerCore::getGame(const QString &name) const +MOBase::IPluginGame* OrganizerCore::getGame(const QString& name) const { - for (IPluginGame *game : m_PluginContainer->plugins()) { - if (game != nullptr && game->gameShortName().compare(name, Qt::CaseInsensitive) == 0) + for (IPluginGame* game : m_PluginContainer->plugins()) { + if (game != nullptr && + game->gameShortName().compare(name, Qt::CaseInsensitive) == 0) return game; } return nullptr; } -MOBase::IModInterface *OrganizerCore::createMod(GuessedValue &name) +MOBase::IModInterface* OrganizerCore::createMod(GuessedValue& name) { auto result = m_InstallationManager.testOverwrite(name); if (!result) { @@ -659,10 +663,8 @@ MOBase::IModInterface *OrganizerCore::createMod(GuessedValue &name) m_InstallationManager.setModsDirectory(m_Settings.paths().mods()); - QString targetDirectory - = QDir::fromNativeSeparators(m_Settings.paths().mods()) - .append("/") - .append(name); + QString targetDirectory = + QDir::fromNativeSeparators(m_Settings.paths().mods()).append("/").append(name); QSettings settingsFile(targetDirectory + "/meta.ini", QSettings::IniFormat); @@ -678,98 +680,102 @@ MOBase::IModInterface *OrganizerCore::createMod(GuessedValue &name) settingsFile.endArray(); } - // shouldn't this use the existing mod in case of a merge? also, this does not refresh the indices - // in the ModInfo structure + // shouldn't this use the existing mod in case of a merge? also, this does not refresh + // the indices in the ModInfo structure return ModInfo::createFrom(QDir(targetDirectory), *this).data(); } -void OrganizerCore::modDataChanged(MOBase::IModInterface *) +void OrganizerCore::modDataChanged(MOBase::IModInterface*) { refresh(false); } -QVariant OrganizerCore::pluginSetting(const QString &pluginName, - const QString &key) const +QVariant OrganizerCore::pluginSetting(const QString& pluginName, + const QString& key) const { return m_Settings.plugins().setting(pluginName, key); } -void OrganizerCore::setPluginSetting(const QString &pluginName, - const QString &key, const QVariant &value) +void OrganizerCore::setPluginSetting(const QString& pluginName, const QString& key, + const QVariant& value) { m_Settings.plugins().setSetting(pluginName, key, value); } -QVariant OrganizerCore::persistent(const QString &pluginName, - const QString &key, - const QVariant &def) const +QVariant OrganizerCore::persistent(const QString& pluginName, const QString& key, + const QVariant& def) const { return m_Settings.plugins().persistent(pluginName, key, def); } -void OrganizerCore::setPersistent(const QString &pluginName, const QString &key, - const QVariant &value, bool sync) +void OrganizerCore::setPersistent(const QString& pluginName, const QString& key, + const QVariant& value, bool sync) { m_Settings.plugins().setPersistent(pluginName, key, value, sync); } QString OrganizerCore::pluginDataPath() { - return qApp->applicationDirPath() + "/" + ToQString(AppConfig::pluginPath()) - + "/data"; + return qApp->applicationDirPath() + "/" + ToQString(AppConfig::pluginPath()) + + "/data"; } -MOBase::IModInterface *OrganizerCore::installMod(const QString & archivePath, - int priority, - bool reinstallation, +MOBase::IModInterface* OrganizerCore::installMod(const QString& archivePath, + int priority, bool reinstallation, ModInfo::Ptr currentMod, - const QString &initModName) + const QString& initModName) { - return installArchive(archivePath, reinstallation ? -1 : priority, reinstallation, currentMod, initModName).get(); + return installArchive(archivePath, reinstallation ? -1 : priority, reinstallation, + currentMod, initModName) + .get(); } -std::pair OrganizerCore::doInstall(const QString& archivePath, - GuessedValue modName, ModInfo::Ptr currentMod, int priority, bool reinstallation) +std::pair +OrganizerCore::doInstall(const QString& archivePath, GuessedValue modName, + ModInfo::Ptr currentMod, int priority, bool reinstallation) { if (m_CurrentProfile == nullptr) { - return { -1, nullptr }; + return {-1, nullptr}; } if (m_InstallationManager.isRunning()) { - QMessageBox::information( - qApp->activeWindow(), tr("Installation cancelled"), - tr("Another installation is currently in progress."), QMessageBox::Ok); - return { -1, nullptr }; + QMessageBox::information(qApp->activeWindow(), tr("Installation cancelled"), + tr("Another installation is currently in progress."), + QMessageBox::Ok); + return {-1, nullptr}; } bool hasIniTweaks = false; m_CurrentProfile->writeModlistNow(); m_InstallationManager.setModsDirectory(m_Settings.paths().mods()); - m_InstallationManager.notifyInstallationStart(archivePath, reinstallation, currentMod); + m_InstallationManager.notifyInstallationStart(archivePath, reinstallation, + currentMod); auto result = m_InstallationManager.install(archivePath, modName, hasIniTweaks); if (result) { - MessageDialog::showMessage(tr("Installation successful"), - qApp->activeWindow()); + MessageDialog::showMessage(tr("Installation successful"), qApp->activeWindow()); - // we wait for the directory structure to be ready before notifying the mod list, this - // prevents issue with third-party plugins, e.g., if the installed mod is activated before - // the structure is ready + // we wait for the directory structure to be ready before notifying the mod list, + // this prevents issue with third-party plugins, e.g., if the installed mod is + // activated before the structure is ready // - // we need to fetch modIndex() within the call back because the index is only valid after - // the call to refresh(), but we do not want to connect after refresh() + // we need to fetch modIndex() within the call back because the index is only valid + // after the call to refresh(), but we do not want to connect after refresh() // - connect(this, &OrganizerCore::directoryStructureReady, this, [=] { - const int modIndex = ModInfo::getIndex(modName); - if (modIndex != UINT_MAX) { - const auto modInfo = ModInfo::getByIndex(modIndex); - m_ModList.notifyModInstalled(modInfo.get()); - } - }, Qt::SingleShotConnection); + connect( + this, &OrganizerCore::directoryStructureReady, this, + [=] { + const int modIndex = ModInfo::getIndex(modName); + if (modIndex != UINT_MAX) { + const auto modInfo = ModInfo::getByIndex(modIndex); + m_ModList.notifyModInstalled(modInfo.get()); + } + }, + Qt::SingleShotConnection); refresh(); - const auto modIndex = ModInfo::getIndex(modName); + const auto modIndex = ModInfo::getIndex(modName); ModInfo::Ptr modInfo = nullptr; if (modIndex != UINT_MAX) { modInfo = ModInfo::getByIndex(modIndex); @@ -778,46 +784,46 @@ std::pair OrganizerCore::doInstall(const QString& ar m_ModList.changeModPriority(modIndex, priority); } - if (hasIniTweaks && m_UserInterface != nullptr - && (QMessageBox::question(qApp->activeWindow(), tr("Configure Mod"), - tr("This mod contains ini tweaks. Do you " - "want to configure them now?"), - QMessageBox::Yes | QMessageBox::No) - == QMessageBox::Yes)) { - m_UserInterface->displayModInformation( - modInfo, modIndex, ModInfoTabIDs::IniFiles); + if (hasIniTweaks && m_UserInterface != nullptr && + (QMessageBox::question(qApp->activeWindow(), tr("Configure Mod"), + tr("This mod contains ini tweaks. Do you " + "want to configure them now?"), + QMessageBox::Yes | QMessageBox::No) == + QMessageBox::Yes)) { + m_UserInterface->displayModInformation(modInfo, modIndex, + ModInfoTabIDs::IniFiles); } m_InstallationManager.notifyInstallationEnd(result, modInfo); - } - else { + } else { reportError(tr("mod not found: %1").arg(qUtf8Printable(modName))); } emit modInstalled(modName); - return { modIndex, modInfo }; - } - else { + return {modIndex, modInfo}; + } else { m_InstallationManager.notifyInstallationEnd(result, nullptr); if (m_InstallationManager.wasCancelled()) { - QMessageBox::information(qApp->activeWindow(), tr("Extraction cancelled"), - tr("The installation was cancelled while extracting files. " - "If this was prior to a FOMOD setup, this warning may be ignored. " - "However, if this was during installation, the mod will likely be missing files."), - QMessageBox::Ok); + QMessageBox::information( + qApp->activeWindow(), tr("Extraction cancelled"), + tr("The installation was cancelled while extracting files. " + "If this was prior to a FOMOD setup, this warning may be ignored. " + "However, if this was during installation, the mod will likely be missing " + "files."), + QMessageBox::Ok); refresh(); } } - return { -1, nullptr }; + return {-1, nullptr}; } ModInfo::Ptr OrganizerCore::installDownload(int index, int priority) { try { - QString fileName = m_DownloadManager.getFilePath(index); - QString gameName = m_DownloadManager.getGameName(index); - int modID = m_DownloadManager.getModID(index); - int fileID = m_DownloadManager.getFileInfo(index)->fileID; + QString fileName = m_DownloadManager.getFilePath(index); + QString gameName = m_DownloadManager.getGameName(index); + int modID = m_DownloadManager.getModID(index); + int fileID = m_DownloadManager.getFileInfo(index)->fileID; ModInfo::Ptr currentMod = nullptr; GuessedValue modName; @@ -826,8 +832,8 @@ ModInfo::Ptr OrganizerCore::installDownload(int index, int priority) std::vector modInfo = ModInfo::getByModID(gameName, modID); for (auto iter = modInfo.begin(); iter != modInfo.end(); ++iter) { std::vector flags = (*iter)->getFlags(); - if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_BACKUP) - == flags.end()) { + if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_BACKUP) == + flags.end()) { modName.update((*iter)->name(), GUESS_PRESET); currentMod = *iter; (*iter)->saveMeta(); @@ -835,7 +841,8 @@ ModInfo::Ptr OrganizerCore::installDownload(int index, int priority) } } - const auto [modIndex, modInfo] = doInstall(fileName, modName, currentMod, priority, false); + const auto [modIndex, modInfo] = + doInstall(fileName, modName, currentMod, priority, false); if (modInfo != nullptr) { modInfo->addInstalledFile(modID, fileID); @@ -846,22 +853,23 @@ ModInfo::Ptr OrganizerCore::installDownload(int index, int priority) } return modInfo; - } catch (const std::exception &e) { + } catch (const std::exception& e) { reportError(QString(e.what())); } return nullptr; } -ModInfo::Ptr OrganizerCore::installArchive( - const QString& archivePath, int priority, bool reinstallation, - ModInfo::Ptr currentMod, const QString& initModName) +ModInfo::Ptr OrganizerCore::installArchive(const QString& archivePath, int priority, + bool reinstallation, ModInfo::Ptr currentMod, + const QString& initModName) { GuessedValue modName; if (!initModName.isEmpty()) { modName.update(initModName, GUESS_USER); } - const auto [modIndex, modInfo] = doInstall(archivePath, modName, currentMod, priority, reinstallation); + const auto [modIndex, modInfo] = + doInstall(archivePath, modName, currentMod, priority, reinstallation); if (m_CurrentProfile == nullptr) { return nullptr; } @@ -869,7 +877,7 @@ ModInfo::Ptr OrganizerCore::installArchive( if (modInfo != nullptr) { auto dlIdx = m_DownloadManager.indexByName(QFileInfo(archivePath).fileName()); if (dlIdx != -1) { - int modId = m_DownloadManager.getModID(dlIdx); + int modId = m_DownloadManager.getModID(dlIdx); int fileId = m_DownloadManager.getFileInfo(dlIdx)->fileID; modInfo->addInstalledFile(modId, fileId); } @@ -878,13 +886,13 @@ ModInfo::Ptr OrganizerCore::installArchive( return modInfo; } -QString OrganizerCore::resolvePath(const QString &fileName) const +QString OrganizerCore::resolvePath(const QString& fileName) const { if (m_DirectoryStructure == nullptr) { return QString(); } - const FileEntryPtr file - = m_DirectoryStructure->searchFile(ToWString(fileName), nullptr); + const FileEntryPtr file = + m_DirectoryStructure->searchFile(ToWString(fileName), nullptr); if (file.get() != nullptr) { return ToQString(file->getFullPath()); } else { @@ -892,10 +900,10 @@ QString OrganizerCore::resolvePath(const QString &fileName) const } } -QStringList OrganizerCore::listDirectories(const QString &directoryName) const +QStringList OrganizerCore::listDirectories(const QString& directoryName) const { QStringList result; - DirectoryEntry *dir = m_DirectoryStructure; + DirectoryEntry* dir = m_DirectoryStructure; if (!directoryName.isEmpty()) dir = dir->findSubDirectoryRecursive(ToWString(directoryName)); if (dir != nullptr) { @@ -906,17 +914,17 @@ QStringList OrganizerCore::listDirectories(const QString &directoryName) const return result; } -QStringList OrganizerCore::findFiles( - const QString &path, - const std::function &filter) const +QStringList +OrganizerCore::findFiles(const QString& path, + const std::function& filter) const { QStringList result; - DirectoryEntry *dir = m_DirectoryStructure; + DirectoryEntry* dir = m_DirectoryStructure; if (!path.isEmpty() && path != ".") dir = dir->findSubDirectoryRecursive(ToWString(path)); if (dir != nullptr) { std::vector files = dir->getFiles(); - for (FileEntryPtr &file: files) { + for (FileEntryPtr& file : files) { QString fullPath = ToQString(file->getFullPath()); if (filter(ToQString(file->getName()))) { result.append(fullPath); @@ -926,14 +934,15 @@ QStringList OrganizerCore::findFiles( return result; } -QStringList OrganizerCore::getFileOrigins(const QString &fileName) const +QStringList OrganizerCore::getFileOrigins(const QString& fileName) const { QStringList result; - const FileEntryPtr file = m_DirectoryStructure->searchFile(ToWString(fileName), nullptr); + const FileEntryPtr file = + m_DirectoryStructure->searchFile(ToWString(fileName), nullptr); if (file.get() != nullptr) { - result.append(ToQString( - m_DirectoryStructure->getOriginByID(file->getOrigin()).getName())); + result.append( + ToQString(m_DirectoryStructure->getOriginByID(file->getOrigin()).getName())); foreach (const auto& i, file->getAlternatives()) { result.append( ToQString(m_DirectoryStructure->getOriginByID(i.originID()).getName())); @@ -943,12 +952,11 @@ QStringList OrganizerCore::getFileOrigins(const QString &fileName) const } QList OrganizerCore::findFileInfos( - const QString &path, - const std::function &filter) - const + const QString& path, + const std::function& filter) const { QList result; - DirectoryEntry *dir = m_DirectoryStructure; + DirectoryEntry* dir = m_DirectoryStructure; if (!path.isEmpty() && path != ".") dir = dir->findSubDirectoryRecursive(ToWString(path)); if (dir != nullptr) { @@ -958,8 +966,7 @@ QList OrganizerCore::findFileInfos( info.filePath = ToQString(file->getFullPath()); bool fromArchive = false; info.origins.append(ToQString( - m_DirectoryStructure->getOriginByID(file->getOrigin(fromArchive)) - .getName())); + m_DirectoryStructure->getOriginByID(file->getOrigin(fromArchive)).getName())); info.archive = fromArchive ? ToQString(file->getArchive().name()) : ""; for (const auto& idx : file->getAlternatives()) { info.origins.append( @@ -974,33 +981,34 @@ QList OrganizerCore::findFileInfos( return result; } -DownloadManager *OrganizerCore::downloadManager() +DownloadManager* OrganizerCore::downloadManager() { return &m_DownloadManager; } -PluginList *OrganizerCore::pluginList() +PluginList* OrganizerCore::pluginList() { return &m_PluginList; } -ModList *OrganizerCore::modList() +ModList* OrganizerCore::modList() { return &m_ModList; } -bool OrganizerCore::previewFileWithAlternatives( - QWidget* parent, QString fileName, int selectedOrigin) +bool OrganizerCore::previewFileWithAlternatives(QWidget* parent, QString fileName, + int selectedOrigin) { fileName = QDir::fromNativeSeparators(fileName); - // what we have is an absolute path to the file in its actual location (for the primary origin) - // what we want is the path relative to the virtual data directory + // what we have is an absolute path to the file in its actual location (for the + // primary origin) what we want is the path relative to the virtual data directory - // we need to look in the virtual directory for the file to make sure the info is up to date. + // we need to look in the virtual directory for the file to make sure the info is up + // to date. // check if the file comes from the actual data folder instead of a mod - QDir gameDirectory = managedGame()->dataDirectory().absolutePath(); + QDir gameDirectory = managedGame()->dataDirectory().absolutePath(); QString relativePath = gameDirectory.relativeFilePath(fileName); QDir dirRelativePath = gameDirectory.relativeFilePath(fileName); @@ -1008,18 +1016,16 @@ bool OrganizerCore::previewFileWithAlternatives( // absolute path so we make sure that is not the case if (!dirRelativePath.isAbsolute() && !relativePath.startsWith("..")) { fileName = relativePath; - } - else { + } else { // crude: we search for the next slash after the base mod directory to skip // everything up to the data-relative directory int offset = settings().paths().mods().size() + 1; - offset = fileName.indexOf("/", offset); - fileName = fileName.mid(offset + 1); + offset = fileName.indexOf("/", offset); + fileName = fileName.mid(offset + 1); } - - - const FileEntryPtr file = directoryStructure()->searchFile(ToWString(fileName), nullptr); + const FileEntryPtr file = + directoryStructure()->searchFile(ToWString(fileName), nullptr); if (file.get() == nullptr) { reportError(tr("file not found: %1").arg(qUtf8Printable(fileName))); @@ -1030,15 +1036,16 @@ bool OrganizerCore::previewFileWithAlternatives( PreviewDialog preview(fileName, parent); auto addFunc = [&](int originId) { - FilesOrigin &origin = directoryStructure()->getOriginByID(originId); - QString filePath = QDir::fromNativeSeparators(ToQString(origin.getPath())) + "/" + fileName; + FilesOrigin& origin = directoryStructure()->getOriginByID(originId); + QString filePath = + QDir::fromNativeSeparators(ToQString(origin.getPath())) + "/" + fileName; if (QFile::exists(filePath)) { - // it's very possible the file doesn't exist, because it's inside an archive. we don't support that - QWidget *wid = m_PluginContainer->previewGenerator().genPreview(filePath); + // it's very possible the file doesn't exist, because it's inside an archive. we + // don't support that + QWidget* wid = m_PluginContainer->previewGenerator().genPreview(filePath); if (wid == nullptr) { reportError(tr("failed to generate preview for %1").arg(filePath)); - } - else { + } else { preview.addVariant(ToQString(origin.getName()), wid); } } @@ -1072,9 +1079,8 @@ bool OrganizerCore::previewFileWithAlternatives( // sanity check, this shouldn't happen unless the caller passed an // incorrect id - log::warn( - "selected preview origin {} not found in list of alternatives", - selectedOrigin); + log::warn("selected preview origin {} not found in list of alternatives", + selectedOrigin); } for (int id : origins) { @@ -1085,18 +1091,17 @@ bool OrganizerCore::previewFileWithAlternatives( if (preview.numVariants() > 0) { preview.exec(); return true; - } - else { - QMessageBox::information( - parent, tr("Sorry"), - tr("Sorry, can't preview anything. This function currently does not support extracting from bsas.")); + } else { + QMessageBox::information(parent, tr("Sorry"), + tr("Sorry, can't preview anything. This function " + "currently does not support extracting from bsas.")); return false; } } -bool OrganizerCore::previewFile( - QWidget* parent, const QString& originName, const QString& path) +bool OrganizerCore::previewFile(QWidget* parent, const QString& originName, + const QString& path) { if (!QFile::exists(path)) { reportError(tr("File '%1' not found.").arg(path)); @@ -1105,7 +1110,7 @@ bool OrganizerCore::previewFile( PreviewDialog preview(path, parent); - QWidget *wid = m_PluginContainer->previewGenerator().genPreview(path); + QWidget* wid = m_PluginContainer->previewGenerator().genPreview(path); if (wid == nullptr) { reportError(tr("Failed to generate preview for %1").arg(path)); return false; @@ -1117,54 +1122,63 @@ bool OrganizerCore::previewFile( return true; } -boost::signals2::connection OrganizerCore::onAboutToRun( - const std::function &func) +boost::signals2::connection +OrganizerCore::onAboutToRun(const std::function& func) { return m_AboutToRun.connect(func); } boost::signals2::connection OrganizerCore::onFinishedRun( - const std::function &func) + const std::function& func) { return m_FinishedRun.connect(func); } -boost::signals2::connection OrganizerCore::onUserInterfaceInitialized(std::function const& func) +boost::signals2::connection +OrganizerCore::onUserInterfaceInitialized(std::function const& func) { return m_UserInterfaceInitialized.connect(func); } -boost::signals2::connection OrganizerCore::onProfileCreated(std::function const& func) +boost::signals2::connection +OrganizerCore::onProfileCreated(std::function const& func) { return m_ProfileCreated.connect(func); } -boost::signals2::connection OrganizerCore::onProfileRenamed(std::function const& func) +boost::signals2::connection OrganizerCore::onProfileRenamed( + std::function const& func) { return m_ProfileRenamed.connect(func); } -boost::signals2::connection OrganizerCore::onProfileRemoved(std::function const& func) +boost::signals2::connection +OrganizerCore::onProfileRemoved(std::function const& func) { return m_ProfileRemoved.connect(func); } -boost::signals2::connection OrganizerCore::onProfileChanged(std::function const& func) +boost::signals2::connection +OrganizerCore::onProfileChanged(std::function const& func) { return m_ProfileChanged.connect(func); } -boost::signals2::connection OrganizerCore::onPluginSettingChanged(std::function const& func) +boost::signals2::connection OrganizerCore::onPluginSettingChanged( + std::function const& func) { return m_PluginSettingChanged.connect(func); } -boost::signals2::connection OrganizerCore::onPluginEnabled(std::function const& func) +boost::signals2::connection +OrganizerCore::onPluginEnabled(std::function const& func) { return m_PluginEnabled.connect(func); } -boost::signals2::connection OrganizerCore::onPluginDisabled(std::function const& func) +boost::signals2::connection +OrganizerCore::onPluginDisabled(std::function const& func) { return m_PluginDisabled.connect(func); } @@ -1202,7 +1216,7 @@ void OrganizerCore::refreshESPList(bool force) try { m_PluginList.refresh(m_CurrentProfile->name(), *m_DirectoryStructure, m_CurrentProfile->getLockedOrderFileName(), force); - } catch (const std::exception &e) { + } catch (const std::exception& e) { reportError(tr("Failed to refresh list of esps: %1").arg(e.what())); } } @@ -1211,7 +1225,7 @@ void OrganizerCore::refreshBSAList() { TimeThis tt("OrganizerCore::refreshBSAList()"); - DataArchives *archives = m_GamePlugin->feature(); + DataArchives* archives = m_GamePlugin->feature(); if (archives != nullptr) { m_ArchivesInit = false; @@ -1247,8 +1261,8 @@ void OrganizerCore::refreshLists() if ((m_CurrentProfile != nullptr) && m_DirectoryStructure->isPopulated()) { refreshESPList(true); refreshBSAList(); - } // no point in refreshing lists if no files have been added to the directory - // tree + } // no point in refreshing lists if no files have been added to the directory + // tree } void OrganizerCore::updateModActiveState(int index, bool active) @@ -1258,38 +1272,35 @@ void OrganizerCore::updateModActiveState(int index, bool active) updateModsActiveState(modsToUpdate, active); } -void OrganizerCore::updateModsActiveState(const QList &modIndices, bool active) +void OrganizerCore::updateModsActiveState(const QList& modIndices, + bool active) { int enabled = 0; for (auto index : modIndices) { ModInfo::Ptr modInfo = ModInfo::getByIndex(index); QDir dir(modInfo->absolutePath()); - for (const QString &esm : - dir.entryList(QStringList() << "*.esm", QDir::Files)) { + for (const QString& esm : dir.entryList(QStringList() << "*.esm", QDir::Files)) { const FileEntryPtr file = m_DirectoryStructure->findFile(ToWString(esm)); if (file.get() == nullptr) { log::warn("failed to activate {}", esm); continue; } - if (active != m_PluginList.isEnabled(esm) - && file->getAlternatives().empty()) { + if (active != m_PluginList.isEnabled(esm) && file->getAlternatives().empty()) { m_PluginList.blockSignals(true); m_PluginList.enableESP(esm, active); m_PluginList.blockSignals(false); } } - for (const QString &esl : - dir.entryList(QStringList() << "*.esl", QDir::Files)) { + for (const QString& esl : dir.entryList(QStringList() << "*.esl", QDir::Files)) { const FileEntryPtr file = m_DirectoryStructure->findFile(ToWString(esl)); if (file.get() == nullptr) { log::warn("failed to activate {}", esl); continue; } - if (active != m_PluginList.isEnabled(esl) - && file->getAlternatives().empty()) { + if (active != m_PluginList.isEnabled(esl) && file->getAlternatives().empty()) { m_PluginList.blockSignals(true); m_PluginList.enableESP(esl, active); m_PluginList.blockSignals(false); @@ -1297,15 +1308,14 @@ void OrganizerCore::updateModsActiveState(const QList &modIndices, } } QStringList esps = dir.entryList(QStringList() << "*.esp", QDir::Files); - for (const QString &esp : esps) { + for (const QString& esp : esps) { const FileEntryPtr file = m_DirectoryStructure->findFile(ToWString(esp)); if (file.get() == nullptr) { log::warn("failed to activate {}", esp); continue; } - if (active != m_PluginList.isEnabled(esp) - && file->getAlternatives().empty()) { + if (active != m_PluginList.isEnabled(esp) && file->getAlternatives().empty()) { m_PluginList.blockSignals(true); m_PluginList.enableESP(esp, active); m_PluginList.blockSignals(false); @@ -1315,8 +1325,8 @@ void OrganizerCore::updateModsActiveState(const QList &modIndices, } if (active && (enabled > 1)) { MessageDialog::showMessage( - tr("Multiple esps/esls activated, please check that they don't conflict."), - qApp->activeWindow()); + tr("Multiple esps/esls activated, please check that they don't conflict."), + qApp->activeWindow()); } m_PluginList.refreshLoadOrder(); // immediately save affected lists @@ -1331,18 +1341,20 @@ void OrganizerCore::updateModInDirectoryStructure(unsigned int index, updateModsInDirectoryStructure(allModInfo); } -void OrganizerCore::updateModsInDirectoryStructure(QMap modInfo) +void OrganizerCore::updateModsInDirectoryStructure( + QMap modInfo) { std::vector entries; for (auto idx : modInfo.keys()) { - entries.push_back({ - modInfo[idx]->name(), modInfo[idx]->absolutePath(), - modInfo[idx]->stealFiles(), {}, m_CurrentProfile->getModPriority(idx)}); + entries.push_back({modInfo[idx]->name(), + modInfo[idx]->absolutePath(), + modInfo[idx]->stealFiles(), + {}, + m_CurrentProfile->getModPriority(idx)}); } - m_DirectoryRefresher->addMultipleModsFilesToStructure( - m_DirectoryStructure, entries); + m_DirectoryRefresher->addMultipleModsFilesToStructure(m_DirectoryStructure, entries); DirectoryRefresher::cleanStructure(m_DirectoryStructure); // need to refresh plugin list now so we can activate esps @@ -1360,27 +1372,28 @@ void OrganizerCore::updateModsInDirectoryStructure(QMap archives = enabledArchives(); - m_DirectoryRefresher->setMods( - m_CurrentProfile->getActiveMods(), - std::set(archives.begin(), archives.end())); + m_DirectoryRefresher->setMods(m_CurrentProfile->getActiveMods(), + std::set(archives.begin(), archives.end())); // finally also add files from bsas to the directory structure for (auto idx : modInfo.keys()) { m_DirectoryRefresher->addModBSAToStructure( - m_DirectoryStructure, modInfo[idx]->name(), - m_CurrentProfile->getModPriority(idx), modInfo[idx]->absolutePath(), - modInfo[idx]->archives()); + m_DirectoryStructure, modInfo[idx]->name(), + m_CurrentProfile->getModPriority(idx), modInfo[idx]->absolutePath(), + modInfo[idx]->archives()); } } -void OrganizerCore::loggedInAction(QWidget* parent, std::function f) +void OrganizerCore::loggedInAction(QWidget* parent, std::function f) { if (NexusInterface::instance().getAccessManager()->validated()) { f(); } else if (!m_Settings.network().offlineMode()) { QString apiKey; if (GlobalSettings::nexusApiKey(apiKey)) { - doAfterLogin([f]{ f(); }); + doAfterLogin([f] { + f(); + }); NexusInterface::instance().getAccessManager()->apiCheck(apiKey); } else { MessageDialog::showMessage(tr("You need to be logged in with Nexus"), parent); @@ -1388,13 +1401,12 @@ void OrganizerCore::loggedInAction(QWidget* parent, std::function f) } } -void OrganizerCore::requestDownload(const QUrl &url, QNetworkReply *reply) +void OrganizerCore::requestDownload(const QUrl& url, QNetworkReply* reply) { if (!m_PluginContainer) { return; } - for (IPluginModPage *modPage : - m_PluginContainer->plugins()) { + for (IPluginModPage* modPage : m_PluginContainer->plugins()) { if (m_PluginContainer->isEnabled(modPage)) { ModRepositoryFileInfo* fileInfo = new ModRepositoryFileInfo(); if (modPage->handlesDownload(url, reply->url(), *fileInfo)) { @@ -1408,8 +1420,8 @@ void OrganizerCore::requestDownload(const QUrl &url, QNetworkReply *reply) // no mod found that could handle the download. Is it a nexus mod? if (url.host() == "www.nexusmods.com") { QString gameName = ""; - int modID = 0; - int fileID = 0; + int modID = 0; + int fileID = 0; QRegularExpression nameExp("www\\.nexusmods\\.com/(\\a+)/"); auto match = nameExp.match(url.toString()); if (match.hasMatch()) { @@ -1435,8 +1447,7 @@ void OrganizerCore::requestDownload(const QUrl &url, QNetworkReply *reply) "version) will be associated with the " "download.\n" "Continue?"), - QMessageBox::Yes | QMessageBox::No) - == QMessageBox::Yes) { + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { m_DownloadManager.addDownload(reply, new ModRepositoryFileInfo()); } } @@ -1447,7 +1458,7 @@ PluginContainer& OrganizerCore::pluginContainer() const return *m_PluginContainer; } -IPluginGame const *OrganizerCore::managedGame() const +IPluginGame const* OrganizerCore::managedGame() const { return m_GamePlugin; } @@ -1484,10 +1495,10 @@ void OrganizerCore::refreshDirectoryStructure() m_CurrentProfile->writeModlistNow(true); const auto activeModList = m_CurrentProfile->getActiveMods(); - const auto archives = enabledArchives(); + const auto archives = enabledArchives(); - m_DirectoryRefresher->setMods( - activeModList, std::set(archives.begin(), archives.end())); + m_DirectoryRefresher->setMods(activeModList, + std::set(archives.begin(), archives.end())); // runs refresh() in a thread QTimer::singleShot(0, m_DirectoryRefresher.get(), SLOT(refresh())); @@ -1498,7 +1509,7 @@ void OrganizerCore::directory_refreshed() log::debug("directory refreshed, finishing up"); TimeThis tt("OrganizerCore::directory_refreshed()"); - DirectoryEntry *newStructure = m_DirectoryRefresher->stealDirectoryStructure(); + DirectoryEntry* newStructure = m_DirectoryRefresher->stealDirectoryStructure(); Q_ASSERT(newStructure != m_DirectoryStructure); if (newStructure == nullptr) { @@ -1514,7 +1525,7 @@ void OrganizerCore::directory_refreshed() m_StructureDeleter.join(); } - m_StructureDeleter = MOShared::startSafeThread([=]{ + m_StructureDeleter = MOShared::startSafeThread([=] { log::debug("structure deleter thread start"); delete newStructure; log::debug("structure deleter thread done"); @@ -1573,8 +1584,7 @@ void OrganizerCore::clearCaches(std::vector const& indices) const insert(allIndices, modInfo->getModArchiveOverwritten()); insert(allIndices, modInfo->getModArchiveLooseOverwrite()); insert(allIndices, modInfo->getModArchiveLooseOverwritten()); - } - else { + } else { // if the mod is disabled, we need to first fetch the conflicting // mods, and then clear the cache insert(allIndices, modInfo->getModOverwrite()); @@ -1599,7 +1609,9 @@ void OrganizerCore::modPrioritiesChanged(const QModelIndexList& indices) if (currentProfile()->modEnabled(i)) { ModInfo::Ptr modInfo = ModInfo::getByIndex(i); // priorities in the directory structure are one higher because data is 0 - directoryStructure()->getOriginByName(MOBase::ToWString(modInfo->internalName())).setPriority(priority + 1); + directoryStructure() + ->getOriginByName(MOBase::ToWString(modInfo->internalName())) + .setPriority(priority + 1); } } refreshBSAList(); @@ -1624,8 +1636,8 @@ void OrganizerCore::modStatusChanged(unsigned int index) } else { updateModActiveState(index, false); if (m_DirectoryStructure->originExists(ToWString(modInfo->name()))) { - FilesOrigin &origin - = m_DirectoryStructure->getOriginByName(ToWString(modInfo->name())); + FilesOrigin& origin = + m_DirectoryStructure->getOriginByName(ToWString(modInfo->name())); origin.enable(false); } if (m_UserInterface != nullptr) { @@ -1635,7 +1647,7 @@ void OrganizerCore::modStatusChanged(unsigned int index) for (unsigned int i = 0; i < m_CurrentProfile->numMods(); ++i) { ModInfo::Ptr modInfo = ModInfo::getByIndex(i); - int priority = m_CurrentProfile->getModPriority(i); + int priority = m_CurrentProfile->getModPriority(i); if (m_DirectoryStructure->originExists(ToWString(modInfo->name()))) { // priorities in the directory structure are one higher because data is // 0 @@ -1646,15 +1658,16 @@ void OrganizerCore::modStatusChanged(unsigned int index) m_DirectoryStructure->getFileRegister()->sortOrigins(); refreshLists(); - clearCaches({ index }); - m_ModList.notifyModStateChanged({ index }); + clearCaches({index}); + m_ModList.notifyModStateChanged({index}); - } catch (const std::exception &e) { + } catch (const std::exception& e) { reportError(tr("failed to update mod list: %1").arg(e.what())); } } -void OrganizerCore::modStatusChanged(QList index) { +void OrganizerCore::modStatusChanged(QList index) +{ try { QMap modsToEnable; QMap modsToDisable; @@ -1674,8 +1687,8 @@ void OrganizerCore::modStatusChanged(QList index) { updateModsActiveState(modsToDisable.keys(), false); for (auto idx : modsToDisable.keys()) { if (m_DirectoryStructure->originExists(ToWString(modsToDisable[idx]->name()))) { - FilesOrigin &origin - = m_DirectoryStructure->getOriginByName(ToWString(modsToDisable[idx]->name())); + FilesOrigin& origin = m_DirectoryStructure->getOriginByName( + ToWString(modsToDisable[idx]->name())); origin.enable(false); } } @@ -1686,12 +1699,12 @@ void OrganizerCore::modStatusChanged(QList index) { for (unsigned int i = 0; i < m_CurrentProfile->numMods(); ++i) { ModInfo::Ptr modInfo = ModInfo::getByIndex(i); - int priority = m_CurrentProfile->getModPriority(i); + int priority = m_CurrentProfile->getModPriority(i); if (m_DirectoryStructure->originExists(ToWString(modInfo->name()))) { // priorities in the directory structure are one higher because data is // 0 m_DirectoryStructure->getOriginByName(ToWString(modInfo->name())) - .setPriority(priority + 1); + .setPriority(priority + 1); } } m_DirectoryStructure->getFileRegister()->sortOrigins(); @@ -1700,7 +1713,7 @@ void OrganizerCore::modStatusChanged(QList index) { clearCaches(vindices); m_ModList.notifyModStateChanged(index); - } catch (const std::exception &e) { + } catch (const std::exception& e) { reportError(tr("failed to update mod list: %1").arg(e.what())); } } @@ -1730,14 +1743,12 @@ void OrganizerCore::loginSuccessfulUpdate(bool necessary) m_Updater.startUpdate(); } -void OrganizerCore::loginFailed(const QString &message) +void OrganizerCore::loginFailed(const QString& message) { - qCritical().nospace().noquote() - << "Nexus API validation failed: " << message; + qCritical().nospace().noquote() << "Nexus API validation failed: " << message; if (QMessageBox::question(qApp->activeWindow(), tr("Login failed"), - tr("Login failed, try again?")) - == QMessageBox::Yes) { + tr("Login failed, try again?")) == QMessageBox::Yes) { if (nexusApi(true)) { return; } @@ -1760,11 +1771,10 @@ void OrganizerCore::loginFailed(const QString &message) NexusInterface::instance().loginCompleted(); } -void OrganizerCore::loginFailedUpdate(const QString &message) +void OrganizerCore::loginFailedUpdate(const QString& message) { MessageDialog::showMessage( - tr("login failed: %1. You need to log-in with Nexus to update MO.") - .arg(message), + tr("login failed: %1. You need to log-in with Nexus to update MO.").arg(message), qApp->activeWindow()); } @@ -1783,8 +1793,9 @@ void OrganizerCore::syncOverwrite() QString OrganizerCore::oldMO1HookDll() const { if (auto extender = managedGame()->feature()) { - QString hookdll = QDir::toNativeSeparators( - managedGame()->dataDirectory().absoluteFilePath(extender->PluginPath() + "/hook.dll")); + QString hookdll = + QDir::toNativeSeparators(managedGame()->dataDirectory().absoluteFilePath( + extender->PluginPath() + "/hook.dll")); if (QFile(hookdll).exists()) return hookdll; } @@ -1796,10 +1807,10 @@ std::vector OrganizerCore::activeProblems() const std::vector problems; const auto& hookdll = oldMO1HookDll(); if (!hookdll.isEmpty()) { - // This warning will now be shown every time the problems are checked, which is a bit - // of a "log spam". But since this is a sever error which will most likely make the - // game crash/freeze/etc. and is very hard to diagnose, this "log spam" will make it - // easier for the user to notice the warning. + // This warning will now be shown every time the problems are checked, which is a + // bit of a "log spam". But since this is a sever error which will most likely make + // the game crash/freeze/etc. and is very hard to diagnose, this "log spam" will + // make it easier for the user to notice the warning. log::warn("hook.dll found in game folder: {}", hookdll); problems.push_back(PROBLEM_MO1SCRIPTEXTENDERWORKAROUND); } @@ -1809,28 +1820,34 @@ std::vector OrganizerCore::activeProblems() const QString OrganizerCore::shortDescription(unsigned int key) const { switch (key) { - case PROBLEM_MO1SCRIPTEXTENDERWORKAROUND: { - return tr("MO1 \"Script Extender\" load mechanism has left hook.dll in your game folder"); - } break; - default: { - return tr("Description missing"); - } break; + case PROBLEM_MO1SCRIPTEXTENDERWORKAROUND: { + return tr( + "MO1 \"Script Extender\" load mechanism has left hook.dll in your game folder"); + } break; + default: { + return tr("Description missing"); + } break; } } QString OrganizerCore::fullDescription(unsigned int key) const { switch (key) { - case PROBLEM_MO1SCRIPTEXTENDERWORKAROUND: { - return tr("hook.dll has been found in your game folder (right click to copy the full path). " - "This is most likely a leftover of setting the ModOrganizer 1 load mechanism to \"Script Extender\", " - "in which case you must remove this file either by changing the load mechanism in ModOrganizer 1 or " - "manually removing the file, otherwise the game is likely to crash and burn.").arg(oldMO1HookDll()); - break; - } - default: { - return tr("Description missing"); - } break; + case PROBLEM_MO1SCRIPTEXTENDERWORKAROUND: { + return tr("hook.dll has been found in your game folder (right " + "click to copy the full path). " + "This is most likely a leftover of setting the ModOrganizer 1 load " + "mechanism to \"Script Extender\", " + "in which case you must remove this file either by changing the load " + "mechanism in ModOrganizer 1 or " + "manually removing the file, otherwise the game is likely to crash and " + "burn.") + .arg(oldMO1HookDll()); + break; + } + default: { + return tr("Description missing"); + } break; } } @@ -1839,9 +1856,7 @@ bool OrganizerCore::hasGuidedFix(unsigned int) const return false; } -void OrganizerCore::startGuidedFix(unsigned int) const -{ -} +void OrganizerCore::startGuidedFix(unsigned int) const {} bool OrganizerCore::saveCurrentLists() { @@ -1855,7 +1870,7 @@ bool OrganizerCore::saveCurrentLists() if (m_UserInterface != nullptr) { m_UserInterface->archivesWriter().write(); } - } catch (const std::exception &e) { + } catch (const std::exception& e) { reportError(tr("failed to save load order: %1").arg(e.what())); } @@ -1893,9 +1908,8 @@ ProcessRunner OrganizerCore::processRunner() } bool OrganizerCore::beforeRun( - const QFileInfo& binary, const QString& profileName, - const QString& customOverwrite, - const QList& forcedLibraries) + const QFileInfo& binary, const QString& profileName, const QString& customOverwrite, + const QList& forcedLibraries) { saveCurrentProfile(); @@ -1903,7 +1917,7 @@ bool OrganizerCore::beforeRun( if (m_DirectoryUpdate) { QEventLoop loop; connect(this, &OrganizerCore::directoryStructureReady, &loop, &QEventLoop::quit, - Qt::ConnectionType::QueuedConnection); + Qt::ConnectionType::QueuedConnection); loop.exec(); } @@ -1918,18 +1932,13 @@ bool OrganizerCore::beforeRun( return false; } - try - { + try { m_USVFS.updateMapping(fileMapping(profileName, customOverwrite)); m_USVFS.updateForcedLibraries(forcedLibraries); - } - catch (const UsvfsConnectorException &e) - { + } catch (const UsvfsConnectorException& e) { log::debug(e.what()); return false; - } - catch (const std::exception &e) - { + } catch (const std::exception& e) { QWidget* w = nullptr; if (m_UserInterface) { w = m_UserInterface->mainWindow(); @@ -1946,7 +1955,8 @@ void OrganizerCore::afterRun(const QFileInfo& binary, DWORD exitCode) // need to remove our stored load order because it may be outdated if a // foreign tool changed the file time. After removing that file, // refreshESPList will use the file time as the order - if (managedGame()->loadOrderMechanism() == IPluginGame::LoadOrderMechanism::FileTime) { + if (managedGame()->loadOrderMechanism() == + IPluginGame::LoadOrderMechanism::FileTime) { log::debug("removing loadorder.txt"); QFile::remove(m_CurrentProfile->getLoadOrderFileName()); } @@ -1957,35 +1967,37 @@ void OrganizerCore::afterRun(const QFileInfo& binary, DWORD exitCode) savePluginList(); cycleDiagnostics(); - //These callbacks should not fiddle with directory structure and ESPs. + // These callbacks should not fiddle with directory structure and ESPs. m_FinishedRun(binary.absoluteFilePath(), exitCode); } -ProcessRunner::Results OrganizerCore::waitForAllUSVFSProcesses( - UILocker::Reasons reason) +ProcessRunner::Results OrganizerCore::waitForAllUSVFSProcesses(UILocker::Reasons reason) { return processRunner().waitForAllUSVFSProcessesWithLock(reason); } -std::vector OrganizerCore::fileMapping(const QString &profileName, - const QString &customOverwrite) +std::vector OrganizerCore::fileMapping(const QString& profileName, + const QString& customOverwrite) { // need to wait until directory structure is ready if (m_DirectoryUpdate) { QEventLoop loop; connect(this, &OrganizerCore::directoryStructureReady, &loop, &QEventLoop::quit, - Qt::ConnectionType::QueuedConnection); + Qt::ConnectionType::QueuedConnection); loop.exec(); } - IPluginGame *game = qApp->property("managed_game").value(); - Profile profile(QDir(m_Settings.paths().profiles() + "/" + profileName), - game); + IPluginGame* game = qApp->property("managed_game").value(); + Profile profile(QDir(m_Settings.paths().profiles() + "/" + profileName), game); MappingType result; - QString dataPath - = QDir::toNativeSeparators(game->dataDirectory().absolutePath()); + QStringList dataPaths; + dataPaths.append(QDir::toNativeSeparators(game->dataDirectory().absolutePath())); + + for (auto directory : game->secondaryDataDirectories()) { + dataPaths.append(directory.absolutePath()); + } bool overwriteActive = false; @@ -2002,21 +2014,23 @@ std::vector OrganizerCore::fileMapping(const QString &profileName, overwriteActive |= createTarget; if (modPtr->isRegular()) { - result.insert(result.end(), {QDir::toNativeSeparators(std::get<1>(mod)), - dataPath, true, createTarget}); + for (auto dataPath : dataPaths) { + result.insert(result.end(), { QDir::toNativeSeparators(std::get<1>(mod)), + dataPath, true, createTarget }); + } } } if (!overwriteActive && !customOverwrite.isEmpty()) { - throw MyException(tr("The designated write target \"%1\" is not enabled.") - .arg(customOverwrite)); + throw MyException( + tr("The designated write target \"%1\" is not enabled.").arg(customOverwrite)); } if (m_CurrentProfile->localSavesEnabled()) { - LocalSavegames *localSaves = game->feature(); + LocalSavegames* localSaves = game->feature(); if (localSaves != nullptr) { - MappingType saveMap - = localSaves->mappings(currentProfile()->absolutePath() + "/saves"); + MappingType saveMap = + localSaves->mappings(currentProfile()->absolutePath() + "/saves"); result.reserve(result.size() + saveMap.size()); result.insert(result.end(), saveMap.begin(), saveMap.end()); } else { @@ -2024,16 +2038,18 @@ std::vector OrganizerCore::fileMapping(const QString &profileName, } } - result.insert(result.end(), { - QDir::toNativeSeparators(m_Settings.paths().overwrite()), - dataPath, - true, - customOverwrite.isEmpty() - }); + for (auto dataPath : dataPaths) { + result.insert(result.end(), { + QDir::toNativeSeparators(m_Settings.paths().overwrite()), + dataPath, + true, + customOverwrite.isEmpty() + }); + } - for (MOBase::IPluginFileMapper *mapper : + for (MOBase::IPluginFileMapper* mapper : m_PluginContainer->plugins()) { - IPlugin *plugin = dynamic_cast(mapper); + IPlugin* plugin = dynamic_cast(mapper); if (m_PluginContainer->isEnabled(plugin)) { MappingType pluginMap = mapper->mappings(); result.reserve(result.size() + pluginMap.size()); @@ -2044,26 +2060,26 @@ std::vector OrganizerCore::fileMapping(const QString &profileName, return result; } - -std::vector OrganizerCore::fileMapping( - const QString &dataPath, const QString &relPath, const DirectoryEntry *base, - const DirectoryEntry *directoryEntry, int createDestination) +std::vector OrganizerCore::fileMapping(const QString& dataPath, + const QString& relPath, + const DirectoryEntry* base, + const DirectoryEntry* directoryEntry, + int createDestination) { std::vector result; for (FileEntryPtr current : directoryEntry->getFiles()) { bool isArchive = false; - int origin = current->getOrigin(isArchive); + int origin = current->getOrigin(isArchive); if (isArchive || (origin == 0)) { continue; } - QString originPath - = QString::fromStdWString(base->getOriginByID(origin).getPath()); - QString fileName = QString::fromStdWString(current->getName()); -// QString fileName = ToQString(current->getName()); - QString source = originPath + relPath + fileName; - QString target = dataPath + relPath + fileName; + QString originPath = QString::fromStdWString(base->getOriginByID(origin).getPath()); + QString fileName = QString::fromStdWString(current->getName()); + // QString fileName = ToQString(current->getName()); + QString source = originPath + relPath + fileName; + QString target = dataPath + relPath + fileName; if (source != target) { result.push_back({source, target, false, false}); } @@ -2073,18 +2089,16 @@ std::vector OrganizerCore::fileMapping( for (const auto& d : directoryEntry->getSubDirectories()) { int origin = d->anyOrigin(); - QString originPath - = QString::fromStdWString(base->getOriginByID(origin).getPath()); - QString dirName = QString::fromStdWString(d->getName()); - QString source = originPath + relPath + dirName; - QString target = dataPath + relPath + dirName; + QString originPath = QString::fromStdWString(base->getOriginByID(origin).getPath()); + QString dirName = QString::fromStdWString(d->getName()); + QString source = originPath + relPath + dirName; + QString target = dataPath + relPath + dirName; - bool writeDestination - = (base == directoryEntry) && (origin == createDestination); + bool writeDestination = (base == directoryEntry) && (origin == createDestination); result.push_back({source, target, true, writeDestination}); - std::vector subRes = fileMapping( - dataPath, relPath + dirName + "\\", base, d, createDestination); + std::vector subRes = + fileMapping(dataPath, relPath + dirName + "\\", base, d, createDestination); result.insert(result.end(), subRes.begin(), subRes.end()); } return result; diff --git a/src/organizercore.h b/src/organizercore.h index 120583570..daeb7a7f3 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -1,502 +1,541 @@ -#ifndef ORGANIZERCORE_H -#define ORGANIZERCORE_H - -#include "selfupdater.h" -#include "settings.h" -#include "modlist.h" -#include "modinfo.h" -#include "pluginlist.h" -#include "installationmanager.h" -#include "downloadmanager.h" -#include "executableslist.h" -#include "usvfsconnector.h" -#include "guessedvalue.h" -#include "moshortcut.h" -#include "memoizedlock.h" -#include "processrunner.h" -#include "uilocker.h" -#include "envdump.h" -#include -#include -#include -#include -#include -#include "executableinfo.h" -#include "moddatacontent.h" -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -class ModListSortProxy; -class PluginListSortProxy; -class Profile; -class IUserInterface; -class PluginContainer; -class DirectoryRefresher; - -namespace MOBase -{ - template class GuessedValue; - class IModInterface; - class IPluginGame; -} - -namespace MOShared -{ - class DirectoryEntry; -} - - -class OrganizerCore : public QObject, public MOBase::IPluginDiagnose -{ - - Q_OBJECT - Q_INTERFACES(MOBase::IPluginDiagnose) - -private: - - friend class OrganizerProxy; - - struct SignalCombinerAnd - { - using result_type = bool; - - template - bool operator()(InputIterator first, InputIterator last) const - { - while (first != last) { - if (!(*first)) { - return false; - } - ++first; - } - return true; - } - }; - -private: - - using SignalAboutToRunApplication = boost::signals2::signal; - using SignalFinishedRunApplication = boost::signals2::signal; - using SignalUserInterfaceInitialized = boost::signals2::signal; - using SignalProfileCreated = boost::signals2::signal; - using SignalProfileRenamed = boost::signals2::signal; - using SignalProfileRemoved = boost::signals2::signal; - using SignalProfileChanged = boost::signals2::signal; - using SignalPluginSettingChanged = boost::signals2::signal; - using SignalPluginEnabled = boost::signals2::signal; - -public: - - /** - * Small holder for the game content returned by the ModDataContent feature (the - * list of all possible contents, not the per-mod content). - */ - struct ModDataContentHolder { - - using Content = ModDataContent::Content; - - /** - * @return true if the hold list of contents is empty, false otherwise. - */ - bool empty() const { return m_Contents.empty(); } - - /** - * @param id ID of the content to retrieve. - * - * @return the content with the given ID, or a null pointer if it is not found. - */ - const Content* findById(int id) const { - auto it = std::find_if(std::begin(m_Contents), std::end(m_Contents), [&id](auto const& content) { return content.id() == id; }); - return it == std::end(m_Contents) ? nullptr : &(*it); - } - - /** - * Apply the given function to each content whose ID is in the given set. - * - * @param ids The set of content IDs. - * @param fn The function to apply. - * @param includeFilter true to also apply the function to filter-only contents, false otherwise. - */ - template - void forEachContentIn(std::set const& ids, Fn const& fn, bool includeFilter = false) const { - for (const auto& content : m_Contents) { - if ((includeFilter || !content.isOnlyForFilter()) - && ids.find(content.id()) != ids.end()) { - fn(content); - } - } - } - - /** - * @brief Apply fnIn to each content whose ID is in the given set, and fnOut to each content not in the - * given set, excluding filter-only content (from both cases) unless includeFilter is true. - * - * @param ids The set of content IDs. - * @param fnIn Function to apply to content whose IDs are in ids. - * @param fnOut Function to apply to content whose IDs are not in ids. - * @param includeFilter true to also apply the function to filter-only contents, false otherwise. - */ - template - void forEachContentInOrOut(std::set const& ids, FnIn const& fnIn, FnOut const& fnOut, bool includeFilter = false) const { - for (const auto& content : m_Contents) { - if ((includeFilter || !content.isOnlyForFilter())) { - if (ids.find(content.id()) != ids.end()) { - fnIn(content); - } - else { - fnOut(content); - } - } - } - } - - /** - * Apply the given function to each content. - * - * @param fn The function to apply. - * @param includeFilter true to also apply the function to filter-only contents, false otherwise. - */ - template - void forEachContent(Fn const& fn, bool includeFilter = false) const { - for (const auto& content : m_Contents) { - if (includeFilter || !content.isOnlyForFilter()) { - fn(content); - } - } - } - - - ModDataContentHolder& operator=(ModDataContentHolder const&) = delete; - ModDataContentHolder& operator=(ModDataContentHolder&&) = default; - - private: - - std::vector m_Contents; - - /** - * @brief Construct a ModDataContentHolder without any contents (e.g., if the feature is - * missing). - */ - ModDataContentHolder() { } - - /** - * @brief Construct a ModDataContentHold holding the given list of contents. - */ - ModDataContentHolder(std::vector contents) : - m_Contents(std::move(contents)) { } - - friend class OrganizerCore; - }; - -public: - OrganizerCore(Settings &settings); - - ~OrganizerCore(); - - void setUserInterface(IUserInterface* ui); - void connectPlugins(PluginContainer *container); - - void setManagedGame(MOBase::IPluginGame *game); - - void updateExecutablesList(); - void updateModInfoFromDisc(); - - void checkForUpdates(); - void startMOUpdate(); - - Settings &settings(); - SelfUpdater *updater() { return &m_Updater; } - InstallationManager *installationManager(); - MOShared::DirectoryEntry *directoryStructure() { return m_DirectoryStructure; } - DirectoryRefresher *directoryRefresher() { return m_DirectoryRefresher.get(); } - ExecutablesList *executablesList() { return &m_ExecutablesList; } - void setExecutablesList(const ExecutablesList &executablesList) { - m_ExecutablesList = executablesList; - } - - Profile *currentProfile() const { return m_CurrentProfile.get(); } - void setCurrentProfile(const QString &profileName); - - std::vector enabledArchives(); - - MOBase::VersionInfo getVersion() const { return m_Updater.getVersion(); } - - // return the plugin container - // - PluginContainer& pluginContainer() const; - - MOBase::IPluginGame const *managedGame() const; - - /** - * @brief Retrieve the organizer proxy of the currently managed game. - * - */ - MOBase::IOrganizer const* managedGameOrganizer() const; - - - /** - * @return the list of contents for the currently managed game, or an empty vector - * if the game plugin does not implement the ModDataContent feature. - */ - const ModDataContentHolder& modDataContents() const { return m_Contents; } - - bool isArchivesInit() const { return m_ArchivesInit; } - - bool saveCurrentLists(); - - ProcessRunner processRunner(); - - bool beforeRun( - const QFileInfo& binary, const QString& profileName, - const QString& customOverwrite, - const QList& forcedLibraries); - - void afterRun(const QFileInfo& binary, DWORD exitCode); - - ProcessRunner::Results waitForAllUSVFSProcesses( - UILocker::Reasons reason=UILocker::PreventExit); - - void refreshESPList(bool force = false); - void refreshBSAList(); - - void refreshDirectoryStructure(); - void updateModInDirectoryStructure(unsigned int index, ModInfo::Ptr modInfo); - void updateModsInDirectoryStructure(QMap modInfos); - - void doAfterLogin(const std::function &function) { m_PostLoginTasks.append(function); } - void loggedInAction(QWidget* parent, std::function f); - - bool previewFileWithAlternatives(QWidget* parent, QString filename, int selectedOrigin=-1); - bool previewFile(QWidget* parent, const QString& originName, const QString& path); - - void loginSuccessfulUpdate(bool necessary); - void loginFailedUpdate(const QString &message); - - static bool createAndMakeWritable(const QString &path); - bool checkPathSymlinks(); - bool bootstrap(); - void createDefaultProfile(); - - MOBase::DelayedFileWriter &pluginsWriter() { return m_PluginListsWriter; } - - void prepareVFS(); - - void updateVFSParams( - MOBase::log::Levels logLevel, env::CoreDumpTypes coreDumpType, - const QString& coreDumpsPath, std::chrono::seconds spawnDelay, - QString executableBlacklist); - - void setLogLevel(MOBase::log::Levels level); - - bool cycleDiagnostics(); - - static env::CoreDumpTypes getGlobalCoreDumpType(); - static void setGlobalCoreDumpType(env::CoreDumpTypes type); - static std::wstring getGlobalCoreDumpPath(); - -public: - MOBase::IModRepositoryBridge *createNexusBridge() const; - QString profileName() const; - QString profilePath() const; - QString downloadsPath() const; - QString overwritePath() const; - QString basePath() const; - QString modsPath() const; - MOBase::VersionInfo appVersion() const; - MOBase::IPluginGame *getGame(const QString &gameName) const; - MOBase::IModInterface *createMod(MOBase::GuessedValue &name); - void modDataChanged(MOBase::IModInterface *mod); - QVariant pluginSetting(const QString &pluginName, const QString &key) const; - void setPluginSetting(const QString &pluginName, const QString &key, const QVariant &value); - QVariant persistent(const QString &pluginName, const QString &key, const QVariant &def) const; - void setPersistent(const QString &pluginName, const QString &key, const QVariant &value, bool sync); - static QString pluginDataPath(); - virtual MOBase::IModInterface *installMod(const QString &fileName, int priority, bool reinstallation, ModInfo::Ptr currentMod, const QString &initModName); - QString resolvePath(const QString &fileName) const; - QStringList listDirectories(const QString &directoryName) const; - QStringList findFiles(const QString &path, const std::function &filter) const; - QStringList getFileOrigins(const QString &fileName) const; - QList findFileInfos(const QString &path, const std::function &filter) const; - DownloadManager *downloadManager(); - PluginList *pluginList(); - ModList *modList(); - void refresh(bool saveChanges = true); - - boost::signals2::connection onAboutToRun(const std::function& func); - boost::signals2::connection onFinishedRun(const std::function& func); - boost::signals2::connection onUserInterfaceInitialized(std::function const& func); - boost::signals2::connection onProfileCreated(std::function const& func); - boost::signals2::connection onProfileRenamed(std::function const& func); - boost::signals2::connection onProfileRemoved(std::function const& func); - boost::signals2::connection onProfileChanged(std::function const& func); - boost::signals2::connection onPluginSettingChanged(std::function const& func); - boost::signals2::connection onPluginEnabled(std::function const& func); - boost::signals2::connection onPluginDisabled(std::function const& func); - -public: // IPluginDiagnose interface - - virtual std::vector activeProblems() const; - virtual QString shortDescription(unsigned int key) const; - virtual QString fullDescription(unsigned int key) const; - virtual bool hasGuidedFix(unsigned int key) const; - virtual void startGuidedFix(unsigned int key) const; - -public slots: - - void profileRefresh(); - - void syncOverwrite(); - - void savePluginList(); - - void refreshLists(); - - ModInfo::Ptr installDownload(int downloadIndex, int priority = -1); - ModInfo::Ptr installArchive(const QString& archivePath, int priority = -1, bool reinstallation = false, - ModInfo::Ptr currentMod = nullptr, const QString& modName = QString()); - - void modPrioritiesChanged(QModelIndexList const& indexes); - void modStatusChanged(unsigned int index); - void modStatusChanged(QList index); - void requestDownload(const QUrl &url, QNetworkReply *reply); - void downloadRequestedNXM(const QString &url); - - void userInterfaceInitialized(); - - void profileCreated(MOBase::IProfile* profile); - void profileRenamed(MOBase::IProfile* profile, QString const& oldName, QString const& newName); - void profileRemoved(QString const& profileName); - - bool nexusApi(bool retry = false); - -signals: - - // emitted after a mod has been installed - // - void modInstalled(const QString &modName); - - // emitted when the managed game changes - // - void managedGameChanged(MOBase::IPluginGame const *gamePlugin); - - // emitted when the profile is changed, before notifying plugins - // - // the new profile can be stored but the old one is temporary and - // should not be - // - void profileChanged(Profile* oldProfile, Profile* newProfile); - - // Notify that the directory structure is ready to be used on the main thread - // Use queued connections - void directoryStructureReady(); - -private: - - std::pair doInstall(const QString& archivePath, - MOBase::GuessedValue modName, ModInfo::Ptr currentMod, int priority, bool reinstallation); - - void saveCurrentProfile(); - void storeSettings(); - - void updateModActiveState(int index, bool active); - void updateModsActiveState(const QList &modIndices, bool active); - - // clear the conflict caches of all the given mods, and the mods in conflict - // with the given mods - // - void clearCaches(std::vector const& indices) const; - - bool createDirectory(const QString &path); - - QString oldMO1HookDll() const; - - /** - * @brief return a descriptor of the mappings real file->virtual file - */ - std::vector fileMapping(const QString &profile, - const QString &customOverwrite); - - std::vector - fileMapping(const QString &dataPath, const QString &relPath, - const MOShared::DirectoryEntry *base, - const MOShared::DirectoryEntry *directoryEntry, - int createDestination); - -private slots: - - void directory_refreshed(); - void downloadRequested(QNetworkReply *reply, QString gameName, int modID, const QString &fileName); - void removeOrigin(const QString &name); - void downloadSpeed(const QString &serverName, int bytesPerSecond); - void loginSuccessful(bool necessary); - void loginFailed(const QString &message); - -private: - static const unsigned int PROBLEM_MO1SCRIPTEXTENDERWORKAROUND = 1; - -private: - IUserInterface* m_UserInterface; - PluginContainer *m_PluginContainer; - QString m_GameName; - MOBase::IPluginGame *m_GamePlugin; - ModDataContentHolder m_Contents; - - std::unique_ptr m_CurrentProfile; - - Settings& m_Settings; - - SelfUpdater m_Updater; - - SignalAboutToRunApplication m_AboutToRun; - SignalFinishedRunApplication m_FinishedRun; - SignalUserInterfaceInitialized m_UserInterfaceInitialized; - SignalProfileCreated m_ProfileCreated; - SignalProfileRenamed m_ProfileRenamed; - SignalProfileRemoved m_ProfileRemoved; - SignalProfileChanged m_ProfileChanged; - SignalPluginSettingChanged m_PluginSettingChanged; - SignalPluginEnabled m_PluginEnabled; - SignalPluginEnabled m_PluginDisabled; - - ModList m_ModList; - PluginList m_PluginList; - - - QList> m_PostLoginTasks; - QList> m_PostRefreshTasks; - - ExecutablesList m_ExecutablesList; - QStringList m_PendingDownloads; - QStringList m_DefaultArchives; - QStringList m_ActiveArchives; - - std::unique_ptr m_DirectoryRefresher; - MOShared::DirectoryEntry *m_DirectoryStructure; - MOBase::MemoizedLocked> m_VirtualFileTree; - - DownloadManager m_DownloadManager; - InstallationManager m_InstallationManager; - - QThread m_RefresherThread; - - std::thread m_StructureDeleter; - - bool m_DirectoryUpdate; - bool m_ArchivesInit; - - MOBase::DelayedFileWriter m_PluginListsWriter; - UsvfsConnector m_USVFS; - - UILocker m_UILocker; -}; - -#endif // ORGANIZERCORE_H +#ifndef ORGANIZERCORE_H +#define ORGANIZERCORE_H + +#include "downloadmanager.h" +#include "envdump.h" +#include "executableinfo.h" +#include "executableslist.h" +#include "guessedvalue.h" +#include "installationmanager.h" +#include "memoizedlock.h" +#include "moddatacontent.h" +#include "modinfo.h" +#include "modlist.h" +#include "moshortcut.h" +#include "pluginlist.h" +#include "processrunner.h" +#include "selfupdater.h" +#include "settings.h" +#include "uilocker.h" +#include "usvfsconnector.h" +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class ModListSortProxy; +class PluginListSortProxy; +class Profile; +class IUserInterface; +class PluginContainer; +class DirectoryRefresher; + +namespace MOBase +{ +template +class GuessedValue; +class IModInterface; +class IPluginGame; +} // namespace MOBase + +namespace MOShared +{ +class DirectoryEntry; +} + +class OrganizerCore : public QObject, public MOBase::IPluginDiagnose +{ + + Q_OBJECT + Q_INTERFACES(MOBase::IPluginDiagnose) + +private: + friend class OrganizerProxy; + + struct SignalCombinerAnd + { + using result_type = bool; + + template + bool operator()(InputIterator first, InputIterator last) const + { + while (first != last) { + if (!(*first)) { + return false; + } + ++first; + } + return true; + } + }; + +private: + using SignalAboutToRunApplication = + boost::signals2::signal; + using SignalFinishedRunApplication = + boost::signals2::signal; + using SignalUserInterfaceInitialized = boost::signals2::signal; + using SignalProfileCreated = boost::signals2::signal; + using SignalProfileRenamed = + boost::signals2::signal; + using SignalProfileRemoved = boost::signals2::signal; + using SignalProfileChanged = + boost::signals2::signal; + using SignalPluginSettingChanged = boost::signals2::signal; + using SignalPluginEnabled = boost::signals2::signal; + +public: + /** + * Small holder for the game content returned by the ModDataContent feature (the + * list of all possible contents, not the per-mod content). + */ + struct ModDataContentHolder + { + + using Content = ModDataContent::Content; + + /** + * @return true if the hold list of contents is empty, false otherwise. + */ + bool empty() const { return m_Contents.empty(); } + + /** + * @param id ID of the content to retrieve. + * + * @return the content with the given ID, or a null pointer if it is not found. + */ + const Content* findById(int id) const + { + auto it = std::find_if(std::begin(m_Contents), std::end(m_Contents), + [&id](auto const& content) { + return content.id() == id; + }); + return it == std::end(m_Contents) ? nullptr : &(*it); + } + + /** + * Apply the given function to each content whose ID is in the given set. + * + * @param ids The set of content IDs. + * @param fn The function to apply. + * @param includeFilter true to also apply the function to filter-only contents, + * false otherwise. + */ + template + void forEachContentIn(std::set const& ids, Fn const& fn, + bool includeFilter = false) const + { + for (const auto& content : m_Contents) { + if ((includeFilter || !content.isOnlyForFilter()) && + ids.find(content.id()) != ids.end()) { + fn(content); + } + } + } + + /** + * @brief Apply fnIn to each content whose ID is in the given set, and fnOut to each + * content not in the given set, excluding filter-only content (from both cases) + * unless includeFilter is true. + * + * @param ids The set of content IDs. + * @param fnIn Function to apply to content whose IDs are in ids. + * @param fnOut Function to apply to content whose IDs are not in ids. + * @param includeFilter true to also apply the function to filter-only contents, + * false otherwise. + */ + template + void forEachContentInOrOut(std::set const& ids, FnIn const& fnIn, + FnOut const& fnOut, bool includeFilter = false) const + { + for (const auto& content : m_Contents) { + if ((includeFilter || !content.isOnlyForFilter())) { + if (ids.find(content.id()) != ids.end()) { + fnIn(content); + } else { + fnOut(content); + } + } + } + } + + /** + * Apply the given function to each content. + * + * @param fn The function to apply. + * @param includeFilter true to also apply the function to filter-only contents, + * false otherwise. + */ + template + void forEachContent(Fn const& fn, bool includeFilter = false) const + { + for (const auto& content : m_Contents) { + if (includeFilter || !content.isOnlyForFilter()) { + fn(content); + } + } + } + + ModDataContentHolder& operator=(ModDataContentHolder const&) = delete; + ModDataContentHolder& operator=(ModDataContentHolder&&) = default; + + private: + std::vector m_Contents; + + /** + * @brief Construct a ModDataContentHolder without any contents (e.g., if the + * feature is missing). + */ + ModDataContentHolder() {} + + /** + * @brief Construct a ModDataContentHold holding the given list of contents. + */ + ModDataContentHolder(std::vector contents) + : m_Contents(std::move(contents)) + {} + + friend class OrganizerCore; + }; + +public: + OrganizerCore(Settings& settings); + + ~OrganizerCore(); + + void setUserInterface(IUserInterface* ui); + void connectPlugins(PluginContainer* container); + + void setManagedGame(MOBase::IPluginGame* game); + + void updateExecutablesList(); + void updateModInfoFromDisc(); + + void checkForUpdates(); + void startMOUpdate(); + + Settings& settings(); + SelfUpdater* updater() { return &m_Updater; } + InstallationManager* installationManager(); + MOShared::DirectoryEntry* directoryStructure() { return m_DirectoryStructure; } + DirectoryRefresher* directoryRefresher() { return m_DirectoryRefresher.get(); } + ExecutablesList* executablesList() { return &m_ExecutablesList; } + void setExecutablesList(const ExecutablesList& executablesList) + { + m_ExecutablesList = executablesList; + } + + Profile* currentProfile() const { return m_CurrentProfile.get(); } + void setCurrentProfile(const QString& profileName); + + std::vector enabledArchives(); + + MOBase::VersionInfo getVersion() const { return m_Updater.getVersion(); } + + // return the plugin container + // + PluginContainer& pluginContainer() const; + + MOBase::IPluginGame const* managedGame() const; + + /** + * @brief Retrieve the organizer proxy of the currently managed game. + * + */ + MOBase::IOrganizer const* managedGameOrganizer() const; + + /** + * @return the list of contents for the currently managed game, or an empty vector + * if the game plugin does not implement the ModDataContent feature. + */ + const ModDataContentHolder& modDataContents() const { return m_Contents; } + + bool isArchivesInit() const { return m_ArchivesInit; } + + bool saveCurrentLists(); + + ProcessRunner processRunner(); + + bool beforeRun(const QFileInfo& binary, const QString& profileName, + const QString& customOverwrite, + const QList& forcedLibraries); + + void afterRun(const QFileInfo& binary, DWORD exitCode); + + ProcessRunner::Results + waitForAllUSVFSProcesses(UILocker::Reasons reason = UILocker::PreventExit); + + void refreshESPList(bool force = false); + void refreshBSAList(); + + void refreshDirectoryStructure(); + void updateModInDirectoryStructure(unsigned int index, ModInfo::Ptr modInfo); + void updateModsInDirectoryStructure(QMap modInfos); + + void doAfterLogin(const std::function& function) + { + m_PostLoginTasks.append(function); + } + void loggedInAction(QWidget* parent, std::function f); + + bool previewFileWithAlternatives(QWidget* parent, QString filename, + int selectedOrigin = -1); + bool previewFile(QWidget* parent, const QString& originName, const QString& path); + + void loginSuccessfulUpdate(bool necessary); + void loginFailedUpdate(const QString& message); + + static bool createAndMakeWritable(const QString& path); + bool checkPathSymlinks(); + bool bootstrap(); + void createDefaultProfile(); + + MOBase::DelayedFileWriter& pluginsWriter() { return m_PluginListsWriter; } + + void prepareVFS(); + + void updateVFSParams(MOBase::log::Levels logLevel, env::CoreDumpTypes coreDumpType, + const QString& coreDumpsPath, std::chrono::seconds spawnDelay, + QString executableBlacklist); + + void setLogLevel(MOBase::log::Levels level); + + bool cycleDiagnostics(); + + static env::CoreDumpTypes getGlobalCoreDumpType(); + static void setGlobalCoreDumpType(env::CoreDumpTypes type); + static std::wstring getGlobalCoreDumpPath(); + +public: + MOBase::IModRepositoryBridge* createNexusBridge() const; + QString profileName() const; + QString profilePath() const; + QString downloadsPath() const; + QString overwritePath() const; + QString basePath() const; + QString modsPath() const; + MOBase::VersionInfo appVersion() const; + MOBase::IPluginGame* getGame(const QString& gameName) const; + MOBase::IModInterface* createMod(MOBase::GuessedValue& name); + void modDataChanged(MOBase::IModInterface* mod); + QVariant pluginSetting(const QString& pluginName, const QString& key) const; + void setPluginSetting(const QString& pluginName, const QString& key, + const QVariant& value); + QVariant persistent(const QString& pluginName, const QString& key, + const QVariant& def) const; + void setPersistent(const QString& pluginName, const QString& key, + const QVariant& value, bool sync); + static QString pluginDataPath(); + virtual MOBase::IModInterface* installMod(const QString& fileName, int priority, + bool reinstallation, + ModInfo::Ptr currentMod, + const QString& initModName); + QString resolvePath(const QString& fileName) const; + QStringList listDirectories(const QString& directoryName) const; + QStringList findFiles(const QString& path, + const std::function& filter) const; + QStringList getFileOrigins(const QString& fileName) const; + QList findFileInfos( + const QString& path, + const std::function& filter) const; + DownloadManager* downloadManager(); + PluginList* pluginList(); + ModList* modList(); + void refresh(bool saveChanges = true); + + boost::signals2::connection + onAboutToRun(const std::function& func); + boost::signals2::connection + onFinishedRun(const std::function& func); + boost::signals2::connection + onUserInterfaceInitialized(std::function const& func); + boost::signals2::connection + onProfileCreated(std::function const& func); + boost::signals2::connection onProfileRenamed( + std::function const& + func); + boost::signals2::connection + onProfileRemoved(std::function const& func); + boost::signals2::connection onProfileChanged( + std::function const& func); + boost::signals2::connection onPluginSettingChanged( + std::function const& func); + boost::signals2::connection + onPluginEnabled(std::function const& func); + boost::signals2::connection + onPluginDisabled(std::function const& func); + +public: // IPluginDiagnose interface + virtual std::vector activeProblems() const; + virtual QString shortDescription(unsigned int key) const; + virtual QString fullDescription(unsigned int key) const; + virtual bool hasGuidedFix(unsigned int key) const; + virtual void startGuidedFix(unsigned int key) const; + +public slots: + + void profileRefresh(); + + void syncOverwrite(); + + void savePluginList(); + + void refreshLists(); + + ModInfo::Ptr installDownload(int downloadIndex, int priority = -1); + ModInfo::Ptr installArchive(const QString& archivePath, int priority = -1, + bool reinstallation = false, + ModInfo::Ptr currentMod = nullptr, + const QString& modName = QString()); + + void modPrioritiesChanged(QModelIndexList const& indexes); + void modStatusChanged(unsigned int index); + void modStatusChanged(QList index); + void requestDownload(const QUrl& url, QNetworkReply* reply); + void downloadRequestedNXM(const QString& url); + + void userInterfaceInitialized(); + + void profileCreated(MOBase::IProfile* profile); + void profileRenamed(MOBase::IProfile* profile, QString const& oldName, + QString const& newName); + void profileRemoved(QString const& profileName); + + bool nexusApi(bool retry = false); + +signals: + + // emitted after a mod has been installed + // + void modInstalled(const QString& modName); + + // emitted when the managed game changes + // + void managedGameChanged(MOBase::IPluginGame const* gamePlugin); + + // emitted when the profile is changed, before notifying plugins + // + // the new profile can be stored but the old one is temporary and + // should not be + // + void profileChanged(Profile* oldProfile, Profile* newProfile); + + // Notify that the directory structure is ready to be used on the main thread + // Use queued connections + void directoryStructureReady(); + +private: + std::pair doInstall(const QString& archivePath, + MOBase::GuessedValue modName, + ModInfo::Ptr currentMod, int priority, + bool reinstallation); + + void saveCurrentProfile(); + void storeSettings(); + + void updateModActiveState(int index, bool active); + void updateModsActiveState(const QList& modIndices, bool active); + + // clear the conflict caches of all the given mods, and the mods in conflict + // with the given mods + // + void clearCaches(std::vector const& indices) const; + + bool createDirectory(const QString& path); + + QString oldMO1HookDll() const; + + /** + * @brief return a descriptor of the mappings real file->virtual file + */ + std::vector fileMapping(const QString& profile, + const QString& customOverwrite); + + std::vector fileMapping(const QString& dataPath, const QString& relPath, + const MOShared::DirectoryEntry* base, + const MOShared::DirectoryEntry* directoryEntry, + int createDestination); + +private slots: + + void directory_refreshed(); + void downloadRequested(QNetworkReply* reply, QString gameName, int modID, + const QString& fileName); + void removeOrigin(const QString& name); + void downloadSpeed(const QString& serverName, int bytesPerSecond); + void loginSuccessful(bool necessary); + void loginFailed(const QString& message); + +private: + static const unsigned int PROBLEM_MO1SCRIPTEXTENDERWORKAROUND = 1; + +private: + IUserInterface* m_UserInterface; + PluginContainer* m_PluginContainer; + QString m_GameName; + MOBase::IPluginGame* m_GamePlugin; + ModDataContentHolder m_Contents; + + std::unique_ptr m_CurrentProfile; + + Settings& m_Settings; + + SelfUpdater m_Updater; + + SignalAboutToRunApplication m_AboutToRun; + SignalFinishedRunApplication m_FinishedRun; + SignalUserInterfaceInitialized m_UserInterfaceInitialized; + SignalProfileCreated m_ProfileCreated; + SignalProfileRenamed m_ProfileRenamed; + SignalProfileRemoved m_ProfileRemoved; + SignalProfileChanged m_ProfileChanged; + SignalPluginSettingChanged m_PluginSettingChanged; + SignalPluginEnabled m_PluginEnabled; + SignalPluginEnabled m_PluginDisabled; + + ModList m_ModList; + PluginList m_PluginList; + + QList> m_PostLoginTasks; + QList> m_PostRefreshTasks; + + ExecutablesList m_ExecutablesList; + QStringList m_PendingDownloads; + QStringList m_DefaultArchives; + QStringList m_ActiveArchives; + + std::unique_ptr m_DirectoryRefresher; + MOShared::DirectoryEntry* m_DirectoryStructure; + MOBase::MemoizedLocked> m_VirtualFileTree; + + DownloadManager m_DownloadManager; + InstallationManager m_InstallationManager; + + QThread m_RefresherThread; + + std::thread m_StructureDeleter; + + bool m_DirectoryUpdate; + bool m_ArchivesInit; + + MOBase::DelayedFileWriter m_PluginListsWriter; + UsvfsConnector m_USVFS; + + UILocker m_UILocker; +}; + +#endif // ORGANIZERCORE_H diff --git a/src/organizerproxy.cpp b/src/organizerproxy.cpp index e9fe61c21..a47e8798a 100644 --- a/src/organizerproxy.cpp +++ b/src/organizerproxy.cpp @@ -1,375 +1,403 @@ -#include "organizerproxy.h" - -#include "shared/appconfig.h" -#include "organizercore.h" -#include "plugincontainer.h" -#include "settings.h" -#include "glob_matching.h" -#include "downloadmanagerproxy.h" -#include "modlistproxy.h" -#include "pluginlistproxy.h" -#include "proxyutils.h" -#include "shared/util.h" - -#include -#include - -using namespace MOBase; -using namespace MOShared; - - -OrganizerProxy::OrganizerProxy(OrganizerCore* organizer, PluginContainer* pluginContainer, MOBase::IPlugin* plugin) - : m_Proxied(organizer) - , m_PluginContainer(pluginContainer) - , m_Plugin(plugin) - , m_DownloadManagerProxy(std::make_unique(this, organizer->downloadManager())) - , m_ModListProxy(std::make_unique(this, organizer->modList())) - , m_PluginListProxy(std::make_unique(this, organizer->pluginList())) { } - -OrganizerProxy::~OrganizerProxy() -{ - disconnectSignals(); -} - -void OrganizerProxy::connectSignals() -{ - m_Connections.push_back(m_Proxied->onAboutToRun(callSignalIfPluginActive(this, m_AboutToRun, true))); - m_Connections.push_back(m_Proxied->onFinishedRun(callSignalIfPluginActive(this, m_FinishedRun))); - m_Connections.push_back(m_Proxied->onProfileCreated(callSignalIfPluginActive(this, m_ProfileCreated))); - m_Connections.push_back(m_Proxied->onProfileRenamed(callSignalIfPluginActive(this, m_ProfileRenamed))); - m_Connections.push_back(m_Proxied->onProfileRemoved(callSignalIfPluginActive(this, m_ProfileRemoved))); - m_Connections.push_back(m_Proxied->onProfileChanged(callSignalIfPluginActive(this, m_ProfileChanged))); - - m_Connections.push_back(m_Proxied->onUserInterfaceInitialized(callSignalAlways(m_UserInterfaceInitialized))); - m_Connections.push_back(m_Proxied->onPluginSettingChanged(callSignalAlways(m_PluginSettingChanged))); - m_Connections.push_back(m_Proxied->onPluginEnabled(callSignalAlways(m_PluginEnabled))); - m_Connections.push_back(m_Proxied->onPluginDisabled(callSignalAlways(m_PluginDisabled))); - - // Connect the child proxies. - m_DownloadManagerProxy->connectSignals(); - m_ModListProxy->connectSignals(); - m_PluginListProxy->connectSignals(); -} - -void OrganizerProxy::disconnectSignals() -{ - // Disconnect the child proxies. - m_DownloadManagerProxy->disconnectSignals(); - m_ModListProxy->disconnectSignals(); - m_PluginListProxy->disconnectSignals(); - - for (auto& conn : m_Connections) { - conn.disconnect(); - } - m_Connections.clear(); -} - -IModRepositoryBridge *OrganizerProxy::createNexusBridge() const -{ - return new NexusBridge(m_PluginContainer, m_Plugin->name()); -} - -QString OrganizerProxy::profileName() const -{ - return m_Proxied->profileName(); -} - -QString OrganizerProxy::profilePath() const -{ - return m_Proxied->profilePath(); -} - -QString OrganizerProxy::downloadsPath() const -{ - return m_Proxied->downloadsPath(); -} - -QString OrganizerProxy::overwritePath() const -{ - return m_Proxied->overwritePath(); -} - -QString OrganizerProxy::basePath() const -{ - return m_Proxied->basePath(); -} - -QString OrganizerProxy::modsPath() const -{ - return m_Proxied->modsPath(); -} - -VersionInfo OrganizerProxy::appVersion() const -{ - return m_Proxied->appVersion(); -} - -IPluginGame *OrganizerProxy::getGame(const QString &gameName) const -{ - return m_Proxied->getGame(gameName); -} - -IModInterface *OrganizerProxy::createMod(MOBase::GuessedValue &name) -{ - return m_Proxied->createMod(name); -} - -void OrganizerProxy::modDataChanged(IModInterface *mod) -{ - m_Proxied->modDataChanged(mod); -} - -bool OrganizerProxy::isPluginEnabled(QString const& pluginName) const -{ - return m_PluginContainer->isEnabled(pluginName); -} - -bool OrganizerProxy::isPluginEnabled(IPlugin* plugin) const -{ - return m_PluginContainer->isEnabled(plugin); -} - -QVariant OrganizerProxy::pluginSetting(const QString &pluginName, const QString &key) const -{ - return m_Proxied->pluginSetting(pluginName, key); -} - -void OrganizerProxy::setPluginSetting(const QString &pluginName, const QString &key, const QVariant &value) -{ - m_Proxied->setPluginSetting(pluginName, key, value); -} - -QVariant OrganizerProxy::persistent(const QString &pluginName, const QString &key, const QVariant &def) const -{ - return m_Proxied->persistent(pluginName, key, def); -} - -void OrganizerProxy::setPersistent(const QString &pluginName, const QString &key, const QVariant &value, bool sync) -{ - m_Proxied->setPersistent(pluginName, key, value, sync); -} - -QString OrganizerProxy::pluginDataPath() const -{ - return OrganizerCore::pluginDataPath(); -} - -HANDLE OrganizerProxy::startApplication( - const QString& exe, const QStringList& args, const QString &cwd, - const QString& profile, const QString &overwrite, bool ignoreOverwrite) -{ - log::debug( - "a plugin has requested to start an application:\n" - " . executable: '{}'\n" - " . args: '{}'\n" - " . cwd: '{}'\n" - " . profile: '{}'\n" - " . overwrite: '{}'\n" - " . ignore overwrite: {}", - exe, args.join(" "), cwd, profile, overwrite, ignoreOverwrite); - - auto runner = m_Proxied->processRunner(); - - // don't wait for completion - runner - .setFromFileOrExecutable(exe, args, cwd, profile, overwrite, ignoreOverwrite) - .run(); - - // the plugin is in charge of closing the handle, unless waitForApplication() - // is called on it - return runner.stealProcessHandle().release(); -} - -bool OrganizerProxy::waitForApplication(HANDLE handle, bool refresh, LPDWORD exitCode) const -{ - const auto pid = ::GetProcessId(handle); - - log::debug( - "a plugin wants to wait for an application to complete, pid {}{}", - pid, (pid == 0 ? "unknown (probably already completed)" : "")); - - auto runner = m_Proxied->processRunner(); - - ProcessRunner::WaitFlags waitFlags = ProcessRunner::ForceWait; - - if (refresh) { - waitFlags |= ProcessRunner::TriggerRefresh | ProcessRunner::WaitForRefresh; - } - - const auto r = runner - .setWaitForCompletion(waitFlags, UILocker::OutputRequired) - .attachToProcess(handle); - - if (exitCode) { - *exitCode = runner.exitCode(); - } - - switch (r) - { - case ProcessRunner::Completed: - return true; - - case ProcessRunner::Cancelled: // fall-through - case ProcessRunner::ForceUnlocked: - // this is always an error because the application should have run to - // completion - return false; - - case ProcessRunner::Error: // fall-through - default: - return false; - } -} - -void OrganizerProxy::refresh(bool saveChanges) -{ - m_Proxied->refresh(saveChanges); -} - -IModInterface *OrganizerProxy::installMod(const QString &fileName, const QString &nameSuggestion) -{ - return m_Proxied->installMod(fileName, -1, false, nullptr, nameSuggestion); -} - -QString OrganizerProxy::resolvePath(const QString &fileName) const -{ - return m_Proxied->resolvePath(fileName); -} - -QStringList OrganizerProxy::listDirectories(const QString &directoryName) const -{ - return m_Proxied->listDirectories(directoryName); -} - -QStringList OrganizerProxy::findFiles(const QString &path, const std::function &filter) const -{ - return m_Proxied->findFiles(path, filter); -} - -QStringList OrganizerProxy::findFiles(const QString& path, const QStringList& globFilters) const -{ - QList> patterns; - for (auto& gfilter : globFilters) { - patterns.append(GlobPattern(gfilter)); - } - return findFiles(path, [&patterns](const QString& filename) { - for (auto& p : patterns) { - if (p.match(filename)) { - return true; - } - } - return false; - }); -} - -QStringList OrganizerProxy::getFileOrigins(const QString &fileName) const -{ - return m_Proxied->getFileOrigins(fileName); -} - -QList OrganizerProxy::findFileInfos(const QString &path, const std::function &filter) const -{ - return m_Proxied->findFileInfos(path, filter); -} - -std::shared_ptr OrganizerProxy::virtualFileTree() const -{ - return m_Proxied->m_VirtualFileTree.value(); -} - -MOBase::IDownloadManager *OrganizerProxy::downloadManager() const -{ - return m_DownloadManagerProxy.get(); -} - -MOBase::IPluginList *OrganizerProxy::pluginList() const -{ - return m_PluginListProxy.get(); -} - -MOBase::IModList *OrganizerProxy::modList() const -{ - return m_ModListProxy.get(); -} - -MOBase::IProfile *OrganizerProxy::profile() const -{ - return m_Proxied->currentProfile(); -} - -MOBase::IPluginGame const *OrganizerProxy::managedGame() const -{ - return m_Proxied->managedGame(); -} - -// CALLBACKS - -bool OrganizerProxy::onAboutToRun(const std::function& func) -{ - return m_Proxied->onAboutToRun(MOShared::callIfPluginActive(this, func, true)).connected(); -} - -bool OrganizerProxy::onFinishedRun(const std::function& func) -{ - return m_Proxied->onFinishedRun(MOShared::callIfPluginActive(this, func)).connected(); -} - -bool OrganizerProxy::onProfileCreated(std::function const& func) -{ - return m_ProfileCreated.connect(func).connected(); -} - -bool OrganizerProxy::onProfileRenamed(std::function const& func) -{ - return m_ProfileRenamed.connect(func).connected(); -} - -bool OrganizerProxy::onProfileRemoved(std::function const& func) -{ - return m_ProfileRemoved.connect(func).connected(); -} - -bool OrganizerProxy::onProfileChanged(std::function const& func) -{ - return m_ProfileChanged.connect(func).connected(); -} - -bool OrganizerProxy::onUserInterfaceInitialized(std::function const& func) -{ - // Always call this one to allow plugin to initialize themselves even when not active: - return m_UserInterfaceInitialized.connect(func).connected(); -} - -// Always call these one, otherwise plugin cannot detect they are being enabled / disabled: -bool OrganizerProxy::onPluginSettingChanged(std::function const& func) -{ - return m_PluginSettingChanged.connect(func).connected(); -} - -bool OrganizerProxy::onPluginEnabled(std::function const& func) -{ - return m_PluginEnabled.connect(func).connected(); -} - -bool OrganizerProxy::onPluginEnabled(const QString& pluginName, std::function const& func) -{ - return onPluginEnabled([=](const IPlugin* plugin) { - if (plugin->name().compare(pluginName, Qt::CaseInsensitive) == 0) { - func(); - } - }); -} - -bool OrganizerProxy::onPluginDisabled(std::function const& func) -{ - return m_PluginDisabled.connect(func).connected(); -} - -bool OrganizerProxy::onPluginDisabled(const QString& pluginName, std::function const& func) -{ - return onPluginDisabled([=](const IPlugin* plugin) { - if (plugin->name().compare(pluginName, Qt::CaseInsensitive) == 0) { - func(); - } - }); -} +#include "organizerproxy.h" + +#include "downloadmanagerproxy.h" +#include "glob_matching.h" +#include "modlistproxy.h" +#include "organizercore.h" +#include "plugincontainer.h" +#include "pluginlistproxy.h" +#include "proxyutils.h" +#include "settings.h" +#include "shared/appconfig.h" +#include "shared/util.h" + +#include +#include + +using namespace MOBase; +using namespace MOShared; + +OrganizerProxy::OrganizerProxy(OrganizerCore* organizer, + PluginContainer* pluginContainer, + MOBase::IPlugin* plugin) + : m_Proxied(organizer), m_PluginContainer(pluginContainer), m_Plugin(plugin), + m_DownloadManagerProxy( + std::make_unique(this, organizer->downloadManager())), + m_ModListProxy(std::make_unique(this, organizer->modList())), + m_PluginListProxy( + std::make_unique(this, organizer->pluginList())) +{} + +OrganizerProxy::~OrganizerProxy() +{ + disconnectSignals(); +} + +void OrganizerProxy::connectSignals() +{ + m_Connections.push_back( + m_Proxied->onAboutToRun(callSignalIfPluginActive(this, m_AboutToRun, true))); + m_Connections.push_back( + m_Proxied->onFinishedRun(callSignalIfPluginActive(this, m_FinishedRun))); + m_Connections.push_back( + m_Proxied->onProfileCreated(callSignalIfPluginActive(this, m_ProfileCreated))); + m_Connections.push_back( + m_Proxied->onProfileRenamed(callSignalIfPluginActive(this, m_ProfileRenamed))); + m_Connections.push_back( + m_Proxied->onProfileRemoved(callSignalIfPluginActive(this, m_ProfileRemoved))); + m_Connections.push_back( + m_Proxied->onProfileChanged(callSignalIfPluginActive(this, m_ProfileChanged))); + + m_Connections.push_back(m_Proxied->onUserInterfaceInitialized( + callSignalAlways(m_UserInterfaceInitialized))); + m_Connections.push_back( + m_Proxied->onPluginSettingChanged(callSignalAlways(m_PluginSettingChanged))); + m_Connections.push_back( + m_Proxied->onPluginEnabled(callSignalAlways(m_PluginEnabled))); + m_Connections.push_back( + m_Proxied->onPluginDisabled(callSignalAlways(m_PluginDisabled))); + + // Connect the child proxies. + m_DownloadManagerProxy->connectSignals(); + m_ModListProxy->connectSignals(); + m_PluginListProxy->connectSignals(); +} + +void OrganizerProxy::disconnectSignals() +{ + // Disconnect the child proxies. + m_DownloadManagerProxy->disconnectSignals(); + m_ModListProxy->disconnectSignals(); + m_PluginListProxy->disconnectSignals(); + + for (auto& conn : m_Connections) { + conn.disconnect(); + } + m_Connections.clear(); +} + +IModRepositoryBridge* OrganizerProxy::createNexusBridge() const +{ + return new NexusBridge(m_PluginContainer, m_Plugin->name()); +} + +QString OrganizerProxy::profileName() const +{ + return m_Proxied->profileName(); +} + +QString OrganizerProxy::profilePath() const +{ + return m_Proxied->profilePath(); +} + +QString OrganizerProxy::downloadsPath() const +{ + return m_Proxied->downloadsPath(); +} + +QString OrganizerProxy::overwritePath() const +{ + return m_Proxied->overwritePath(); +} + +QString OrganizerProxy::basePath() const +{ + return m_Proxied->basePath(); +} + +QString OrganizerProxy::modsPath() const +{ + return m_Proxied->modsPath(); +} + +VersionInfo OrganizerProxy::appVersion() const +{ + return m_Proxied->appVersion(); +} + +IPluginGame* OrganizerProxy::getGame(const QString& gameName) const +{ + return m_Proxied->getGame(gameName); +} + +IModInterface* OrganizerProxy::createMod(MOBase::GuessedValue& name) +{ + return m_Proxied->createMod(name); +} + +void OrganizerProxy::modDataChanged(IModInterface* mod) +{ + m_Proxied->modDataChanged(mod); +} + +bool OrganizerProxy::isPluginEnabled(QString const& pluginName) const +{ + return m_PluginContainer->isEnabled(pluginName); +} + +bool OrganizerProxy::isPluginEnabled(IPlugin* plugin) const +{ + return m_PluginContainer->isEnabled(plugin); +} + +QVariant OrganizerProxy::pluginSetting(const QString& pluginName, + const QString& key) const +{ + return m_Proxied->pluginSetting(pluginName, key); +} + +void OrganizerProxy::setPluginSetting(const QString& pluginName, const QString& key, + const QVariant& value) +{ + m_Proxied->setPluginSetting(pluginName, key, value); +} + +QVariant OrganizerProxy::persistent(const QString& pluginName, const QString& key, + const QVariant& def) const +{ + return m_Proxied->persistent(pluginName, key, def); +} + +void OrganizerProxy::setPersistent(const QString& pluginName, const QString& key, + const QVariant& value, bool sync) +{ + m_Proxied->setPersistent(pluginName, key, value, sync); +} + +QString OrganizerProxy::pluginDataPath() const +{ + return OrganizerCore::pluginDataPath(); +} + +HANDLE OrganizerProxy::startApplication(const QString& exe, const QStringList& args, + const QString& cwd, const QString& profile, + const QString& overwrite, bool ignoreOverwrite) +{ + log::debug("a plugin has requested to start an application:\n" + " . executable: '{}'\n" + " . args: '{}'\n" + " . cwd: '{}'\n" + " . profile: '{}'\n" + " . overwrite: '{}'\n" + " . ignore overwrite: {}", + exe, args.join(" "), cwd, profile, overwrite, ignoreOverwrite); + + auto runner = m_Proxied->processRunner(); + + // don't wait for completion + runner.setFromFileOrExecutable(exe, args, cwd, profile, overwrite, ignoreOverwrite) + .run(); + + // the plugin is in charge of closing the handle, unless waitForApplication() + // is called on it + return runner.stealProcessHandle().release(); +} + +bool OrganizerProxy::waitForApplication(HANDLE handle, bool refresh, + LPDWORD exitCode) const +{ + const auto pid = ::GetProcessId(handle); + + log::debug("a plugin wants to wait for an application to complete, pid {}{}", pid, + (pid == 0 ? "unknown (probably already completed)" : "")); + + auto runner = m_Proxied->processRunner(); + + ProcessRunner::WaitFlags waitFlags = ProcessRunner::ForceWait; + + if (refresh) { + waitFlags |= ProcessRunner::TriggerRefresh | ProcessRunner::WaitForRefresh; + } + + const auto r = runner.setWaitForCompletion(waitFlags, UILocker::OutputRequired) + .attachToProcess(handle); + + if (exitCode) { + *exitCode = runner.exitCode(); + } + + switch (r) { + case ProcessRunner::Completed: + return true; + + case ProcessRunner::Cancelled: // fall-through + case ProcessRunner::ForceUnlocked: + // this is always an error because the application should have run to + // completion + return false; + + case ProcessRunner::Error: // fall-through + default: + return false; + } +} + +void OrganizerProxy::refresh(bool saveChanges) +{ + m_Proxied->refresh(saveChanges); +} + +IModInterface* OrganizerProxy::installMod(const QString& fileName, + const QString& nameSuggestion) +{ + return m_Proxied->installMod(fileName, -1, false, nullptr, nameSuggestion); +} + +QString OrganizerProxy::resolvePath(const QString& fileName) const +{ + return m_Proxied->resolvePath(fileName); +} + +QStringList OrganizerProxy::listDirectories(const QString& directoryName) const +{ + return m_Proxied->listDirectories(directoryName); +} + +QStringList +OrganizerProxy::findFiles(const QString& path, + const std::function& filter) const +{ + return m_Proxied->findFiles(path, filter); +} + +QStringList OrganizerProxy::findFiles(const QString& path, + const QStringList& globFilters) const +{ + QList> patterns; + for (auto& gfilter : globFilters) { + patterns.append(GlobPattern(gfilter)); + } + return findFiles(path, [&patterns](const QString& filename) { + for (auto& p : patterns) { + if (p.match(filename)) { + return true; + } + } + return false; + }); +} + +QStringList OrganizerProxy::getFileOrigins(const QString& fileName) const +{ + return m_Proxied->getFileOrigins(fileName); +} + +QList OrganizerProxy::findFileInfos( + const QString& path, + const std::function& filter) const +{ + return m_Proxied->findFileInfos(path, filter); +} + +std::shared_ptr OrganizerProxy::virtualFileTree() const +{ + return m_Proxied->m_VirtualFileTree.value(); +} + +MOBase::IDownloadManager* OrganizerProxy::downloadManager() const +{ + return m_DownloadManagerProxy.get(); +} + +MOBase::IPluginList* OrganizerProxy::pluginList() const +{ + return m_PluginListProxy.get(); +} + +MOBase::IModList* OrganizerProxy::modList() const +{ + return m_ModListProxy.get(); +} + +MOBase::IProfile* OrganizerProxy::profile() const +{ + return m_Proxied->currentProfile(); +} + +MOBase::IPluginGame const* OrganizerProxy::managedGame() const +{ + return m_Proxied->managedGame(); +} + +// CALLBACKS + +bool OrganizerProxy::onAboutToRun(const std::function& func) +{ + return m_Proxied->onAboutToRun(MOShared::callIfPluginActive(this, func, true)) + .connected(); +} + +bool OrganizerProxy::onFinishedRun( + const std::function& func) +{ + return m_Proxied->onFinishedRun(MOShared::callIfPluginActive(this, func)).connected(); +} + +bool OrganizerProxy::onProfileCreated(std::function const& func) +{ + return m_ProfileCreated.connect(func).connected(); +} + +bool OrganizerProxy::onProfileRenamed( + std::function const& func) +{ + return m_ProfileRenamed.connect(func).connected(); +} + +bool OrganizerProxy::onProfileRemoved(std::function const& func) +{ + return m_ProfileRemoved.connect(func).connected(); +} + +bool OrganizerProxy::onProfileChanged( + std::function const& func) +{ + return m_ProfileChanged.connect(func).connected(); +} + +bool OrganizerProxy::onUserInterfaceInitialized( + std::function const& func) +{ + // Always call this one to allow plugin to initialize themselves even when not active: + return m_UserInterfaceInitialized.connect(func).connected(); +} + +// Always call these one, otherwise plugin cannot detect they are being enabled / +// disabled: +bool OrganizerProxy::onPluginSettingChanged( + std::function const& func) +{ + return m_PluginSettingChanged.connect(func).connected(); +} + +bool OrganizerProxy::onPluginEnabled(std::function const& func) +{ + return m_PluginEnabled.connect(func).connected(); +} + +bool OrganizerProxy::onPluginEnabled(const QString& pluginName, + std::function const& func) +{ + return onPluginEnabled([=](const IPlugin* plugin) { + if (plugin->name().compare(pluginName, Qt::CaseInsensitive) == 0) { + func(); + } + }); +} + +bool OrganizerProxy::onPluginDisabled(std::function const& func) +{ + return m_PluginDisabled.connect(func).connected(); +} + +bool OrganizerProxy::onPluginDisabled(const QString& pluginName, + std::function const& func) +{ + return onPluginDisabled([=](const IPlugin* plugin) { + if (plugin->name().compare(pluginName, Qt::CaseInsensitive) == 0) { + func(); + } + }); +} diff --git a/src/organizerproxy.h b/src/organizerproxy.h index f419ba59a..771d09810 100644 --- a/src/organizerproxy.h +++ b/src/organizerproxy.h @@ -1,130 +1,152 @@ -#ifndef ORGANIZERPROXY_H -#define ORGANIZERPROXY_H - -#include - -#include -#include - -#include "organizercore.h" - -class PluginContainer; -class DownloadManagerProxy; -class ModListProxy; -class PluginListProxy; - -class OrganizerProxy : public MOBase::IOrganizer -{ - -public: - - OrganizerProxy(OrganizerCore *organizer, PluginContainer *pluginContainer, MOBase::IPlugin *plugin); - ~OrganizerProxy(); - -public: - - /** - * @return the plugin corresponding to this proxy. - */ - MOBase::IPlugin* plugin() const { return m_Plugin; } - -public: // IOrganizer interface - - virtual MOBase::IModRepositoryBridge *createNexusBridge() const; - virtual QString profileName() const; - virtual QString profilePath() const; - virtual QString downloadsPath() const; - virtual QString overwritePath() const; - virtual QString basePath() const; - virtual QString modsPath() const; - virtual MOBase::VersionInfo appVersion() const; - virtual MOBase::IPluginGame *getGame(const QString &gameName) const; - virtual MOBase::IModInterface *createMod(MOBase::GuessedValue &name); - virtual void modDataChanged(MOBase::IModInterface *mod); - 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 MOBase::IModInterface *installMod(const QString &fileName, const QString &nameSuggestion = QString()); - virtual QString resolvePath(const QString &fileName) const; - virtual QStringList listDirectories(const QString &directoryName) const; - virtual QStringList findFiles(const QString &path, const std::function &filter) const override; - virtual QStringList findFiles(const QString &path, const QStringList &globFilters) const override; - virtual QStringList getFileOrigins(const QString &fileName) const override; - virtual QList findFileInfos(const QString &path, const std::function &filter) const override; - virtual std::shared_ptr virtualFileTree() const override; - - virtual MOBase::IDownloadManager *downloadManager() const; - virtual MOBase::IPluginList *pluginList() const; - virtual MOBase::IModList *modList() const; - virtual MOBase::IProfile *profile() const override; - virtual HANDLE startApplication(const QString &executable, const QStringList &args = QStringList(), const QString &cwd = "", - const QString &profile = "", const QString &forcedCustomOverwrite = "", bool ignoreCustomOverwrite = false); - virtual bool waitForApplication(HANDLE handle, bool refresh = true, LPDWORD exitCode = nullptr) const; - virtual void refresh(bool saveChanges); - - virtual bool onAboutToRun(const std::function &func) override; - virtual bool onFinishedRun(const std::function &func) override; - virtual bool onUserInterfaceInitialized(std::function const& func) override; - virtual bool onProfileCreated(std::function const& func) override; - virtual bool onProfileRenamed(std::function const& func) override; - virtual bool onProfileRemoved(std::function const& func) override; - virtual bool onProfileChanged(std::function const& func) override; - - // Plugin related: - virtual bool isPluginEnabled(QString const& pluginName) const override; - virtual bool isPluginEnabled(MOBase::IPlugin* plugin) const override; - virtual QVariant pluginSetting(const QString& pluginName, const QString& key) const override; - virtual void setPluginSetting(const QString& pluginName, const QString& key, const QVariant& value) override; - virtual bool onPluginSettingChanged(std::function const& func) override; - virtual bool onPluginEnabled(std::function const& func) override; - virtual bool onPluginEnabled(const QString& pluginName, std::function const& func) override; - virtual bool onPluginDisabled(std::function const& func) override; - virtual bool onPluginDisabled(const QString& pluginName, std::function const& func) override; - - virtual MOBase::IPluginGame const *managedGame() const; - -protected: - - // The container needs access to some callbacks to simulate startup. - friend class PluginContainer; - - /** - * @brief Connect the signals from this proxy and all the child proxies (plugin list, mod - * list, etc.) to the actual implementation. Before this call, plugins can register signals - * but they won't be triggered. - */ - void connectSignals(); - - /** - * @brief Disconnect the signals from this proxy and all the child proxies (plugin list, mod - * list, etc.) from the actual implementation. - */ - void disconnectSignals(); - -private: - - OrganizerCore *m_Proxied; - PluginContainer *m_PluginContainer; - - MOBase::IPlugin *m_Plugin; - - OrganizerCore::SignalAboutToRunApplication m_AboutToRun; - OrganizerCore::SignalFinishedRunApplication m_FinishedRun; - OrganizerCore::SignalUserInterfaceInitialized m_UserInterfaceInitialized; - OrganizerCore::SignalProfileCreated m_ProfileCreated; - OrganizerCore::SignalProfileRenamed m_ProfileRenamed; - OrganizerCore::SignalProfileRemoved m_ProfileRemoved; - OrganizerCore::SignalProfileChanged m_ProfileChanged; - OrganizerCore::SignalPluginSettingChanged m_PluginSettingChanged; - OrganizerCore::SignalPluginEnabled m_PluginEnabled; - OrganizerCore::SignalPluginEnabled m_PluginDisabled; - - std::vector m_Connections; - - std::unique_ptr m_DownloadManagerProxy; - std::unique_ptr m_ModListProxy; - std::unique_ptr m_PluginListProxy; - -}; - -#endif // ORGANIZERPROXY_H +#ifndef ORGANIZERPROXY_H +#define ORGANIZERPROXY_H + +#include + +#include +#include + +#include "organizercore.h" + +class PluginContainer; +class DownloadManagerProxy; +class ModListProxy; +class PluginListProxy; + +class OrganizerProxy : public MOBase::IOrganizer +{ + +public: + OrganizerProxy(OrganizerCore* organizer, PluginContainer* pluginContainer, + MOBase::IPlugin* plugin); + ~OrganizerProxy(); + +public: + /** + * @return the plugin corresponding to this proxy. + */ + MOBase::IPlugin* plugin() const { return m_Plugin; } + +public: // IOrganizer interface + virtual MOBase::IModRepositoryBridge* createNexusBridge() const; + virtual QString profileName() const; + virtual QString profilePath() const; + virtual QString downloadsPath() const; + virtual QString overwritePath() const; + virtual QString basePath() const; + virtual QString modsPath() const; + virtual MOBase::VersionInfo appVersion() const; + virtual MOBase::IPluginGame* getGame(const QString& gameName) const; + virtual MOBase::IModInterface* createMod(MOBase::GuessedValue& name); + virtual void modDataChanged(MOBase::IModInterface* mod); + 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 MOBase::IModInterface* installMod(const QString& fileName, + const QString& nameSuggestion = QString()); + virtual QString resolvePath(const QString& fileName) const; + virtual QStringList listDirectories(const QString& directoryName) const; + virtual QStringList + findFiles(const QString& path, + const std::function& filter) const override; + virtual QStringList findFiles(const QString& path, + const QStringList& globFilters) const override; + virtual QStringList getFileOrigins(const QString& fileName) const override; + virtual QList + findFileInfos(const QString& path, + const std::function& filter) const override; + virtual std::shared_ptr virtualFileTree() const override; + + virtual MOBase::IDownloadManager* downloadManager() const; + virtual MOBase::IPluginList* pluginList() const; + virtual MOBase::IModList* modList() const; + virtual MOBase::IProfile* profile() const override; + virtual HANDLE startApplication(const QString& executable, + const QStringList& args = QStringList(), + const QString& cwd = "", const QString& profile = "", + const QString& forcedCustomOverwrite = "", + bool ignoreCustomOverwrite = false); + virtual bool waitForApplication(HANDLE handle, bool refresh = true, + LPDWORD exitCode = nullptr) const; + virtual void refresh(bool saveChanges); + + virtual bool onAboutToRun(const std::function& func) override; + virtual bool + onFinishedRun(const std::function& func) override; + virtual bool + onUserInterfaceInitialized(std::function const& func) override; + virtual bool + onProfileCreated(std::function const& func) override; + virtual bool onProfileRenamed( + std::function const& + func) override; + virtual bool + onProfileRemoved(std::function const& func) override; + virtual bool onProfileChanged( + std::function const& func) override; + + // Plugin related: + virtual bool isPluginEnabled(QString const& pluginName) const override; + virtual bool isPluginEnabled(MOBase::IPlugin* plugin) const override; + virtual QVariant pluginSetting(const QString& pluginName, + const QString& key) const override; + virtual void setPluginSetting(const QString& pluginName, const QString& key, + const QVariant& value) override; + virtual bool onPluginSettingChanged( + std::function const& func) override; + virtual bool + onPluginEnabled(std::function const& func) override; + virtual bool onPluginEnabled(const QString& pluginName, + std::function const& func) override; + virtual bool + onPluginDisabled(std::function const& func) override; + virtual bool onPluginDisabled(const QString& pluginName, + std::function const& func) override; + + virtual MOBase::IPluginGame const* managedGame() const; + +protected: + // The container needs access to some callbacks to simulate startup. + friend class PluginContainer; + + /** + * @brief Connect the signals from this proxy and all the child proxies (plugin list, + * mod list, etc.) to the actual implementation. Before this call, plugins can + * register signals but they won't be triggered. + */ + void connectSignals(); + + /** + * @brief Disconnect the signals from this proxy and all the child proxies (plugin + * list, mod list, etc.) from the actual implementation. + */ + void disconnectSignals(); + +private: + OrganizerCore* m_Proxied; + PluginContainer* m_PluginContainer; + + MOBase::IPlugin* m_Plugin; + + OrganizerCore::SignalAboutToRunApplication m_AboutToRun; + OrganizerCore::SignalFinishedRunApplication m_FinishedRun; + OrganizerCore::SignalUserInterfaceInitialized m_UserInterfaceInitialized; + OrganizerCore::SignalProfileCreated m_ProfileCreated; + OrganizerCore::SignalProfileRenamed m_ProfileRenamed; + OrganizerCore::SignalProfileRemoved m_ProfileRemoved; + OrganizerCore::SignalProfileChanged m_ProfileChanged; + OrganizerCore::SignalPluginSettingChanged m_PluginSettingChanged; + OrganizerCore::SignalPluginEnabled m_PluginEnabled; + OrganizerCore::SignalPluginEnabled m_PluginDisabled; + + std::vector m_Connections; + + std::unique_ptr m_DownloadManagerProxy; + std::unique_ptr m_ModListProxy; + std::unique_ptr m_PluginListProxy; +}; + +#endif // ORGANIZERPROXY_H diff --git a/src/overwriteinfodialog.cpp b/src/overwriteinfodialog.cpp index 32aae07a5..366671e7c 100644 --- a/src/overwriteinfodialog.cpp +++ b/src/overwriteinfodialog.cpp @@ -1,275 +1,278 @@ -/* -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 "overwriteinfodialog.h" -#include "ui_overwriteinfodialog.h" -#include "report.h" -#include "utility.h" -#include "organizercore.h" -#include -#include -#include -#include - -using namespace MOBase; - -OverwriteInfoDialog::OverwriteInfoDialog(ModInfo::Ptr modInfo, QWidget *parent) - : QDialog(parent), ui(new Ui::OverwriteInfoDialog), m_FileSystemModel(nullptr), - m_DeleteAction(nullptr), m_RenameAction(nullptr), m_OpenAction(nullptr) -{ - ui->setupUi(this); - - this->setWindowModality(Qt::NonModal); - - m_FileSystemModel = new OverwriteFileSystemModel(this); - m_FileSystemModel->setReadOnly(false); - setModInfo(modInfo); - ui->filesView->setModel(m_FileSystemModel); - ui->filesView->setRootIndex(m_FileSystemModel->index(modInfo->absolutePath())); - ui->filesView->setColumnWidth(0, 250); - - m_DeleteAction = new QAction(tr("&Delete"), ui->filesView); - m_RenameAction = new QAction(tr("&Rename"), ui->filesView); - m_OpenAction = new QAction(tr("&Open"), ui->filesView); - m_NewFolderAction = new QAction(tr("&New Folder"), ui->filesView); - - new QShortcut(QKeySequence::Delete, this, SLOT(delete_activated())); - - QObject::connect(m_DeleteAction, SIGNAL(triggered()), this, SLOT(deleteTriggered())); - QObject::connect(m_RenameAction, SIGNAL(triggered()), this, SLOT(renameTriggered())); - QObject::connect(m_OpenAction, SIGNAL(triggered()), this, SLOT(openTriggered())); - QObject::connect(m_NewFolderAction, SIGNAL(triggered()), this, SLOT(createDirectoryTriggered())); -} - -OverwriteInfoDialog::~OverwriteInfoDialog() -{ - delete ui; -} - -void OverwriteInfoDialog::showEvent(QShowEvent* e) -{ - const auto& s = Settings::instance(); - - s.geometry().restoreGeometry(this); - - if (!s.geometry().restoreState(ui->filesView->header())) { - ui->filesView->sortByColumn(0, Qt::AscendingOrder); - } - - QDialog::showEvent(e); -} - -void OverwriteInfoDialog::done(int r) -{ - auto& s = Settings::instance(); - - s.geometry().saveGeometry(this); - s.geometry().saveState(ui->filesView->header()); - - QDialog::done(r); -} - -void OverwriteInfoDialog::setModInfo(ModInfo::Ptr modInfo) -{ - m_ModInfo = modInfo; - if (QDir(modInfo->absolutePath()).exists()) { - m_FileSystemModel->setRootPath(modInfo->absolutePath()); - } else { - throw MyException(tr("mod not found: %1").arg(qUtf8Printable(modInfo->absolutePath()))); - } -} - -bool OverwriteInfoDialog::recursiveDelete(const QModelIndex &index) -{ - for (int childRow = 0; childRow < m_FileSystemModel->rowCount(index); ++childRow) { - QModelIndex childIndex = m_FileSystemModel->index(childRow, 0, index); - if (m_FileSystemModel->isDir(childIndex)) { - if (!recursiveDelete(childIndex)) { - log::error("failed to delete {}", m_FileSystemModel->fileName(childIndex)); - return false; - } - } else { - if (!m_FileSystemModel->remove(childIndex)) { - log::error("failed to delete {}", m_FileSystemModel->fileName(childIndex)); - return false; - } - } - } - if (!m_FileSystemModel->remove(index)) { - log::error("failed to delete {}", m_FileSystemModel->fileName(index)); - return false; - } - return true; -} - - -void OverwriteInfoDialog::deleteFile(const QModelIndex &index) -{ - - bool res = m_FileSystemModel->isDir(index) ? recursiveDelete(index) - : m_FileSystemModel->remove(index); - if (!res) { - QString fileName = m_FileSystemModel->fileName(index); - reportError(tr("Failed to delete \"%1\"").arg(fileName)); - } -} - -void OverwriteInfoDialog::delete_activated() -{ - if (ui->filesView->hasFocus()) { - QItemSelectionModel *selection = ui->filesView->selectionModel(); - - if (selection->hasSelection() && selection->selectedRows().count() >= 1) { - - if (selection->selectedRows().count() == 0) { - return; - } - else if (selection->selectedRows().count() == 1) { - QString fileName = m_FileSystemModel->fileName(selection->selectedRows().at(0)); - if (QMessageBox::question(this, tr("Confirm"), tr("Are you sure you want to delete \"%1\"?").arg(fileName), - QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { - return; - } - } - else { - if (QMessageBox::question(this, tr("Confirm"), tr("Are you sure you want to delete the selected files?"), - QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { - return; - } - } - - foreach(QModelIndex index, selection->selectedRows()) { - deleteFile(index); - } - } - } -} - -void OverwriteInfoDialog::deleteTriggered() -{ - if (m_FileSelection.count() == 0) { - return; - } else if (m_FileSelection.count() == 1) { - QString fileName = m_FileSystemModel->fileName(m_FileSelection.at(0)); - if (QMessageBox::question(this, tr("Confirm"), tr("Are you sure you want to delete \"%1\"?").arg(fileName), - QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { - return; - } - } else { - if (QMessageBox::question(this, tr("Confirm"), tr("Are you sure you want to delete the selected files?"), - QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { - return; - } - } - - foreach(QModelIndex index, m_FileSelection) { - deleteFile(index); - } -} - - -void OverwriteInfoDialog::renameTriggered() -{ - QModelIndex selection = m_FileSelection.at(0); - QModelIndex index = selection.sibling(selection.row(), 0); - if (!index.isValid() || m_FileSystemModel->isReadOnly()) { - return; - } - - ui->filesView->edit(index); -} - - -void OverwriteInfoDialog::openFile(const QModelIndex &index) -{ - shell::Open(m_FileSystemModel->filePath(index)); -} - - -void OverwriteInfoDialog::openTriggered() -{ - foreach(QModelIndex idx, m_FileSelection) { - openFile(idx); - } -} - -void OverwriteInfoDialog::createDirectoryTriggered() -{ - QModelIndex selection = m_FileSelection.at(0); - - QModelIndex index = m_FileSystemModel->isDir(selection) ? selection - : selection.parent(); - index = index.sibling(index.row(), 0); - - QString name = tr("New Folder"); - QString path = m_FileSystemModel->filePath(index).append("/"); - - QModelIndex existingIndex = m_FileSystemModel->index(path + name); - int suffix = 1; - while (existingIndex.isValid()) { - name = tr("New Folder") + QString::number(suffix++); - existingIndex = m_FileSystemModel->index(path + name); - } - - QModelIndex newIndex = m_FileSystemModel->mkdir(index, name); - if (!newIndex.isValid()) { - reportError(tr("Failed to create \"%1\"").arg(name)); - return; - } - - ui->filesView->setCurrentIndex(newIndex); - ui->filesView->edit(newIndex); -} - -void OverwriteInfoDialog::on_explorerButton_clicked() -{ - shell::Explore(m_ModInfo->absolutePath()); -} - -void OverwriteInfoDialog::on_filesView_customContextMenuRequested(const QPoint &pos) -{ - QItemSelectionModel *selectionModel = ui->filesView->selectionModel(); - m_FileSelection = selectionModel->selectedRows(0); - - QMenu menu(ui->filesView); - - menu.addAction(m_NewFolderAction); - - bool hasFiles = false; - - foreach(QModelIndex idx, m_FileSelection) { - if (m_FileSystemModel->fileInfo(idx).isFile()) { - hasFiles = true; - break; - } - } - - if (selectionModel->hasSelection()) { - if (hasFiles) { - menu.addAction(m_OpenAction); - } - menu.addAction(m_RenameAction); - menu.addAction(m_DeleteAction); - } else { - m_FileSelection.clear(); - m_FileSelection.append(m_FileSystemModel->index(m_FileSystemModel->rootPath(), 0)); - } - - menu.exec(ui->filesView->viewport()->mapToGlobal(pos)); -} +/* +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 "overwriteinfodialog.h" +#include "organizercore.h" +#include "report.h" +#include "ui_overwriteinfodialog.h" +#include "utility.h" +#include +#include +#include +#include + +using namespace MOBase; + +OverwriteInfoDialog::OverwriteInfoDialog(ModInfo::Ptr modInfo, QWidget* parent) + : QDialog(parent), ui(new Ui::OverwriteInfoDialog), m_FileSystemModel(nullptr), + m_DeleteAction(nullptr), m_RenameAction(nullptr), m_OpenAction(nullptr) +{ + ui->setupUi(this); + + this->setWindowModality(Qt::NonModal); + + m_FileSystemModel = new OverwriteFileSystemModel(this); + m_FileSystemModel->setReadOnly(false); + setModInfo(modInfo); + ui->filesView->setModel(m_FileSystemModel); + ui->filesView->setRootIndex(m_FileSystemModel->index(modInfo->absolutePath())); + ui->filesView->setColumnWidth(0, 250); + + m_DeleteAction = new QAction(tr("&Delete"), ui->filesView); + m_RenameAction = new QAction(tr("&Rename"), ui->filesView); + m_OpenAction = new QAction(tr("&Open"), ui->filesView); + m_NewFolderAction = new QAction(tr("&New Folder"), ui->filesView); + + new QShortcut(QKeySequence::Delete, this, SLOT(delete_activated())); + + QObject::connect(m_DeleteAction, SIGNAL(triggered()), this, SLOT(deleteTriggered())); + QObject::connect(m_RenameAction, SIGNAL(triggered()), this, SLOT(renameTriggered())); + QObject::connect(m_OpenAction, SIGNAL(triggered()), this, SLOT(openTriggered())); + QObject::connect(m_NewFolderAction, SIGNAL(triggered()), this, + SLOT(createDirectoryTriggered())); +} + +OverwriteInfoDialog::~OverwriteInfoDialog() +{ + delete ui; +} + +void OverwriteInfoDialog::showEvent(QShowEvent* e) +{ + const auto& s = Settings::instance(); + + s.geometry().restoreGeometry(this); + + if (!s.geometry().restoreState(ui->filesView->header())) { + ui->filesView->sortByColumn(0, Qt::AscendingOrder); + } + + QDialog::showEvent(e); +} + +void OverwriteInfoDialog::done(int r) +{ + auto& s = Settings::instance(); + + s.geometry().saveGeometry(this); + s.geometry().saveState(ui->filesView->header()); + + QDialog::done(r); +} + +void OverwriteInfoDialog::setModInfo(ModInfo::Ptr modInfo) +{ + m_ModInfo = modInfo; + if (QDir(modInfo->absolutePath()).exists()) { + m_FileSystemModel->setRootPath(modInfo->absolutePath()); + } else { + throw MyException( + tr("mod not found: %1").arg(qUtf8Printable(modInfo->absolutePath()))); + } +} + +bool OverwriteInfoDialog::recursiveDelete(const QModelIndex& index) +{ + for (int childRow = 0; childRow < m_FileSystemModel->rowCount(index); ++childRow) { + QModelIndex childIndex = m_FileSystemModel->index(childRow, 0, index); + if (m_FileSystemModel->isDir(childIndex)) { + if (!recursiveDelete(childIndex)) { + log::error("failed to delete {}", m_FileSystemModel->fileName(childIndex)); + return false; + } + } else { + if (!m_FileSystemModel->remove(childIndex)) { + log::error("failed to delete {}", m_FileSystemModel->fileName(childIndex)); + return false; + } + } + } + if (!m_FileSystemModel->remove(index)) { + log::error("failed to delete {}", m_FileSystemModel->fileName(index)); + return false; + } + return true; +} + +void OverwriteInfoDialog::deleteFile(const QModelIndex& index) +{ + + bool res = m_FileSystemModel->isDir(index) ? recursiveDelete(index) + : m_FileSystemModel->remove(index); + if (!res) { + QString fileName = m_FileSystemModel->fileName(index); + reportError(tr("Failed to delete \"%1\"").arg(fileName)); + } +} + +void OverwriteInfoDialog::delete_activated() +{ + if (ui->filesView->hasFocus()) { + QItemSelectionModel* selection = ui->filesView->selectionModel(); + + if (selection->hasSelection() && selection->selectedRows().count() >= 1) { + + if (selection->selectedRows().count() == 0) { + return; + } else if (selection->selectedRows().count() == 1) { + QString fileName = m_FileSystemModel->fileName(selection->selectedRows().at(0)); + if (QMessageBox::question( + this, tr("Confirm"), + tr("Are you sure you want to delete \"%1\"?").arg(fileName), + QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { + return; + } + } else { + if (QMessageBox::question( + this, tr("Confirm"), + tr("Are you sure you want to delete the selected files?"), + QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { + return; + } + } + + foreach (QModelIndex index, selection->selectedRows()) { + deleteFile(index); + } + } + } +} + +void OverwriteInfoDialog::deleteTriggered() +{ + if (m_FileSelection.count() == 0) { + return; + } else if (m_FileSelection.count() == 1) { + QString fileName = m_FileSystemModel->fileName(m_FileSelection.at(0)); + if (QMessageBox::question( + this, tr("Confirm"), + tr("Are you sure you want to delete \"%1\"?").arg(fileName), + QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { + return; + } + } else { + if (QMessageBox::question(this, tr("Confirm"), + tr("Are you sure you want to delete the selected files?"), + QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { + return; + } + } + + foreach (QModelIndex index, m_FileSelection) { + deleteFile(index); + } +} + +void OverwriteInfoDialog::renameTriggered() +{ + QModelIndex selection = m_FileSelection.at(0); + QModelIndex index = selection.sibling(selection.row(), 0); + if (!index.isValid() || m_FileSystemModel->isReadOnly()) { + return; + } + + ui->filesView->edit(index); +} + +void OverwriteInfoDialog::openFile(const QModelIndex& index) +{ + shell::Open(m_FileSystemModel->filePath(index)); +} + +void OverwriteInfoDialog::openTriggered() +{ + foreach (QModelIndex idx, m_FileSelection) { + openFile(idx); + } +} + +void OverwriteInfoDialog::createDirectoryTriggered() +{ + QModelIndex selection = m_FileSelection.at(0); + + QModelIndex index = + m_FileSystemModel->isDir(selection) ? selection : selection.parent(); + index = index.sibling(index.row(), 0); + + QString name = tr("New Folder"); + QString path = m_FileSystemModel->filePath(index).append("/"); + + QModelIndex existingIndex = m_FileSystemModel->index(path + name); + int suffix = 1; + while (existingIndex.isValid()) { + name = tr("New Folder") + QString::number(suffix++); + existingIndex = m_FileSystemModel->index(path + name); + } + + QModelIndex newIndex = m_FileSystemModel->mkdir(index, name); + if (!newIndex.isValid()) { + reportError(tr("Failed to create \"%1\"").arg(name)); + return; + } + + ui->filesView->setCurrentIndex(newIndex); + ui->filesView->edit(newIndex); +} + +void OverwriteInfoDialog::on_explorerButton_clicked() +{ + shell::Explore(m_ModInfo->absolutePath()); +} + +void OverwriteInfoDialog::on_filesView_customContextMenuRequested(const QPoint& pos) +{ + QItemSelectionModel* selectionModel = ui->filesView->selectionModel(); + m_FileSelection = selectionModel->selectedRows(0); + + QMenu menu(ui->filesView); + + menu.addAction(m_NewFolderAction); + + bool hasFiles = false; + + foreach (QModelIndex idx, m_FileSelection) { + if (m_FileSystemModel->fileInfo(idx).isFile()) { + hasFiles = true; + break; + } + } + + if (selectionModel->hasSelection()) { + if (hasFiles) { + menu.addAction(m_OpenAction); + } + menu.addAction(m_RenameAction); + menu.addAction(m_DeleteAction); + } else { + m_FileSelection.clear(); + m_FileSelection.append(m_FileSystemModel->index(m_FileSystemModel->rootPath(), 0)); + } + + menu.exec(ui->filesView->viewport()->mapToGlobal(pos)); +} diff --git a/src/overwriteinfodialog.h b/src/overwriteinfodialog.h index 6b3ab0eab..e9fa34397 100644 --- a/src/overwriteinfodialog.h +++ b/src/overwriteinfodialog.h @@ -1,128 +1,127 @@ -/* -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 OVERWRITEINFODIALOG_H -#define OVERWRITEINFODIALOG_H - -#include "modinfo.h" -#include -#include - -namespace Ui { -class OverwriteInfoDialog; -} - -class OverwriteFileSystemModel : public QFileSystemModel -{ - Q_OBJECT; - -public: - OverwriteFileSystemModel(QObject *parent) - : QFileSystemModel(parent), m_RegularColumnCount(0) {} - - virtual int columnCount(const QModelIndex &parent) const { - m_RegularColumnCount = QFileSystemModel::columnCount(parent); - return m_RegularColumnCount; - } - - virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const { - if ((orientation == Qt::Horizontal) && - (section >= m_RegularColumnCount)) { - if (role == Qt::DisplayRole) { - return tr("Overwrites"); - } else { - return QVariant(); - } - } else { - return QFileSystemModel::headerData(section, orientation, role); - } - } - - virtual QVariant data(const QModelIndex &index, int role) const { - if (index.column() == m_RegularColumnCount + 0) { - if (role == Qt::DisplayRole) { - return tr("not implemented"); - } else { - return QVariant(); - } - } else { - return QFileSystemModel::data(index, role); - } - } - -private: - mutable int m_RegularColumnCount; -}; - - -class OverwriteInfoDialog : public QDialog -{ - Q_OBJECT - -public: - - explicit OverwriteInfoDialog(ModInfo::Ptr modInfo, QWidget *parent = 0); - ~OverwriteInfoDialog(); - - ModInfo::Ptr modInfo() const { return m_ModInfo; } - - // saves geometry - // - void done(int r) override; - - void setModInfo(ModInfo::Ptr modInfo); - -protected: - // restores geometry - // - void showEvent(QShowEvent* e) override; - -private: - - void openFile(const QModelIndex &index); - bool recursiveDelete(const QModelIndex &index); - void deleteFile(const QModelIndex &index); - -private slots: - - void delete_activated(); - - void deleteTriggered(); - void renameTriggered(); - void openTriggered(); - void createDirectoryTriggered(); - - void on_explorerButton_clicked(); - void on_filesView_customContextMenuRequested(const QPoint &pos); - -private: - - Ui::OverwriteInfoDialog *ui; - QFileSystemModel *m_FileSystemModel; - QModelIndexList m_FileSelection; - QAction *m_DeleteAction; - QAction *m_RenameAction; - QAction *m_OpenAction; - QAction *m_NewFolderAction; - - ModInfo::Ptr m_ModInfo; - -}; - -#endif // OVERWRITEINFODIALOG_H +/* +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 OVERWRITEINFODIALOG_H +#define OVERWRITEINFODIALOG_H + +#include "modinfo.h" +#include +#include + +namespace Ui +{ +class OverwriteInfoDialog; +} + +class OverwriteFileSystemModel : public QFileSystemModel +{ + Q_OBJECT; + +public: + OverwriteFileSystemModel(QObject* parent) + : QFileSystemModel(parent), m_RegularColumnCount(0) + {} + + virtual int columnCount(const QModelIndex& parent) const + { + m_RegularColumnCount = QFileSystemModel::columnCount(parent); + return m_RegularColumnCount; + } + + virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const + { + if ((orientation == Qt::Horizontal) && (section >= m_RegularColumnCount)) { + if (role == Qt::DisplayRole) { + return tr("Overwrites"); + } else { + return QVariant(); + } + } else { + return QFileSystemModel::headerData(section, orientation, role); + } + } + + virtual QVariant data(const QModelIndex& index, int role) const + { + if (index.column() == m_RegularColumnCount + 0) { + if (role == Qt::DisplayRole) { + return tr("not implemented"); + } else { + return QVariant(); + } + } else { + return QFileSystemModel::data(index, role); + } + } + +private: + mutable int m_RegularColumnCount; +}; + +class OverwriteInfoDialog : public QDialog +{ + Q_OBJECT + +public: + explicit OverwriteInfoDialog(ModInfo::Ptr modInfo, QWidget* parent = 0); + ~OverwriteInfoDialog(); + + ModInfo::Ptr modInfo() const { return m_ModInfo; } + + // saves geometry + // + void done(int r) override; + + void setModInfo(ModInfo::Ptr modInfo); + +protected: + // restores geometry + // + void showEvent(QShowEvent* e) override; + +private: + void openFile(const QModelIndex& index); + bool recursiveDelete(const QModelIndex& index); + void deleteFile(const QModelIndex& index); + +private slots: + + void delete_activated(); + + void deleteTriggered(); + void renameTriggered(); + void openTriggered(); + void createDirectoryTriggered(); + + void on_explorerButton_clicked(); + void on_filesView_customContextMenuRequested(const QPoint& pos); + +private: + Ui::OverwriteInfoDialog* ui; + QFileSystemModel* m_FileSystemModel; + QModelIndexList m_FileSelection; + QAction* m_DeleteAction; + QAction* m_RenameAction; + QAction* m_OpenAction; + QAction* m_NewFolderAction; + + ModInfo::Ptr m_ModInfo; +}; + +#endif // OVERWRITEINFODIALOG_H diff --git a/src/pch.h b/src/pch.h index f12e91105..a5bb4fb7d 100644 --- a/src/pch.h +++ b/src/pch.h @@ -7,8 +7,8 @@ #include #include #include -#include #include +#include #include #include #include @@ -31,15 +31,18 @@ #include #include -// windows +// keep this header separated to avoid ordering issue since this must be +// included before DbgHelp.h #include + +// windows #include -#include #include #include #include -#include #include +#include +#include #include #include #include @@ -55,6 +58,7 @@ #include #include #include +#include #include #include #include @@ -64,7 +68,6 @@ #include #include #include -#include // openssl #include @@ -263,4 +266,3 @@ #include #include #include -#include diff --git a/src/persistentcookiejar.cpp b/src/persistentcookiejar.cpp index 8657f3563..4afd9891e 100644 --- a/src/persistentcookiejar.cpp +++ b/src/persistentcookiejar.cpp @@ -1,74 +1,78 @@ -#include "persistentcookiejar.h" -#include -#include -#include -#include - -using namespace MOBase; - -PersistentCookieJar::PersistentCookieJar(const QString &fileName, QObject *parent) -: QNetworkCookieJar(parent), m_FileName(fileName) -{ - restore(); -} - -PersistentCookieJar::~PersistentCookieJar() { - log::debug("save {}", m_FileName); - save(); -} - -void PersistentCookieJar::clear() { - for (const QNetworkCookie &cookie : allCookies()) { - deleteCookie(cookie); - } -} - -void PersistentCookieJar::save() { - QTemporaryFile file; - if (!file.open()) { - log::error("failed to save cookies: couldn't create temporary file"); - return; - } - QDataStream data(&file); - - QList cookies = allCookies(); - data << static_cast(cookies.size()); - - for (const QNetworkCookie &cookie : allCookies()) { - data << cookie.toRawForm(); - } - - { - QFile oldCookies(m_FileName); - if (oldCookies.exists()) { - if (!oldCookies.remove()) { - log::error("failed to save cookies: failed to remove {}", m_FileName); - return; - } - } // if it doesn't exists that's fine - } - - if (!file.copy(m_FileName)) { - log::error("failed to save cookies: failed to write {}", m_FileName); - } -} - -void PersistentCookieJar::restore() { - QFile file(m_FileName); - if (!file.open(QIODevice::ReadOnly)) { - // not necessarily a problem, the file may just not exist (yet) - return; - } - - QList allCookies; - - QDataStream data(&file); - quint32 count; - data >> count; - for (quint32 i = 0; i < count; ++i) { - QByteArray cookieRaw; - data >> cookieRaw; - allCookies.append(QNetworkCookie::parseCookies(cookieRaw)); - } - setAllCookies(allCookies); -} +#include "persistentcookiejar.h" +#include +#include +#include +#include + +using namespace MOBase; + +PersistentCookieJar::PersistentCookieJar(const QString& fileName, QObject* parent) + : QNetworkCookieJar(parent), m_FileName(fileName) +{ + restore(); +} + +PersistentCookieJar::~PersistentCookieJar() +{ + log::debug("save {}", m_FileName); + save(); +} + +void PersistentCookieJar::clear() +{ + for (const QNetworkCookie& cookie : allCookies()) { + deleteCookie(cookie); + } +} + +void PersistentCookieJar::save() +{ + QTemporaryFile file; + if (!file.open()) { + log::error("failed to save cookies: couldn't create temporary file"); + return; + } + QDataStream data(&file); + + QList cookies = allCookies(); + data << static_cast(cookies.size()); + + for (const QNetworkCookie& cookie : allCookies()) { + data << cookie.toRawForm(); + } + + { + QFile oldCookies(m_FileName); + if (oldCookies.exists()) { + if (!oldCookies.remove()) { + log::error("failed to save cookies: failed to remove {}", m_FileName); + return; + } + } // if it doesn't exists that's fine + } + + if (!file.copy(m_FileName)) { + log::error("failed to save cookies: failed to write {}", m_FileName); + } +} + +void PersistentCookieJar::restore() +{ + QFile file(m_FileName); + if (!file.open(QIODevice::ReadOnly)) { + // not necessarily a problem, the file may just not exist (yet) + return; + } + + QList allCookies; + + QDataStream data(&file); + quint32 count; + data >> count; + for (quint32 i = 0; i < count; ++i) { + QByteArray cookieRaw; + data >> cookieRaw; + allCookies.append(QNetworkCookie::parseCookies(cookieRaw)); + } + setAllCookies(allCookies); +} diff --git a/src/persistentcookiejar.h b/src/persistentcookiejar.h index 0ff747eed..483886ab8 100644 --- a/src/persistentcookiejar.h +++ b/src/persistentcookiejar.h @@ -1,30 +1,26 @@ -#ifndef PERSISTENTCOOKIEJAR_H -#define PERSISTENTCOOKIEJAR_H - -#include - - -class PersistentCookieJar : public QNetworkCookieJar { - - Q_OBJECT - -public: - PersistentCookieJar(const QString &fileName, QObject *parent = 0); - virtual ~PersistentCookieJar(); - - void clear(); - -private: - - void save(); - - void restore(); - -private: - - QString m_FileName; - -}; - - -#endif // PERSISTENTCOOKIEJAR_H +#ifndef PERSISTENTCOOKIEJAR_H +#define PERSISTENTCOOKIEJAR_H + +#include + +class PersistentCookieJar : public QNetworkCookieJar +{ + + Q_OBJECT + +public: + PersistentCookieJar(const QString& fileName, QObject* parent = 0); + virtual ~PersistentCookieJar(); + + void clear(); + +private: + void save(); + + void restore(); + +private: + QString m_FileName; +}; + +#endif // PERSISTENTCOOKIEJAR_H diff --git a/src/plugincontainer.cpp b/src/plugincontainer.cpp index f71d89e48..157d33995 100644 --- a/src/plugincontainer.cpp +++ b/src/plugincontainer.cpp @@ -1,1190 +1,1249 @@ -#include "plugincontainer.h" -#include "organizercore.h" -#include "organizerproxy.h" -#include "report.h" -#include -#include "iuserinterface.h" -#include -#include "shared/appconfig.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace MOBase; -using namespace MOShared; - -namespace bf = boost::fusion; - -// Welcome to the wonderful world of MO2 plugin management! -// -// We'll start by the C++ side. -// -// There are 9 types of MO2 plugins, two of which cannot be standalone: IPluginDiagnose -// and IPluginFileMapper. This means that you can have a class implementing IPluginGame, -// IPluginDiagnose and IPluginFileMapper. It is not possible for a class to implement -// two full plugin types (e.g. IPluginPreview and IPluginTool). -// -// Plugins are fetch as QObject initially and must be "qobject-casted" to the right type. -// -// Plugins are stored in the PluginContainer class in various C++ containers: there is a vector -// that stores all the plugin as QObject, multiple vectors that stores the plugin of each types, -// a map to find IPlugin object from their names or from IPluginDiagnose or IFileMapper (since -// these do not inherit IPlugin, they cannot be downcasted). -// -// Requirements for plugins are stored in m_Requirements: -// - IPluginGame cannot be enabled by user. A game plugin is considered enable only if it is -// the one corresponding to the currently managed games. -// - If a plugin has a master plugin (IPlugin::master()), it cannot be enabled/disabled by users, -// and will follow the enabled/disabled state of its parent. -// - Each plugin has an "enabled" setting stored in persistence. If the setting does not exist, -// the plugin's enabledByDefault is used instead. -// - A plugin is considered disabled if the setting is false. -// - If the setting is true, a plugin is considered disabled if one of its -// requirements is not met. -// - Users cannot enable a plugin if one of its requirements is not met. -// -// Now let's move to the Proxy side... Or the as of now, the Python side. -// -// Proxied plugins are much more annoying because they can implement all interfaces, and are -// given to MO2 as separate plugins... A Python class implementing IPluginGame and IPluginDiagnose -// will be seen by MO2 as two separate QObject, and they will all have the same name. -// -// When a proxied plugin is registered, a few things must be taken care of: -// - There can only be one plugin mapped to a name in the PluginContainer class, so we keep the -// plugin corresponding to the most relevant class (see PluginTypeOrder), e.g. if the class -// inherits both IPluginGame and IPluginFileMapper, we map the name to the C++ QObject corresponding -// to the IPluginGame. -// - When a proxied plugin implements multiple interfaces, the IPlugin corresponding to the most -// important interface is set as the parent (hidden) of the other IPlugin through PluginRequirements. -// This way, the plugin are managed together (enabled/disabled state). The "fake" children plugins -// will not be returned by PluginRequirements::children(). -// - Since each interface corresponds to a different QObject, we need to take care not to call -// IPlugin::init() on each QObject, but only on the first one. -// -// All the proxied plugins are linked to the proxy plugin by PluginRequirements. If the proxy plugin -// is disabled, the proxied plugins are not even loaded so not visible in the plugin management tab. - -template -struct PluginTypeName; - -template <> struct PluginTypeName { static QString value() { return QT_TR_NOOP("Plugin"); } }; -template <> struct PluginTypeName { static QString value() { return QT_TR_NOOP("Diagnose"); } }; -template <> struct PluginTypeName { static QString value() { return QT_TR_NOOP("Game"); } }; -template <> struct PluginTypeName { static QString value() { return QT_TR_NOOP("Installer"); } }; -template <> struct PluginTypeName { static QString value() { return QT_TR_NOOP("Mod Page"); } }; -template <> struct PluginTypeName { static QString value() { return QT_TR_NOOP("Preview"); } }; -template <> struct PluginTypeName { static QString value() { return QT_TR_NOOP("Tool"); } }; -template <> struct PluginTypeName { static QString value() { return QT_TR_NOOP("Proxy"); } }; -template <> struct PluginTypeName { static QString value() { return QT_TR_NOOP("File Mapper"); } }; - - -QStringList PluginContainer::pluginInterfaces() -{ - // Find all the names: - QStringList names; - boost::mp11::mp_for_each([&names](const auto* p) { - using plugin_type = std::decay_t; - auto name = PluginTypeName::value(); - if (!name.isEmpty()) { - names.append(name); - } - }); - - return names; -} - - -// PluginRequirementProxy - -const std::set PluginRequirements::s_CorePlugins{ - "INI Bakery" -}; - -PluginRequirements::PluginRequirements( - PluginContainer* pluginContainer, MOBase::IPlugin* plugin, OrganizerProxy* proxy, - MOBase::IPluginProxy* pluginProxy) - : m_PluginContainer(pluginContainer) - , m_Plugin(plugin) - , m_PluginProxy(pluginProxy) - , m_Master(nullptr) - , m_Organizer(proxy) -{ - // There are a lots of things we cannot set here (e.g. m_Master) because we do not - // know the order plugins are loaded. -} - -void PluginRequirements::fetchRequirements() { - m_Requirements = m_Plugin->requirements(); -} - -IPluginProxy* PluginRequirements::proxy() const -{ - return m_PluginProxy; -} - -std::vector PluginRequirements::proxied() const -{ - std::vector children; - if (dynamic_cast(m_Plugin)) { - for (auto* obj : m_PluginContainer->plugins()) { - auto* plugin = qobject_cast(obj); - if (plugin && m_PluginContainer->requirements(plugin).proxy() == m_Plugin) { - children.push_back(plugin); - } - } - } - return children; -} - -IPlugin* PluginRequirements::master() const -{ - // If we have a m_Master, it was forced and thus override the default master(). - if (m_Master) { - return m_Master; - } - - if (m_Plugin->master().isEmpty()) { - return nullptr; - } - - return m_PluginContainer->plugin(m_Plugin->master()); -} - -void PluginRequirements::setMaster(IPlugin* master) -{ - m_Master = master; -} - -std::vector PluginRequirements::children() const -{ - std::vector children; - for (auto* obj : m_PluginContainer->plugins()) { - auto* plugin = qobject_cast(obj); - - // Not checking master() but requirements().master() due to "hidden" - // masters. - // If the master has the same name as the plugin, this is a "hidden" - // master, we do not add it here. - if (plugin - && m_PluginContainer->requirements(plugin).master() == m_Plugin - && plugin->name() != m_Plugin->name()) { - children.push_back(plugin); - } - } - return children; -} - -std::vector PluginRequirements::problems() const -{ - std::vector result; - for (auto& requirement : m_Requirements) { - if (auto p = requirement->check(m_Organizer)) { - result.push_back(*p); - } - } - return result; -} - -bool PluginRequirements::canEnable() const -{ - return problems().empty(); -} - -bool PluginRequirements::isCorePlugin() const -{ - // Let's consider game plugins as "core": - if (m_PluginContainer->implementInterface(m_Plugin)) { - return true; - } - - return s_CorePlugins.contains(m_Plugin->name()); -} - -bool PluginRequirements::hasRequirements() const -{ - return !m_Requirements.empty(); -} - -QStringList PluginRequirements::requiredGames() const -{ - // We look for a "GameDependencyRequirement" - There can be only one since otherwise - // it'd mean that the plugin requires two games at once. - for (auto& requirement : m_Requirements) { - if (auto* gdep = dynamic_cast(requirement.get())) { - return gdep->gameNames(); - } - } - - return {}; -} - -std::vector PluginRequirements::requiredFor() const -{ - std::vector required; - std::set visited; - requiredFor(required, visited); - return required; -} - - -void PluginRequirements::requiredFor(std::vector &required, std::set& visited) const -{ - // Handle cyclic dependencies. - if (visited.contains(m_Plugin)) { - return; - } - visited.insert(m_Plugin); - - - for (auto& [plugin, requirements] : m_PluginContainer->m_Requirements) { - - // If the plugin is not enabled, discard: - if (!m_PluginContainer->isEnabled(plugin)) { - continue; - } - - // Check the requirements: - for (auto& requirement : requirements.m_Requirements) { - - // We check for plugin dependency. Game dependency are not checked this way. - if (auto* pdep = dynamic_cast(requirement.get())) { - - // Check if at least one of the plugin in the requirements is enabled (except this - // one): - bool oneEnabled = false; - for (auto& pluginName : pdep->pluginNames()) { - if (pluginName != m_Plugin->name() && m_PluginContainer->isEnabled(pluginName)) { - oneEnabled = true; - break; - } - } - - // No plugin enabled found, so the plugin requires this plugin: - if (!oneEnabled) { - required.push_back(plugin); - requirements.requiredFor(required, visited); - break; - } - } - } - } -} - -// PluginContainer - -PluginContainer::PluginContainer(OrganizerCore *organizer) - : m_Organizer(organizer) - , m_UserInterface(nullptr) - , m_PreviewGenerator(*this) -{ -} - -PluginContainer::~PluginContainer() { - m_Organizer = nullptr; - unloadPlugins(); -} - -void PluginContainer::startPlugins(IUserInterface *userInterface) -{ - m_UserInterface = userInterface; - startPluginsImpl(plugins()); -} - -QStringList PluginContainer::implementedInterfaces(IPlugin* plugin) const -{ - // We need a QObject to be able to qobject_cast<> to the plugin types: - QObject* oPlugin = as_qobject(plugin); - - if (!oPlugin) { - return {}; - } - - return implementedInterfaces(oPlugin); -} - -QStringList PluginContainer::implementedInterfaces(QObject * oPlugin) const -{ - // Find all the names: - QStringList names; - boost::mp11::mp_for_each([oPlugin, &names](const auto* p) { - using plugin_type = std::decay_t; - if (qobject_cast(oPlugin)) { - auto name = PluginTypeName::value(); - if (!name.isEmpty()) { - names.append(name); - } - } - }); - - // If the plugin implements at least one interface other than IPlugin, remove IPlugin: - if (names.size() > 1) { - names.removeAll(PluginTypeName::value()); - } - - return names; -} - -QString PluginContainer::topImplementedInterface(IPlugin* plugin) const -{ - auto interfaces = implementedInterfaces(plugin); - return interfaces.isEmpty() ? "" : interfaces[0]; -} - -bool PluginContainer::isBetterInterface(QObject* lhs, QObject* rhs) const -{ - int count = 0, lhsIdx = -1, rhsIdx = -1; - boost::mp11::mp_for_each([&](const auto* p) { - using plugin_type = std::decay_t; - if (lhsIdx < 0 && qobject_cast(lhs)) { - lhsIdx = count; - } - if (rhsIdx < 0 && qobject_cast(rhs)) { - rhsIdx = count; - } - ++count; - }); - return lhsIdx < rhsIdx; -} - -QStringList PluginContainer::pluginFileNames() const -{ - QStringList result; - for (QPluginLoader *loader : m_PluginLoaders) { - result.append(loader->fileName()); - } - std::vector proxyList = bf::at_key(m_Plugins); - for (IPluginProxy *proxy : proxyList) { - QStringList proxiedPlugins = proxy->pluginList( - QCoreApplication::applicationDirPath() + "/" + ToQString(AppConfig::pluginPath())); - result.append(proxiedPlugins); - } - return result; -} - -QObject* PluginContainer::as_qobject(MOBase::IPlugin* plugin) const -{ - // Find the correspond QObject - Can this be done safely with a cast? - auto& objects = bf::at_key(m_Plugins); - auto it = std::find_if(std::begin(objects), std::end(objects), [plugin](QObject* obj) { - return qobject_cast(obj) == plugin; - }); - - if (it == std::end(objects)) { - return nullptr; - } - - return *it; -} - -bool PluginContainer::initPlugin(IPlugin *plugin, IPluginProxy *pluginProxy, bool skipInit) -{ - // when MO has no instance loaded, init() is not called on plugins, except - // for proxy plugins, where init() is called with a null IOrganizer - // - // after proxies are initialized, instantiate() is called for all the plugins - // they've discovered, but as for regular plugins, init() won't be - // called on them if m_OrganizerCore is null - - if (plugin == nullptr) { - return false; - } - - OrganizerProxy* proxy = nullptr; - if (m_Organizer) { - proxy = new OrganizerProxy(m_Organizer, this, plugin); - proxy->setParent(as_qobject(plugin)); - } - - // Check if it is a proxy plugin: - bool isProxy = dynamic_cast(plugin); - - auto [it, bl] = m_Requirements.emplace(plugin, PluginRequirements(this, plugin, proxy, pluginProxy)); - - if (!m_Organizer && !isProxy) { - return true; - } - - if (skipInit) { - return true; - } - - if (!plugin->init(proxy)) { - log::warn("plugin failed to initialize"); - return false; - } - - // Update requirements: - it->second.fetchRequirements(); - - return true; -} - -void PluginContainer::registerGame(IPluginGame *game) -{ - m_SupportedGames.insert({ game->gameName(), game }); -} - -void PluginContainer::unregisterGame(MOBase::IPluginGame* game) -{ - m_SupportedGames.erase(game->gameName()); -} - -IPlugin* PluginContainer::registerPlugin(QObject *plugin, const QString& filepath, MOBase::IPluginProxy* pluginProxy) -{ - - // generic treatment for all plugins - IPlugin *pluginObj = qobject_cast(plugin); - if (pluginObj == nullptr) { - log::debug("PluginContainer::registerPlugin() called with a non IPlugin QObject."); - return nullptr; - } - - // If we already a plugin with this name: - bool skipInit = false; - auto& mapNames = bf::at_key(m_AccessPlugins); - if (mapNames.contains(pluginObj->name())) { - - IPlugin* other = mapNames[pluginObj->name()]; - - // If both plugins are from the same proxy and the same file, this is usually - // ok (in theory some one could write two different classes from the same Python file/module): - if (pluginProxy && m_Requirements.at(other).proxy() == pluginProxy - && this->filepath(other) == QDir::cleanPath(filepath)) { - - // Plugin has already been initialized: - skipInit = true; - - if (isBetterInterface(plugin, as_qobject(other))) { - log::debug("replacing plugin '{}' with interfaces [{}] by one with interfaces [{}]", - pluginObj->name(), implementedInterfaces(other).join(", "), implementedInterfaces(plugin).join(", ")); - bf::at_key(m_AccessPlugins)[pluginObj->name()] = pluginObj; - } - } - else { - log::warn("Trying to register two plugins with the name '{}' (from {} and {}), the second one will not be registered.", - pluginObj->name(), this->filepath(other), QDir::cleanPath(filepath)); - return nullptr; - } - } - else { - bf::at_key(m_AccessPlugins)[pluginObj->name()] = pluginObj; - } - - // Storing the original QObject* is a bit of a hack as I couldn't figure out any - // way to cast directly between IPlugin* and IPluginDiagnose* - bf::at_key(m_Plugins).push_back(plugin); - - plugin->setProperty("filepath", QDir::cleanPath(filepath)); - plugin->setParent(this); - - if (m_Organizer) { - m_Organizer->settings().plugins().registerPlugin(pluginObj); - } - - { // diagnosis plugin - IPluginDiagnose *diagnose = qobject_cast(plugin); - if (diagnose != nullptr) { - bf::at_key(m_Plugins).push_back(diagnose); - bf::at_key(m_AccessPlugins)[diagnose] = pluginObj; - diagnose->onInvalidated([&]() { emit diagnosisUpdate(); }); - } - } - { // file mapper plugin - IPluginFileMapper *mapper = qobject_cast(plugin); - if (mapper != nullptr) { - bf::at_key(m_Plugins).push_back(mapper); - bf::at_key(m_AccessPlugins)[mapper] = pluginObj; - } - } - { // mod page plugin - IPluginModPage *modPage = qobject_cast(plugin); - if (initPlugin(modPage, pluginProxy, skipInit)) { - bf::at_key(m_Plugins).push_back(modPage); - emit pluginRegistered(modPage); - return modPage; - } - } - { // game plugin - IPluginGame *game = qobject_cast(plugin); - if (game) { - game->detectGame(); - if (initPlugin(game, pluginProxy, skipInit)) { - bf::at_key(m_Plugins).push_back(game); - registerGame(game); - emit pluginRegistered(game); - return game; - } - } - } - { // tool plugins - IPluginTool *tool = qobject_cast(plugin); - if (initPlugin(tool, pluginProxy, skipInit)) { - bf::at_key(m_Plugins).push_back(tool); - emit pluginRegistered(tool); - return tool; - } - } - { // installer plugins - IPluginInstaller *installer = qobject_cast(plugin); - if (initPlugin(installer, pluginProxy, skipInit)) { - bf::at_key(m_Plugins).push_back(installer); - if (m_Organizer) { - installer->setInstallationManager(m_Organizer->installationManager()); - } - emit pluginRegistered(installer); - return installer; - } - } - { // preview plugins - IPluginPreview *preview = qobject_cast(plugin); - if (initPlugin(preview, pluginProxy, skipInit)) { - bf::at_key(m_Plugins).push_back(preview); - return preview; - } - } - { // proxy plugins - IPluginProxy *proxy = qobject_cast(plugin); - if (initPlugin(proxy, pluginProxy, skipInit)) { - bf::at_key(m_Plugins).push_back(proxy); - emit pluginRegistered(proxy); - - QStringList filepaths = proxy->pluginList( - QCoreApplication::applicationDirPath() + "/" + ToQString(AppConfig::pluginPath())); - for (const QString& filepath : filepaths) { - loadProxied(filepath, proxy); - } - return proxy; - } - } - - { // dummy plugins - // only initialize these, no processing otherwise - IPlugin *dummy = qobject_cast(plugin); - if (initPlugin(dummy, pluginProxy, skipInit)) { - bf::at_key(m_Plugins).push_back(dummy); - emit pluginRegistered(dummy); - return dummy; - } - } - - return nullptr; -} - -IPlugin* PluginContainer::managedGame() const -{ - // TODO: This const_cast is safe but ugly. Most methods require a IPlugin*, so - // returning a const-version if painful. This should be fixed by making methods accept - // a const IPlugin* instead, but there are a few tricks with qobject_cast and const. - return const_cast(m_Organizer->managedGame()); -} - -bool PluginContainer::isEnabled(IPlugin* plugin) const -{ - // Check if it's a game plugin: - if (implementInterface(plugin)) { - return plugin == m_Organizer->managedGame(); - } - - // Check the master, if any: - auto& requirements = m_Requirements.at(plugin); - - if (requirements.master()) { - return isEnabled(requirements.master()); - } - - // Check if the plugin is enabled: - if (!m_Organizer->persistent(plugin->name(), "enabled", plugin->enabledByDefault()).toBool()) { - return false; - } - - // Check the requirements: - return m_Requirements.at(plugin).canEnable(); -} - -void PluginContainer::setEnabled(MOBase::IPlugin* plugin, bool enable, bool dependencies) -{ - // If required, disable dependencies: - if (!enable && dependencies) { - for (auto* p : requirements(plugin).requiredFor()) { - setEnabled(p, false, false); // No need to "recurse" here since requiredFor already does it. - } - } - - // Always disable/enable child plugins: - for (auto* p : requirements(plugin).children()) { - // "Child" plugin should have no dependencies. - setEnabled(p, enable, false); - } - - m_Organizer->setPersistent(plugin->name(), "enabled", enable, true); - - if (enable) { - emit pluginEnabled(plugin); - } - else { - emit pluginDisabled(plugin); - } -} - -MOBase::IPlugin* PluginContainer::plugin(QString const& pluginName) const -{ - auto& map = bf::at_key(m_AccessPlugins); - auto it = map.find(pluginName); - if (it == std::end(map)) { - return nullptr; - } - return it->second; -} - -MOBase::IPlugin* PluginContainer::plugin(MOBase::IPluginDiagnose* diagnose) const -{ - auto& map = bf::at_key(m_AccessPlugins); - auto it = map.find(diagnose); - if (it == std::end(map)) { - return nullptr; - } - return it->second; -} - -MOBase::IPlugin* PluginContainer::plugin(MOBase::IPluginFileMapper* mapper) const -{ - auto& map = bf::at_key(m_AccessPlugins); - auto it = map.find(mapper); - if (it == std::end(map)) { - return nullptr; - } - return it->second; -} - -bool PluginContainer::isEnabled(QString const& pluginName) const { - IPlugin* p = plugin(pluginName); - return p ? isEnabled(p) : false; -} -bool PluginContainer::isEnabled(MOBase::IPluginDiagnose* diagnose) const { - IPlugin* p = plugin(diagnose); - return p ? isEnabled(p) : false; -} -bool PluginContainer::isEnabled(MOBase::IPluginFileMapper* mapper) const { - IPlugin* p = plugin(mapper); - return p ? isEnabled(p) : false; -} - -const PluginRequirements& PluginContainer::requirements(IPlugin* plugin) const -{ - return m_Requirements.at(plugin); -} - -OrganizerProxy* PluginContainer::organizerProxy(MOBase::IPlugin* plugin) const -{ - return requirements(plugin).m_Organizer; -} - -MOBase::IPluginProxy* PluginContainer::pluginProxy(MOBase::IPlugin* plugin) const -{ - return requirements(plugin).proxy(); -} - -QString PluginContainer::filepath(MOBase::IPlugin* plugin) const -{ - return as_qobject(plugin)->property("filepath").toString(); -} - -IPluginGame *PluginContainer::game(const QString &name) const -{ - auto iter = m_SupportedGames.find(name); - if (iter != m_SupportedGames.end()) { - return iter->second; - } else { - return nullptr; - } -} - -const PreviewGenerator &PluginContainer::previewGenerator() const -{ - return m_PreviewGenerator; -} - -void PluginContainer::startPluginsImpl(const std::vector& plugins) const -{ - // setUserInterface() - if (m_UserInterface) { - for (auto* plugin : plugins) { - if (auto* proxy = qobject_cast(plugin)) { - proxy->setParentWidget(m_UserInterface->mainWindow()); - } - if (auto* modPage = qobject_cast(plugin)) { - modPage->setParentWidget(m_UserInterface->mainWindow()); - } - if (auto* tool = qobject_cast(plugin)) { - tool->setParentWidget(m_UserInterface->mainWindow()); - } - if (auto* installer = qobject_cast(plugin)) { - installer->setParentWidget(m_UserInterface->mainWindow()); - } - } - } - - // Trigger initial callbacks, e.g. onUserInterfaceInitialized and onProfileChanged. - if (m_Organizer) { - for (auto* object : plugins) { - auto* plugin = qobject_cast(object); - auto* oproxy = organizerProxy(plugin); - oproxy->connectSignals(); - oproxy->m_ProfileChanged(nullptr, m_Organizer->currentProfile()); - - if (m_UserInterface) { - oproxy->m_UserInterfaceInitialized(m_UserInterface->mainWindow()); - } - } - } -} - -std::vector PluginContainer::loadProxied(const QString& filepath, IPluginProxy* proxy) -{ - std::vector proxiedPlugins; - - try { - // We get a list of matching plugins as proxies can return multiple plugins - // per file and do not have a good way of supporting multiple inheritance. - QList matchingPlugins = proxy->load(filepath); - - // We are going to group plugin by names and "fix" them later: - std::map> proxiedByNames; - - for (QObject* proxiedPlugin : matchingPlugins) { - if (proxiedPlugin != nullptr) { - - if (IPlugin* proxied = registerPlugin(proxiedPlugin, filepath, proxy); proxied) { - log::debug("loaded plugin '{}' from '{}' - [{}]", - proxied->name(), QFileInfo(filepath).fileName(), implementedInterfaces(proxied).join(", ")); - - // Store the plugin for later: - proxiedPlugins.push_back(proxiedPlugin); - proxiedByNames[proxied->name()].push_back(proxied); - } - else { - log::warn( - "plugin \"{}\" failed to load. If this plugin is for an older version of MO " - "you have to update it or delete it if no update exists.", - filepath); - } - } - } - - // Fake masters: - for (auto& [name, proxiedPlugins] : proxiedByNames) { - if (proxiedPlugins.size() > 1) { - auto it = std::min_element(std::begin(proxiedPlugins), std::end(proxiedPlugins), - [&](auto const& lhs, auto const& rhs) { - return isBetterInterface(as_qobject(lhs), as_qobject(rhs)); - }); - - for (auto& proxiedPlugin : proxiedPlugins) { - if (proxiedPlugin != *it) { - m_Requirements.at(proxiedPlugin).setMaster(*it); - } - } - } - } - } - catch (const std::exception& e) { - reportError(QObject::tr("failed to initialize plugin %1: %2").arg(filepath).arg(e.what())); - } - - return proxiedPlugins; -} - -QObject* PluginContainer::loadQtPlugin(const QString& filepath) -{ - std::unique_ptr pluginLoader(new QPluginLoader(filepath, this)); - if (pluginLoader->instance() == nullptr) { - m_FailedPlugins.push_back(filepath); - log::error("failed to load plugin {}: {}", filepath, pluginLoader->errorString()); - } - else { - QObject* object = pluginLoader->instance(); - if (IPlugin* plugin = registerPlugin(object, filepath, nullptr); plugin) { - log::debug("loaded plugin '{}' from '{}' - [{}]", - plugin->name(), QFileInfo(filepath).fileName(), implementedInterfaces(plugin).join(", ")); - m_PluginLoaders.push_back(pluginLoader.release()); - return object; - } - else { - m_FailedPlugins.push_back(filepath); - log::warn("plugin '{}' failed to load (may be outdated)", filepath); - } - } - return nullptr; -} - -std::optional PluginContainer::isQtPluginFolder(const QString& filepath) const { - - if (!QFileInfo(filepath).isDir()) { - return {}; - } - - QDirIterator iter(filepath, QDir::Files | QDir::NoDotAndDotDot); - while (iter.hasNext()) { - iter.next(); - const auto filePath = iter.filePath(); - - // not a library, skip - if (!QLibrary::isLibrary(filePath)) { - continue; - } - - // check if we have proper metadata - this does not load the plugin (metaData() should - // be very lightweight) - const QPluginLoader loader(filePath); - if (!loader.metaData().isEmpty()) { - return filePath; - } - } - - return {}; -} - -void PluginContainer::loadPlugin(QString const& filepath) -{ - std::vector plugins; - if (QFileInfo(filepath).isFile() && QLibrary::isLibrary(filepath)) { - QObject* plugin = loadQtPlugin(filepath); - if (plugin) { - plugins.push_back(plugin); - } - } - else if (auto p = isQtPluginFolder(filepath)) { - QObject* plugin = loadQtPlugin(*p); - if (plugin) { - plugins.push_back(plugin); - } - } - else { - // We need to check if this can be handled by a proxy. - for (auto* proxy : this->plugins()) { - auto filepaths = proxy->pluginList( - QCoreApplication::applicationDirPath() + "/" + ToQString(AppConfig::pluginPath())); - if (filepaths.contains(filepath)) { - plugins = loadProxied(filepath, proxy); - break; - } - } - } - - for (auto* plugin : plugins) { - emit pluginRegistered(qobject_cast(plugin)); - } - - startPluginsImpl(plugins); -} - -void PluginContainer::unloadPlugin(MOBase::IPlugin* plugin, QObject* object) -{ - if (auto* game = qobject_cast(object)) { - - if (game == managedGame()) { - throw Exception("cannot unload the plugin for the currently managed game"); - } - - unregisterGame(game); - } - - // We need to remove from the m_Plugins maps BEFORE unloading from the proxy - // otherwise the qobject_cast to check the plugin type will not work. - bf::for_each(m_Plugins, [object](auto& t) { - using type = typename std::decay_t::value_type; - - // We do not want to remove from QObject since we are iterating over them. - if constexpr (!std::is_same{}) { - auto itp = std::find(t.second.begin(), t.second.end(), qobject_cast(object)); - if (itp != t.second.end()) { - t.second.erase(itp); - } - } - }); - - emit pluginUnregistered(plugin); - - // Remove from the members. - if (auto* diagnose = qobject_cast(object)) { - bf::at_key(m_AccessPlugins).erase(diagnose); - } - if (auto* mapper = qobject_cast(object)) { - bf::at_key(m_AccessPlugins).erase(mapper); - } - - auto& mapNames = bf::at_key(m_AccessPlugins); - if (mapNames.contains(plugin->name())) { - mapNames.erase(plugin->name()); - } - - m_Organizer->settings().plugins().unregisterPlugin(plugin); - - // Force disconnection of the signals from the proxies. This is a safety - // operations since those signals should be disconnected when the proxies - // are destroyed anyway. - organizerProxy(plugin)->disconnectSignals(); - - // Is this a proxied plugin? - auto* proxy = pluginProxy(plugin); - - if (proxy) { - proxy->unload(filepath(plugin)); - } - else { - // We need to find the loader. - auto it = std::find_if(m_PluginLoaders.begin(), m_PluginLoaders.end(), - [object](auto* loader) { return loader->instance() == object; }); - - if (it != m_PluginLoaders.end()) { - if (!(*it)->unload()) { - log::error("failed to unload {}: {}", (*it)->fileName(), (*it)->errorString()); - } - delete* it; - m_PluginLoaders.erase(it); - } - else { - log::error("loader for plugin {} does not exist, cannot unload", plugin->name()); - } - - } - - object->deleteLater(); - - // Do this at the end. - m_Requirements.erase(plugin); -} - -void PluginContainer::unloadPlugin(QString const& filepath) -{ - // We need to find all the plugins from the given path and - // unload them: - QString cleanPath = QDir::cleanPath(filepath); - auto &objects = bf::at_key(m_Plugins); - for (auto it = objects.begin(); it != objects.end(); ) { - auto* plugin = qobject_cast(*it); - if (this->filepath(plugin) == filepath) { - unloadPlugin(plugin, *it); - it = objects.erase(it); - } - else { - ++it; - } - } -} - -void PluginContainer::reloadPlugin(QString const& filepath) -{ - unloadPlugin(filepath); - loadPlugin(filepath); -} - -void PluginContainer::unloadPlugins() -{ - if (m_Organizer) { - // this will clear several structures that can hold on to pointers to - // plugins, as well as read the plugin blacklist from the ini file, which - // is used in loadPlugins() below to skip plugins - // - // note that the first thing loadPlugins() does is call unloadPlugins(), - // so this makes sure the blacklist is always available - m_Organizer->settings().plugins().clearPlugins(); - } - - bf::for_each(m_Plugins, [](auto& t) { t.second.clear(); }); - bf::for_each(m_AccessPlugins, [](auto& t) { t.second.clear(); }); - m_Requirements.clear(); - - while (!m_PluginLoaders.empty()) { - QPluginLoader* loader = m_PluginLoaders.back(); - m_PluginLoaders.pop_back(); - if ((loader != nullptr) && !loader->unload()) { - log::debug("failed to unload {}: {}", loader->fileName(), loader->errorString()); - } - delete loader; - } -} - -void PluginContainer::loadPlugins() -{ - TimeThis tt("PluginContainer::loadPlugins()"); - - unloadPlugins(); - - for (QObject *plugin : QPluginLoader::staticInstances()) { - registerPlugin(plugin, "", nullptr); - } - - QFile loadCheck; - QString skipPlugin; - - if (m_Organizer) { - loadCheck.setFileName(qApp->property("dataPath").toString() + "/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(); - } - - log::warn("loadcheck file found for plugin '{}'", fileName); - - MOBase::TaskDialog dlg; - - const auto Skip = QMessageBox::Ignore; - const auto Blacklist = QMessageBox::Cancel; - const auto Load = QMessageBox::Ok; - - const auto r = dlg - .title(tr("Plugin error")) - .main(tr( - "Mod Organizer failed to load the plugin '%1' last time it was started.") - .arg(fileName)) - .content(tr( - "The plugin can be skipped for this session, blacklisted, " - "or loaded normally, in which case it might fail again. Blacklisted " - "plugins can be re-enabled later in the settings.")) - .icon(QMessageBox::Warning) - .button({tr("Skip this plugin"), Skip}) - .button({tr("Blacklist this plugin"), Blacklist}) - .button({tr("Load this plugin"), Load}) - .exec(); - - switch (r) - { - case Skip: - log::warn("user wants to skip plugin '{}'", fileName); - skipPlugin = fileName; - break; - - case Blacklist: - log::warn("user wants to blacklist plugin '{}'", fileName); - m_Organizer->settings().plugins().addBlacklist(fileName); - break; - - case Load: - log::warn("user wants to load plugin '{}' anyway", fileName); - break; - } - - loadCheck.close(); - } - - loadCheck.open(QIODevice::WriteOnly); - } - - QString pluginPath = qApp->applicationDirPath() + "/" + ToQString(AppConfig::pluginPath()); - log::debug("looking for plugins in {}", QDir::toNativeSeparators(pluginPath)); - QDirIterator iter(pluginPath, QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot); - - while (iter.hasNext()) { - iter.next(); - - if (skipPlugin == iter.fileName()) { - log::debug("plugin \"{}\" skipped for this session", iter.fileName()); - continue; - } - - if (m_Organizer) { - if (m_Organizer->settings().plugins().blacklisted(iter.fileName())) { - log::debug("plugin \"{}\" blacklisted", iter.fileName()); - continue; - } - } - - if (loadCheck.isOpen()) { - loadCheck.write(iter.fileName().toUtf8()); - loadCheck.write("\n"); - loadCheck.flush(); - } - - QString filepath = iter.filePath(); - if (QLibrary::isLibrary(filepath)) { - loadQtPlugin(filepath); - } - else if (auto p = isQtPluginFolder(filepath)) { - loadQtPlugin(*p); - } - } - - if (skipPlugin.isEmpty()) { - // remove the load check file on success - if (loadCheck.isOpen()) { - loadCheck.remove(); - } - } else { - // remember the plugin for next time - if (loadCheck.isOpen()) { - loadCheck.close(); - } - - log::warn("user skipped plugin '{}', remembering in loadcheck", skipPlugin); - loadCheck.open(QIODevice::WriteOnly); - loadCheck.write(skipPlugin.toUtf8()); - loadCheck.write("\n"); - loadCheck.flush(); - } - - - bf::at_key(m_Plugins).push_back(this); - - if (m_Organizer) { - bf::at_key(m_Plugins).push_back(m_Organizer); - m_Organizer->connectPlugins(this); - } -} - -std::vector PluginContainer::activeProblems() const -{ - std::vector problems; - if (m_FailedPlugins.size()) { - problems.push_back(PROBLEM_PLUGINSNOTLOADED); - } - return problems; -} - -QString PluginContainer::shortDescription(unsigned int key) const -{ - switch (key) { - case PROBLEM_PLUGINSNOTLOADED: { - return tr("Some plugins could not be loaded"); - } break; - default: { - return tr("Description missing"); - } break; - } -} - -QString PluginContainer::fullDescription(unsigned int key) const -{ - switch (key) { - case PROBLEM_PLUGINSNOTLOADED: { - QString result = tr("The following plugins could not be loaded. The reason may be missing " - "dependencies (i.e. python) or an outdated version:") + "
        "; - for (const QString &plugin : m_FailedPlugins) { - result += "
      • " + plugin + "
      • "; - } - result += "
          "; - return result; - } break; - default: { - return tr("Description missing"); - } break; - } -} - -bool PluginContainer::hasGuidedFix(unsigned int) const -{ - return false; -} - -void PluginContainer::startGuidedFix(unsigned int) const -{ -} +#include "plugincontainer.h" +#include "iuserinterface.h" +#include "organizercore.h" +#include "organizerproxy.h" +#include "report.h" +#include "shared/appconfig.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace MOBase; +using namespace MOShared; + +namespace bf = boost::fusion; + +// Welcome to the wonderful world of MO2 plugin management! +// +// We'll start by the C++ side. +// +// There are 9 types of MO2 plugins, two of which cannot be standalone: IPluginDiagnose +// and IPluginFileMapper. This means that you can have a class implementing IPluginGame, +// IPluginDiagnose and IPluginFileMapper. It is not possible for a class to implement +// two full plugin types (e.g. IPluginPreview and IPluginTool). +// +// Plugins are fetch as QObject initially and must be "qobject-casted" to the right +// type. +// +// Plugins are stored in the PluginContainer class in various C++ containers: there is a +// vector that stores all the plugin as QObject, multiple vectors that stores the plugin +// of each types, a map to find IPlugin object from their names or from IPluginDiagnose +// or IFileMapper (since these do not inherit IPlugin, they cannot be downcasted). +// +// Requirements for plugins are stored in m_Requirements: +// - IPluginGame cannot be enabled by user. A game plugin is considered enable only if +// it is +// the one corresponding to the currently managed games. +// - If a plugin has a master plugin (IPlugin::master()), it cannot be enabled/disabled +// by users, +// and will follow the enabled/disabled state of its parent. +// - Each plugin has an "enabled" setting stored in persistence. If the setting does +// not exist, +// the plugin's enabledByDefault is used instead. +// - A plugin is considered disabled if the setting is false. +// - If the setting is true, a plugin is considered disabled if one of its +// requirements is not met. +// - Users cannot enable a plugin if one of its requirements is not met. +// +// Now let's move to the Proxy side... Or the as of now, the Python side. +// +// Proxied plugins are much more annoying because they can implement all interfaces, and +// are given to MO2 as separate plugins... A Python class implementing IPluginGame and +// IPluginDiagnose will be seen by MO2 as two separate QObject, and they will all have +// the same name. +// +// When a proxied plugin is registered, a few things must be taken care of: +// - There can only be one plugin mapped to a name in the PluginContainer class, so we +// keep the +// plugin corresponding to the most relevant class (see PluginTypeOrder), e.g. if the +// class inherits both IPluginGame and IPluginFileMapper, we map the name to the C++ +// QObject corresponding to the IPluginGame. +// - When a proxied plugin implements multiple interfaces, the IPlugin corresponding to +// the most +// important interface is set as the parent (hidden) of the other IPlugin through +// PluginRequirements. This way, the plugin are managed together (enabled/disabled +// state). The "fake" children plugins will not be returned by +// PluginRequirements::children(). +// - Since each interface corresponds to a different QObject, we need to take care not +// to call +// IPlugin::init() on each QObject, but only on the first one. +// +// All the proxied plugins are linked to the proxy plugin by PluginRequirements. If the +// proxy plugin is disabled, the proxied plugins are not even loaded so not visible in +// the plugin management tab. + +template +struct PluginTypeName; + +template <> +struct PluginTypeName +{ + static QString value() { return QT_TR_NOOP("Plugin"); } +}; +template <> +struct PluginTypeName +{ + static QString value() { return QT_TR_NOOP("Diagnose"); } +}; +template <> +struct PluginTypeName +{ + static QString value() { return QT_TR_NOOP("Game"); } +}; +template <> +struct PluginTypeName +{ + static QString value() { return QT_TR_NOOP("Installer"); } +}; +template <> +struct PluginTypeName +{ + static QString value() { return QT_TR_NOOP("Mod Page"); } +}; +template <> +struct PluginTypeName +{ + static QString value() { return QT_TR_NOOP("Preview"); } +}; +template <> +struct PluginTypeName +{ + static QString value() { return QT_TR_NOOP("Tool"); } +}; +template <> +struct PluginTypeName +{ + static QString value() { return QT_TR_NOOP("Proxy"); } +}; +template <> +struct PluginTypeName +{ + static QString value() { return QT_TR_NOOP("File Mapper"); } +}; + +QStringList PluginContainer::pluginInterfaces() +{ + // Find all the names: + QStringList names; + boost::mp11::mp_for_each([&names](const auto* p) { + using plugin_type = std::decay_t; + auto name = PluginTypeName::value(); + if (!name.isEmpty()) { + names.append(name); + } + }); + + return names; +} + +// PluginRequirementProxy + +const std::set PluginRequirements::s_CorePlugins{"INI Bakery"}; + +PluginRequirements::PluginRequirements(PluginContainer* pluginContainer, + MOBase::IPlugin* plugin, OrganizerProxy* proxy, + MOBase::IPluginProxy* pluginProxy) + : m_PluginContainer(pluginContainer), m_Plugin(plugin), m_PluginProxy(pluginProxy), + m_Master(nullptr), m_Organizer(proxy) +{ + // There are a lots of things we cannot set here (e.g. m_Master) because we do not + // know the order plugins are loaded. +} + +void PluginRequirements::fetchRequirements() +{ + m_Requirements = m_Plugin->requirements(); +} + +IPluginProxy* PluginRequirements::proxy() const +{ + return m_PluginProxy; +} + +std::vector PluginRequirements::proxied() const +{ + std::vector children; + if (dynamic_cast(m_Plugin)) { + for (auto* obj : m_PluginContainer->plugins()) { + auto* plugin = qobject_cast(obj); + if (plugin && m_PluginContainer->requirements(plugin).proxy() == m_Plugin) { + children.push_back(plugin); + } + } + } + return children; +} + +IPlugin* PluginRequirements::master() const +{ + // If we have a m_Master, it was forced and thus override the default master(). + if (m_Master) { + return m_Master; + } + + if (m_Plugin->master().isEmpty()) { + return nullptr; + } + + return m_PluginContainer->plugin(m_Plugin->master()); +} + +void PluginRequirements::setMaster(IPlugin* master) +{ + m_Master = master; +} + +std::vector PluginRequirements::children() const +{ + std::vector children; + for (auto* obj : m_PluginContainer->plugins()) { + auto* plugin = qobject_cast(obj); + + // Not checking master() but requirements().master() due to "hidden" + // masters. + // If the master has the same name as the plugin, this is a "hidden" + // master, we do not add it here. + if (plugin && m_PluginContainer->requirements(plugin).master() == m_Plugin && + plugin->name() != m_Plugin->name()) { + children.push_back(plugin); + } + } + return children; +} + +std::vector PluginRequirements::problems() const +{ + std::vector result; + for (auto& requirement : m_Requirements) { + if (auto p = requirement->check(m_Organizer)) { + result.push_back(*p); + } + } + return result; +} + +bool PluginRequirements::canEnable() const +{ + return problems().empty(); +} + +bool PluginRequirements::isCorePlugin() const +{ + // Let's consider game plugins as "core": + if (m_PluginContainer->implementInterface(m_Plugin)) { + return true; + } + + return s_CorePlugins.contains(m_Plugin->name()); +} + +bool PluginRequirements::hasRequirements() const +{ + return !m_Requirements.empty(); +} + +QStringList PluginRequirements::requiredGames() const +{ + // We look for a "GameDependencyRequirement" - There can be only one since otherwise + // it'd mean that the plugin requires two games at once. + for (auto& requirement : m_Requirements) { + if (auto* gdep = + dynamic_cast(requirement.get())) { + return gdep->gameNames(); + } + } + + return {}; +} + +std::vector PluginRequirements::requiredFor() const +{ + std::vector required; + std::set visited; + requiredFor(required, visited); + return required; +} + +void PluginRequirements::requiredFor(std::vector& required, + std::set& visited) const +{ + // Handle cyclic dependencies. + if (visited.contains(m_Plugin)) { + return; + } + visited.insert(m_Plugin); + + for (auto& [plugin, requirements] : m_PluginContainer->m_Requirements) { + + // If the plugin is not enabled, discard: + if (!m_PluginContainer->isEnabled(plugin)) { + continue; + } + + // Check the requirements: + for (auto& requirement : requirements.m_Requirements) { + + // We check for plugin dependency. Game dependency are not checked this way. + if (auto* pdep = + dynamic_cast(requirement.get())) { + + // Check if at least one of the plugin in the requirements is enabled (except + // this one): + bool oneEnabled = false; + for (auto& pluginName : pdep->pluginNames()) { + if (pluginName != m_Plugin->name() && + m_PluginContainer->isEnabled(pluginName)) { + oneEnabled = true; + break; + } + } + + // No plugin enabled found, so the plugin requires this plugin: + if (!oneEnabled) { + required.push_back(plugin); + requirements.requiredFor(required, visited); + break; + } + } + } + } +} + +// PluginContainer + +PluginContainer::PluginContainer(OrganizerCore* organizer) + : m_Organizer(organizer), m_UserInterface(nullptr), m_PreviewGenerator(*this) +{} + +PluginContainer::~PluginContainer() +{ + m_Organizer = nullptr; + unloadPlugins(); +} + +void PluginContainer::startPlugins(IUserInterface* userInterface) +{ + m_UserInterface = userInterface; + startPluginsImpl(plugins()); +} + +QStringList PluginContainer::implementedInterfaces(IPlugin* plugin) const +{ + // We need a QObject to be able to qobject_cast<> to the plugin types: + QObject* oPlugin = as_qobject(plugin); + + if (!oPlugin) { + return {}; + } + + return implementedInterfaces(oPlugin); +} + +QStringList PluginContainer::implementedInterfaces(QObject* oPlugin) const +{ + // Find all the names: + QStringList names; + boost::mp11::mp_for_each([oPlugin, &names](const auto* p) { + using plugin_type = std::decay_t; + if (qobject_cast(oPlugin)) { + auto name = PluginTypeName::value(); + if (!name.isEmpty()) { + names.append(name); + } + } + }); + + // If the plugin implements at least one interface other than IPlugin, remove IPlugin: + if (names.size() > 1) { + names.removeAll(PluginTypeName::value()); + } + + return names; +} + +QString PluginContainer::topImplementedInterface(IPlugin* plugin) const +{ + auto interfaces = implementedInterfaces(plugin); + return interfaces.isEmpty() ? "" : interfaces[0]; +} + +bool PluginContainer::isBetterInterface(QObject* lhs, QObject* rhs) const +{ + int count = 0, lhsIdx = -1, rhsIdx = -1; + boost::mp11::mp_for_each([&](const auto* p) { + using plugin_type = std::decay_t; + if (lhsIdx < 0 && qobject_cast(lhs)) { + lhsIdx = count; + } + if (rhsIdx < 0 && qobject_cast(rhs)) { + rhsIdx = count; + } + ++count; + }); + return lhsIdx < rhsIdx; +} + +QStringList PluginContainer::pluginFileNames() const +{ + QStringList result; + for (QPluginLoader* loader : m_PluginLoaders) { + result.append(loader->fileName()); + } + std::vector proxyList = bf::at_key(m_Plugins); + for (IPluginProxy* proxy : proxyList) { + QStringList proxiedPlugins = + proxy->pluginList(QCoreApplication::applicationDirPath() + "/" + + ToQString(AppConfig::pluginPath())); + result.append(proxiedPlugins); + } + return result; +} + +QObject* PluginContainer::as_qobject(MOBase::IPlugin* plugin) const +{ + // Find the correspond QObject - Can this be done safely with a cast? + auto& objects = bf::at_key(m_Plugins); + auto it = + std::find_if(std::begin(objects), std::end(objects), [plugin](QObject* obj) { + return qobject_cast(obj) == plugin; + }); + + if (it == std::end(objects)) { + return nullptr; + } + + return *it; +} + +bool PluginContainer::initPlugin(IPlugin* plugin, IPluginProxy* pluginProxy, + bool skipInit) +{ + // when MO has no instance loaded, init() is not called on plugins, except + // for proxy plugins, where init() is called with a null IOrganizer + // + // after proxies are initialized, instantiate() is called for all the plugins + // they've discovered, but as for regular plugins, init() won't be + // called on them if m_OrganizerCore is null + + if (plugin == nullptr) { + return false; + } + + OrganizerProxy* proxy = nullptr; + if (m_Organizer) { + proxy = new OrganizerProxy(m_Organizer, this, plugin); + proxy->setParent(as_qobject(plugin)); + } + + // Check if it is a proxy plugin: + bool isProxy = dynamic_cast(plugin); + + auto [it, bl] = m_Requirements.emplace( + plugin, PluginRequirements(this, plugin, proxy, pluginProxy)); + + if (!m_Organizer && !isProxy) { + return true; + } + + if (skipInit) { + return true; + } + + if (!plugin->init(proxy)) { + log::warn("plugin failed to initialize"); + return false; + } + + // Update requirements: + it->second.fetchRequirements(); + + return true; +} + +void PluginContainer::registerGame(IPluginGame* game) +{ + m_SupportedGames.insert({game->gameName(), game}); +} + +void PluginContainer::unregisterGame(MOBase::IPluginGame* game) +{ + m_SupportedGames.erase(game->gameName()); +} + +IPlugin* PluginContainer::registerPlugin(QObject* plugin, const QString& filepath, + MOBase::IPluginProxy* pluginProxy) +{ + + // generic treatment for all plugins + IPlugin* pluginObj = qobject_cast(plugin); + if (pluginObj == nullptr) { + log::debug("PluginContainer::registerPlugin() called with a non IPlugin QObject."); + return nullptr; + } + + // If we already a plugin with this name: + bool skipInit = false; + auto& mapNames = bf::at_key(m_AccessPlugins); + if (mapNames.contains(pluginObj->name())) { + + IPlugin* other = mapNames[pluginObj->name()]; + + // If both plugins are from the same proxy and the same file, this is usually + // ok (in theory some one could write two different classes from the same Python + // file/module): + if (pluginProxy && m_Requirements.at(other).proxy() == pluginProxy && + this->filepath(other) == QDir::cleanPath(filepath)) { + + // Plugin has already been initialized: + skipInit = true; + + if (isBetterInterface(plugin, as_qobject(other))) { + log::debug( + "replacing plugin '{}' with interfaces [{}] by one with interfaces [{}]", + pluginObj->name(), implementedInterfaces(other).join(", "), + implementedInterfaces(plugin).join(", ")); + bf::at_key(m_AccessPlugins)[pluginObj->name()] = pluginObj; + } + } else { + log::warn("Trying to register two plugins with the name '{}' (from {} and {}), " + "the second one will not be registered.", + pluginObj->name(), this->filepath(other), QDir::cleanPath(filepath)); + return nullptr; + } + } else { + bf::at_key(m_AccessPlugins)[pluginObj->name()] = pluginObj; + } + + // Storing the original QObject* is a bit of a hack as I couldn't figure out any + // way to cast directly between IPlugin* and IPluginDiagnose* + bf::at_key(m_Plugins).push_back(plugin); + + plugin->setProperty("filepath", QDir::cleanPath(filepath)); + plugin->setParent(this); + + if (m_Organizer) { + m_Organizer->settings().plugins().registerPlugin(pluginObj); + } + + { // diagnosis plugin + IPluginDiagnose* diagnose = qobject_cast(plugin); + if (diagnose != nullptr) { + bf::at_key(m_Plugins).push_back(diagnose); + bf::at_key(m_AccessPlugins)[diagnose] = pluginObj; + diagnose->onInvalidated([&]() { + emit diagnosisUpdate(); + }); + } + } + { // file mapper plugin + IPluginFileMapper* mapper = qobject_cast(plugin); + if (mapper != nullptr) { + bf::at_key(m_Plugins).push_back(mapper); + bf::at_key(m_AccessPlugins)[mapper] = pluginObj; + } + } + { // mod page plugin + IPluginModPage* modPage = qobject_cast(plugin); + if (initPlugin(modPage, pluginProxy, skipInit)) { + bf::at_key(m_Plugins).push_back(modPage); + emit pluginRegistered(modPage); + return modPage; + } + } + { // game plugin + IPluginGame* game = qobject_cast(plugin); + if (game) { + game->detectGame(); + if (initPlugin(game, pluginProxy, skipInit)) { + bf::at_key(m_Plugins).push_back(game); + registerGame(game); + emit pluginRegistered(game); + return game; + } + } + } + { // tool plugins + IPluginTool* tool = qobject_cast(plugin); + if (initPlugin(tool, pluginProxy, skipInit)) { + bf::at_key(m_Plugins).push_back(tool); + emit pluginRegistered(tool); + return tool; + } + } + { // installer plugins + IPluginInstaller* installer = qobject_cast(plugin); + if (initPlugin(installer, pluginProxy, skipInit)) { + bf::at_key(m_Plugins).push_back(installer); + if (m_Organizer) { + installer->setInstallationManager(m_Organizer->installationManager()); + } + emit pluginRegistered(installer); + return installer; + } + } + { // preview plugins + IPluginPreview* preview = qobject_cast(plugin); + if (initPlugin(preview, pluginProxy, skipInit)) { + bf::at_key(m_Plugins).push_back(preview); + return preview; + } + } + { // proxy plugins + IPluginProxy* proxy = qobject_cast(plugin); + if (initPlugin(proxy, pluginProxy, skipInit)) { + bf::at_key(m_Plugins).push_back(proxy); + emit pluginRegistered(proxy); + + QStringList filepaths = + proxy->pluginList(QCoreApplication::applicationDirPath() + "/" + + ToQString(AppConfig::pluginPath())); + for (const QString& filepath : filepaths) { + loadProxied(filepath, proxy); + } + return proxy; + } + } + + { // dummy plugins + // only initialize these, no processing otherwise + IPlugin* dummy = qobject_cast(plugin); + if (initPlugin(dummy, pluginProxy, skipInit)) { + bf::at_key(m_Plugins).push_back(dummy); + emit pluginRegistered(dummy); + return dummy; + } + } + + return nullptr; +} + +IPlugin* PluginContainer::managedGame() const +{ + // TODO: This const_cast is safe but ugly. Most methods require a IPlugin*, so + // returning a const-version if painful. This should be fixed by making methods accept + // a const IPlugin* instead, but there are a few tricks with qobject_cast and const. + return const_cast(m_Organizer->managedGame()); +} + +bool PluginContainer::isEnabled(IPlugin* plugin) const +{ + // Check if it's a game plugin: + if (implementInterface(plugin)) { + return plugin == m_Organizer->managedGame(); + } + + // Check the master, if any: + auto& requirements = m_Requirements.at(plugin); + + if (requirements.master()) { + return isEnabled(requirements.master()); + } + + // Check if the plugin is enabled: + if (!m_Organizer->persistent(plugin->name(), "enabled", plugin->enabledByDefault()) + .toBool()) { + return false; + } + + // Check the requirements: + return m_Requirements.at(plugin).canEnable(); +} + +void PluginContainer::setEnabled(MOBase::IPlugin* plugin, bool enable, + bool dependencies) +{ + // If required, disable dependencies: + if (!enable && dependencies) { + for (auto* p : requirements(plugin).requiredFor()) { + setEnabled( + p, false, + false); // No need to "recurse" here since requiredFor already does it. + } + } + + // Always disable/enable child plugins: + for (auto* p : requirements(plugin).children()) { + // "Child" plugin should have no dependencies. + setEnabled(p, enable, false); + } + + m_Organizer->setPersistent(plugin->name(), "enabled", enable, true); + + if (enable) { + emit pluginEnabled(plugin); + } else { + emit pluginDisabled(plugin); + } +} + +MOBase::IPlugin* PluginContainer::plugin(QString const& pluginName) const +{ + auto& map = bf::at_key(m_AccessPlugins); + auto it = map.find(pluginName); + if (it == std::end(map)) { + return nullptr; + } + return it->second; +} + +MOBase::IPlugin* PluginContainer::plugin(MOBase::IPluginDiagnose* diagnose) const +{ + auto& map = bf::at_key(m_AccessPlugins); + auto it = map.find(diagnose); + if (it == std::end(map)) { + return nullptr; + } + return it->second; +} + +MOBase::IPlugin* PluginContainer::plugin(MOBase::IPluginFileMapper* mapper) const +{ + auto& map = bf::at_key(m_AccessPlugins); + auto it = map.find(mapper); + if (it == std::end(map)) { + return nullptr; + } + return it->second; +} + +bool PluginContainer::isEnabled(QString const& pluginName) const +{ + IPlugin* p = plugin(pluginName); + return p ? isEnabled(p) : false; +} +bool PluginContainer::isEnabled(MOBase::IPluginDiagnose* diagnose) const +{ + IPlugin* p = plugin(diagnose); + return p ? isEnabled(p) : false; +} +bool PluginContainer::isEnabled(MOBase::IPluginFileMapper* mapper) const +{ + IPlugin* p = plugin(mapper); + return p ? isEnabled(p) : false; +} + +const PluginRequirements& PluginContainer::requirements(IPlugin* plugin) const +{ + return m_Requirements.at(plugin); +} + +OrganizerProxy* PluginContainer::organizerProxy(MOBase::IPlugin* plugin) const +{ + return requirements(plugin).m_Organizer; +} + +MOBase::IPluginProxy* PluginContainer::pluginProxy(MOBase::IPlugin* plugin) const +{ + return requirements(plugin).proxy(); +} + +QString PluginContainer::filepath(MOBase::IPlugin* plugin) const +{ + return as_qobject(plugin)->property("filepath").toString(); +} + +IPluginGame* PluginContainer::game(const QString& name) const +{ + auto iter = m_SupportedGames.find(name); + if (iter != m_SupportedGames.end()) { + return iter->second; + } else { + return nullptr; + } +} + +const PreviewGenerator& PluginContainer::previewGenerator() const +{ + return m_PreviewGenerator; +} + +void PluginContainer::startPluginsImpl(const std::vector& plugins) const +{ + // setUserInterface() + if (m_UserInterface) { + for (auto* plugin : plugins) { + if (auto* proxy = qobject_cast(plugin)) { + proxy->setParentWidget(m_UserInterface->mainWindow()); + } + if (auto* modPage = qobject_cast(plugin)) { + modPage->setParentWidget(m_UserInterface->mainWindow()); + } + if (auto* tool = qobject_cast(plugin)) { + tool->setParentWidget(m_UserInterface->mainWindow()); + } + if (auto* installer = qobject_cast(plugin)) { + installer->setParentWidget(m_UserInterface->mainWindow()); + } + } + } + + // Trigger initial callbacks, e.g. onUserInterfaceInitialized and onProfileChanged. + if (m_Organizer) { + for (auto* object : plugins) { + auto* plugin = qobject_cast(object); + auto* oproxy = organizerProxy(plugin); + oproxy->connectSignals(); + oproxy->m_ProfileChanged(nullptr, m_Organizer->currentProfile()); + + if (m_UserInterface) { + oproxy->m_UserInterfaceInitialized(m_UserInterface->mainWindow()); + } + } + } +} + +std::vector PluginContainer::loadProxied(const QString& filepath, + IPluginProxy* proxy) +{ + std::vector proxiedPlugins; + + try { + // We get a list of matching plugins as proxies can return multiple plugins + // per file and do not have a good way of supporting multiple inheritance. + QList matchingPlugins = proxy->load(filepath); + + // We are going to group plugin by names and "fix" them later: + std::map> proxiedByNames; + + for (QObject* proxiedPlugin : matchingPlugins) { + if (proxiedPlugin != nullptr) { + + if (IPlugin* proxied = registerPlugin(proxiedPlugin, filepath, proxy); + proxied) { + log::debug("loaded plugin '{}' from '{}' - [{}]", proxied->name(), + QFileInfo(filepath).fileName(), + implementedInterfaces(proxied).join(", ")); + + // Store the plugin for later: + proxiedPlugins.push_back(proxiedPlugin); + proxiedByNames[proxied->name()].push_back(proxied); + } else { + log::warn("plugin \"{}\" failed to load. If this plugin is for an older " + "version of MO " + "you have to update it or delete it if no update exists.", + filepath); + } + } + } + + // Fake masters: + for (auto& [name, proxiedPlugins] : proxiedByNames) { + if (proxiedPlugins.size() > 1) { + auto it = std::min_element(std::begin(proxiedPlugins), std::end(proxiedPlugins), + [&](auto const& lhs, auto const& rhs) { + return isBetterInterface(as_qobject(lhs), + as_qobject(rhs)); + }); + + for (auto& proxiedPlugin : proxiedPlugins) { + if (proxiedPlugin != *it) { + m_Requirements.at(proxiedPlugin).setMaster(*it); + } + } + } + } + } catch (const std::exception& e) { + reportError( + QObject::tr("failed to initialize plugin %1: %2").arg(filepath).arg(e.what())); + } + + return proxiedPlugins; +} + +QObject* PluginContainer::loadQtPlugin(const QString& filepath) +{ + std::unique_ptr pluginLoader(new QPluginLoader(filepath, this)); + if (pluginLoader->instance() == nullptr) { + m_FailedPlugins.push_back(filepath); + log::error("failed to load plugin {}: {}", filepath, pluginLoader->errorString()); + } else { + QObject* object = pluginLoader->instance(); + if (IPlugin* plugin = registerPlugin(object, filepath, nullptr); plugin) { + log::debug("loaded plugin '{}' from '{}' - [{}]", plugin->name(), + QFileInfo(filepath).fileName(), + implementedInterfaces(plugin).join(", ")); + m_PluginLoaders.push_back(pluginLoader.release()); + return object; + } else { + m_FailedPlugins.push_back(filepath); + log::warn("plugin '{}' failed to load (may be outdated)", filepath); + } + } + return nullptr; +} + +std::optional PluginContainer::isQtPluginFolder(const QString& filepath) const +{ + + if (!QFileInfo(filepath).isDir()) { + return {}; + } + + QDirIterator iter(filepath, QDir::Files | QDir::NoDotAndDotDot); + while (iter.hasNext()) { + iter.next(); + const auto filePath = iter.filePath(); + + // not a library, skip + if (!QLibrary::isLibrary(filePath)) { + continue; + } + + // check if we have proper metadata - this does not load the plugin (metaData() + // should be very lightweight) + const QPluginLoader loader(filePath); + if (!loader.metaData().isEmpty()) { + return filePath; + } + } + + return {}; +} + +void PluginContainer::loadPlugin(QString const& filepath) +{ + std::vector plugins; + if (QFileInfo(filepath).isFile() && QLibrary::isLibrary(filepath)) { + QObject* plugin = loadQtPlugin(filepath); + if (plugin) { + plugins.push_back(plugin); + } + } else if (auto p = isQtPluginFolder(filepath)) { + QObject* plugin = loadQtPlugin(*p); + if (plugin) { + plugins.push_back(plugin); + } + } else { + // We need to check if this can be handled by a proxy. + for (auto* proxy : this->plugins()) { + auto filepaths = proxy->pluginList(QCoreApplication::applicationDirPath() + "/" + + ToQString(AppConfig::pluginPath())); + if (filepaths.contains(filepath)) { + plugins = loadProxied(filepath, proxy); + break; + } + } + } + + for (auto* plugin : plugins) { + emit pluginRegistered(qobject_cast(plugin)); + } + + startPluginsImpl(plugins); +} + +void PluginContainer::unloadPlugin(MOBase::IPlugin* plugin, QObject* object) +{ + if (auto* game = qobject_cast(object)) { + + if (game == managedGame()) { + throw Exception("cannot unload the plugin for the currently managed game"); + } + + unregisterGame(game); + } + + // We need to remove from the m_Plugins maps BEFORE unloading from the proxy + // otherwise the qobject_cast to check the plugin type will not work. + bf::for_each(m_Plugins, [object](auto& t) { + using type = typename std::decay_t::value_type; + + // We do not want to remove from QObject since we are iterating over them. + if constexpr (!std::is_same{}) { + auto itp = + std::find(t.second.begin(), t.second.end(), qobject_cast(object)); + if (itp != t.second.end()) { + t.second.erase(itp); + } + } + }); + + emit pluginUnregistered(plugin); + + // Remove from the members. + if (auto* diagnose = qobject_cast(object)) { + bf::at_key(m_AccessPlugins).erase(diagnose); + } + if (auto* mapper = qobject_cast(object)) { + bf::at_key(m_AccessPlugins).erase(mapper); + } + + auto& mapNames = bf::at_key(m_AccessPlugins); + if (mapNames.contains(plugin->name())) { + mapNames.erase(plugin->name()); + } + + m_Organizer->settings().plugins().unregisterPlugin(plugin); + + // Force disconnection of the signals from the proxies. This is a safety + // operations since those signals should be disconnected when the proxies + // are destroyed anyway. + organizerProxy(plugin)->disconnectSignals(); + + // Is this a proxied plugin? + auto* proxy = pluginProxy(plugin); + + if (proxy) { + proxy->unload(filepath(plugin)); + } else { + // We need to find the loader. + auto it = std::find_if(m_PluginLoaders.begin(), m_PluginLoaders.end(), + [object](auto* loader) { + return loader->instance() == object; + }); + + if (it != m_PluginLoaders.end()) { + if (!(*it)->unload()) { + log::error("failed to unload {}: {}", (*it)->fileName(), (*it)->errorString()); + } + delete *it; + m_PluginLoaders.erase(it); + } else { + log::error("loader for plugin {} does not exist, cannot unload", plugin->name()); + } + } + + object->deleteLater(); + + // Do this at the end. + m_Requirements.erase(plugin); +} + +void PluginContainer::unloadPlugin(QString const& filepath) +{ + // We need to find all the plugins from the given path and + // unload them: + QString cleanPath = QDir::cleanPath(filepath); + auto& objects = bf::at_key(m_Plugins); + for (auto it = objects.begin(); it != objects.end();) { + auto* plugin = qobject_cast(*it); + if (this->filepath(plugin) == filepath) { + unloadPlugin(plugin, *it); + it = objects.erase(it); + } else { + ++it; + } + } +} + +void PluginContainer::reloadPlugin(QString const& filepath) +{ + unloadPlugin(filepath); + loadPlugin(filepath); +} + +void PluginContainer::unloadPlugins() +{ + if (m_Organizer) { + // this will clear several structures that can hold on to pointers to + // plugins, as well as read the plugin blacklist from the ini file, which + // is used in loadPlugins() below to skip plugins + // + // note that the first thing loadPlugins() does is call unloadPlugins(), + // so this makes sure the blacklist is always available + m_Organizer->settings().plugins().clearPlugins(); + } + + bf::for_each(m_Plugins, [](auto& t) { + t.second.clear(); + }); + bf::for_each(m_AccessPlugins, [](auto& t) { + t.second.clear(); + }); + m_Requirements.clear(); + + while (!m_PluginLoaders.empty()) { + QPluginLoader* loader = m_PluginLoaders.back(); + m_PluginLoaders.pop_back(); + if ((loader != nullptr) && !loader->unload()) { + log::debug("failed to unload {}: {}", loader->fileName(), loader->errorString()); + } + delete loader; + } +} + +void PluginContainer::loadPlugins() +{ + TimeThis tt("PluginContainer::loadPlugins()"); + + unloadPlugins(); + + for (QObject* plugin : QPluginLoader::staticInstances()) { + registerPlugin(plugin, "", nullptr); + } + + QFile loadCheck; + QString skipPlugin; + + if (m_Organizer) { + loadCheck.setFileName(qApp->property("dataPath").toString() + + "/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(); + } + + log::warn("loadcheck file found for plugin '{}'", fileName); + + MOBase::TaskDialog dlg; + + const auto Skip = QMessageBox::Ignore; + const auto Blacklist = QMessageBox::Cancel; + const auto Load = QMessageBox::Ok; + + const auto r = + dlg.title(tr("Plugin error")) + .main(tr("Mod Organizer failed to load the plugin '%1' last time it was " + "started.") + .arg(fileName)) + .content(tr( + "The plugin can be skipped for this session, blacklisted, " + "or loaded normally, in which case it might fail again. Blacklisted " + "plugins can be re-enabled later in the settings.")) + .icon(QMessageBox::Warning) + .button({tr("Skip this plugin"), Skip}) + .button({tr("Blacklist this plugin"), Blacklist}) + .button({tr("Load this plugin"), Load}) + .exec(); + + switch (r) { + case Skip: + log::warn("user wants to skip plugin '{}'", fileName); + skipPlugin = fileName; + break; + + case Blacklist: + log::warn("user wants to blacklist plugin '{}'", fileName); + m_Organizer->settings().plugins().addBlacklist(fileName); + break; + + case Load: + log::warn("user wants to load plugin '{}' anyway", fileName); + break; + } + + loadCheck.close(); + } + + loadCheck.open(QIODevice::WriteOnly); + } + + QString pluginPath = + qApp->applicationDirPath() + "/" + ToQString(AppConfig::pluginPath()); + log::debug("looking for plugins in {}", QDir::toNativeSeparators(pluginPath)); + QDirIterator iter(pluginPath, QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot); + + while (iter.hasNext()) { + iter.next(); + + if (skipPlugin == iter.fileName()) { + log::debug("plugin \"{}\" skipped for this session", iter.fileName()); + continue; + } + + if (m_Organizer) { + if (m_Organizer->settings().plugins().blacklisted(iter.fileName())) { + log::debug("plugin \"{}\" blacklisted", iter.fileName()); + continue; + } + } + + if (loadCheck.isOpen()) { + loadCheck.write(iter.fileName().toUtf8()); + loadCheck.write("\n"); + loadCheck.flush(); + } + + QString filepath = iter.filePath(); + if (QLibrary::isLibrary(filepath)) { + loadQtPlugin(filepath); + } else if (auto p = isQtPluginFolder(filepath)) { + loadQtPlugin(*p); + } + } + + if (skipPlugin.isEmpty()) { + // remove the load check file on success + if (loadCheck.isOpen()) { + loadCheck.remove(); + } + } else { + // remember the plugin for next time + if (loadCheck.isOpen()) { + loadCheck.close(); + } + + log::warn("user skipped plugin '{}', remembering in loadcheck", skipPlugin); + loadCheck.open(QIODevice::WriteOnly); + loadCheck.write(skipPlugin.toUtf8()); + loadCheck.write("\n"); + loadCheck.flush(); + } + + bf::at_key(m_Plugins).push_back(this); + + if (m_Organizer) { + bf::at_key(m_Plugins).push_back(m_Organizer); + m_Organizer->connectPlugins(this); + } +} + +std::vector PluginContainer::activeProblems() const +{ + std::vector problems; + if (m_FailedPlugins.size()) { + problems.push_back(PROBLEM_PLUGINSNOTLOADED); + } + return problems; +} + +QString PluginContainer::shortDescription(unsigned int key) const +{ + switch (key) { + case PROBLEM_PLUGINSNOTLOADED: { + return tr("Some plugins could not be loaded"); + } break; + default: { + return tr("Description missing"); + } break; + } +} + +QString PluginContainer::fullDescription(unsigned int key) const +{ + switch (key) { + case PROBLEM_PLUGINSNOTLOADED: { + QString result = + tr("The following plugins could not be loaded. The reason may be missing " + "dependencies (i.e. python) or an outdated version:") + + "
            "; + for (const QString& plugin : m_FailedPlugins) { + result += "
          • " + plugin + "
          • "; + } + result += "
              "; + return result; + } break; + default: { + return tr("Description missing"); + } break; + } +} + +bool PluginContainer::hasGuidedFix(unsigned int) const +{ + return false; +} + +void PluginContainer::startGuidedFix(unsigned int) const {} diff --git a/src/plugincontainer.h b/src/plugincontainer.h index 2ab9a4b74..d5ceca723 100644 --- a/src/plugincontainer.h +++ b/src/plugincontainer.h @@ -1,498 +1,495 @@ -#ifndef PLUGINCONTAINER_H -#define PLUGINCONTAINER_H - -#include "previewgenerator.h" - -class OrganizerCore; -class IUserInterface; - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#ifndef Q_MOC_RUN -#include -#include -#include -#endif // Q_MOC_RUN -#include -#include - - -class OrganizerProxy; - -/** - * @brief Class that wrap multiple requirements for a plugin together. THis - * class owns the requirements. - */ -class PluginRequirements { -public: - - /** - * @return true if the plugin can be enabled (all requirements are met). - */ - bool canEnable() const; - - /** - * @return true if this is a core plugin, i.e. a plugin that should not be - * manually enabled or disabled by the user. - */ - bool isCorePlugin() const; - - /** - * @return true if this plugin has requirements (satisfied or not). - */ - bool hasRequirements() const; - - /** - * @return the proxy that created this plugin, if any. - */ - MOBase::IPluginProxy* proxy() const; - - /** - * @return the list of plugins this plugin proxies (if it's a proxy plugin). - */ - std::vector proxied() const; - - /** - * @return the master of this plugin, if any. - */ - MOBase::IPlugin* master() const; - - /** - * @return the plugins this plugin is master of. - */ - std::vector children() const; - - /** - * @return the list of problems to be resolved before enabling the plugin. - */ - std::vector problems() const; - - /** - * @return the name of the games (gameName()) this plugin can be used with, or an empty - * list if this plugin does not require particular games. - */ - QStringList requiredGames() const; - - /** - * @return the list of plugins currently enabled that would have to be disabled - * if this plugin was disabled. - */ - std::vector requiredFor() const; - -private: - - // The list of "Core" plugins. - static const std::set s_CorePlugins; - - // Accumulator version for requiredFor() to avoid infinite recursion. - void requiredFor(std::vector& required, std::set& visited) const; - - // Retrieve the requirements from the underlying plugin, take ownership on them - // and store them. We cannot do this in the constructor because we want to have a - // constructed object before calling init(). - void fetchRequirements(); - - // Set the master for this plugin. This is required to "fake" masters for proxied plugins. - void setMaster(MOBase::IPlugin* master); - - friend class OrganizerCore; - friend class PluginContainer; - - PluginContainer* m_PluginContainer; - MOBase::IPlugin* m_Plugin; - MOBase::IPluginProxy* m_PluginProxy; - MOBase::IPlugin* m_Master; - std::vector> m_Requirements; - OrganizerProxy* m_Organizer; - std::vector m_RequiredFor; - - PluginRequirements( - PluginContainer* pluginContainer, MOBase::IPlugin* plugin, - OrganizerProxy* proxy, MOBase::IPluginProxy* pluginProxy); - -}; - - -/** - * - */ -class PluginContainer : public QObject, public MOBase::IPluginDiagnose -{ - - Q_OBJECT - Q_INTERFACES(MOBase::IPluginDiagnose) - -private: - - using PluginMap = boost::fusion::map< - boost::fusion::pair>, - boost::fusion::pair>, - boost::fusion::pair>, - boost::fusion::pair>, - boost::fusion::pair>, - boost::fusion::pair>, - boost::fusion::pair>, - boost::fusion::pair>, - boost::fusion::pair>, - boost::fusion::pair> - >; - - using AccessPluginMap = boost::fusion::map< - boost::fusion::pair>, - boost::fusion::pair>, - boost::fusion::pair> - >; - - static const unsigned int PROBLEM_PLUGINSNOTLOADED = 1; - - /** - * This typedefs defines the order of plugin interface. This is increasing order of - * importance". - * - * @note IPlugin is the less important interface, followed by IPluginDiagnose and - * IPluginFileMapper as those are usually implemented together with another interface. - * Other interfaces are in a alphabetical order since it is unlikely a plugin will - * implement multiple ones. - */ - using PluginTypeOrder = - boost::mp11::mp_transform< - std::add_pointer_t, - boost::mp11::mp_list< - MOBase::IPluginGame, MOBase::IPluginInstaller, MOBase::IPluginModPage, MOBase::IPluginPreview, - MOBase::IPluginProxy, MOBase::IPluginTool, MOBase::IPluginDiagnose, MOBase::IPluginFileMapper, - MOBase::IPlugin - > - >; - - static_assert( - boost::mp11::mp_size::value == boost::mp11::mp_size::value - 1); - -public: - - /** - * @brief Retrieved the (localized) names of the various plugin interfaces. - * - * @return the (localized) names of the various plugin interfaces. - */ - static QStringList pluginInterfaces(); - -public: - - PluginContainer(OrganizerCore* organizer); - virtual ~PluginContainer(); - - /** - * @brief Start the plugins. - * - * This function should not be called before MO2 is ready and plugins can be - * started, and will do the following: - * - connect the callbacks of the plugins, - * - set the parent widget for plugins that can have one, - * - notify plugins that MO2 has been started, including: - * - triggering a call to the "profile changed" callback for the initial profile, - * - triggering a call to the "user interface initialized" callback. - * - * @param userInterface The main user interface to use for the plugins. - */ - void startPlugins(IUserInterface* userInterface); - - /** - * @brief Load, unload or reload the plugin at the given path. - * - */ - void loadPlugin(QString const& filepath); - void unloadPlugin(QString const& filepath); - void reloadPlugin(QString const& filepath); - - /** - * @brief Load all plugins. - * - */ - void loadPlugins(); - - /** - * @brief Retrieve the list of plugins of the given type. - * - * @return the list of plugins of the specified type. - * - * @tparam T The type of plugin to retrieve. - */ - template - const std::vector &plugins() const { - typename boost::fusion::result_of::at_key::type temp = boost::fusion::at_key(m_Plugins); - return temp; - } - - /** - * @brief Check if a plugin implement a given interface. - * - * @param plugin The plugin to check. - * - * @return true if the plugin implements the interface, false otherwise. - * - * @tparam The interface type. - */ - template - bool implementInterface(MOBase::IPlugin* plugin) const { - // We need a QObject to be able to qobject_cast<> to the plugin types: - QObject* oPlugin = as_qobject(plugin); - - if (!oPlugin) { - return false; - } - - return qobject_cast(oPlugin); - } - - /** - * @brief Retrieve a plugin from its name or a corresponding non-IPlugin - * interface. - * - * @param t Name of the plugin to retrieve, or non-IPlugin interface. - * - * @return the corresponding plugin, or a null pointer. - * - * @note It is possible to have multiple plugins for the same name when - * dealing with proxied plugins (e.g. Python), in which case the - * most important one will be returned, as specified in PluginTypeOrder. - */ - MOBase::IPlugin* plugin(QString const& pluginName) const; - MOBase::IPlugin* plugin(MOBase::IPluginDiagnose* diagnose) const; - MOBase::IPlugin* plugin(MOBase::IPluginFileMapper* mapper) const; - - /** - * @brief Find the game plugin corresponding to the given name. - * - * @param name The name of the game to find a plugin for (as returned by - * IPluginGame::gameName()). - * - * @return the game plugin for the given name, or a null pointer if no - * plugin exists for this game. - */ - MOBase::IPluginGame* game(const QString& name) const; - - /** - * @return the IPlugin interface to the currently managed game. - */ - MOBase::IPlugin* managedGame() const; - - /** - * @brief Check if the given plugin is enabled. - * - * @param plugin The plugin to check. - * - * @return true if the plugin is enabled, false otherwise. - */ - bool isEnabled(MOBase::IPlugin* plugin) const; - - // These are friendly methods that called isEnabled(plugin(arg)). - bool isEnabled(QString const& pluginName) const; - bool isEnabled(MOBase::IPluginDiagnose* diagnose) const; - bool isEnabled(MOBase::IPluginFileMapper* mapper) const; - - /** - * @brief Enable or disable a plugin. - * - * @param plugin The plugin to enable or disable. - * @param enable true to enable, false to disable. - * @param dependencies If true and enable is false, dependencies will also - * be disabled (see PluginRequirements::requiredFor). - */ - void setEnabled(MOBase::IPlugin* plugin, bool enable, bool dependencies = true); - - /** - * @brief Retrieve the requirements for the given plugin. - * - * @param plugin The plugin to retrieve the requirements for. - * - * @return the requirements (as proxy) for the given plugin. - */ - const PluginRequirements& requirements(MOBase::IPlugin* plugin) const; - - /** - * @brief Retrieved the (localized) names of interfaces implemented by the given - * plugin. - * - * @param plugin The plugin to retrieve interface for. - * - * @return the (localized) names of interfaces implemented by this plugin. - */ - QStringList implementedInterfaces(MOBase::IPlugin* plugin) const; - - /** - * @brief Return the (localized) name of the most important interface implemented by - * the given plugin. - * - * The order of interfaces is defined in X. - * - * @param plugin The plugin to retrieve the interface for. - * - * @return the (localized) name of the most important interface implemented by this plugin. - */ - QString topImplementedInterface(MOBase::IPlugin* plugin) const; - - /** - * @return the preview generator. - */ - const PreviewGenerator &previewGenerator() const; - - /** - * @return the list of plugin file names, including proxied plugins. - */ - QStringList pluginFileNames() const; - -public: // IPluginDiagnose interface - - virtual std::vector activeProblems() const; - virtual QString shortDescription(unsigned int key) const; - virtual QString fullDescription(unsigned int key) const; - virtual bool hasGuidedFix(unsigned int key) const; - virtual void startGuidedFix(unsigned int key) const; - -signals: - - /** - * @brief Emitted when plugins are enabled or disabled. - */ - void pluginEnabled(MOBase::IPlugin*); - void pluginDisabled(MOBase::IPlugin*); - - /** - * @brief Emitted when plugins are registered or unregistered. - */ - void pluginRegistered(MOBase::IPlugin*); - void pluginUnregistered(MOBase::IPlugin*); - - void diagnosisUpdate(); - -private: - - friend class PluginRequirements; - - // Unload all the plugins. - void unloadPlugins(); - - // Retrieve the organizer proxy for the given plugin. - OrganizerProxy* organizerProxy(MOBase::IPlugin* plugin) const; - - // Retrieve the proxy plugin that instantiated the given plugin, or a null pointer - // if the plugin was not instantiated by a proxy. - MOBase::IPluginProxy* pluginProxy(MOBase::IPlugin* plugin) const; - - // Retrieve the path to the file or folder corresponding to the plugin. - QString filepath(MOBase::IPlugin* plugin) const; - - // Load plugins from the given filepath using the given proxy. - std::vector loadProxied(const QString& filepath, MOBase::IPluginProxy* proxy); - - // Load the Qt plugin from the given file. - QObject* loadQtPlugin(const QString& filepath); - - // check if a plugin is folder containing a Qt plugin, it is, return the path to the - // DLL containing the plugin in the folder, otherwise return an empty optional - // - // a Qt plugin folder is a folder with a DLL containing a library (not in a subdirectory), - // if multiple plugins are present, only the first one is returned - // - // extra DLLs are ignored by Qt so can be present in the folder - // - std::optional isQtPluginFolder(const QString& filepath) const; - - // See startPlugins for more details. This is simply an intermediate function - // that can be used when loading plugins after initialization. This uses the - // user interface in m_UserInterface. - void startPluginsImpl(const std::vector& plugins) const; - - /** - * @brief Unload the given plugin. - * - * This function is not public because it's kind of dangerous trying to unload - * plugin directly since some plugins are linked together. - * - * @param plugin The plugin to unload/unregister. - * @param object The QObject corresponding to the plugin. - */ - void unloadPlugin(MOBase::IPlugin* plugin, QObject* object); - - /** - * @brief Retrieved the (localized) names of interfaces implemented by the given - * plugin. - * - * @param plugin The plugin to retrieve interface for. - * - * @return the (localized) names of interfaces implemented by this plugin. - * - * @note This function can be used to get implemented interfaces before registering - * a plugin. - */ - QStringList implementedInterfaces(QObject* plugin) const; - - /** - * @brief Check if a plugin implements a "better" interface than another - * one, as specified by PluginTypeOrder. - * - * @param lhs, rhs The plugin to compare. - * - * @return true if the left plugin implements a better interface than the right - * one, false otherwise (or if both implements the same interface). - */ - bool isBetterInterface(QObject* lhs, QObject* rhs) const; - - /** - * @brief Find the QObject* corresponding to the given plugin. - * - * @param plugin The plugin to find the QObject* for. - * - * @return a QObject* for the given plugin. - */ - QObject* as_qobject(MOBase::IPlugin* plugin) const; - - /** - * @brief Initialize a plugin. - * - * @param plugin The plugin to initialize. - * @param proxy The proxy that created this plugin (can be null). - * @param skipInit If true, IPlugin::init() will not be called, regardless - * of the state of the container. - * - * @return true if the plugin was initialized correctly, false otherwise. - */ - bool initPlugin(MOBase::IPlugin *plugin, MOBase::IPluginProxy* proxy, bool skipInit); - - void registerGame(MOBase::IPluginGame *game); - void unregisterGame(MOBase::IPluginGame* game); - - MOBase::IPlugin* registerPlugin(QObject *pluginObj, const QString &fileName, MOBase::IPluginProxy *proxy); - - // Core organizer, can be null (e.g. on first MO2 startup). - OrganizerCore *m_Organizer; - - // Main user interface, can be null until MW has been initialized. - IUserInterface *m_UserInterface; - - PluginMap m_Plugins; - - // This maps allow access to IPlugin* from name or diagnose/mapper object. - AccessPluginMap m_AccessPlugins; - - std::map m_Requirements; - - std::map m_SupportedGames; - QStringList m_FailedPlugins; - std::vector m_PluginLoaders; - - PreviewGenerator m_PreviewGenerator; - - QFile m_PluginsCheck; -}; - - -#endif // PLUGINCONTAINER_H +#ifndef PLUGINCONTAINER_H +#define PLUGINCONTAINER_H + +#include "previewgenerator.h" + +class OrganizerCore; +class IUserInterface; + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifndef Q_MOC_RUN +#include +#include +#include +#endif // Q_MOC_RUN +#include +#include + +class OrganizerProxy; + +/** + * @brief Class that wrap multiple requirements for a plugin together. THis + * class owns the requirements. + */ +class PluginRequirements +{ +public: + /** + * @return true if the plugin can be enabled (all requirements are met). + */ + bool canEnable() const; + + /** + * @return true if this is a core plugin, i.e. a plugin that should not be + * manually enabled or disabled by the user. + */ + bool isCorePlugin() const; + + /** + * @return true if this plugin has requirements (satisfied or not). + */ + bool hasRequirements() const; + + /** + * @return the proxy that created this plugin, if any. + */ + MOBase::IPluginProxy* proxy() const; + + /** + * @return the list of plugins this plugin proxies (if it's a proxy plugin). + */ + std::vector proxied() const; + + /** + * @return the master of this plugin, if any. + */ + MOBase::IPlugin* master() const; + + /** + * @return the plugins this plugin is master of. + */ + std::vector children() const; + + /** + * @return the list of problems to be resolved before enabling the plugin. + */ + std::vector problems() const; + + /** + * @return the name of the games (gameName()) this plugin can be used with, or an + * empty list if this plugin does not require particular games. + */ + QStringList requiredGames() const; + + /** + * @return the list of plugins currently enabled that would have to be disabled + * if this plugin was disabled. + */ + std::vector requiredFor() const; + +private: + // The list of "Core" plugins. + static const std::set s_CorePlugins; + + // Accumulator version for requiredFor() to avoid infinite recursion. + void requiredFor(std::vector& required, + std::set& visited) const; + + // Retrieve the requirements from the underlying plugin, take ownership on them + // and store them. We cannot do this in the constructor because we want to have a + // constructed object before calling init(). + void fetchRequirements(); + + // Set the master for this plugin. This is required to "fake" masters for proxied + // plugins. + void setMaster(MOBase::IPlugin* master); + + friend class OrganizerCore; + friend class PluginContainer; + + PluginContainer* m_PluginContainer; + MOBase::IPlugin* m_Plugin; + MOBase::IPluginProxy* m_PluginProxy; + MOBase::IPlugin* m_Master; + std::vector> m_Requirements; + OrganizerProxy* m_Organizer; + std::vector m_RequiredFor; + + PluginRequirements(PluginContainer* pluginContainer, MOBase::IPlugin* plugin, + OrganizerProxy* proxy, MOBase::IPluginProxy* pluginProxy); +}; + +/** + * + */ +class PluginContainer : public QObject, public MOBase::IPluginDiagnose +{ + + Q_OBJECT + Q_INTERFACES(MOBase::IPluginDiagnose) + +private: + using PluginMap = boost::fusion::map< + boost::fusion::pair>, + boost::fusion::pair>, + boost::fusion::pair>, + boost::fusion::pair>, + boost::fusion::pair>, + boost::fusion::pair>, + boost::fusion::pair>, + boost::fusion::pair>, + boost::fusion::pair>, + boost::fusion::pair>>; + + using AccessPluginMap = boost::fusion::map< + boost::fusion::pair>, + boost::fusion::pair>, + boost::fusion::pair>>; + + static const unsigned int PROBLEM_PLUGINSNOTLOADED = 1; + + /** + * This typedefs defines the order of plugin interface. This is increasing order of + * importance". + * + * @note IPlugin is the less important interface, followed by IPluginDiagnose and + * IPluginFileMapper as those are usually implemented together with another + * interface. Other interfaces are in a alphabetical order since it is unlikely a + * plugin will implement multiple ones. + */ + using PluginTypeOrder = boost::mp11::mp_transform< + std::add_pointer_t, + boost::mp11::mp_list< + MOBase::IPluginGame, MOBase::IPluginInstaller, MOBase::IPluginModPage, + MOBase::IPluginPreview, MOBase::IPluginProxy, MOBase::IPluginTool, + MOBase::IPluginDiagnose, MOBase::IPluginFileMapper, MOBase::IPlugin>>; + + static_assert(boost::mp11::mp_size::value == + boost::mp11::mp_size::value - 1); + +public: + /** + * @brief Retrieved the (localized) names of the various plugin interfaces. + * + * @return the (localized) names of the various plugin interfaces. + */ + static QStringList pluginInterfaces(); + +public: + PluginContainer(OrganizerCore* organizer); + virtual ~PluginContainer(); + + /** + * @brief Start the plugins. + * + * This function should not be called before MO2 is ready and plugins can be + * started, and will do the following: + * - connect the callbacks of the plugins, + * - set the parent widget for plugins that can have one, + * - notify plugins that MO2 has been started, including: + * - triggering a call to the "profile changed" callback for the initial profile, + * - triggering a call to the "user interface initialized" callback. + * + * @param userInterface The main user interface to use for the plugins. + */ + void startPlugins(IUserInterface* userInterface); + + /** + * @brief Load, unload or reload the plugin at the given path. + * + */ + void loadPlugin(QString const& filepath); + void unloadPlugin(QString const& filepath); + void reloadPlugin(QString const& filepath); + + /** + * @brief Load all plugins. + * + */ + void loadPlugins(); + + /** + * @brief Retrieve the list of plugins of the given type. + * + * @return the list of plugins of the specified type. + * + * @tparam T The type of plugin to retrieve. + */ + template + const std::vector& plugins() const + { + typename boost::fusion::result_of::at_key::type temp = + boost::fusion::at_key(m_Plugins); + return temp; + } + + /** + * @brief Check if a plugin implement a given interface. + * + * @param plugin The plugin to check. + * + * @return true if the plugin implements the interface, false otherwise. + * + * @tparam The interface type. + */ + template + bool implementInterface(MOBase::IPlugin* plugin) const + { + // We need a QObject to be able to qobject_cast<> to the plugin types: + QObject* oPlugin = as_qobject(plugin); + + if (!oPlugin) { + return false; + } + + return qobject_cast(oPlugin); + } + + /** + * @brief Retrieve a plugin from its name or a corresponding non-IPlugin + * interface. + * + * @param t Name of the plugin to retrieve, or non-IPlugin interface. + * + * @return the corresponding plugin, or a null pointer. + * + * @note It is possible to have multiple plugins for the same name when + * dealing with proxied plugins (e.g. Python), in which case the + * most important one will be returned, as specified in PluginTypeOrder. + */ + MOBase::IPlugin* plugin(QString const& pluginName) const; + MOBase::IPlugin* plugin(MOBase::IPluginDiagnose* diagnose) const; + MOBase::IPlugin* plugin(MOBase::IPluginFileMapper* mapper) const; + + /** + * @brief Find the game plugin corresponding to the given name. + * + * @param name The name of the game to find a plugin for (as returned by + * IPluginGame::gameName()). + * + * @return the game plugin for the given name, or a null pointer if no + * plugin exists for this game. + */ + MOBase::IPluginGame* game(const QString& name) const; + + /** + * @return the IPlugin interface to the currently managed game. + */ + MOBase::IPlugin* managedGame() const; + + /** + * @brief Check if the given plugin is enabled. + * + * @param plugin The plugin to check. + * + * @return true if the plugin is enabled, false otherwise. + */ + bool isEnabled(MOBase::IPlugin* plugin) const; + + // These are friendly methods that called isEnabled(plugin(arg)). + bool isEnabled(QString const& pluginName) const; + bool isEnabled(MOBase::IPluginDiagnose* diagnose) const; + bool isEnabled(MOBase::IPluginFileMapper* mapper) const; + + /** + * @brief Enable or disable a plugin. + * + * @param plugin The plugin to enable or disable. + * @param enable true to enable, false to disable. + * @param dependencies If true and enable is false, dependencies will also + * be disabled (see PluginRequirements::requiredFor). + */ + void setEnabled(MOBase::IPlugin* plugin, bool enable, bool dependencies = true); + + /** + * @brief Retrieve the requirements for the given plugin. + * + * @param plugin The plugin to retrieve the requirements for. + * + * @return the requirements (as proxy) for the given plugin. + */ + const PluginRequirements& requirements(MOBase::IPlugin* plugin) const; + + /** + * @brief Retrieved the (localized) names of interfaces implemented by the given + * plugin. + * + * @param plugin The plugin to retrieve interface for. + * + * @return the (localized) names of interfaces implemented by this plugin. + */ + QStringList implementedInterfaces(MOBase::IPlugin* plugin) const; + + /** + * @brief Return the (localized) name of the most important interface implemented by + * the given plugin. + * + * The order of interfaces is defined in X. + * + * @param plugin The plugin to retrieve the interface for. + * + * @return the (localized) name of the most important interface implemented by this + * plugin. + */ + QString topImplementedInterface(MOBase::IPlugin* plugin) const; + + /** + * @return the preview generator. + */ + const PreviewGenerator& previewGenerator() const; + + /** + * @return the list of plugin file names, including proxied plugins. + */ + QStringList pluginFileNames() const; + +public: // IPluginDiagnose interface + virtual std::vector activeProblems() const; + virtual QString shortDescription(unsigned int key) const; + virtual QString fullDescription(unsigned int key) const; + virtual bool hasGuidedFix(unsigned int key) const; + virtual void startGuidedFix(unsigned int key) const; + +signals: + + /** + * @brief Emitted when plugins are enabled or disabled. + */ + void pluginEnabled(MOBase::IPlugin*); + void pluginDisabled(MOBase::IPlugin*); + + /** + * @brief Emitted when plugins are registered or unregistered. + */ + void pluginRegistered(MOBase::IPlugin*); + void pluginUnregistered(MOBase::IPlugin*); + + void diagnosisUpdate(); + +private: + friend class PluginRequirements; + + // Unload all the plugins. + void unloadPlugins(); + + // Retrieve the organizer proxy for the given plugin. + OrganizerProxy* organizerProxy(MOBase::IPlugin* plugin) const; + + // Retrieve the proxy plugin that instantiated the given plugin, or a null pointer + // if the plugin was not instantiated by a proxy. + MOBase::IPluginProxy* pluginProxy(MOBase::IPlugin* plugin) const; + + // Retrieve the path to the file or folder corresponding to the plugin. + QString filepath(MOBase::IPlugin* plugin) const; + + // Load plugins from the given filepath using the given proxy. + std::vector loadProxied(const QString& filepath, + MOBase::IPluginProxy* proxy); + + // Load the Qt plugin from the given file. + QObject* loadQtPlugin(const QString& filepath); + + // check if a plugin is folder containing a Qt plugin, it is, return the path to the + // DLL containing the plugin in the folder, otherwise return an empty optional + // + // a Qt plugin folder is a folder with a DLL containing a library (not in a + // subdirectory), if multiple plugins are present, only the first one is returned + // + // extra DLLs are ignored by Qt so can be present in the folder + // + std::optional isQtPluginFolder(const QString& filepath) const; + + // See startPlugins for more details. This is simply an intermediate function + // that can be used when loading plugins after initialization. This uses the + // user interface in m_UserInterface. + void startPluginsImpl(const std::vector& plugins) const; + + /** + * @brief Unload the given plugin. + * + * This function is not public because it's kind of dangerous trying to unload + * plugin directly since some plugins are linked together. + * + * @param plugin The plugin to unload/unregister. + * @param object The QObject corresponding to the plugin. + */ + void unloadPlugin(MOBase::IPlugin* plugin, QObject* object); + + /** + * @brief Retrieved the (localized) names of interfaces implemented by the given + * plugin. + * + * @param plugin The plugin to retrieve interface for. + * + * @return the (localized) names of interfaces implemented by this plugin. + * + * @note This function can be used to get implemented interfaces before registering + * a plugin. + */ + QStringList implementedInterfaces(QObject* plugin) const; + + /** + * @brief Check if a plugin implements a "better" interface than another + * one, as specified by PluginTypeOrder. + * + * @param lhs, rhs The plugin to compare. + * + * @return true if the left plugin implements a better interface than the right + * one, false otherwise (or if both implements the same interface). + */ + bool isBetterInterface(QObject* lhs, QObject* rhs) const; + + /** + * @brief Find the QObject* corresponding to the given plugin. + * + * @param plugin The plugin to find the QObject* for. + * + * @return a QObject* for the given plugin. + */ + QObject* as_qobject(MOBase::IPlugin* plugin) const; + + /** + * @brief Initialize a plugin. + * + * @param plugin The plugin to initialize. + * @param proxy The proxy that created this plugin (can be null). + * @param skipInit If true, IPlugin::init() will not be called, regardless + * of the state of the container. + * + * @return true if the plugin was initialized correctly, false otherwise. + */ + bool initPlugin(MOBase::IPlugin* plugin, MOBase::IPluginProxy* proxy, bool skipInit); + + void registerGame(MOBase::IPluginGame* game); + void unregisterGame(MOBase::IPluginGame* game); + + MOBase::IPlugin* registerPlugin(QObject* pluginObj, const QString& fileName, + MOBase::IPluginProxy* proxy); + + // Core organizer, can be null (e.g. on first MO2 startup). + OrganizerCore* m_Organizer; + + // Main user interface, can be null until MW has been initialized. + IUserInterface* m_UserInterface; + + PluginMap m_Plugins; + + // This maps allow access to IPlugin* from name or diagnose/mapper object. + AccessPluginMap m_AccessPlugins; + + std::map m_Requirements; + + std::map m_SupportedGames; + QStringList m_FailedPlugins; + std::vector m_PluginLoaders; + + PreviewGenerator m_PreviewGenerator; + + QFile m_PluginsCheck; +}; + +#endif // PLUGINCONTAINER_H diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp index 28e0af515..6e54b46d7 100644 --- a/src/pluginlist.cpp +++ b/src/pluginlist.cpp @@ -1,1721 +1,1739 @@ -/* -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 "pluginlist.h" -#include "settings.h" -#include "scopeguard.h" -#include "modinfo.h" -#include "modlist.h" -#include "viewmarkingscrollbar.h" -#include "shared/directoryentry.h" -#include "shared/filesorigin.h" -#include "shared/fileentry.h" - -#include -#include -#include -#include -#include "shared/windows_error.h" -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include "organizercore.h" - -using namespace MOBase; -using namespace MOShared; - - -static QString TruncateString(const QString& text) -{ - QString new_text = text; - - if (new_text.length() > 1024) { - new_text.truncate(1024); - new_text += "..."; - } - - return new_text; -} - - -PluginList::PluginList(OrganizerCore& organizer) - : QAbstractItemModel(&organizer) - , m_Organizer(organizer) - , m_FontMetrics(QFont()) -{ - connect(this, SIGNAL(writePluginsList()), this, SLOT(generatePluginIndexes())); - m_LastCheck.start(); -} - -PluginList::~PluginList() -{ - m_Refreshed.disconnect_all_slots(); - m_PluginMoved.disconnect_all_slots(); - m_PluginStateChanged.disconnect_all_slots(); -} - -QString PluginList::getColumnName(int column) -{ - switch (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"); - } -} - - -QString PluginList::getColumnToolTip(int column) -{ - switch (column) { - case COL_NAME: return tr("Name of the plugin"); - case COL_FLAGS: return tr("Emblems to highlight things that might require attention."); - case COL_PRIORITY: return tr("Load priority of plugins. The higher, the more \"important\" it is and thus " - "overwrites data from plugins with lower priority."); - case COL_MODINDEX: return tr("Determines the formids of objects originating from this mods."); - default: return tr("unknown"); - } -} - -void PluginList::highlightPlugins( - const std::vector& modIndices, - const MOShared::DirectoryEntry &directoryEntry) -{ - auto* profile = m_Organizer.currentProfile(); - - for (auto &esp : m_ESPs) { - esp.modSelected = false; - } - - for (auto& modIndex : modIndices) { - ModInfo::Ptr selectedMod = ModInfo::getByIndex(modIndex); - if (!selectedMod.isNull() && profile->modEnabled(modIndex)) { - QDir dir(selectedMod->absolutePath()); - QStringList plugins = dir.entryList(QStringList() << "*.esp" << "*.esm" << "*.esl"); - const MOShared::FilesOrigin& origin = directoryEntry.getOriginByName(selectedMod->internalName().toStdWString()); - if (plugins.size() > 0) { - for (auto plugin : plugins) { - MOShared::FileEntryPtr file = directoryEntry.findFile(plugin.toStdWString()); - if (file && file->getOrigin() != origin.getID()) { - const auto alternatives = file->getAlternatives(); - if (std::find_if(alternatives.begin(), alternatives.end(), [&](const FileAlternative& element) { return element.originID() == origin.getID(); }) == alternatives.end()) - continue; - } - std::map::iterator iter = m_ESPsByName.find(plugin); - if (iter != m_ESPsByName.end()) { - m_ESPs[iter->second].modSelected = true; - } - } - } - } - } - - emit dataChanged(this->index(0, 0), this->index(static_cast(m_ESPs.size()) - 1, this->columnCount() - 1)); -} - -void PluginList::refresh(const QString &profileName - , const DirectoryEntry &baseDirectory - , const QString &lockedOrderFile - , bool force) -{ - TimeThis tt("PluginList::refresh()"); - - if (force) { - m_ESPs.clear(); - m_ESPsByName.clear(); - m_ESPsByPriority.clear(); - } - - ChangeBracket layoutChange(this); - - QStringList primaryPlugins = m_GamePlugin->primaryPlugins(); - GamePlugins *gamePlugins = m_GamePlugin->feature(); - const bool lightPluginsAreSupported = gamePlugins ? gamePlugins->lightPluginsAreSupported() : false; - - m_CurrentProfile = profileName; - - QStringList availablePlugins; - - std::vector files = baseDirectory.getFiles(); - for (FileEntryPtr current : files) { - if (current.get() == nullptr) { - continue; - } - QString filename = ToQString(current->getName()); - - if (filename.endsWith(".esp", Qt::CaseInsensitive) || - filename.endsWith(".esm", Qt::CaseInsensitive) || - filename.endsWith(".esl", Qt::CaseInsensitive)) { - - availablePlugins.append(filename); - - if (m_ESPsByName.find(filename) != m_ESPsByName.end()) { - continue; - } - - bool forceEnabled = Settings::instance().game().forceEnableCoreFiles() && - primaryPlugins.contains(filename, Qt::CaseInsensitive); - - bool archive = false; - try { - FilesOrigin &origin = baseDirectory.getOriginByID(current->getOrigin(archive)); - - //name without extension - QString baseName = QFileInfo(filename).completeBaseName(); - - QString iniPath = baseName + ".ini"; - bool hasIni = baseDirectory.findFile(ToWString(iniPath)).get() != nullptr; - std::set loadedArchives; - QString candidateName; - for (FileEntryPtr archiveCandidate : files) { - candidateName = ToQString(archiveCandidate->getName()); - if (candidateName.startsWith(baseName, Qt::CaseInsensitive) && - (candidateName.endsWith(".bsa", Qt::CaseInsensitive) || - candidateName.endsWith(".ba2", Qt::CaseInsensitive))) { - loadedArchives.insert(candidateName); - } - } - - QString originName = ToQString(origin.getName()); - unsigned int modIndex = ModInfo::getIndex(originName); - if (modIndex != UINT_MAX) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); - originName = modInfo->name(); - } - - m_ESPs.push_back(ESPInfo(filename, forceEnabled, originName, ToQString(current->getFullPath()), hasIni, loadedArchives, lightPluginsAreSupported)); - m_ESPs.rbegin()->priority = -1; - } catch (const std::exception &e) { - reportError(tr("failed to update esp info for file %1 (source id: %2), error: %3").arg(filename).arg(current->getOrigin(archive)).arg(e.what())); - } - } - } - - for (const auto &espName : m_ESPsByName) { - if (!availablePlugins.contains(espName.first, Qt::CaseInsensitive)) { - m_ESPs[espName.second].name = ""; - } - } - - m_ESPs.erase(std::remove_if(m_ESPs.begin(), m_ESPs.end(), - [](const ESPInfo &info) -> bool { - return info.name.isEmpty(); - }), - m_ESPs.end()); - - fixPriorities(); - - // functions in GamePlugins will use the IPluginList interface of this, so - // indices need to work. priority will be off however - updateIndices(); - - if (gamePlugins) { - gamePlugins->readPluginLists(m_Organizer.managedGameOrganizer()->pluginList()); - } - - fixPrimaryPlugins(); - fixPluginRelationships(); - - testMasters(); - - updateIndices(); - - readLockedOrderFrom(lockedOrderFile); - - layoutChange.finish(); - - refreshLoadOrder(); - emit dataChanged(this->index(0, 0), - this->index(static_cast(m_ESPs.size()), columnCount())); - - m_Refreshed(); -} - -void PluginList::fixPrimaryPlugins() -{ - if (!m_Organizer.settings().game().forceEnableCoreFiles()) { - return; - } - - // This function ensures that the primary plugins are first and in the correct order - QStringList primaryPlugins = m_Organizer.managedGame()->primaryPlugins(); - int prio = 0; - bool somethingChanged = false; - for (QString plugin : primaryPlugins) { - std::map::iterator iter = m_ESPsByName.find(plugin); - // Plugin is present? - if (iter != m_ESPsByName.end()) { - if (prio != m_ESPs[iter->second].priority) { - // Priority is wrong! Fix it! - int newPrio = prio; - setPluginPriority(iter->second, newPrio, true /* isForced */); - somethingChanged = true; - } - prio++; - } - } - - if (somethingChanged) { - writePluginsList(); - } -} - -void PluginList::fixPluginRelationships() -{ - TimeThis timer("PluginList::fixPluginRelationships"); - - // Count the types of plugins - int masterCount = 0; - for (auto plugin : m_ESPs) { - if (plugin.hasLightExtension || - plugin.hasMasterExtension || - plugin.isMasterFlagged) { - masterCount++; - } - } - - // Ensure masters are up top and normal plugins are down below - for (int i = 0; i < m_ESPs.size(); i++) { - ESPInfo& plugin = m_ESPs[i]; - if (plugin.hasLightExtension || - plugin.hasMasterExtension || - plugin.isMasterFlagged) { - if (plugin.priority > masterCount) { - int newPriority = masterCount + 1; - setPluginPriority(i, newPriority); - } - } - else { - if (plugin.priority < masterCount) { - int newPriority = masterCount + 1; - setPluginPriority(i, newPriority); - } - } - } - - // Ensure master/child relationships are observed - for (int i = 0; i < m_ESPs.size(); i++) { - ESPInfo& plugin = m_ESPs[i]; - int newPriority = plugin.priority; - for (auto master : plugin.masters) { - auto iter = m_ESPsByName.find(master); - if (iter != m_ESPsByName.end()) { - newPriority = std::max(newPriority, m_ESPs[iter->second].priority); - } - } - if (newPriority != plugin.priority) { - setPluginPriority(i, newPriority); - } - } -} - -void PluginList::fixPriorities() -{ - std::vector> espPrios; - - for (int i = 0; i < m_ESPs.size(); ++i) { - int prio = m_ESPs[i].priority; - if (prio == -1) { - prio = INT_MAX; - } - espPrios.push_back(std::make_pair(prio, i)); - } - - std::sort(espPrios.begin(), espPrios.end(), - [](const std::pair &lhs, const std::pair &rhs) { - return lhs.first < rhs.first; - }); - - for (int i = 0; i < espPrios.size(); ++i) { - m_ESPs[espPrios[i].second].priority = i; - } -} - -void PluginList::enableESP(const QString &name, bool enable) -{ - std::map::iterator iter = m_ESPsByName.find(name); - - if (iter != m_ESPsByName.end()) { - auto enabled = m_ESPs[iter->second].enabled; - m_ESPs[iter->second].enabled = - enable || m_ESPs[iter->second].forceEnabled; - - emit writePluginsList(); - if (enabled != m_ESPs[iter->second].enabled) { - pluginStatesChanged({ name }, state(name)); - } - } else { - reportError(tr("Plugin not found: %1").arg(qUtf8Printable(name))); - } -} - -int PluginList::findPluginByPriority(int priority) -{ - for (int i = 0; i < m_ESPs.size(); i++ ) { - if (m_ESPs[i].priority == priority) { - return i; - } - } - log::error("No plugin with priority {}", priority); - return -1; -} - -void PluginList::setEnabled(const QModelIndexList& indices, bool enabled) -{ - QStringList dirty; - for (auto& idx : indices) { - if (m_ESPs[idx.row()].enabled != enabled) { - m_ESPs[idx.row()].enabled = enabled; - dirty.append(m_ESPs[idx.row()].name); - } - } - if (!dirty.isEmpty()) { - emit writePluginsList(); - pluginStatesChanged(dirty, - enabled ? IPluginList::PluginState::STATE_ACTIVE : IPluginList::PluginState::STATE_INACTIVE); - } -} - -void PluginList::setEnabledAll(bool enabled) -{ - QStringList dirty; - for (ESPInfo &info : m_ESPs) { - if (info.enabled != enabled) { - info.enabled = enabled; - dirty.append(info.name); - } - } - if (!dirty.isEmpty()) { - emit writePluginsList(); - pluginStatesChanged(dirty, - enabled ? IPluginList::PluginState::STATE_ACTIVE : IPluginList::PluginState::STATE_INACTIVE); - } -} - -void PluginList::sendToPriority(const QModelIndexList& indices, int newPriority) -{ - std::vector pluginsToMove; - for (auto& idx : indices) { - if (!m_ESPs[idx.row()].forceEnabled) { - pluginsToMove.push_back(idx.row()); - } - } - if (pluginsToMove.size()) { - changePluginPriority(pluginsToMove, newPriority); - } -} - -void PluginList::shiftPluginsPriority(const QModelIndexList& indices, int offset) -{ - // retrieve the plugin index and sort them by priority to avoid issue - // when moving them - std::vector allIndex; - for (auto& idx : indices) { - allIndex.push_back(idx.row()); - } - std::sort(allIndex.begin(), allIndex.end(), [=](int lhs, int rhs) { - bool cmp = m_ESPs[lhs].priority < m_ESPs[rhs].priority; - return offset > 0 ? !cmp : cmp; - }); - - for (auto index : allIndex) { - int newPriority = m_ESPs[index].priority + offset; - if (newPriority >= 0 && newPriority < rowCount()) { - setPluginPriority(index, newPriority); - } - } - - refreshLoadOrder(); -} - -void PluginList::toggleState(const QModelIndexList& indices) -{ - QModelIndex minRow, maxRow; - for (auto& idx : indices) { - if (!minRow.isValid() || (idx.row() < minRow.row())) { - minRow = idx; - } - if (!maxRow.isValid() || (idx.row() > maxRow.row())) { - maxRow = idx; - } - int oldState = idx.data(Qt::CheckStateRole).toInt(); - setData(idx, oldState == Qt::Unchecked ? Qt::Checked : Qt::Unchecked, Qt::CheckStateRole); - } - - emit dataChanged(minRow, maxRow); -} - -bool PluginList::isEnabled(const QString &name) -{ - std::map::iterator iter = m_ESPsByName.find(name); - - if (iter != m_ESPsByName.end()) { - return m_ESPs[iter->second].enabled; - } else { - return false; - } -} - -void PluginList::clearInformation(const QString &name) -{ - std::map::iterator iter = m_ESPsByName.find(name); - - if (iter != m_ESPsByName.end()) { - m_AdditionalInfo[name].messages.clear(); - } -} - -void PluginList::clearAdditionalInformation() -{ - m_AdditionalInfo.clear(); -} - -void PluginList::addInformation(const QString &name, const QString &message) -{ - std::map::iterator iter = m_ESPsByName.find(name); - - if (iter != m_ESPsByName.end()) { - m_AdditionalInfo[name].messages.append(message); - } else { - log::warn("failed to associate message for \"{}\"", name); - } -} - -void PluginList::addLootReport(const QString& name, Loot::Plugin plugin) -{ - auto iter = m_ESPsByName.find(name); - - if (iter != m_ESPsByName.end()) { - m_AdditionalInfo[name].loot = std::move(plugin); - } else { - log::warn("failed to associate loot report for \"{}\"", name); - } -} - -bool PluginList::isEnabled(int index) -{ - return m_ESPs.at(index).enabled; -} - -void PluginList::readLockedOrderFrom(const QString &fileName) -{ - m_LockedOrder.clear(); - - QFile file(fileName); - if (!file.exists()) { - // no locked load order, that's ok - return; - } - - file.open(QIODevice::ReadOnly); - int lineNumber = 0; - while (!file.atEnd()) - { - QByteArray line = file.readLine(); - ++lineNumber; - - // Skip empty lines or commented out lines (#) - if ((line.size() <= 0) || (line.at(0) == '#')) - { - continue; - } - - QList fields = line.split('|'); - if (fields.count() != 2) - { - // Don't know how to parse this so run away - log::error("locked order file: invalid line #{}: {}", lineNumber, QString::fromUtf8(line).trimmed()); - continue; - } - - // Read the plugin name and priority - QString pluginName = QString::fromUtf8(fields.at(0)); - int priority = fields.at(1).trimmed().toInt(); - if (priority < 0) - { - // WTF do you mean a negative priority? - log::error("locked order file: invalid line #{}: {}", lineNumber, QString::fromUtf8(line).trimmed()); - continue; - } - - // Determine the index of the plugin - auto it = m_ESPsByName.find(pluginName); - if (it == m_ESPsByName.end()) - { - // Plugin does not exist in the current set of plugins - m_LockedOrder[pluginName] = priority; - continue; - } - int pluginIndex = it->second; - - // Do not allow locking forced plugins - if (m_ESPs[pluginIndex].forceEnabled) - { - continue; - } - - // If the priority is larger than the number of plugins, just keep it locked - if (priority >= m_ESPsByPriority.size()) - { - m_LockedOrder[pluginName] = priority; - continue; - } - - // These are some helper functions for figuring out what is already locked - auto findLocked = [&](const std::pair& a) { return a.second == priority; }; - auto alreadyLocked = [&](){ return std::find_if(m_LockedOrder.begin(), m_LockedOrder.end(), findLocked) != m_LockedOrder.end(); }; - - // See if we can just set the given priority - if (!m_ESPs[m_ESPsByPriority.at(priority)].forceEnabled && !alreadyLocked()) - { - m_LockedOrder[pluginName] = priority; - continue; - } - - // Find the next higher priority we can set the plugin to - while (++priority < m_ESPs.size()) - { - if (!m_ESPs[m_ESPsByPriority.at(priority)].forceEnabled && !alreadyLocked()) - { - m_LockedOrder[pluginName] = priority; - break; - } - } - - // See if we walked off the end of the plugin list - if (priority >= m_ESPs.size()) - { - // I guess go ahead and lock it here at the end of the list? - m_LockedOrder[pluginName] = priority; - continue; - } - } /* while (!file.atEnd()) */ - file.close(); -} - -void PluginList::writeLockedOrder(const QString &fileName) const -{ - SafeWriteFile file(fileName); - - 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.commit(); -} - -void PluginList::saveTo(const QString &lockedOrderFileName) const -{ - GamePlugins *gamePlugins = m_GamePlugin->feature(); - if (gamePlugins) { - gamePlugins->writePluginLists(m_Organizer.managedGameOrganizer()->pluginList()); - } - - writeLockedOrder(lockedOrderFileName); -} - - -bool PluginList::saveLoadOrder(DirectoryEntry &directoryStructure) -{ - if (m_GamePlugin->loadOrderMechanism() != IPluginGame::LoadOrderMechanism::FileTime) { - // nothing to do - return true; - } - - log::debug("setting file times on esps"); - - for (ESPInfo &esp : m_ESPs) { - std::wstring espName = ToWString(esp.name); - const FileEntryPtr fileEntry = directoryStructure.findFile(espName); - if (fileEntry.get() != nullptr) { - QString fileName; - bool archive = false; - int originid = fileEntry->getOrigin(archive); - - fileName = QString("%1\\%2") - .arg(QDir::toNativeSeparators(ToQString(directoryStructure.getOriginByID(originid).getPath()))) - .arg(esp.name); - - HANDLE file = ::CreateFile(ToWString(fileName).c_str(), GENERIC_READ | GENERIC_WRITE, - 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); - if (file == INVALID_HANDLE_VALUE) { - if (::GetLastError() == ERROR_SHARING_VIOLATION) { - // file is locked, probably the game is running - return false; - } else { - throw windows_error(QObject::tr("failed to access %1").arg(fileName).toUtf8().constData()); - } - } - - ULONGLONG temp = 0; - temp = (145731ULL + esp.priority) * 24 * 60 * 60 * 10000000ULL; - - FILETIME newWriteTime; - - newWriteTime.dwLowDateTime = (DWORD)(temp & 0xFFFFFFFF); - newWriteTime.dwHighDateTime = (DWORD)(temp >> 32); - esp.time = newWriteTime; - fileEntry->setFileTime(newWriteTime); - if (!::SetFileTime(file, nullptr, nullptr, &newWriteTime)) { - throw windows_error(QObject::tr("failed to set file time %1").arg(fileName).toUtf8().constData()); - } - - CloseHandle(file); - } - } - return true; -} - -int PluginList::enabledCount() const -{ - int enabled = 0; - for (const auto &info : m_ESPs) { - if (info.enabled) { - ++enabled; - } - } - return enabled; -} - -QString PluginList::getIndexPriority(int index) const -{ - return m_ESPs[index].index; -} - -bool PluginList::isESPLocked(int index) const -{ - return m_LockedOrder.find(m_ESPs.at(index).name) != m_LockedOrder.end(); -} - -void PluginList::lockESPIndex(int index, bool lock) -{ - if (lock) { - if (!m_ESPs.at(index).forceEnabled) - m_LockedOrder[getName(index)] = m_ESPs.at(index).loadOrder; - else - return; - } else { - auto iter = m_LockedOrder.find(getName(index)); - if (iter != m_LockedOrder.end()) { - m_LockedOrder.erase(iter); - } - } - emit writePluginsList(); -} - -void PluginList::syncLoadOrder() -{ - int loadOrder = 0; - for (unsigned int i = 0; i < m_ESPs.size(); ++i) { - int index = m_ESPsByPriority[i]; - - if (m_ESPs[index].enabled) { - m_ESPs[index].loadOrder = loadOrder++; - } else { - m_ESPs[index].loadOrder = -1; - } - } -} - -void PluginList::refreshLoadOrder() -{ - ChangeBracket layoutChange(this); - syncLoadOrder(); - // set priorities according to locked load order - std::map lockedLoadOrder; - std::for_each(m_LockedOrder.begin(), m_LockedOrder.end(), - [&lockedLoadOrder] (const std::pair &ele) { - lockedLoadOrder[ele.second] = ele.first; }); - - int targetPrio = 0; - bool savePluginsList = false; - // this is guaranteed to iterate from lowest key (load order) to highest - for (auto iter = lockedLoadOrder.begin(); iter != lockedLoadOrder.end(); ++iter) { - auto nameIter = m_ESPsByName.find(iter->second); - if (nameIter != m_ESPsByName.end()) { - // locked esp exists - - // find the location to insert at - while ((targetPrio < static_cast(m_ESPs.size() - 1)) && - (m_ESPs[m_ESPsByPriority[targetPrio]].loadOrder < iter->first)) { - ++targetPrio; - } - - if (static_cast(targetPrio) >= m_ESPs.size()) { - continue; - } - - int temp = targetPrio; - int index = nameIter->second; - if (m_ESPs[index].priority != temp) { - setPluginPriority(index, temp); - m_ESPs[index].loadOrder = iter->first; - syncLoadOrder(); - savePluginsList = true; - } - } - } - if (savePluginsList) { - emit writePluginsList(); - } -} - -void PluginList::disconnectSlots() { - m_PluginMoved.disconnect_all_slots(); - m_Refreshed.disconnect_all_slots(); - m_PluginStateChanged.disconnect_all_slots(); -} - -int PluginList::timeElapsedSinceLastChecked() const -{ - return m_LastCheck.elapsed(); -} - -QStringList PluginList::pluginNames() const -{ - QStringList result; - - for (const ESPInfo &info : m_ESPs) { - result.append(info.name); - } - - return result; -} - -IPluginList::PluginStates PluginList::state(const QString &name) const -{ - auto iter = m_ESPsByName.find(name); - if (iter == m_ESPsByName.end()) { - return IPluginList::STATE_MISSING; - } else { - return m_ESPs[iter->second].enabled ? IPluginList::STATE_ACTIVE : IPluginList::STATE_INACTIVE; - } -} - -void PluginList::setState(const QString &name, PluginStates state) { - auto iter = m_ESPsByName.find(name); - if (iter != m_ESPsByName.end()) { - m_ESPs[iter->second].enabled = (state == IPluginList::STATE_ACTIVE) || - m_ESPs[iter->second].forceEnabled; - } else { - log::warn("Plugin not found: {}", name); - } -} - -void PluginList::setLoadOrder(const QStringList &pluginList) -{ - for (ESPInfo &info : m_ESPs) { - info.priority = -1; - } - int maxPriority = 0; - for (const QString &plugin : pluginList) { - auto iter = m_ESPsByName.find(plugin); - if (iter !=m_ESPsByName.end()) { - m_ESPs[iter->second].priority = maxPriority++; - } - } - - // use old priorities - for (ESPInfo &info : m_ESPs) { - if (info.priority == -1) { - info.priority = maxPriority++; - } - } - updateIndices(); -} - -int PluginList::priority(const QString &name) const -{ - auto iter = m_ESPsByName.find(name); - if (iter == m_ESPsByName.end()) { - return -1; - } else { - return m_ESPs[iter->second].priority; - } -} - -bool PluginList::setPriority(const QString& name, int newPriority) { - - if (newPriority < 0 || newPriority >= static_cast(m_ESPsByPriority.size())) { - return false; - } - - auto oldPriority = priority(name); - if (oldPriority == -1) { - return false; - } - - int rowIndex = findPluginByPriority(oldPriority); - - // We need to increment newPriority if its above the old one, otherwise the - // plugin is place right below the new priority. - if (oldPriority < newPriority) { - newPriority += 1; - } - changePluginPriority({ rowIndex }, newPriority); - - return true; -} - -int PluginList::loadOrder(const QString &name) const -{ - auto iter = m_ESPsByName.find(name); - if (iter == m_ESPsByName.end()) { - return -1; - } else { - return m_ESPs[iter->second].loadOrder; - } -} - -QStringList PluginList::masters(const QString &name) const -{ - auto iter = m_ESPsByName.find(name); - if (iter == m_ESPsByName.end()) { - return QStringList(); - } else { - QStringList result; - for (const QString &master : m_ESPs[iter->second].masters) { - result.append(master); - } - return result; - } -} - -QString PluginList::origin(const QString &name) const -{ - auto iter = m_ESPsByName.find(name); - if (iter == m_ESPsByName.end()) { - return QString(); - } else { - return m_ESPs[iter->second].originName; - } -} - -bool PluginList::hasMasterExtension(const QString& name) const -{ - auto iter = m_ESPsByName.find(name); - if (iter == m_ESPsByName.end()) { - return false; - } - else { - return m_ESPs[iter->second].hasMasterExtension; - } -} - -bool PluginList::hasLightExtension(const QString& name) const -{ - auto iter = m_ESPsByName.find(name); - if (iter == m_ESPsByName.end()) { - return false; - } - else { - return m_ESPs[iter->second].hasLightExtension; - } -} - -bool PluginList::isMasterFlagged(const QString& name) const -{ - auto iter = m_ESPsByName.find(name); - if (iter == m_ESPsByName.end()) { - return false; - } - else { - return m_ESPs[iter->second].isMasterFlagged; - } -} - -bool PluginList::isLightFlagged(const QString& name) const -{ - auto iter = m_ESPsByName.find(name); - if (iter == m_ESPsByName.end()) { - return false; - } - else { - return m_ESPs[iter->second].isLightFlagged; - } -} - -boost::signals2::connection PluginList::onPluginStateChanged(const std::function&)>& func) -{ - return m_PluginStateChanged.connect(func); -} - -void PluginList::pluginStatesChanged(QStringList const& pluginNames, PluginStates state) const { - if (pluginNames.isEmpty()) { - return; - } - std::map infos; - for (auto& name : pluginNames) { - infos[name] = state; - } - m_PluginStateChanged(infos); -} - -boost::signals2::connection PluginList::onRefreshed(const std::function &callback) -{ - return m_Refreshed.connect(callback); -} - -boost::signals2::connection PluginList::onPluginMoved(const std::function &func) -{ - return m_PluginMoved.connect(func); -} - -void PluginList::updateIndices() -{ - m_ESPsByName.clear(); - m_ESPsByPriority.clear(); - m_ESPsByPriority.resize(m_ESPs.size()); - for (unsigned int i = 0; i < m_ESPs.size(); ++i) { - if (m_ESPs[i].priority < 0) { - continue; - } - if (m_ESPs[i].priority >= static_cast(m_ESPs.size())) { - log::error("invalid plugin priority: {}", m_ESPs[i].priority); - continue; - } - m_ESPsByName[m_ESPs[i].name] = i; - m_ESPsByPriority.at(static_cast(m_ESPs[i].priority)) = i; - } - - generatePluginIndexes(); -} - -void PluginList::generatePluginIndexes() -{ - int numESLs = 0; - int numSkipped = 0; - - GamePlugins* gamePlugins = m_GamePlugin->feature(); - const bool lightPluginsSupported = gamePlugins ? gamePlugins->lightPluginsAreSupported() : false; - - for (int l = 0; l < m_ESPs.size(); ++l) { - int i = m_ESPsByPriority.at(l); - if (!m_ESPs[i].enabled) { - m_ESPs[i].index = QString(); - ++numSkipped; - continue; - } - if (lightPluginsSupported && (m_ESPs[i].hasLightExtension || m_ESPs[i].isLightFlagged)) { - int ESLpos = 254 + ((numESLs + 1) / 4096); - m_ESPs[i].index = QString("%1:%2").arg(ESLpos, 2, 16, QChar('0')).arg((numESLs) % 4096, 3, 16, QChar('0')).toUpper(); - ++numESLs; - } else { - m_ESPs[i].index = QString("%1").arg(l - numESLs - numSkipped, 2, 16, QChar('0')).toUpper(); - } - } - emit esplist_changed(); -} - -int PluginList::rowCount(const QModelIndex &parent) const -{ - if (!parent.isValid()) { - return static_cast(m_ESPs.size()); - } else { - return 0; - } -} - -int PluginList::columnCount(const QModelIndex &) const -{ - return COL_LASTCOLUMN + 1; -} - -void PluginList::testMasters() -{ - std::set enabledMasters; - for (const auto& iter: m_ESPs) { - if (iter.enabled) { - enabledMasters.insert(iter.name); - } - } - - for (auto& iter: m_ESPs) { - iter.masterUnset.clear(); - if (iter.enabled) { - for (const auto& master: iter.masters) { - if (enabledMasters.find(master) == enabledMasters.end()) { - iter.masterUnset.insert(master); - } - } - } - } -} - -QVariant PluginList::data(const QModelIndex &modelIndex, int role) const -{ - int index = modelIndex.row(); - - if ((role == Qt::DisplayRole) || (role == Qt::EditRole)) { - return displayData(modelIndex); - } else if ((role == Qt::CheckStateRole) && (modelIndex.column() == 0)) { - return checkstateData(modelIndex); - } else if (role == Qt::ForegroundRole) { - return foregroundData(modelIndex); - } else if (role == Qt::BackgroundRole) { - return backgroundData(modelIndex); - } else if (role == Qt::FontRole) { - return fontData(modelIndex); - } else if (role == Qt::TextAlignmentRole) { - return alignmentData(modelIndex); - } else if (role == Qt::ToolTipRole) { - return tooltipData(modelIndex); - } else if (role == Qt::UserRole + 1) { - return iconData(modelIndex); - } - return QVariant(); -} - -QVariant PluginList::displayData(const QModelIndex &modelIndex) const -{ - const int index = modelIndex.row(); - - switch (modelIndex.column()) - { - case COL_NAME: - return m_ESPs[index].name; - - case COL_PRIORITY: - return m_ESPs[index].priority; - - case COL_MODINDEX: - return m_ESPs[index].index; - - default: - return {}; - } -} - -QVariant PluginList::checkstateData(const QModelIndex &modelIndex) const -{ - const int index = modelIndex.row(); - - if (m_ESPs[index].forceEnabled) { - return {}; - } - - return m_ESPs[index].enabled ? Qt::Checked : Qt::Unchecked; -} - -QVariant PluginList::foregroundData(const QModelIndex &modelIndex) const -{ - const int index = modelIndex.row(); - - if ((modelIndex.column() == COL_NAME) && m_ESPs[index].forceEnabled) { - return QBrush(Qt::gray); - } - - return {}; -} - -QVariant PluginList::backgroundData(const QModelIndex &modelIndex) const -{ - const int index = modelIndex.row(); - - if (m_ESPs[index].modSelected) { - return Settings::instance().colors().pluginListContained(); - } - - return {}; -} - -QVariant PluginList::fontData(const QModelIndex &modelIndex) const -{ - const int index = modelIndex.row(); - - QFont result; - - if (m_ESPs[index].hasMasterExtension || - m_ESPs[index].isMasterFlagged || - m_ESPs[index].hasLightExtension) { - result.setItalic(true); - result.setWeight(QFont::Bold); - } else if (m_ESPs[index].isLightFlagged) { - result.setItalic(true); - } - - return result; -} - -QVariant PluginList::alignmentData(const QModelIndex &modelIndex) const -{ - const int index = modelIndex.row(); - - if (modelIndex.column() == 0) { - return QVariant(Qt::AlignLeft | Qt::AlignVCenter); - } else { - return QVariant(Qt::AlignHCenter | Qt::AlignVCenter); - } -} - -QVariant PluginList::tooltipData(const QModelIndex &modelIndex) const -{ - const int index = modelIndex.row(); - const auto& esp = m_ESPs[index]; - - QString toolTip; - - toolTip += "" + tr("Origin") + ": " + esp.originName; - - if (esp.forceEnabled) { - toolTip += - "
              " + - tr("This plugin can't be disabled (enforced by the game).") + - ""; - } else { - if (!esp.author.isEmpty()) { - toolTip += - "
              " + tr("Author") + ": " + - TruncateString(esp.author); - } - - if (esp.description.size() > 0) { - toolTip += - "
              " + tr("Description") + ": " + - TruncateString(esp.description); - } - - if (esp.masterUnset.size() > 0) { - toolTip += - "
              " + tr("Missing Masters") + ": " + - "" + TruncateString(QStringList(esp.masterUnset.begin(), esp.masterUnset.end()).join(", ")) + ""; - } - - std::set enabledMasters; - std::set_difference(esp.masters.begin(), esp.masters.end(), - esp.masterUnset.begin(), esp.masterUnset.end(), - std::inserter(enabledMasters, enabledMasters.end())); - - if (!enabledMasters.empty()) { - toolTip += - "
              " + tr("Enabled Masters") + ": " + - TruncateString(SetJoin(enabledMasters, ", ")); - } - - if (!esp.archives.empty()) { - toolTip += - "
              " + tr("Loads Archives") + ": " + - TruncateString(QStringList(esp.archives.begin(), esp.archives.end()).join(", ")) + - "
              " + tr( - "There are Archives connected to this plugin. Their assets will be " - "added to your game, overwriting in case of conflicts following the " - "plugin order. Loose files will always overwrite assets from " - "Archives. (This flag only checks for Archives from the same mod as " - "the plugin)"); - } - - if (esp.hasIni) { - toolTip += - "
              " + tr("Loads INI settings") + ": " - "
              " + tr( - "There is an ini file connected to this plugin. Its settings will " - "be added to your game settings, overwriting in case of conflicts."); - } - - if (esp.isLightFlagged && !esp.hasLightExtension) { - toolTip += - "

              " + tr( - "This ESP is flagged as an ESL. It will adhere to the ESP load " - "order but the records will be loaded in ESL space."); - } - } - - - // additional info - auto itor = m_AdditionalInfo.find(esp.name); - - if (itor != m_AdditionalInfo.end()) { - if (!itor->second.messages.isEmpty()) { - toolTip += "
                "; - - for (auto&& message : itor->second.messages) { - toolTip += "
              • " + message + "
              • "; - } - - toolTip += "
              "; - } - - // loot - toolTip += makeLootTooltip(itor->second.loot); - } - - return toolTip; -} - -QString PluginList::makeLootTooltip(const Loot::Plugin& loot) const -{ - QString s; - - for (auto&& f : loot.incompatibilities) { - s += - "
            • " + tr("Incompatible with %1") - .arg(f.displayName.isEmpty() ? f.name : f.displayName) + - "
            • "; - } - - for (auto&& m : loot.missingMasters) { - s += "
            • " + tr("Depends on missing %1").arg(m) + "
            • "; - } - - for (auto&& m : loot.messages) { - s += "
            • "; - - switch (m.type) - { - case log::Warning: - s += tr("Warning") + ": "; - break; - - case log::Error: - s += tr("Error") + ": "; - break; - - case log::Info: // fall-through - case log::Debug: - default: - // nothing - break; - } - - s += m.text + "
            • "; - } - - for (auto&& d : loot.dirty) { - s += "
            • " + d.toString(false) + "
            • "; - } - - for (auto&& c : loot.clean) { - s += "
            • " + c.toString(true) + "
            • "; - } - - if (!s.isEmpty()) { - s = - "
              " - "
                " + - s + - "
              "; - } - - return s; -} - -QVariant PluginList::iconData(const QModelIndex &modelIndex) const -{ - int index = modelIndex.row(); - - QVariantList result; - - const auto& esp = m_ESPs[index]; - - auto infoItor = m_AdditionalInfo.find(esp.name); - - const AdditionalInfo* info = nullptr; - if (infoItor != m_AdditionalInfo.end()) { - info = &infoItor->second; - } - - if (isProblematic(esp, info)) { - result.append(":/MO/gui/warning"); - } - - if (m_LockedOrder.find(esp.name) != m_LockedOrder.end()) { - result.append(":/MO/gui/locked"); - } - - if (hasInfo(esp, info)) { - result.append(":/MO/gui/information"); - } - - if (esp.hasIni) { - result.append(":/MO/gui/attachment"); - } - - if (!esp.archives.empty()) { - result.append(":/MO/gui/archive_conflict_neutral"); - } - - if (esp.isLightFlagged && !esp.hasLightExtension) { - result.append(":/MO/gui/awaiting"); - } - - if (info && !info->loot.dirty.empty()) { - result.append(":/MO/gui/edit_clear"); - } - - return result; -} - -bool PluginList::isProblematic(const ESPInfo& esp, const AdditionalInfo* info) const -{ - if (esp.masterUnset.size() > 0) { - return true; - } - - if (info) { - if (!info->loot.incompatibilities.empty()) { - return true; - } - - if (!info->loot.missingMasters.empty()) { - return true; - } - } - - return false; -} - -bool PluginList::hasInfo(const ESPInfo& esp, const AdditionalInfo* info) const -{ - if (info) { - if (!info->messages.empty()) { - return true; - } - - if (!info->loot.messages.empty()) { - return true; - } - } - - return false; -} - -bool PluginList::setData(const QModelIndex &modIndex, const QVariant &value, int role) -{ - QString modName = modIndex.data().toString(); - IPluginList::PluginStates oldState = state(modName); - - bool result = false; - - if (role == Qt::CheckStateRole) { - m_ESPs[modIndex.row()].enabled = - value.toInt() == Qt::Checked || m_ESPs[modIndex.row()].forceEnabled; - m_LastCheck.restart(); - emit dataChanged(modIndex, modIndex); - - refreshLoadOrder(); - emit writePluginsList(); - - result = true; - } else if (role == Qt::EditRole) { - if (modIndex.column() == COL_PRIORITY) { - bool ok = false; - int newPriority = value.toInt(&ok); - if (ok) { - setPluginPriority(modIndex.row(), newPriority); - result = true; - } - refreshLoadOrder(); - emit writePluginsList(); - } - } - - IPluginList::PluginStates newState = state(modName); - if (oldState != newState) { - try { - pluginStatesChanged({ modName }, newState); - testMasters(); - emit dataChanged( - this->index(0, 0), - this->index(static_cast(m_ESPs.size()), columnCount())); - } catch (const std::exception &e) { - log::error("failed to invoke state changed notification: {}", e.what()); - } catch (...) { - log::error("failed to invoke state changed notification: unknown exception"); - } - } - - return result; -} - -QVariant PluginList::headerData(int section, Qt::Orientation orientation, - int role) const -{ - if (orientation == Qt::Horizontal) { - if (role == Qt::DisplayRole) { - return getColumnName(section); - } else if (role == Qt::ToolTipRole) { - return getColumnToolTip(section); - } - } - return QAbstractItemModel::headerData(section, orientation, role); -} - -Qt::ItemFlags PluginList::flags(const QModelIndex &modelIndex) const -{ - int index = modelIndex.row(); - Qt::ItemFlags result = QAbstractItemModel::flags(modelIndex); - - if (modelIndex.isValid()) { - if (!m_ESPs[index].forceEnabled) { - result |= Qt::ItemIsUserCheckable | Qt::ItemIsDragEnabled; - } - if (modelIndex.column() == COL_PRIORITY) { - result |= Qt::ItemIsEditable; - } - result &= ~Qt::ItemIsDropEnabled; - } else { - result |= Qt::ItemIsDropEnabled; - } - - return result; -} - -void PluginList::setPluginPriority(int row, int &newPriority, bool isForced) -{ - int newPriorityTemp = newPriority; - - // enforce valid range - if (newPriorityTemp < 0) - newPriorityTemp = 0; - else if (newPriorityTemp >= static_cast(m_ESPsByPriority.size())) - newPriorityTemp = static_cast(m_ESPsByPriority.size()) - 1; - - if (!m_ESPs[row].isMasterFlagged && - !m_ESPs[row].hasLightExtension && - !m_ESPs[row].hasMasterExtension) { - // don't allow esps to be moved above esms - while ((newPriorityTemp < static_cast(m_ESPsByPriority.size() - 1)) && - (m_ESPs.at(m_ESPsByPriority.at(newPriorityTemp)).isMasterFlagged || - m_ESPs.at(m_ESPsByPriority.at(newPriorityTemp)).hasLightExtension || - m_ESPs.at(m_ESPsByPriority.at(newPriorityTemp)).hasMasterExtension)) { - ++newPriorityTemp; - } - } else { - // don't allow esms to be moved below esps - while ((newPriorityTemp > 0) && - !m_ESPs.at(m_ESPsByPriority.at(newPriorityTemp)).isMasterFlagged && - !m_ESPs.at(m_ESPsByPriority.at(newPriorityTemp)).hasLightExtension && - !m_ESPs.at(m_ESPsByPriority.at(newPriorityTemp)).hasMasterExtension) { - --newPriorityTemp; - } - // also don't allow "regular" esms to be moved above primary plugins - while ((newPriorityTemp < static_cast(m_ESPsByPriority.size() - 1)) && - (m_ESPs.at(m_ESPsByPriority.at(newPriorityTemp)).forceEnabled)) { - ++newPriorityTemp; - } - } - - int oldPriority = m_ESPs.at(row).priority; - if (newPriorityTemp < oldPriority) { // moving up - // don't allow plugins to be moved above their masters - for (auto master : m_ESPs[row].masters) { - auto iter = m_ESPsByName.find(master); - if (iter != m_ESPsByName.end()) { - int masterPriority = m_ESPs[iter->second].priority; - if (masterPriority >= newPriorityTemp) - { - newPriorityTemp = masterPriority + 1; - } - } - } - } - else if (newPriorityTemp > oldPriority) { // moving down - // don't allow masters to be moved below their children - for (int i = oldPriority + 1; i <= newPriorityTemp; i++) { - PluginList::ESPInfo* otherInfo = &m_ESPs.at(m_ESPsByPriority[i]); - for (auto master : otherInfo->masters) { - if (master.compare(m_ESPs[row].name, Qt::CaseInsensitive) == 0) { - newPriorityTemp = otherInfo->priority - 1; - break; - } - } - } - } - - try { - if (newPriorityTemp != oldPriority) { - 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_ESPs.at(m_ESPsByPriority.at(i)).priority; - } - emit dataChanged(index(oldPriority + 1, 0), index(newPriorityTemp, columnCount())); - } - else { - for (int i = newPriorityTemp; i < oldPriority; ++i) { - ++m_ESPs.at(m_ESPsByPriority.at(i)).priority; - } - emit dataChanged(index(newPriorityTemp, 0), index(oldPriority - 1, columnCount())); - ++newPriority; - } - - m_ESPs.at(row).priority = newPriorityTemp; - emit dataChanged(index(row, 0), index(row, columnCount())); - m_PluginMoved(m_ESPs[row].name, oldPriority, newPriorityTemp); - } - } catch (const std::out_of_range&) { - reportError(tr("failed to restore load order for %1").arg(m_ESPs[row].name)); - } - - updateIndices(); -} - -void PluginList::changePluginPriority(std::vector rows, int newPriority) -{ - ChangeBracket layoutChange(this); - const std::vector &esp = m_ESPs; - - int minPriority = INT_MAX; - int maxPriority = INT_MIN; - - // don't try to move plugins before force-enabled plugins - for (std::vector::const_iterator iter = m_ESPs.begin(); - iter != m_ESPs.end(); ++iter) { - if (iter->forceEnabled) { - newPriority = std::max(newPriority, iter->priority + 1); - } - maxPriority = std::max(maxPriority, iter->priority + 1); - minPriority = std::min(minPriority, iter->priority); - } - - // limit the new priority to existing priorities - newPriority = std::min(newPriority, maxPriority); - newPriority = std::max(newPriority, minPriority); - - // sort the moving plugins by ascending priorities - std::sort(rows.begin(), rows.end(), - [&esp](const int &LHS, const int &RHS) { - return esp[LHS].priority < esp[RHS].priority; - }); - - // if at least on plugin is increasing in priority, the target index is - // that of the row BELOW the dropped location, otherwise it's the one above - for (std::vector::const_iterator iter = rows.begin(); - iter != rows.end(); ++iter) { - if (m_ESPs[*iter].priority < newPriority) { - --newPriority; - break; - } - } - - for (std::vector::const_iterator iter = rows.begin(); iter != rows.end(); ++iter) { - setPluginPriority(*iter, newPriority); - } - - layoutChange.finish(); - refreshLoadOrder(); - emit writePluginsList(); -} - -bool PluginList::dropMimeData(const QMimeData *mimeData, Qt::DropAction action, int row, int, const QModelIndex &parent) -{ - if (action == Qt::IgnoreAction) { - return true; - } - - QByteArray encoded = mimeData->data("application/x-qabstractitemmodeldatalist"); - QDataStream stream(&encoded, QIODevice::ReadOnly); - - std::vector sourceRows; - - while (!stream.atEnd()) { - int sourceRow, col; - QMap roleDataMap; - stream >> sourceRow >> col >> roleDataMap; - if (col == 0) { // only add each row once - sourceRows.push_back(sourceRow); - } - } - - if (row == -1) { - row = parent.row(); - } - - int newPriority; - - if ((row < 0) || - (row >= static_cast(m_ESPs.size()))) { - newPriority = static_cast(m_ESPs.size()); - } else { - newPriority = m_ESPs[row].priority; - } - changePluginPriority(sourceRows, newPriority); - - return false; -} - -QModelIndex PluginList::index(int row, int column, const QModelIndex&) const -{ - if ((row < 0) || (row >= rowCount()) || (column < 0) || (column >= columnCount())) { - return QModelIndex(); - } - return createIndex(row, column, row); -} - -QModelIndex PluginList::parent(const QModelIndex&) const -{ - return QModelIndex(); -} - -PluginList::ESPInfo::ESPInfo(const QString &name, bool enabled, - const QString &originName, const QString &fullPath, - bool hasIni, std::set archives, bool lightPluginsAreSupported) - : name(name), fullPath(fullPath), enabled(enabled), forceEnabled(enabled), - priority(0), loadOrder(-1), originName(originName), hasIni(hasIni), - archives(archives.begin(), archives.end()), modSelected(false) -{ - try { - ESP::File file(ToWString(fullPath)); - auto extension = name.right(3).toLower(); - hasMasterExtension = (extension == "esm"); - hasLightExtension = lightPluginsAreSupported && (extension == "esl"); - isMasterFlagged = file.isMaster(); - isLightFlagged = lightPluginsAreSupported && file.isLight(); - - author = QString::fromLatin1(file.author().c_str()); - description = QString::fromLatin1(file.description().c_str()); - - for (auto&& m : file.masters()) { - masters.insert(QString::fromStdString(m)); - } - } catch (const std::exception &e) { - log::error("failed to parse plugin file {}: {}", fullPath, e.what()); - hasMasterExtension = false; - hasLightExtension = false; - isMasterFlagged = false; - isLightFlagged = false; - } -} - -void PluginList::managedGameChanged(const IPluginGame *gamePlugin) -{ - m_GamePlugin = gamePlugin; -} +/* +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 "pluginlist.h" +#include "modinfo.h" +#include "modlist.h" +#include "scopeguard.h" +#include "settings.h" +#include "shared/directoryentry.h" +#include "shared/fileentry.h" +#include "shared/filesorigin.h" +#include "viewmarkingscrollbar.h" + +#include "shared/windows_error.h" +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "organizercore.h" + +using namespace MOBase; +using namespace MOShared; + +static QString TruncateString(const QString& text) +{ + QString new_text = text; + + if (new_text.length() > 1024) { + new_text.truncate(1024); + new_text += "..."; + } + + return new_text; +} + +PluginList::PluginList(OrganizerCore& organizer) + : QAbstractItemModel(&organizer), m_Organizer(organizer), m_FontMetrics(QFont()) +{ + connect(this, SIGNAL(writePluginsList()), this, SLOT(generatePluginIndexes())); + m_LastCheck.start(); +} + +PluginList::~PluginList() +{ + m_Refreshed.disconnect_all_slots(); + m_PluginMoved.disconnect_all_slots(); + m_PluginStateChanged.disconnect_all_slots(); +} + +QString PluginList::getColumnName(int column) +{ + switch (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"); + } +} + +QString PluginList::getColumnToolTip(int column) +{ + switch (column) { + case COL_NAME: + return tr("Name of the plugin"); + case COL_FLAGS: + return tr("Emblems to highlight things that might require attention."); + case COL_PRIORITY: + return tr( + "Load priority of plugins. The higher, the more \"important\" it is and thus " + "overwrites data from plugins with lower priority."); + case COL_MODINDEX: + return tr("Determines the formids of objects originating from this mods."); + default: + return tr("unknown"); + } +} + +void PluginList::highlightPlugins(const std::vector& modIndices, + const MOShared::DirectoryEntry& directoryEntry) +{ + auto* profile = m_Organizer.currentProfile(); + + for (auto& esp : m_ESPs) { + esp.modSelected = false; + } + + for (auto& modIndex : modIndices) { + ModInfo::Ptr selectedMod = ModInfo::getByIndex(modIndex); + if (!selectedMod.isNull() && profile->modEnabled(modIndex)) { + QDir dir(selectedMod->absolutePath()); + QStringList plugins = dir.entryList(QStringList() << "*.esp" + << "*.esm" + << "*.esl"); + const MOShared::FilesOrigin& origin = + directoryEntry.getOriginByName(selectedMod->internalName().toStdWString()); + if (plugins.size() > 0) { + for (auto plugin : plugins) { + MOShared::FileEntryPtr file = directoryEntry.findFile(plugin.toStdWString()); + if (file && file->getOrigin() != origin.getID()) { + const auto alternatives = file->getAlternatives(); + if (std::find_if(alternatives.begin(), alternatives.end(), + [&](const FileAlternative& element) { + return element.originID() == origin.getID(); + }) == alternatives.end()) + continue; + } + std::map::iterator iter = m_ESPsByName.find(plugin); + if (iter != m_ESPsByName.end()) { + m_ESPs[iter->second].modSelected = true; + } + } + } + } + } + + emit dataChanged(this->index(0, 0), this->index(static_cast(m_ESPs.size()) - 1, + this->columnCount() - 1)); +} + +void PluginList::refresh(const QString& profileName, + const DirectoryEntry& baseDirectory, + const QString& lockedOrderFile, bool force) +{ + TimeThis tt("PluginList::refresh()"); + + if (force) { + m_ESPs.clear(); + m_ESPsByName.clear(); + m_ESPsByPriority.clear(); + } + + ChangeBracket layoutChange(this); + + QStringList primaryPlugins = m_GamePlugin->primaryPlugins(); + GamePlugins* gamePlugins = m_GamePlugin->feature(); + const bool lightPluginsAreSupported = + gamePlugins ? gamePlugins->lightPluginsAreSupported() : false; + + m_CurrentProfile = profileName; + + QStringList availablePlugins; + + std::vector files = baseDirectory.getFiles(); + for (FileEntryPtr current : files) { + if (current.get() == nullptr) { + continue; + } + QString filename = ToQString(current->getName()); + + if (filename.endsWith(".esp", Qt::CaseInsensitive) || + filename.endsWith(".esm", Qt::CaseInsensitive) || + filename.endsWith(".esl", Qt::CaseInsensitive)) { + + availablePlugins.append(filename); + + if (m_ESPsByName.find(filename) != m_ESPsByName.end()) { + continue; + } + + bool forceEnabled = Settings::instance().game().forceEnableCoreFiles() && + primaryPlugins.contains(filename, Qt::CaseInsensitive); + + bool archive = false; + try { + FilesOrigin& origin = baseDirectory.getOriginByID(current->getOrigin(archive)); + + // name without extension + QString baseName = QFileInfo(filename).completeBaseName(); + + QString iniPath = baseName + ".ini"; + bool hasIni = baseDirectory.findFile(ToWString(iniPath)).get() != nullptr; + std::set loadedArchives; + QString candidateName; + for (FileEntryPtr archiveCandidate : files) { + candidateName = ToQString(archiveCandidate->getName()); + if (candidateName.startsWith(baseName, Qt::CaseInsensitive) && + (candidateName.endsWith(".bsa", Qt::CaseInsensitive) || + candidateName.endsWith(".ba2", Qt::CaseInsensitive))) { + loadedArchives.insert(candidateName); + } + } + + QString originName = ToQString(origin.getName()); + unsigned int modIndex = ModInfo::getIndex(originName); + if (modIndex != UINT_MAX) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); + originName = modInfo->name(); + } + + m_ESPs.push_back(ESPInfo(filename, forceEnabled, originName, + ToQString(current->getFullPath()), hasIni, + loadedArchives, lightPluginsAreSupported)); + m_ESPs.rbegin()->priority = -1; + } catch (const std::exception& e) { + reportError( + tr("failed to update esp info for file %1 (source id: %2), error: %3") + .arg(filename) + .arg(current->getOrigin(archive)) + .arg(e.what())); + } + } + } + + for (const auto& espName : m_ESPsByName) { + if (!availablePlugins.contains(espName.first, Qt::CaseInsensitive)) { + m_ESPs[espName.second].name = ""; + } + } + + m_ESPs.erase(std::remove_if(m_ESPs.begin(), m_ESPs.end(), + [](const ESPInfo& info) -> bool { + return info.name.isEmpty(); + }), + m_ESPs.end()); + + fixPriorities(); + + // functions in GamePlugins will use the IPluginList interface of this, so + // indices need to work. priority will be off however + updateIndices(); + + if (gamePlugins) { + gamePlugins->readPluginLists(m_Organizer.managedGameOrganizer()->pluginList()); + } + + fixPrimaryPlugins(); + fixPluginRelationships(); + + testMasters(); + + updateIndices(); + + readLockedOrderFrom(lockedOrderFile); + + layoutChange.finish(); + + refreshLoadOrder(); + emit dataChanged(this->index(0, 0), + this->index(static_cast(m_ESPs.size()), columnCount())); + + m_Refreshed(); +} + +void PluginList::fixPrimaryPlugins() +{ + if (!m_Organizer.settings().game().forceEnableCoreFiles()) { + return; + } + + // This function ensures that the primary plugins are first and in the correct order + QStringList primaryPlugins = m_Organizer.managedGame()->primaryPlugins(); + int prio = 0; + bool somethingChanged = false; + for (QString plugin : primaryPlugins) { + std::map::iterator iter = m_ESPsByName.find(plugin); + // Plugin is present? + if (iter != m_ESPsByName.end()) { + if (prio != m_ESPs[iter->second].priority) { + // Priority is wrong! Fix it! + int newPrio = prio; + setPluginPriority(iter->second, newPrio, true /* isForced */); + somethingChanged = true; + } + prio++; + } + } + + if (somethingChanged) { + writePluginsList(); + } +} + +void PluginList::fixPluginRelationships() +{ + TimeThis timer("PluginList::fixPluginRelationships"); + + // Count the types of plugins + int masterCount = 0; + for (auto plugin : m_ESPs) { + if (plugin.hasLightExtension || plugin.hasMasterExtension || + plugin.isMasterFlagged) { + masterCount++; + } + } + + // Ensure masters are up top and normal plugins are down below + for (int i = 0; i < m_ESPs.size(); i++) { + ESPInfo& plugin = m_ESPs[i]; + if (plugin.hasLightExtension || plugin.hasMasterExtension || + plugin.isMasterFlagged) { + if (plugin.priority > masterCount) { + int newPriority = masterCount + 1; + setPluginPriority(i, newPriority); + } + } else { + if (plugin.priority < masterCount) { + int newPriority = masterCount + 1; + setPluginPriority(i, newPriority); + } + } + } + + // Ensure master/child relationships are observed + for (int i = 0; i < m_ESPs.size(); i++) { + ESPInfo& plugin = m_ESPs[i]; + int newPriority = plugin.priority; + for (auto master : plugin.masters) { + auto iter = m_ESPsByName.find(master); + if (iter != m_ESPsByName.end()) { + newPriority = std::max(newPriority, m_ESPs[iter->second].priority); + } + } + if (newPriority != plugin.priority) { + setPluginPriority(i, newPriority); + } + } +} + +void PluginList::fixPriorities() +{ + std::vector> espPrios; + + for (int i = 0; i < m_ESPs.size(); ++i) { + int prio = m_ESPs[i].priority; + if (prio == -1) { + prio = INT_MAX; + } + espPrios.push_back(std::make_pair(prio, i)); + } + + std::sort(espPrios.begin(), espPrios.end(), + [](const std::pair& lhs, const std::pair& rhs) { + return lhs.first < rhs.first; + }); + + for (int i = 0; i < espPrios.size(); ++i) { + m_ESPs[espPrios[i].second].priority = i; + } +} + +void PluginList::enableESP(const QString& name, bool enable) +{ + std::map::iterator iter = m_ESPsByName.find(name); + + if (iter != m_ESPsByName.end()) { + auto enabled = m_ESPs[iter->second].enabled; + m_ESPs[iter->second].enabled = enable || m_ESPs[iter->second].forceEnabled; + + emit writePluginsList(); + if (enabled != m_ESPs[iter->second].enabled) { + pluginStatesChanged({name}, state(name)); + } + } else { + reportError(tr("Plugin not found: %1").arg(qUtf8Printable(name))); + } +} + +int PluginList::findPluginByPriority(int priority) +{ + for (int i = 0; i < m_ESPs.size(); i++) { + if (m_ESPs[i].priority == priority) { + return i; + } + } + log::error("No plugin with priority {}", priority); + return -1; +} + +void PluginList::setEnabled(const QModelIndexList& indices, bool enabled) +{ + QStringList dirty; + for (auto& idx : indices) { + if (m_ESPs[idx.row()].enabled != enabled) { + m_ESPs[idx.row()].enabled = enabled; + dirty.append(m_ESPs[idx.row()].name); + } + } + if (!dirty.isEmpty()) { + emit writePluginsList(); + pluginStatesChanged(dirty, enabled ? IPluginList::PluginState::STATE_ACTIVE + : IPluginList::PluginState::STATE_INACTIVE); + } +} + +void PluginList::setEnabledAll(bool enabled) +{ + QStringList dirty; + for (ESPInfo& info : m_ESPs) { + if (info.enabled != enabled) { + info.enabled = enabled; + dirty.append(info.name); + } + } + if (!dirty.isEmpty()) { + emit writePluginsList(); + pluginStatesChanged(dirty, enabled ? IPluginList::PluginState::STATE_ACTIVE + : IPluginList::PluginState::STATE_INACTIVE); + } +} + +void PluginList::sendToPriority(const QModelIndexList& indices, int newPriority) +{ + std::vector pluginsToMove; + for (auto& idx : indices) { + if (!m_ESPs[idx.row()].forceEnabled) { + pluginsToMove.push_back(idx.row()); + } + } + if (pluginsToMove.size()) { + changePluginPriority(pluginsToMove, newPriority); + } +} + +void PluginList::shiftPluginsPriority(const QModelIndexList& indices, int offset) +{ + // retrieve the plugin index and sort them by priority to avoid issue + // when moving them + std::vector allIndex; + for (auto& idx : indices) { + allIndex.push_back(idx.row()); + } + std::sort(allIndex.begin(), allIndex.end(), [=](int lhs, int rhs) { + bool cmp = m_ESPs[lhs].priority < m_ESPs[rhs].priority; + return offset > 0 ? !cmp : cmp; + }); + + for (auto index : allIndex) { + int newPriority = m_ESPs[index].priority + offset; + if (newPriority >= 0 && newPriority < rowCount()) { + setPluginPriority(index, newPriority); + } + } + + refreshLoadOrder(); +} + +void PluginList::toggleState(const QModelIndexList& indices) +{ + QModelIndex minRow, maxRow; + for (auto& idx : indices) { + if (!minRow.isValid() || (idx.row() < minRow.row())) { + minRow = idx; + } + if (!maxRow.isValid() || (idx.row() > maxRow.row())) { + maxRow = idx; + } + int oldState = idx.data(Qt::CheckStateRole).toInt(); + setData(idx, oldState == Qt::Unchecked ? Qt::Checked : Qt::Unchecked, + Qt::CheckStateRole); + } + + emit dataChanged(minRow, maxRow); +} + +bool PluginList::isEnabled(const QString& name) +{ + std::map::iterator iter = m_ESPsByName.find(name); + + if (iter != m_ESPsByName.end()) { + return m_ESPs[iter->second].enabled; + } else { + return false; + } +} + +void PluginList::clearInformation(const QString& name) +{ + std::map::iterator iter = m_ESPsByName.find(name); + + if (iter != m_ESPsByName.end()) { + m_AdditionalInfo[name].messages.clear(); + } +} + +void PluginList::clearAdditionalInformation() +{ + m_AdditionalInfo.clear(); +} + +void PluginList::addInformation(const QString& name, const QString& message) +{ + std::map::iterator iter = m_ESPsByName.find(name); + + if (iter != m_ESPsByName.end()) { + m_AdditionalInfo[name].messages.append(message); + } else { + log::warn("failed to associate message for \"{}\"", name); + } +} + +void PluginList::addLootReport(const QString& name, Loot::Plugin plugin) +{ + auto iter = m_ESPsByName.find(name); + + if (iter != m_ESPsByName.end()) { + m_AdditionalInfo[name].loot = std::move(plugin); + } else { + log::warn("failed to associate loot report for \"{}\"", name); + } +} + +bool PluginList::isEnabled(int index) +{ + return m_ESPs.at(index).enabled; +} + +void PluginList::readLockedOrderFrom(const QString& fileName) +{ + m_LockedOrder.clear(); + + QFile file(fileName); + if (!file.exists()) { + // no locked load order, that's ok + return; + } + + file.open(QIODevice::ReadOnly); + int lineNumber = 0; + while (!file.atEnd()) { + QByteArray line = file.readLine(); + ++lineNumber; + + // Skip empty lines or commented out lines (#) + if ((line.size() <= 0) || (line.at(0) == '#')) { + continue; + } + + QList fields = line.split('|'); + if (fields.count() != 2) { + // Don't know how to parse this so run away + log::error("locked order file: invalid line #{}: {}", lineNumber, + QString::fromUtf8(line).trimmed()); + continue; + } + + // Read the plugin name and priority + QString pluginName = QString::fromUtf8(fields.at(0)); + int priority = fields.at(1).trimmed().toInt(); + if (priority < 0) { + // WTF do you mean a negative priority? + log::error("locked order file: invalid line #{}: {}", lineNumber, + QString::fromUtf8(line).trimmed()); + continue; + } + + // Determine the index of the plugin + auto it = m_ESPsByName.find(pluginName); + if (it == m_ESPsByName.end()) { + // Plugin does not exist in the current set of plugins + m_LockedOrder[pluginName] = priority; + continue; + } + int pluginIndex = it->second; + + // Do not allow locking forced plugins + if (m_ESPs[pluginIndex].forceEnabled) { + continue; + } + + // If the priority is larger than the number of plugins, just keep it locked + if (priority >= m_ESPsByPriority.size()) { + m_LockedOrder[pluginName] = priority; + continue; + } + + // These are some helper functions for figuring out what is already locked + auto findLocked = [&](const std::pair& a) { + return a.second == priority; + }; + auto alreadyLocked = [&]() { + return std::find_if(m_LockedOrder.begin(), m_LockedOrder.end(), findLocked) != + m_LockedOrder.end(); + }; + + // See if we can just set the given priority + if (!m_ESPs[m_ESPsByPriority.at(priority)].forceEnabled && !alreadyLocked()) { + m_LockedOrder[pluginName] = priority; + continue; + } + + // Find the next higher priority we can set the plugin to + while (++priority < m_ESPs.size()) { + if (!m_ESPs[m_ESPsByPriority.at(priority)].forceEnabled && !alreadyLocked()) { + m_LockedOrder[pluginName] = priority; + break; + } + } + + // See if we walked off the end of the plugin list + if (priority >= m_ESPs.size()) { + // I guess go ahead and lock it here at the end of the list? + m_LockedOrder[pluginName] = priority; + continue; + } + } /* while (!file.atEnd()) */ + file.close(); +} + +void PluginList::writeLockedOrder(const QString& fileName) const +{ + SafeWriteFile file(fileName); + + 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.commit(); +} + +void PluginList::saveTo(const QString& lockedOrderFileName) const +{ + GamePlugins* gamePlugins = m_GamePlugin->feature(); + if (gamePlugins) { + gamePlugins->writePluginLists(m_Organizer.managedGameOrganizer()->pluginList()); + } + + writeLockedOrder(lockedOrderFileName); +} + +bool PluginList::saveLoadOrder(DirectoryEntry& directoryStructure) +{ + if (m_GamePlugin->loadOrderMechanism() != IPluginGame::LoadOrderMechanism::FileTime) { + // nothing to do + return true; + } + + log::debug("setting file times on esps"); + + for (ESPInfo& esp : m_ESPs) { + std::wstring espName = ToWString(esp.name); + const FileEntryPtr fileEntry = directoryStructure.findFile(espName); + if (fileEntry.get() != nullptr) { + QString fileName; + bool archive = false; + int originid = fileEntry->getOrigin(archive); + + fileName = QString("%1\\%2") + .arg(QDir::toNativeSeparators(ToQString( + directoryStructure.getOriginByID(originid).getPath()))) + .arg(esp.name); + + HANDLE file = + ::CreateFile(ToWString(fileName).c_str(), GENERIC_READ | GENERIC_WRITE, 0, + nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); + if (file == INVALID_HANDLE_VALUE) { + if (::GetLastError() == ERROR_SHARING_VIOLATION) { + // file is locked, probably the game is running + return false; + } else { + throw windows_error( + QObject::tr("failed to access %1").arg(fileName).toUtf8().constData()); + } + } + + ULONGLONG temp = 0; + temp = (145731ULL + esp.priority) * 24 * 60 * 60 * 10000000ULL; + + FILETIME newWriteTime; + + newWriteTime.dwLowDateTime = (DWORD)(temp & 0xFFFFFFFF); + newWriteTime.dwHighDateTime = (DWORD)(temp >> 32); + esp.time = newWriteTime; + fileEntry->setFileTime(newWriteTime); + if (!::SetFileTime(file, nullptr, nullptr, &newWriteTime)) { + throw windows_error(QObject::tr("failed to set file time %1") + .arg(fileName) + .toUtf8() + .constData()); + } + + CloseHandle(file); + } + } + return true; +} + +int PluginList::enabledCount() const +{ + int enabled = 0; + for (const auto& info : m_ESPs) { + if (info.enabled) { + ++enabled; + } + } + return enabled; +} + +QString PluginList::getIndexPriority(int index) const +{ + return m_ESPs[index].index; +} + +bool PluginList::isESPLocked(int index) const +{ + return m_LockedOrder.find(m_ESPs.at(index).name) != m_LockedOrder.end(); +} + +void PluginList::lockESPIndex(int index, bool lock) +{ + if (lock) { + if (!m_ESPs.at(index).forceEnabled) + m_LockedOrder[getName(index)] = m_ESPs.at(index).loadOrder; + else + return; + } else { + auto iter = m_LockedOrder.find(getName(index)); + if (iter != m_LockedOrder.end()) { + m_LockedOrder.erase(iter); + } + } + emit writePluginsList(); +} + +void PluginList::syncLoadOrder() +{ + int loadOrder = 0; + for (unsigned int i = 0; i < m_ESPs.size(); ++i) { + int index = m_ESPsByPriority[i]; + + if (m_ESPs[index].enabled) { + m_ESPs[index].loadOrder = loadOrder++; + } else { + m_ESPs[index].loadOrder = -1; + } + } +} + +void PluginList::refreshLoadOrder() +{ + ChangeBracket layoutChange(this); + syncLoadOrder(); + // set priorities according to locked load order + std::map lockedLoadOrder; + std::for_each(m_LockedOrder.begin(), m_LockedOrder.end(), + [&lockedLoadOrder](const std::pair& ele) { + lockedLoadOrder[ele.second] = ele.first; + }); + + int targetPrio = 0; + bool savePluginsList = false; + // this is guaranteed to iterate from lowest key (load order) to highest + for (auto iter = lockedLoadOrder.begin(); iter != lockedLoadOrder.end(); ++iter) { + auto nameIter = m_ESPsByName.find(iter->second); + if (nameIter != m_ESPsByName.end()) { + // locked esp exists + + // find the location to insert at + while ((targetPrio < static_cast(m_ESPs.size() - 1)) && + (m_ESPs[m_ESPsByPriority[targetPrio]].loadOrder < iter->first)) { + ++targetPrio; + } + + if (static_cast(targetPrio) >= m_ESPs.size()) { + continue; + } + + int temp = targetPrio; + int index = nameIter->second; + if (m_ESPs[index].priority != temp) { + setPluginPriority(index, temp); + m_ESPs[index].loadOrder = iter->first; + syncLoadOrder(); + savePluginsList = true; + } + } + } + if (savePluginsList) { + emit writePluginsList(); + } +} + +void PluginList::disconnectSlots() +{ + m_PluginMoved.disconnect_all_slots(); + m_Refreshed.disconnect_all_slots(); + m_PluginStateChanged.disconnect_all_slots(); +} + +int PluginList::timeElapsedSinceLastChecked() const +{ + return m_LastCheck.elapsed(); +} + +QStringList PluginList::pluginNames() const +{ + QStringList result; + + for (const ESPInfo& info : m_ESPs) { + result.append(info.name); + } + + return result; +} + +IPluginList::PluginStates PluginList::state(const QString& name) const +{ + auto iter = m_ESPsByName.find(name); + if (iter == m_ESPsByName.end()) { + return IPluginList::STATE_MISSING; + } else { + return m_ESPs[iter->second].enabled ? IPluginList::STATE_ACTIVE + : IPluginList::STATE_INACTIVE; + } +} + +void PluginList::setState(const QString& name, PluginStates state) +{ + auto iter = m_ESPsByName.find(name); + if (iter != m_ESPsByName.end()) { + m_ESPs[iter->second].enabled = + (state == IPluginList::STATE_ACTIVE) || m_ESPs[iter->second].forceEnabled; + } else { + log::warn("Plugin not found: {}", name); + } +} + +void PluginList::setLoadOrder(const QStringList& pluginList) +{ + for (ESPInfo& info : m_ESPs) { + info.priority = -1; + } + int maxPriority = 0; + for (const QString& plugin : pluginList) { + auto iter = m_ESPsByName.find(plugin); + if (iter != m_ESPsByName.end()) { + m_ESPs[iter->second].priority = maxPriority++; + } + } + + // use old priorities + for (ESPInfo& info : m_ESPs) { + if (info.priority == -1) { + info.priority = maxPriority++; + } + } + updateIndices(); +} + +int PluginList::priority(const QString& name) const +{ + auto iter = m_ESPsByName.find(name); + if (iter == m_ESPsByName.end()) { + return -1; + } else { + return m_ESPs[iter->second].priority; + } +} + +bool PluginList::setPriority(const QString& name, int newPriority) +{ + + if (newPriority < 0 || newPriority >= static_cast(m_ESPsByPriority.size())) { + return false; + } + + auto oldPriority = priority(name); + if (oldPriority == -1) { + return false; + } + + int rowIndex = findPluginByPriority(oldPriority); + + // We need to increment newPriority if its above the old one, otherwise the + // plugin is place right below the new priority. + if (oldPriority < newPriority) { + newPriority += 1; + } + changePluginPriority({rowIndex}, newPriority); + + return true; +} + +int PluginList::loadOrder(const QString& name) const +{ + auto iter = m_ESPsByName.find(name); + if (iter == m_ESPsByName.end()) { + return -1; + } else { + return m_ESPs[iter->second].loadOrder; + } +} + +QStringList PluginList::masters(const QString& name) const +{ + auto iter = m_ESPsByName.find(name); + if (iter == m_ESPsByName.end()) { + return QStringList(); + } else { + QStringList result; + for (const QString& master : m_ESPs[iter->second].masters) { + result.append(master); + } + return result; + } +} + +QString PluginList::origin(const QString& name) const +{ + auto iter = m_ESPsByName.find(name); + if (iter == m_ESPsByName.end()) { + return QString(); + } else { + return m_ESPs[iter->second].originName; + } +} + +bool PluginList::hasMasterExtension(const QString& name) const +{ + auto iter = m_ESPsByName.find(name); + if (iter == m_ESPsByName.end()) { + return false; + } else { + return m_ESPs[iter->second].hasMasterExtension; + } +} + +bool PluginList::hasLightExtension(const QString& name) const +{ + auto iter = m_ESPsByName.find(name); + if (iter == m_ESPsByName.end()) { + return false; + } else { + return m_ESPs[iter->second].hasLightExtension; + } +} + +bool PluginList::isMasterFlagged(const QString& name) const +{ + auto iter = m_ESPsByName.find(name); + if (iter == m_ESPsByName.end()) { + return false; + } else { + return m_ESPs[iter->second].isMasterFlagged; + } +} + +bool PluginList::isLightFlagged(const QString& name) const +{ + auto iter = m_ESPsByName.find(name); + if (iter == m_ESPsByName.end()) { + return false; + } else { + return m_ESPs[iter->second].isLightFlagged; + } +} + +boost::signals2::connection PluginList::onPluginStateChanged( + const std::function&)>& func) +{ + return m_PluginStateChanged.connect(func); +} + +void PluginList::pluginStatesChanged(QStringList const& pluginNames, + PluginStates state) const +{ + if (pluginNames.isEmpty()) { + return; + } + std::map infos; + for (auto& name : pluginNames) { + infos[name] = state; + } + m_PluginStateChanged(infos); +} + +boost::signals2::connection +PluginList::onRefreshed(const std::function& callback) +{ + return m_Refreshed.connect(callback); +} + +boost::signals2::connection +PluginList::onPluginMoved(const std::function& func) +{ + return m_PluginMoved.connect(func); +} + +void PluginList::updateIndices() +{ + m_ESPsByName.clear(); + m_ESPsByPriority.clear(); + m_ESPsByPriority.resize(m_ESPs.size()); + for (unsigned int i = 0; i < m_ESPs.size(); ++i) { + if (m_ESPs[i].priority < 0) { + continue; + } + if (m_ESPs[i].priority >= static_cast(m_ESPs.size())) { + log::error("invalid plugin priority: {}", m_ESPs[i].priority); + continue; + } + m_ESPsByName[m_ESPs[i].name] = i; + m_ESPsByPriority.at(static_cast(m_ESPs[i].priority)) = i; + } + + generatePluginIndexes(); +} + +void PluginList::generatePluginIndexes() +{ + int numESLs = 0; + int numSkipped = 0; + + GamePlugins* gamePlugins = m_GamePlugin->feature(); + const bool lightPluginsSupported = + gamePlugins ? gamePlugins->lightPluginsAreSupported() : false; + + for (int l = 0; l < m_ESPs.size(); ++l) { + int i = m_ESPsByPriority.at(l); + if (!m_ESPs[i].enabled) { + m_ESPs[i].index = QString(); + ++numSkipped; + continue; + } + if (lightPluginsSupported && + (m_ESPs[i].hasLightExtension || m_ESPs[i].isLightFlagged)) { + int ESLpos = 254 + ((numESLs + 1) / 4096); + m_ESPs[i].index = QString("%1:%2") + .arg(ESLpos, 2, 16, QChar('0')) + .arg((numESLs) % 4096, 3, 16, QChar('0')) + .toUpper(); + ++numESLs; + } else { + m_ESPs[i].index = + QString("%1").arg(l - numESLs - numSkipped, 2, 16, QChar('0')).toUpper(); + } + } + emit esplist_changed(); +} + +int PluginList::rowCount(const QModelIndex& parent) const +{ + if (!parent.isValid()) { + return static_cast(m_ESPs.size()); + } else { + return 0; + } +} + +int PluginList::columnCount(const QModelIndex&) const +{ + return COL_LASTCOLUMN + 1; +} + +void PluginList::testMasters() +{ + std::set enabledMasters; + for (const auto& iter : m_ESPs) { + if (iter.enabled) { + enabledMasters.insert(iter.name); + } + } + + for (auto& iter : m_ESPs) { + iter.masterUnset.clear(); + if (iter.enabled) { + for (const auto& master : iter.masters) { + if (enabledMasters.find(master) == enabledMasters.end()) { + iter.masterUnset.insert(master); + } + } + } + } +} + +QVariant PluginList::data(const QModelIndex& modelIndex, int role) const +{ + int index = modelIndex.row(); + + if ((role == Qt::DisplayRole) || (role == Qt::EditRole)) { + return displayData(modelIndex); + } else if ((role == Qt::CheckStateRole) && (modelIndex.column() == 0)) { + return checkstateData(modelIndex); + } else if (role == Qt::ForegroundRole) { + return foregroundData(modelIndex); + } else if (role == Qt::BackgroundRole) { + return backgroundData(modelIndex); + } else if (role == Qt::FontRole) { + return fontData(modelIndex); + } else if (role == Qt::TextAlignmentRole) { + return alignmentData(modelIndex); + } else if (role == Qt::ToolTipRole) { + return tooltipData(modelIndex); + } else if (role == Qt::UserRole + 1) { + return iconData(modelIndex); + } + return QVariant(); +} + +QVariant PluginList::displayData(const QModelIndex& modelIndex) const +{ + const int index = modelIndex.row(); + + switch (modelIndex.column()) { + case COL_NAME: + return m_ESPs[index].name; + + case COL_PRIORITY: + return m_ESPs[index].priority; + + case COL_MODINDEX: + return m_ESPs[index].index; + + default: + return {}; + } +} + +QVariant PluginList::checkstateData(const QModelIndex& modelIndex) const +{ + const int index = modelIndex.row(); + + if (m_ESPs[index].forceEnabled) { + return {}; + } + + return m_ESPs[index].enabled ? Qt::Checked : Qt::Unchecked; +} + +QVariant PluginList::foregroundData(const QModelIndex& modelIndex) const +{ + const int index = modelIndex.row(); + + if ((modelIndex.column() == COL_NAME) && m_ESPs[index].forceEnabled) { + return QBrush(Qt::gray); + } + + return {}; +} + +QVariant PluginList::backgroundData(const QModelIndex& modelIndex) const +{ + const int index = modelIndex.row(); + + if (m_ESPs[index].modSelected) { + return Settings::instance().colors().pluginListContained(); + } + + return {}; +} + +QVariant PluginList::fontData(const QModelIndex& modelIndex) const +{ + const int index = modelIndex.row(); + + QFont result; + + if (m_ESPs[index].hasMasterExtension || m_ESPs[index].isMasterFlagged || + m_ESPs[index].hasLightExtension) { + result.setItalic(true); + result.setWeight(QFont::Bold); + } else if (m_ESPs[index].isLightFlagged) { + result.setItalic(true); + } + + return result; +} + +QVariant PluginList::alignmentData(const QModelIndex& modelIndex) const +{ + const int index = modelIndex.row(); + + if (modelIndex.column() == 0) { + return QVariant(Qt::AlignLeft | Qt::AlignVCenter); + } else { + return QVariant(Qt::AlignHCenter | Qt::AlignVCenter); + } +} + +QVariant PluginList::tooltipData(const QModelIndex& modelIndex) const +{ + const int index = modelIndex.row(); + const auto& esp = m_ESPs[index]; + + QString toolTip; + + toolTip += "" + tr("Origin") + ": " + esp.originName; + + if (esp.forceEnabled) { + toolTip += "
              " + + tr("This plugin can't be disabled (enforced by the game).") + ""; + } else { + if (!esp.author.isEmpty()) { + toolTip += "
              " + tr("Author") + ": " + TruncateString(esp.author); + } + + if (esp.description.size() > 0) { + toolTip += + "
              " + tr("Description") + ": " + TruncateString(esp.description); + } + + if (esp.masterUnset.size() > 0) { + toolTip += + "
              " + tr("Missing Masters") + ": " + "" + + TruncateString( + QStringList(esp.masterUnset.begin(), esp.masterUnset.end()).join(", ")) + + ""; + } + + std::set enabledMasters; + std::set_difference(esp.masters.begin(), esp.masters.end(), esp.masterUnset.begin(), + esp.masterUnset.end(), + std::inserter(enabledMasters, enabledMasters.end())); + + if (!enabledMasters.empty()) { + toolTip += "
              " + tr("Enabled Masters") + + ": " + TruncateString(SetJoin(enabledMasters, ", ")); + } + + if (!esp.archives.empty()) { + toolTip += + "
              " + tr("Loads Archives") + ": " + + TruncateString( + QStringList(esp.archives.begin(), esp.archives.end()).join(", ")) + + "
              " + + tr("There are Archives connected to this plugin. Their assets will be " + "added to your game, overwriting in case of conflicts following the " + "plugin order. Loose files will always overwrite assets from " + "Archives. (This flag only checks for Archives from the same mod as " + "the plugin)"); + } + + if (esp.hasIni) { + toolTip += + "
              " + tr("Loads INI settings") + + ": " + "
              " + + tr("There is an ini file connected to this plugin. Its settings will " + "be added to your game settings, overwriting in case of conflicts."); + } + + if (esp.isLightFlagged && !esp.hasLightExtension) { + toolTip += "

              " + + tr("This ESP is flagged as an ESL. It will adhere to the ESP load " + "order but the records will be loaded in ESL space."); + } + } + + // additional info + auto itor = m_AdditionalInfo.find(esp.name); + + if (itor != m_AdditionalInfo.end()) { + if (!itor->second.messages.isEmpty()) { + toolTip += "
                "; + + for (auto&& message : itor->second.messages) { + toolTip += "
              • " + message + "
              • "; + } + + toolTip += "
              "; + } + + // loot + toolTip += makeLootTooltip(itor->second.loot); + } + + return toolTip; +} + +QString PluginList::makeLootTooltip(const Loot::Plugin& loot) const +{ + QString s; + + for (auto&& f : loot.incompatibilities) { + s += "
            • " + + tr("Incompatible with %1") + .arg(f.displayName.isEmpty() ? f.name : f.displayName) + + "
            • "; + } + + for (auto&& m : loot.missingMasters) { + s += "
            • " + tr("Depends on missing %1").arg(m) + "
            • "; + } + + for (auto&& m : loot.messages) { + s += "
            • "; + + switch (m.type) { + case log::Warning: + s += tr("Warning") + ": "; + break; + + case log::Error: + s += tr("Error") + ": "; + break; + + case log::Info: // fall-through + case log::Debug: + default: + // nothing + break; + } + + s += m.text + "
            • "; + } + + for (auto&& d : loot.dirty) { + s += "
            • " + d.toString(false) + "
            • "; + } + + for (auto&& c : loot.clean) { + s += "
            • " + c.toString(true) + "
            • "; + } + + if (!s.isEmpty()) { + s = "
              " + "
                " + + s + "
              "; + } + + return s; +} + +QVariant PluginList::iconData(const QModelIndex& modelIndex) const +{ + int index = modelIndex.row(); + + QVariantList result; + + const auto& esp = m_ESPs[index]; + + auto infoItor = m_AdditionalInfo.find(esp.name); + + const AdditionalInfo* info = nullptr; + if (infoItor != m_AdditionalInfo.end()) { + info = &infoItor->second; + } + + if (isProblematic(esp, info)) { + result.append(":/MO/gui/warning"); + } + + if (m_LockedOrder.find(esp.name) != m_LockedOrder.end()) { + result.append(":/MO/gui/locked"); + } + + if (hasInfo(esp, info)) { + result.append(":/MO/gui/information"); + } + + if (esp.hasIni) { + result.append(":/MO/gui/attachment"); + } + + if (!esp.archives.empty()) { + result.append(":/MO/gui/archive_conflict_neutral"); + } + + if (esp.isLightFlagged && !esp.hasLightExtension) { + result.append(":/MO/gui/awaiting"); + } + + if (info && !info->loot.dirty.empty()) { + result.append(":/MO/gui/edit_clear"); + } + + return result; +} + +bool PluginList::isProblematic(const ESPInfo& esp, const AdditionalInfo* info) const +{ + if (esp.masterUnset.size() > 0) { + return true; + } + + if (info) { + if (!info->loot.incompatibilities.empty()) { + return true; + } + + if (!info->loot.missingMasters.empty()) { + return true; + } + } + + return false; +} + +bool PluginList::hasInfo(const ESPInfo& esp, const AdditionalInfo* info) const +{ + if (info) { + if (!info->messages.empty()) { + return true; + } + + if (!info->loot.messages.empty()) { + return true; + } + } + + return false; +} + +bool PluginList::setData(const QModelIndex& modIndex, const QVariant& value, int role) +{ + QString modName = modIndex.data().toString(); + IPluginList::PluginStates oldState = state(modName); + + bool result = false; + + if (role == Qt::CheckStateRole) { + m_ESPs[modIndex.row()].enabled = + value.toInt() == Qt::Checked || m_ESPs[modIndex.row()].forceEnabled; + m_LastCheck.restart(); + emit dataChanged(modIndex, modIndex); + + refreshLoadOrder(); + emit writePluginsList(); + + result = true; + } else if (role == Qt::EditRole) { + if (modIndex.column() == COL_PRIORITY) { + bool ok = false; + int newPriority = value.toInt(&ok); + if (ok) { + setPluginPriority(modIndex.row(), newPriority); + result = true; + } + refreshLoadOrder(); + emit writePluginsList(); + } + } + + IPluginList::PluginStates newState = state(modName); + if (oldState != newState) { + try { + pluginStatesChanged({modName}, newState); + testMasters(); + emit dataChanged(this->index(0, 0), + this->index(static_cast(m_ESPs.size()), columnCount())); + } catch (const std::exception& e) { + log::error("failed to invoke state changed notification: {}", e.what()); + } catch (...) { + log::error("failed to invoke state changed notification: unknown exception"); + } + } + + return result; +} + +QVariant PluginList::headerData(int section, Qt::Orientation orientation, + int role) const +{ + if (orientation == Qt::Horizontal) { + if (role == Qt::DisplayRole) { + return getColumnName(section); + } else if (role == Qt::ToolTipRole) { + return getColumnToolTip(section); + } + } + return QAbstractItemModel::headerData(section, orientation, role); +} + +Qt::ItemFlags PluginList::flags(const QModelIndex& modelIndex) const +{ + int index = modelIndex.row(); + Qt::ItemFlags result = QAbstractItemModel::flags(modelIndex); + + if (modelIndex.isValid()) { + if (!m_ESPs[index].forceEnabled) { + result |= Qt::ItemIsUserCheckable | Qt::ItemIsDragEnabled; + } + if (modelIndex.column() == COL_PRIORITY) { + result |= Qt::ItemIsEditable; + } + result &= ~Qt::ItemIsDropEnabled; + } else { + result |= Qt::ItemIsDropEnabled; + } + + return result; +} + +void PluginList::setPluginPriority(int row, int& newPriority, bool isForced) +{ + int newPriorityTemp = newPriority; + + // enforce valid range + if (newPriorityTemp < 0) + newPriorityTemp = 0; + else if (newPriorityTemp >= static_cast(m_ESPsByPriority.size())) + newPriorityTemp = static_cast(m_ESPsByPriority.size()) - 1; + + if (!m_ESPs[row].isMasterFlagged && !m_ESPs[row].hasLightExtension && + !m_ESPs[row].hasMasterExtension) { + // don't allow esps to be moved above esms + while ((newPriorityTemp < static_cast(m_ESPsByPriority.size() - 1)) && + (m_ESPs.at(m_ESPsByPriority.at(newPriorityTemp)).isMasterFlagged || + m_ESPs.at(m_ESPsByPriority.at(newPriorityTemp)).hasLightExtension || + m_ESPs.at(m_ESPsByPriority.at(newPriorityTemp)).hasMasterExtension)) { + ++newPriorityTemp; + } + } else { + // don't allow esms to be moved below esps + while ((newPriorityTemp > 0) && + !m_ESPs.at(m_ESPsByPriority.at(newPriorityTemp)).isMasterFlagged && + !m_ESPs.at(m_ESPsByPriority.at(newPriorityTemp)).hasLightExtension && + !m_ESPs.at(m_ESPsByPriority.at(newPriorityTemp)).hasMasterExtension) { + --newPriorityTemp; + } + // also don't allow "regular" esms to be moved above primary plugins + while ((newPriorityTemp < static_cast(m_ESPsByPriority.size() - 1)) && + (m_ESPs.at(m_ESPsByPriority.at(newPriorityTemp)).forceEnabled)) { + ++newPriorityTemp; + } + } + + int oldPriority = m_ESPs.at(row).priority; + if (newPriorityTemp < oldPriority) { // moving up + // don't allow plugins to be moved above their masters + for (auto master : m_ESPs[row].masters) { + auto iter = m_ESPsByName.find(master); + if (iter != m_ESPsByName.end()) { + int masterPriority = m_ESPs[iter->second].priority; + if (masterPriority >= newPriorityTemp) { + newPriorityTemp = masterPriority + 1; + } + } + } + } else if (newPriorityTemp > oldPriority) { // moving down + // don't allow masters to be moved below their children + for (int i = oldPriority + 1; i <= newPriorityTemp; i++) { + PluginList::ESPInfo* otherInfo = &m_ESPs.at(m_ESPsByPriority[i]); + for (auto master : otherInfo->masters) { + if (master.compare(m_ESPs[row].name, Qt::CaseInsensitive) == 0) { + newPriorityTemp = otherInfo->priority - 1; + break; + } + } + } + } + + try { + if (newPriorityTemp != oldPriority) { + 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_ESPs.at(m_ESPsByPriority.at(i)).priority; + } + emit dataChanged(index(oldPriority + 1, 0), + index(newPriorityTemp, columnCount())); + } else { + for (int i = newPriorityTemp; i < oldPriority; ++i) { + ++m_ESPs.at(m_ESPsByPriority.at(i)).priority; + } + emit dataChanged(index(newPriorityTemp, 0), + index(oldPriority - 1, columnCount())); + ++newPriority; + } + + m_ESPs.at(row).priority = newPriorityTemp; + emit dataChanged(index(row, 0), index(row, columnCount())); + m_PluginMoved(m_ESPs[row].name, oldPriority, newPriorityTemp); + } + } catch (const std::out_of_range&) { + reportError(tr("failed to restore load order for %1").arg(m_ESPs[row].name)); + } + + updateIndices(); +} + +void PluginList::changePluginPriority(std::vector rows, int newPriority) +{ + ChangeBracket layoutChange(this); + const std::vector& esp = m_ESPs; + + int minPriority = INT_MAX; + int maxPriority = INT_MIN; + + // don't try to move plugins before force-enabled plugins + for (std::vector::const_iterator iter = m_ESPs.begin(); iter != m_ESPs.end(); + ++iter) { + if (iter->forceEnabled) { + newPriority = std::max(newPriority, iter->priority + 1); + } + maxPriority = std::max(maxPriority, iter->priority + 1); + minPriority = std::min(minPriority, iter->priority); + } + + // limit the new priority to existing priorities + newPriority = std::min(newPriority, maxPriority); + newPriority = std::max(newPriority, minPriority); + + // sort the moving plugins by ascending priorities + std::sort(rows.begin(), rows.end(), [&esp](const int& LHS, const int& RHS) { + return esp[LHS].priority < esp[RHS].priority; + }); + + // if at least on plugin is increasing in priority, the target index is + // that of the row BELOW the dropped location, otherwise it's the one above + for (std::vector::const_iterator iter = rows.begin(); iter != rows.end(); + ++iter) { + if (m_ESPs[*iter].priority < newPriority) { + --newPriority; + break; + } + } + + for (std::vector::const_iterator iter = rows.begin(); iter != rows.end(); + ++iter) { + setPluginPriority(*iter, newPriority); + } + + layoutChange.finish(); + refreshLoadOrder(); + emit writePluginsList(); +} + +bool PluginList::dropMimeData(const QMimeData* mimeData, Qt::DropAction action, int row, + int, const QModelIndex& parent) +{ + if (action == Qt::IgnoreAction) { + return true; + } + + QByteArray encoded = mimeData->data("application/x-qabstractitemmodeldatalist"); + QDataStream stream(&encoded, QIODevice::ReadOnly); + + std::vector sourceRows; + + while (!stream.atEnd()) { + int sourceRow, col; + QMap roleDataMap; + stream >> sourceRow >> col >> roleDataMap; + if (col == 0) { // only add each row once + sourceRows.push_back(sourceRow); + } + } + + if (row == -1) { + row = parent.row(); + } + + int newPriority; + + if ((row < 0) || (row >= static_cast(m_ESPs.size()))) { + newPriority = static_cast(m_ESPs.size()); + } else { + newPriority = m_ESPs[row].priority; + } + changePluginPriority(sourceRows, newPriority); + + return false; +} + +QModelIndex PluginList::index(int row, int column, const QModelIndex&) const +{ + if ((row < 0) || (row >= rowCount()) || (column < 0) || (column >= columnCount())) { + return QModelIndex(); + } + return createIndex(row, column, row); +} + +QModelIndex PluginList::parent(const QModelIndex&) const +{ + return QModelIndex(); +} + +PluginList::ESPInfo::ESPInfo(const QString& name, bool enabled, + const QString& originName, const QString& fullPath, + bool hasIni, std::set archives, + bool lightPluginsAreSupported) + : name(name), fullPath(fullPath), enabled(enabled), forceEnabled(enabled), + priority(0), loadOrder(-1), originName(originName), hasIni(hasIni), + archives(archives.begin(), archives.end()), modSelected(false) +{ + try { + ESP::File file(ToWString(fullPath)); + auto extension = name.right(3).toLower(); + hasMasterExtension = (extension == "esm"); + hasLightExtension = lightPluginsAreSupported && (extension == "esl"); + isMasterFlagged = file.isMaster(); + isLightFlagged = lightPluginsAreSupported && file.isLight(); + + author = QString::fromLatin1(file.author().c_str()); + description = QString::fromLatin1(file.description().c_str()); + + for (auto&& m : file.masters()) { + masters.insert(QString::fromStdString(m)); + } + } catch (const std::exception& e) { + log::error("failed to parse plugin file {}: {}", fullPath, e.what()); + hasMasterExtension = false; + hasLightExtension = false; + isMasterFlagged = false; + isLightFlagged = false; + } +} + +void PluginList::managedGameChanged(const IPluginGame* gamePlugin) +{ + m_GamePlugin = gamePlugin; +} diff --git a/src/pluginlist.h b/src/pluginlist.h index 56ec9d3dd..e7a65c814 100644 --- a/src/pluginlist.h +++ b/src/pluginlist.h @@ -1,419 +1,420 @@ -/* -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 PLUGINLIST_H -#define PLUGINLIST_H - -#include -#include -#include "profile.h" -#include "loot.h" - -namespace MOBase { class IPluginGame; } - -#include -#include -#include -#include -#include -#include - -#pragma warning(push) -#pragma warning(disable: 4100) -#ifndef Q_MOC_RUN -#include -#include -#endif - -#include -#include - -class OrganizerCore; - - -template -class ChangeBracket { -public: - ChangeBracket(C *model) - : m_Model(nullptr) - { - QVariant var = model->property("__aboutToChange"); - bool aboutToChange = var.isValid() && var.toBool(); - if (!aboutToChange) { - model->layoutAboutToBeChanged(); - model->setProperty("__aboutToChange", true); - m_Model = model; - } - } - ~ChangeBracket() { - finish(); - } - - void finish() { - if (m_Model != nullptr) { - m_Model->layoutChanged(); - m_Model->setProperty("__aboutToChange", false); - m_Model = nullptr; - } - } - -private: - C *m_Model; -}; - - - -/** - * @brief model representing the plugins (.esp/.esm) in the current virtual data folder - **/ -class PluginList : public QAbstractItemModel -{ - Q_OBJECT - friend class ChangeBracket; -public: - - enum EColumn { - COL_NAME, - COL_FLAGS, - COL_PRIORITY, - COL_MODINDEX, - - COL_LASTCOLUMN = COL_MODINDEX - }; - - using PluginStates = MOBase::IPluginList::PluginStates; - - friend class PluginListProxy; - - using SignalRefreshed = boost::signals2::signal; - using SignalPluginMoved = boost::signals2::signal; - using SignalPluginStateChanged = boost::signals2::signal&)>; - -public: - - /** - * @brief constructor - * - * @param parent parent object - **/ - PluginList(OrganizerCore &organizer); - - ~PluginList(); - - /** - * @brief does a complete refresh of the list - * - * @param profileName name of the current profile - * @param baseDirectory the root directory structure representing the virtual data directory - * @param lockedOrderFile list of plugins that shouldn't change load order - * @todo the profile is not used? If it was, we should pass the Profile-object instead - **/ - void refresh(const QString &profileName - , const MOShared::DirectoryEntry &baseDirectory - , const QString &lockedOrderFile - , bool refresh); - - /** - * @brief enable a plugin based on its name - * - * @param name name of the plugin to enable - * @param enable set to true to enable the esp, false to disable it - **/ - void enableESP(const QString &name, bool enable = true); - - /** - * @brief test if a plugin is enabled - * - * @param name name of the plugin to look up - * @return true if the plugin is enabled, false otherwise - **/ - bool isEnabled(const QString &name); - - /** - * @brief clear all additional information we stored on plugins - */ - void clearAdditionalInformation(); - - /** - * @brief reset additional information on a mod - * @param name name of the plugin to clear the information of - */ - void clearInformation(const QString &name); - - /** - * @brief add additional information on a mod (i.e. from loot) - * @param name name of the plugin to add information about - * @param message the message to add to the plugin - */ - void addInformation(const QString &name, const QString &message); - - /** - * adds information from a loot report - */ - void addLootReport(const QString& name, Loot::Plugin plugin); - - /** - * @brief test if a plugin is enabled - * - * @param index index of the plugin to look up - * @return true if the plugin is enabled, false otherwise - * @throws std::out_of_range exception is thrown if index is invalid - **/ - bool isEnabled(int index); - - /** - * @brief save the plugin status to the specified file - * - * @param lockedOrderFileName path of the lockedorder.txt to write to - **/ - void saveTo(const QString &lockedOrderFileName) const; - - /** - * @brief save the current load order - * - * the load order used by the game is defined by the last modification time which this - * function sets. An exception is newer version of skyrim where the load order is defined - * by the order of files in plugins.txt - * @param directoryStructure the root directory structure representing the virtual data directory - * @return true on success or if there was nothing to save, false if the load order can't be saved, i.e. because files are locked - * @todo since this works on actual files the load order can't be configured per-profile. Files of the same name - * in different mods can also have different load orders which makes this very intransparent - * @note also stores to disk the list of locked esps - **/ - bool saveLoadOrder(MOShared::DirectoryEntry &directoryStructure); - - /** - * @return number of enabled plugins in the list - */ - int enabledCount() const; - - int timeElapsedSinceLastChecked() const; - - QString getName(int index) const { return m_ESPs.at(index).name; } - int getPriority(int index) const { return m_ESPs.at(index).priority; } - QString getIndexPriority(int index) const; - bool isESPLocked(int index) const; - void lockESPIndex(int index, bool lock); - - static QString getColumnName(int column); - static QString getColumnToolTip(int column); - - // highlight plugins contained in the mods at the given indices - // - void highlightPlugins( - const std::vector& modIndices, - const MOShared::DirectoryEntry &directoryEntry); - - void refreshLoadOrder(); - - void disconnectSlots(); - -public: - - QStringList pluginNames() const; - PluginStates state(const QString &name) const; - void setState(const QString &name, PluginStates state); - int priority(const QString &name) const; - int loadOrder(const QString &name) const; - bool setPriority(const QString& name, int newPriority); - QStringList masters(const QString &name) const; - QString origin(const QString &name) const; - void setLoadOrder(const QStringList& pluginList); - - bool hasMasterExtension(const QString& name) const; - bool hasLightExtension(const QString& name) const; - bool isMasterFlagged(const QString& name) const; - bool isLightFlagged(const QString& name) const; - - boost::signals2::connection onRefreshed(const std::function& callback); - boost::signals2::connection onPluginMoved(const std::function& func); - boost::signals2::connection onPluginStateChanged(const std::function&)> &func); - -public: // implementation of the QAbstractTableModel interface - - virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; - virtual int columnCount(const QModelIndex &parent = QModelIndex()) const; - virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; - virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); - virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; - virtual Qt::ItemFlags flags(const QModelIndex &index) const; - virtual Qt::DropActions supportedDropActions() const { return Qt::MoveAction; } - virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent); - virtual QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const; - virtual QModelIndex parent(const QModelIndex &child) const; - -public slots: - - // enable/disable all plugins - // - void setEnabledAll(bool enabled); - - // enable/disable plugins at the given indices. - // - void setEnabled(const QModelIndexList& indices, bool enabled); - - // send plugins to the given priority - // - void sendToPriority(const QModelIndexList& indices, int priority); - - // shift the priority of mods at the given indices by the given offset - // - void shiftPluginsPriority(const QModelIndexList& indices, int offset); - - // toggle the active state of mods at the given indices - // - void toggleState(const QModelIndexList& indices); - - /** - * @brief The currently managed game has changed - * @param gamePlugin - */ - void managedGameChanged(MOBase::IPluginGame const *gamePlugin); - - /** - * @brief Generate the plugin indexes because something was changed - **/ - void generatePluginIndexes(); - -signals: - - /** - * @brief emitted when the plugin list changed, i.e. the load order was modified or a plugin was checked/unchecked - * @note this is currently only used to signal that there are changes that can be saved, it does - * not immediately cause anything to be written to disc - **/ - void esplist_changed(); - - void writePluginsList(); - - -private: - - struct ESPInfo - { - ESPInfo( - const QString &name, bool enabled, const QString &originName, - const QString &fullPath, bool hasIni, std::set archives, - bool lightSupported); - - QString name; - QString fullPath; - bool enabled; - bool forceEnabled; - int priority; - QString index; - int loadOrder; - FILETIME time; - QString originName; - bool hasMasterExtension; - bool hasLightExtension; - bool isMasterFlagged; - bool isLightFlagged; - bool modSelected; - QString author; - QString description; - bool hasIni; - std::set archives; - std::set masters; - mutable std::set masterUnset; - - bool operator < (const ESPInfo& str) const - { - return (loadOrder < str.loadOrder); - } - }; - - struct AdditionalInfo { - QStringList messages; - Loot::Plugin loot; - }; - -private: - - void syncLoadOrder(); - void updateIndices(); - - void writeLockedOrder(const QString &fileName) const; - - void readLockedOrderFrom(const QString &fileName); - void setPluginPriority(int row, int &newPriority, bool isForced=false); - void changePluginPriority(std::vector rows, int newPriority); - - void testMasters(); - - void fixPrimaryPlugins(); - void fixPriorities(); - void fixPluginRelationships(); - - int findPluginByPriority(int priority); - - /** - * @brief Notify MO2 plugins that the states of the given plugins have changed to the given state. - * - * @param pluginNames Names of the plugin. - * @param state New state of the plugin. - * - */ - void pluginStatesChanged(QStringList const& pluginNames, PluginStates state) const; - -private: - - OrganizerCore& m_Organizer; - - std::vector m_ESPs; - mutable std::map m_LastSaveHash; - - std::map m_ESPsByName; - std::vector m_ESPsByPriority; - - std::map m_LockedOrder; - - std::map m_AdditionalInfo; // maps esp names to boss information - - QString m_CurrentProfile; - QFontMetrics m_FontMetrics; - - SignalRefreshed m_Refreshed; - SignalPluginMoved m_PluginMoved; - SignalPluginStateChanged m_PluginStateChanged; - - QTemporaryFile m_TempFile; - - QElapsedTimer m_LastCheck; - - const MOBase::IPluginGame *m_GamePlugin; - - - QVariant displayData(const QModelIndex &modelIndex) const; - QVariant checkstateData(const QModelIndex &modelIndex) const; - QVariant foregroundData(const QModelIndex &modelIndex) const; - QVariant backgroundData(const QModelIndex &modelIndex) const; - QVariant fontData(const QModelIndex &modelIndex) const; - QVariant alignmentData(const QModelIndex &modelIndex) const; - QVariant tooltipData(const QModelIndex &modelIndex) const; - QVariant iconData(const QModelIndex &modelIndex) const; - - QString makeLootTooltip(const Loot::Plugin& loot) const; - bool isProblematic(const ESPInfo& esp, const AdditionalInfo* info) const; - bool hasInfo(const ESPInfo& esp, const AdditionalInfo* info) const; -}; - -#pragma warning(pop) - -#endif // PLUGINLIST_H +/* +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 PLUGINLIST_H +#define PLUGINLIST_H + +#include "loot.h" +#include "profile.h" +#include +#include + +namespace MOBase +{ +class IPluginGame; +} + +#include +#include +#include +#include +#include +#include + +#pragma warning(push) +#pragma warning(disable : 4100) +#ifndef Q_MOC_RUN +#include +#include +#endif + +#include +#include + +class OrganizerCore; + +template +class ChangeBracket +{ +public: + ChangeBracket(C* model) : m_Model(nullptr) + { + QVariant var = model->property("__aboutToChange"); + bool aboutToChange = var.isValid() && var.toBool(); + if (!aboutToChange) { + model->layoutAboutToBeChanged(); + model->setProperty("__aboutToChange", true); + m_Model = model; + } + } + ~ChangeBracket() { finish(); } + + void finish() + { + if (m_Model != nullptr) { + m_Model->layoutChanged(); + m_Model->setProperty("__aboutToChange", false); + m_Model = nullptr; + } + } + +private: + C* m_Model; +}; + +/** + * @brief model representing the plugins (.esp/.esm) in the current virtual data folder + **/ +class PluginList : public QAbstractItemModel +{ + Q_OBJECT + friend class ChangeBracket; + +public: + enum EColumn + { + COL_NAME, + COL_FLAGS, + COL_PRIORITY, + COL_MODINDEX, + + COL_LASTCOLUMN = COL_MODINDEX + }; + + using PluginStates = MOBase::IPluginList::PluginStates; + + friend class PluginListProxy; + + using SignalRefreshed = boost::signals2::signal; + using SignalPluginMoved = boost::signals2::signal; + using SignalPluginStateChanged = + boost::signals2::signal&)>; + +public: + /** + * @brief constructor + * + * @param parent parent object + **/ + PluginList(OrganizerCore& organizer); + + ~PluginList(); + + /** + * @brief does a complete refresh of the list + * + * @param profileName name of the current profile + * @param baseDirectory the root directory structure representing the virtual data + *directory + * @param lockedOrderFile list of plugins that shouldn't change load order + * @todo the profile is not used? If it was, we should pass the Profile-object instead + **/ + void refresh(const QString& profileName, + const MOShared::DirectoryEntry& baseDirectory, + const QString& lockedOrderFile, bool refresh); + + /** + * @brief enable a plugin based on its name + * + * @param name name of the plugin to enable + * @param enable set to true to enable the esp, false to disable it + **/ + void enableESP(const QString& name, bool enable = true); + + /** + * @brief test if a plugin is enabled + * + * @param name name of the plugin to look up + * @return true if the plugin is enabled, false otherwise + **/ + bool isEnabled(const QString& name); + + /** + * @brief clear all additional information we stored on plugins + */ + void clearAdditionalInformation(); + + /** + * @brief reset additional information on a mod + * @param name name of the plugin to clear the information of + */ + void clearInformation(const QString& name); + + /** + * @brief add additional information on a mod (i.e. from loot) + * @param name name of the plugin to add information about + * @param message the message to add to the plugin + */ + void addInformation(const QString& name, const QString& message); + + /** + * adds information from a loot report + */ + void addLootReport(const QString& name, Loot::Plugin plugin); + + /** + * @brief test if a plugin is enabled + * + * @param index index of the plugin to look up + * @return true if the plugin is enabled, false otherwise + * @throws std::out_of_range exception is thrown if index is invalid + **/ + bool isEnabled(int index); + + /** + * @brief save the plugin status to the specified file + * + * @param lockedOrderFileName path of the lockedorder.txt to write to + **/ + void saveTo(const QString& lockedOrderFileName) const; + + /** + * @brief save the current load order + * + * the load order used by the game is defined by the last modification time which this + * function sets. An exception is newer version of skyrim where the load order is + *defined by the order of files in plugins.txt + * @param directoryStructure the root directory structure representing the virtual + *data directory + * @return true on success or if there was nothing to save, false if the load order + *can't be saved, i.e. because files are locked + * @todo since this works on actual files the load order can't be configured + *per-profile. Files of the same name in different mods can also have different load + *orders which makes this very intransparent + * @note also stores to disk the list of locked esps + **/ + bool saveLoadOrder(MOShared::DirectoryEntry& directoryStructure); + + /** + * @return number of enabled plugins in the list + */ + int enabledCount() const; + + int timeElapsedSinceLastChecked() const; + + QString getName(int index) const { return m_ESPs.at(index).name; } + int getPriority(int index) const { return m_ESPs.at(index).priority; } + QString getIndexPriority(int index) const; + bool isESPLocked(int index) const; + void lockESPIndex(int index, bool lock); + + static QString getColumnName(int column); + static QString getColumnToolTip(int column); + + // highlight plugins contained in the mods at the given indices + // + void highlightPlugins(const std::vector& modIndices, + const MOShared::DirectoryEntry& directoryEntry); + + void refreshLoadOrder(); + + void disconnectSlots(); + +public: + QStringList pluginNames() const; + PluginStates state(const QString& name) const; + void setState(const QString& name, PluginStates state); + int priority(const QString& name) const; + int loadOrder(const QString& name) const; + bool setPriority(const QString& name, int newPriority); + QStringList masters(const QString& name) const; + QString origin(const QString& name) const; + void setLoadOrder(const QStringList& pluginList); + + bool hasMasterExtension(const QString& name) const; + bool hasLightExtension(const QString& name) const; + bool isMasterFlagged(const QString& name) const; + bool isLightFlagged(const QString& name) const; + + boost::signals2::connection onRefreshed(const std::function& callback); + boost::signals2::connection + onPluginMoved(const std::function& func); + boost::signals2::connection onPluginStateChanged( + const std::function&)>& func); + +public: // implementation of the QAbstractTableModel interface + virtual int rowCount(const QModelIndex& parent = QModelIndex()) const; + virtual int columnCount(const QModelIndex& parent = QModelIndex()) const; + virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; + virtual bool setData(const QModelIndex& index, const QVariant& value, + int role = Qt::EditRole); + virtual QVariant headerData(int section, Qt::Orientation orientation, + int role = Qt::DisplayRole) const; + virtual Qt::ItemFlags flags(const QModelIndex& index) const; + virtual Qt::DropActions supportedDropActions() const { return Qt::MoveAction; } + virtual bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, + int column, const QModelIndex& parent); + virtual QModelIndex index(int row, int column, + const QModelIndex& parent = QModelIndex()) const; + virtual QModelIndex parent(const QModelIndex& child) const; + +public slots: + + // enable/disable all plugins + // + void setEnabledAll(bool enabled); + + // enable/disable plugins at the given indices. + // + void setEnabled(const QModelIndexList& indices, bool enabled); + + // send plugins to the given priority + // + void sendToPriority(const QModelIndexList& indices, int priority); + + // shift the priority of mods at the given indices by the given offset + // + void shiftPluginsPriority(const QModelIndexList& indices, int offset); + + // toggle the active state of mods at the given indices + // + void toggleState(const QModelIndexList& indices); + + /** + * @brief The currently managed game has changed + * @param gamePlugin + */ + void managedGameChanged(MOBase::IPluginGame const* gamePlugin); + + /** + * @brief Generate the plugin indexes because something was changed + **/ + void generatePluginIndexes(); + +signals: + + /** + * @brief emitted when the plugin list changed, i.e. the load order was modified or a + *plugin was checked/unchecked + * @note this is currently only used to signal that there are changes that can be + *saved, it does not immediately cause anything to be written to disc + **/ + void esplist_changed(); + + void writePluginsList(); + +private: + struct ESPInfo + { + ESPInfo(const QString& name, bool enabled, const QString& originName, + const QString& fullPath, bool hasIni, std::set archives, + bool lightSupported); + + QString name; + QString fullPath; + bool enabled; + bool forceEnabled; + int priority; + QString index; + int loadOrder; + FILETIME time; + QString originName; + bool hasMasterExtension; + bool hasLightExtension; + bool isMasterFlagged; + bool isLightFlagged; + bool modSelected; + QString author; + QString description; + bool hasIni; + std::set archives; + std::set masters; + mutable std::set masterUnset; + + bool operator<(const ESPInfo& str) const { return (loadOrder < str.loadOrder); } + }; + + struct AdditionalInfo + { + QStringList messages; + Loot::Plugin loot; + }; + +private: + void syncLoadOrder(); + void updateIndices(); + + void writeLockedOrder(const QString& fileName) const; + + void readLockedOrderFrom(const QString& fileName); + void setPluginPriority(int row, int& newPriority, bool isForced = false); + void changePluginPriority(std::vector rows, int newPriority); + + void testMasters(); + + void fixPrimaryPlugins(); + void fixPriorities(); + void fixPluginRelationships(); + + int findPluginByPriority(int priority); + + /** + * @brief Notify MO2 plugins that the states of the given plugins have changed to the + * given state. + * + * @param pluginNames Names of the plugin. + * @param state New state of the plugin. + * + */ + void pluginStatesChanged(QStringList const& pluginNames, PluginStates state) const; + +private: + OrganizerCore& m_Organizer; + + std::vector m_ESPs; + mutable std::map m_LastSaveHash; + + std::map m_ESPsByName; + std::vector m_ESPsByPriority; + + std::map m_LockedOrder; + + std::map + m_AdditionalInfo; // maps esp names to boss information + + QString m_CurrentProfile; + QFontMetrics m_FontMetrics; + + SignalRefreshed m_Refreshed; + SignalPluginMoved m_PluginMoved; + SignalPluginStateChanged m_PluginStateChanged; + + QTemporaryFile m_TempFile; + + QElapsedTimer m_LastCheck; + + const MOBase::IPluginGame* m_GamePlugin; + + QVariant displayData(const QModelIndex& modelIndex) const; + QVariant checkstateData(const QModelIndex& modelIndex) const; + QVariant foregroundData(const QModelIndex& modelIndex) const; + QVariant backgroundData(const QModelIndex& modelIndex) const; + QVariant fontData(const QModelIndex& modelIndex) const; + QVariant alignmentData(const QModelIndex& modelIndex) const; + QVariant tooltipData(const QModelIndex& modelIndex) const; + QVariant iconData(const QModelIndex& modelIndex) const; + + QString makeLootTooltip(const Loot::Plugin& loot) const; + bool isProblematic(const ESPInfo& esp, const AdditionalInfo* info) const; + bool hasInfo(const ESPInfo& esp, const AdditionalInfo* info) const; +}; + +#pragma warning(pop) + +#endif // PLUGINLIST_H diff --git a/src/pluginlistcontextmenu.cpp b/src/pluginlistcontextmenu.cpp index c6d4ecece..80ad75f39 100644 --- a/src/pluginlistcontextmenu.cpp +++ b/src/pluginlistcontextmenu.cpp @@ -3,43 +3,45 @@ #include #include -#include "pluginlistview.h" #include "organizercore.h" +#include "pluginlistview.h" using namespace MOBase; -PluginListContextMenu::PluginListContextMenu( - const QModelIndex& index, OrganizerCore& core, PluginListView* view) : - QMenu(view) - , m_core(core) - , m_index(index.model() == view->model() ? view->indexViewToModel(index) : index) - , m_view(view) +PluginListContextMenu::PluginListContextMenu(const QModelIndex& index, + OrganizerCore& core, PluginListView* view) + : QMenu(view), m_core(core), + m_index(index.model() == view->model() ? view->indexViewToModel(index) : index), + m_view(view) { if (view->selectionModel()->hasSelection()) { m_selected = view->indexViewToModel(view->selectionModel()->selectedRows()); - } - else if (index.isValid()) { - m_selected = { index }; + } else if (index.isValid()) { + m_selected = {index}; } if (!m_selected.isEmpty()) { - addAction(tr("Enable selected"), [=]() { m_core.pluginList()->setEnabled(m_selected, true); }); - addAction(tr("Disable selected"), [=]() { m_core.pluginList()->setEnabled(m_selected, false); }); + addAction(tr("Enable selected"), [=]() { + m_core.pluginList()->setEnabled(m_selected, true); + }); + addAction(tr("Disable selected"), [=]() { + m_core.pluginList()->setEnabled(m_selected, false); + }); addSeparator(); } addAction(tr("Enable all"), [=]() { - if (QMessageBox::question( - m_view->topLevelWidget(), tr("Confirm"), tr("Really enable all plugins?"), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + if (QMessageBox::question(m_view->topLevelWidget(), tr("Confirm"), + tr("Really enable all plugins?"), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { m_core.pluginList()->setEnabledAll(true); } }); addAction(tr("Disable all"), [=]() { - if (QMessageBox::question( - m_view->topLevelWidget(), tr("Confirm"), tr("Really disable all plugins?"), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + if (QMessageBox::question(m_view->topLevelWidget(), tr("Confirm"), + tr("Really disable all plugins?"), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { m_core.pluginList()->setEnabledAll(false); } }); @@ -50,63 +52,77 @@ PluginListContextMenu::PluginListContextMenu( addSeparator(); - bool hasLocked = false; + bool hasLocked = false; bool hasUnlocked = false; for (auto& idx : m_selected) { if (m_core.pluginList()->isEnabled(idx.row())) { if (m_core.pluginList()->isESPLocked(idx.row())) { hasLocked = true; - } - else { + } else { hasUnlocked = true; } } } if (hasLocked) { - addAction(tr("Unlock load order"), [=]() { setESPLock(m_selected, false); }); + addAction(tr("Unlock load order"), [=]() { + setESPLock(m_selected, false); + }); } if (hasUnlocked) { - addAction(tr("Lock load order"), [=]() { setESPLock(m_selected, true); }); + addAction(tr("Lock load order"), [=]() { + setESPLock(m_selected, true); + }); } } if (m_index.isValid()) { addSeparator(); - unsigned int modInfoIndex = ModInfo::getIndex(m_core.pluginList()->origin(m_index.data().toString())); + unsigned int modInfoIndex = + ModInfo::getIndex(m_core.pluginList()->origin(m_index.data().toString())); // this is to avoid showing the option on game files like skyrim.esm if (modInfoIndex != UINT_MAX) { - addAction(tr("Open Origin in Explorer"), [=]() { openOriginExplorer(m_selected); }); - ModInfo::Ptr modInfo = ModInfo::getByIndex(modInfoIndex); + addAction(tr("Open Origin in Explorer"), [=]() { + openOriginExplorer(m_selected); + }); + ModInfo::Ptr modInfo = ModInfo::getByIndex(modInfoIndex); std::vector flags = modInfo->getFlags(); if (!modInfo->isForeign() && m_selected.size() == 1) { - QAction* infoAction = addAction(tr("Open Origin Info..."), [=]() { openOriginInformation(index); }); + QAction* infoAction = addAction(tr("Open Origin Info..."), [=]() { + openOriginInformation(index); + }); setDefaultAction(infoAction); } } } - } QMenu* PluginListContextMenu::createSendToContextMenu() { QMenu* menu = new QMenu(m_view); menu->setTitle(tr("Send to... ")); - menu->addAction(tr("Top"), [=]() { m_core.pluginList()->sendToPriority(m_selected, 0); }); - menu->addAction(tr("Bottom"), [=]() { m_core.pluginList()->sendToPriority(m_selected, INT_MAX); }); - menu->addAction(tr("Priority..."), [=]() { sendPluginsToPriority(m_selected); }); + menu->addAction(tr("Top"), [=]() { + m_core.pluginList()->sendToPriority(m_selected, 0); + }); + menu->addAction(tr("Bottom"), [=]() { + m_core.pluginList()->sendToPriority(m_selected, INT_MAX); + }); + menu->addAction(tr("Priority..."), [=]() { + sendPluginsToPriority(m_selected); + }); return menu; } void PluginListContextMenu::sendPluginsToPriority(const QModelIndexList& indices) { bool ok; - int newPriority = QInputDialog::getInt(m_view->topLevelWidget(), - tr("Set Priority"), tr("Set the priority of the selected plugins"), - 0, 0, INT_MAX, 1, &ok); - if (!ok) return; + int newPriority = QInputDialog::getInt(m_view->topLevelWidget(), tr("Set Priority"), + tr("Set the priority of the selected plugins"), + 0, 0, INT_MAX, 1, &ok); + if (!ok) + return; m_core.pluginList()->sendToPriority(m_selected, newPriority); } @@ -123,7 +139,7 @@ void PluginListContextMenu::setESPLock(const QModelIndexList& indices, bool lock void PluginListContextMenu::openOriginExplorer(const QModelIndexList& indices) { for (auto& idx : indices) { - QString fileName = idx.data().toString(); + QString fileName = idx.data().toString(); unsigned int modIndex = ModInfo::getIndex(m_core.pluginList()->origin(fileName)); if (modIndex == UINT_MAX) { continue; @@ -136,15 +152,14 @@ void PluginListContextMenu::openOriginExplorer(const QModelIndexList& indices) void PluginListContextMenu::openOriginInformation(const QModelIndex& index) { try { - QString fileName = index.data().toString(); + QString fileName = index.data().toString(); unsigned int modIndex = ModInfo::getIndex(m_core.pluginList()->origin(fileName)); - ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); + ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); if (modInfo->isRegular() || modInfo->isOverwrite()) { emit openModInformation(modIndex); } - } - catch (const std::exception& e) { + } catch (const std::exception& e) { reportError(e.what()); } } diff --git a/src/pluginlistcontextmenu.h b/src/pluginlistcontextmenu.h index 0a9a01feb..f7d9757fe 100644 --- a/src/pluginlistcontextmenu.h +++ b/src/pluginlistcontextmenu.h @@ -15,11 +15,11 @@ class PluginListContextMenu : public QMenu Q_OBJECT public: - - // creates a new context menu, the given index is the one for the click and should be valid + // creates a new context menu, the given index is the one for the click and should be + // valid // - PluginListContextMenu( - const QModelIndex& index, OrganizerCore& core, PluginListView* modListView); + PluginListContextMenu(const QModelIndex& index, OrganizerCore& core, + PluginListView* modListView); signals: @@ -28,7 +28,6 @@ class PluginListContextMenu : public QMenu void openModInformation(unsigned int modIndex); public: - // create the "Send to... " context menu // QMenu* createSendToContextMenu(); @@ -43,12 +42,10 @@ class PluginListContextMenu : public QMenu void openOriginExplorer(const QModelIndexList& indices); void openOriginInformation(const QModelIndex& index); - OrganizerCore& m_core; QModelIndex m_index; QModelIndexList m_selected; PluginListView* m_view; - }; #endif diff --git a/src/pluginlistproxy.cpp b/src/pluginlistproxy.cpp index 604aab3ee..e5fa5e013 100644 --- a/src/pluginlistproxy.cpp +++ b/src/pluginlistproxy.cpp @@ -5,8 +5,9 @@ using namespace MOBase; using namespace MOShared; -PluginListProxy::PluginListProxy(OrganizerProxy* oproxy, PluginList* pluginlist) : - m_OrganizerProxy(oproxy), m_Proxied(pluginlist) { } +PluginListProxy::PluginListProxy(OrganizerProxy* oproxy, PluginList* pluginlist) + : m_OrganizerProxy(oproxy), m_Proxied(pluginlist) +{} PluginListProxy::~PluginListProxy() { @@ -15,9 +16,12 @@ PluginListProxy::~PluginListProxy() void PluginListProxy::connectSignals() { - m_Connections.push_back(m_Proxied->onRefreshed(callSignalIfPluginActive(m_OrganizerProxy, m_Refreshed))); - m_Connections.push_back(m_Proxied->onPluginMoved(callSignalIfPluginActive(m_OrganizerProxy, m_PluginMoved))); - m_Connections.push_back(m_Proxied->onPluginStateChanged(callSignalIfPluginActive(m_OrganizerProxy, m_PluginStateChanged))); + m_Connections.push_back( + m_Proxied->onRefreshed(callSignalIfPluginActive(m_OrganizerProxy, m_Refreshed))); + m_Connections.push_back(m_Proxied->onPluginMoved( + callSignalIfPluginActive(m_OrganizerProxy, m_PluginMoved))); + m_Connections.push_back(m_Proxied->onPluginStateChanged( + callSignalIfPluginActive(m_OrganizerProxy, m_PluginStateChanged))); } void PluginListProxy::disconnectSignals() @@ -83,12 +87,14 @@ bool PluginListProxy::onRefreshed(const std::function& func) return m_Refreshed.connect(func).connected(); } -bool PluginListProxy::onPluginMoved(const std::function& func) +bool PluginListProxy::onPluginMoved( + const std::function& func) { return m_PluginMoved.connect(func).connected(); } -bool PluginListProxy::onPluginStateChanged(const std::function&)> &func) +bool PluginListProxy::onPluginStateChanged( + const std::function&)>& func) { return m_PluginStateChanged.connect(func).connected(); } diff --git a/src/pluginlistproxy.h b/src/pluginlistproxy.h index 202b8484f..63602043e 100644 --- a/src/pluginlistproxy.h +++ b/src/pluginlistproxy.h @@ -1,15 +1,14 @@ #ifndef PLUGINLISTPROXY_H #define PLUGINLISTPROXY_H -#include #include "pluginlist.h" +#include class OrganizerProxy; class PluginListProxy : public MOBase::IPluginList { public: - PluginListProxy(OrganizerProxy* oproxy, PluginList* pluginlist); virtual ~PluginListProxy(); @@ -25,8 +24,10 @@ class PluginListProxy : public MOBase::IPluginList QString origin(const QString& name) const override; bool onRefreshed(const std::function& callback) override; - bool onPluginMoved(const std::function& func) override; - bool onPluginStateChanged(const std::function&)>& func) override; + bool + onPluginMoved(const std::function& func) override; + bool onPluginStateChanged( + const std::function&)>& func) override; bool hasMasterExtension(const QString& name) const override; bool hasLightExtension(const QString& name) const override; @@ -34,7 +35,6 @@ class PluginListProxy : public MOBase::IPluginList bool isLightFlagged(const QString& name) const override; private: - friend class OrganizerProxy; // See OrganizerProxy::connectSignals(). @@ -51,4 +51,4 @@ class PluginListProxy : public MOBase::IPluginList std::vector m_Connections; }; -#endif // ORGANIZERPROXY_H +#endif // ORGANIZERPROXY_H diff --git a/src/pluginlistsortproxy.cpp b/src/pluginlistsortproxy.cpp index 308a13984..5083af3f2 100644 --- a/src/pluginlistsortproxy.cpp +++ b/src/pluginlistsortproxy.cpp @@ -1,153 +1,155 @@ -/* -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 "pluginlistsortproxy.h" -#include "messagedialog.h" -#include -#include -#include -#include -#include - - -PluginListSortProxy::PluginListSortProxy(QObject *parent) - : QSortFilterProxyModel(parent), - m_SortIndex(0), m_SortOrder(Qt::AscendingOrder) -{ - m_EnabledColumns.set(PluginList::COL_NAME); - m_EnabledColumns.set(PluginList::COL_PRIORITY); - m_EnabledColumns.set(PluginList::COL_MODINDEX); - this->setDynamicSortFilter(true); -} - -void PluginListSortProxy::setEnabledColumns(unsigned int columns) -{ - emit layoutAboutToBeChanged(); - for (int i = 0; i <= PluginList::COL_LASTCOLUMN; ++i) { - m_EnabledColumns.set(i, (columns & (1 << i)) != 0); - } - emit layoutChanged(); -} - -void PluginListSortProxy::updateFilter(const QString &filter) -{ - m_CurrentFilter = filter; - invalidateFilter(); -} - -bool PluginListSortProxy::filterAcceptsRow(int row, const QModelIndex&) const -{ - return filterMatchesPlugin(sourceModel()->data(sourceModel()->index(row, 0)).toString()); -} - -bool PluginListSortProxy::lessThan(const QModelIndex &left, - const QModelIndex &right) const -{ - PluginList *plugins = qobject_cast(sourceModel()); - switch (left.column()) { - case PluginList::COL_NAME: { - return QString::compare(plugins->getName(left.row()), plugins->getName(right.row()), Qt::CaseInsensitive) < 0; - } break; - case PluginList::COL_FLAGS: { - QVariantList lhsList = left.data(Qt::UserRole + 1).toList(); - QVariantList rhsList = right.data(Qt::UserRole + 1).toList(); - if (lhsList.size() != rhsList.size()) { - return lhsList.size() < rhsList.size(); - } else { - for (int i = 0; i < lhsList.size(); ++i) { - if (lhsList.at(i) != rhsList.at(i)) { - return lhsList.at(i).toString() < rhsList.at(i).toString(); - } - } - return false; - } - } break; - case PluginList::COL_MODINDEX: { - QString leftVal = plugins->getIndexPriority(left.row()); - QString rightVal = plugins->getIndexPriority(right.row()); - return leftVal < rightVal; - } break; - default: { - return plugins->getPriority(left.row()) < plugins->getPriority(right.row()); - } break; - } -} - -bool PluginListSortProxy::dropMimeData(const QMimeData *data, Qt::DropAction action, - int row, int column, const QModelIndex &parent) -{ - if ((sortColumn() != PluginList::COL_PRIORITY) - && (sortColumn() != PluginList::COL_MODINDEX)) { - QWidget *wid = qApp->activeWindow()->findChild("espList"); - MessageDialog::showMessage(tr("Drag&Drop is only supported when sorting by priority or mod index"), wid); - return false; - } - - if ((row == -1) && (column == -1)) { - return this->sourceModel()->dropMimeData(data, action, -1, -1, mapToSource(parent)); - } - // in the regular model, when dropping between rows, the row-value passed to - // the sourceModel is inconsistent between ascending and descending ordering. - // This should fix that - if (sortOrder() == Qt::DescendingOrder) { - --row; - } - - QModelIndex proxyIndex = index(row, column, parent); - QModelIndex sourceIndex = mapToSource(proxyIndex); - return this->sourceModel()->dropMimeData(data, action, sourceIndex.row(), sourceIndex.column(), - sourceIndex.parent()); -} - -bool PluginListSortProxy::filterMatchesPlugin(const QString &plugin) const -{ - if (!m_CurrentFilter.isEmpty()) { - - bool display = false; - QString filterCopy = QString(m_CurrentFilter); - filterCopy.replace("||", ";").replace("OR", ";").replace("|", ";"); - QStringList ORList = filterCopy.split(";", Qt::SkipEmptyParts); - - bool segmentGood = true; - - //split in ORSegments that internally use AND logic - for (auto& ORSegment : ORList) { - QStringList ANDKeywords = ORSegment.split(" ", Qt::SkipEmptyParts); - segmentGood = true; - - //check each word in the segment for match, each word needs to be matched but it doesn't matter where. - for (auto& currentKeyword : ANDKeywords) { - if (!plugin.contains(currentKeyword, Qt::CaseInsensitive)) { - segmentGood = false; - break; - } - } - - if (segmentGood) { - //the last AND loop didn't break so the ORSegments is true so mod matches filter - return true; - } - - }//for ORList loop - - return false; - } - else - return true; -} +/* +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 "pluginlistsortproxy.h" +#include "messagedialog.h" +#include +#include +#include +#include +#include + +PluginListSortProxy::PluginListSortProxy(QObject* parent) + : QSortFilterProxyModel(parent), m_SortIndex(0), m_SortOrder(Qt::AscendingOrder) +{ + m_EnabledColumns.set(PluginList::COL_NAME); + m_EnabledColumns.set(PluginList::COL_PRIORITY); + m_EnabledColumns.set(PluginList::COL_MODINDEX); + this->setDynamicSortFilter(true); +} + +void PluginListSortProxy::setEnabledColumns(unsigned int columns) +{ + emit layoutAboutToBeChanged(); + for (int i = 0; i <= PluginList::COL_LASTCOLUMN; ++i) { + m_EnabledColumns.set(i, (columns & (1 << i)) != 0); + } + emit layoutChanged(); +} + +void PluginListSortProxy::updateFilter(const QString& filter) +{ + m_CurrentFilter = filter; + invalidateFilter(); +} + +bool PluginListSortProxy::filterAcceptsRow(int row, const QModelIndex&) const +{ + return filterMatchesPlugin( + sourceModel()->data(sourceModel()->index(row, 0)).toString()); +} + +bool PluginListSortProxy::lessThan(const QModelIndex& left, + const QModelIndex& right) const +{ + PluginList* plugins = qobject_cast(sourceModel()); + switch (left.column()) { + case PluginList::COL_NAME: { + return QString::compare(plugins->getName(left.row()), plugins->getName(right.row()), + Qt::CaseInsensitive) < 0; + } break; + case PluginList::COL_FLAGS: { + QVariantList lhsList = left.data(Qt::UserRole + 1).toList(); + QVariantList rhsList = right.data(Qt::UserRole + 1).toList(); + if (lhsList.size() != rhsList.size()) { + return lhsList.size() < rhsList.size(); + } else { + for (int i = 0; i < lhsList.size(); ++i) { + if (lhsList.at(i) != rhsList.at(i)) { + return lhsList.at(i).toString() < rhsList.at(i).toString(); + } + } + return false; + } + } break; + case PluginList::COL_MODINDEX: { + QString leftVal = plugins->getIndexPriority(left.row()); + QString rightVal = plugins->getIndexPriority(right.row()); + return leftVal < rightVal; + } break; + default: { + return plugins->getPriority(left.row()) < plugins->getPriority(right.row()); + } break; + } +} + +bool PluginListSortProxy::dropMimeData(const QMimeData* data, Qt::DropAction action, + int row, int column, const QModelIndex& parent) +{ + if ((sortColumn() != PluginList::COL_PRIORITY) && + (sortColumn() != PluginList::COL_MODINDEX)) { + QWidget* wid = qApp->activeWindow()->findChild("espList"); + MessageDialog::showMessage( + tr("Drag&Drop is only supported when sorting by priority or mod index"), wid); + return false; + } + + if ((row == -1) && (column == -1)) { + return this->sourceModel()->dropMimeData(data, action, -1, -1, mapToSource(parent)); + } + // in the regular model, when dropping between rows, the row-value passed to + // the sourceModel is inconsistent between ascending and descending ordering. + // This should fix that + if (sortOrder() == Qt::DescendingOrder) { + --row; + } + + QModelIndex proxyIndex = index(row, column, parent); + QModelIndex sourceIndex = mapToSource(proxyIndex); + return this->sourceModel()->dropMimeData(data, action, sourceIndex.row(), + sourceIndex.column(), sourceIndex.parent()); +} + +bool PluginListSortProxy::filterMatchesPlugin(const QString& plugin) const +{ + if (!m_CurrentFilter.isEmpty()) { + + bool display = false; + QString filterCopy = QString(m_CurrentFilter); + filterCopy.replace("||", ";").replace("OR", ";").replace("|", ";"); + QStringList ORList = filterCopy.split(";", Qt::SkipEmptyParts); + + bool segmentGood = true; + + // split in ORSegments that internally use AND logic + for (auto& ORSegment : ORList) { + QStringList ANDKeywords = ORSegment.split(" ", Qt::SkipEmptyParts); + segmentGood = true; + + // check each word in the segment for match, each word needs to be matched but it + // doesn't matter where. + for (auto& currentKeyword : ANDKeywords) { + if (!plugin.contains(currentKeyword, Qt::CaseInsensitive)) { + segmentGood = false; + break; + } + } + + if (segmentGood) { + // the last AND loop didn't break so the ORSegments is true so mod matches + // filter + return true; + } + + } // for ORList loop + + return false; + } else + return true; +} diff --git a/src/pluginlistsortproxy.h b/src/pluginlistsortproxy.h index 2acf83b39..6a65c21db 100644 --- a/src/pluginlistsortproxy.h +++ b/src/pluginlistsortproxy.h @@ -1,68 +1,63 @@ -/* -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 PLUGINLISTSORTPROXY_H -#define PLUGINLISTSORTPROXY_H - - -#include -#include -#include "pluginlist.h" - - -class PluginListSortProxy : public QSortFilterProxyModel -{ - Q_OBJECT -public: - - enum ESorting { - SORT_ASCENDING, - SORT_DESCENDING - }; - -public: - - explicit PluginListSortProxy(QObject *parent = 0); - - void setEnabledColumns(unsigned int columns); - - virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent); - - bool filterMatchesPlugin(const QString &plugin) const; - -public slots: - - void updateFilter(const QString &filter); - -protected: - - virtual bool filterAcceptsRow(int row, const QModelIndex &parent) const; - virtual bool lessThan(const QModelIndex &left, const QModelIndex &right) const; - -private: - - int m_SortIndex; - Qt::SortOrder m_SortOrder; - - std::bitset m_EnabledColumns; - QString m_CurrentFilter; - -}; - -#endif // PLUGINLISTSORTPROXY_H +/* +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 PLUGINLISTSORTPROXY_H +#define PLUGINLISTSORTPROXY_H + +#include "pluginlist.h" +#include +#include + +class PluginListSortProxy : public QSortFilterProxyModel +{ + Q_OBJECT +public: + enum ESorting + { + SORT_ASCENDING, + SORT_DESCENDING + }; + +public: + explicit PluginListSortProxy(QObject* parent = 0); + + void setEnabledColumns(unsigned int columns); + + virtual bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, + int column, const QModelIndex& parent); + + bool filterMatchesPlugin(const QString& plugin) const; + +public slots: + + void updateFilter(const QString& filter); + +protected: + virtual bool filterAcceptsRow(int row, const QModelIndex& parent) const; + virtual bool lessThan(const QModelIndex& left, const QModelIndex& right) const; + +private: + int m_SortIndex; + Qt::SortOrder m_SortOrder; + + std::bitset m_EnabledColumns; + QString m_CurrentFilter; +}; + +#endif // PLUGINLISTSORTPROXY_H diff --git a/src/pluginlistview.cpp b/src/pluginlistview.cpp index c7183d0b0..a0a0162fb 100644 --- a/src/pluginlistview.cpp +++ b/src/pluginlistview.cpp @@ -1,29 +1,28 @@ #include "pluginlistview.h" -#include #include +#include #include #include -#include "mainwindow.h" -#include "ui_mainwindow.h" -#include "organizercore.h" -#include "pluginlistsortproxy.h" -#include "pluginlistcontextmenu.h" -#include "modlistview.h" #include "copyeventfilter.h" -#include "modlistviewactions.h" #include "genericicondelegate.h" +#include "mainwindow.h" #include "modelutils.h" +#include "modlistview.h" +#include "modlistviewactions.h" +#include "organizercore.h" +#include "pluginlistcontextmenu.h" +#include "pluginlistsortproxy.h" +#include "ui_mainwindow.h" using namespace MOBase; -PluginListView::PluginListView(QWidget *parent) - : QTreeView(parent) - , m_sortProxy(nullptr) - , m_Scrollbar(new ViewMarkingScrollBar(this, Qt::BackgroundRole)) - , m_didUpdateMasterList(false) +PluginListView::PluginListView(QWidget* parent) + : QTreeView(parent), m_sortProxy(nullptr), + m_Scrollbar(new ViewMarkingScrollBar(this, Qt::BackgroundRole)), + m_didUpdateMasterList(false) { setVerticalScrollBar(m_Scrollbar); MOBase::setCustomizableColumns(this); @@ -57,31 +56,29 @@ QModelIndexList PluginListView::indexViewToModel(const QModelIndexList& index) c void PluginListView::updatePluginCount() { - int activeMasterCount = 0; + int activeMasterCount = 0; int activeLightMasterCount = 0; - int activeRegularCount = 0; - int masterCount = 0; - int lightMasterCount = 0; - int regularCount = 0; - int activeVisibleCount = 0; + int activeRegularCount = 0; + int masterCount = 0; + int lightMasterCount = 0; + int regularCount = 0; + int activeVisibleCount = 0; PluginList* list = m_core->pluginList(); - QString filter = ui.filter->text(); + QString filter = ui.filter->text(); for (QString plugin : list->pluginNames()) { - bool active = list->isEnabled(plugin); + bool active = list->isEnabled(plugin); bool visible = m_sortProxy->filterMatchesPlugin(plugin); if (list->hasLightExtension(plugin) || list->isLightFlagged(plugin)) { lightMasterCount++; activeLightMasterCount += active; activeVisibleCount += visible && active; - } - else if (list->hasMasterExtension(plugin) || list->isMasterFlagged(plugin)) { + } else if (list->hasMasterExtension(plugin) || list->isMasterFlagged(plugin)) { masterCount++; activeMasterCount += active; activeVisibleCount += visible && active; - } - else { + } else { regularCount++; activeRegularCount += active; activeVisibleCount += visible && active; @@ -89,23 +86,30 @@ void PluginListView::updatePluginCount() } int activeCount = activeMasterCount + activeLightMasterCount + activeRegularCount; - int totalCount = masterCount + lightMasterCount + regularCount; + int totalCount = masterCount + lightMasterCount + regularCount; ui.counter->display(activeVisibleCount); - ui.counter->setToolTip(tr("" - "" - "" - "" - "" - "" - "" - "
              TypeActive Total
              All plugins:%1 %2
              ESMs:%3 %4
              ESPs:%7 %8
              ESMs+ESPs:%9 %10
              ESLs:%5 %6
              ") - .arg(activeCount).arg(totalCount) - .arg(activeMasterCount).arg(masterCount) - .arg(activeLightMasterCount).arg(lightMasterCount) - .arg(activeRegularCount).arg(regularCount) - .arg(activeMasterCount + activeRegularCount).arg(masterCount + regularCount) - ); + ui.counter->setToolTip( + tr("" + "" + "" + "" + "" + "" + "" + "
              TypeActive Total
              All plugins:%1 %2
              ESMs:%3 %4
              ESPs:%7 %8
              ESMs+ESPs:%9 %10
              ESLs:%5 %6
              ") + .arg(activeCount) + .arg(totalCount) + .arg(activeMasterCount) + .arg(masterCount) + .arg(activeLightMasterCount) + .arg(lightMasterCount) + .arg(activeRegularCount) + .arg(regularCount) + .arg(activeMasterCount + activeRegularCount) + .arg(masterCount + regularCount)); } void PluginListView::onFilterChanged(const QString& filter) @@ -113,8 +117,7 @@ void PluginListView::onFilterChanged(const QString& filter) if (!filter.isEmpty()) { setStyleSheet("QTreeView { border: 2px ridge #f00; }"); ui.counter->setStyleSheet("QLCDNumber { border: 2px ridge #f00; }"); - } - else { + } else { setStyleSheet(""); ui.counter->setStyleSheet(""); } @@ -128,17 +131,16 @@ void PluginListView::onSortButtonClicked() auto r = QMessageBox::No; if (offline) { - r = QMessageBox::question( - topLevelWidget(), tr("Sorting plugins"), - tr("Are you sure you want to sort your plugins list?") + "\r\n\r\n" + - tr("Note: You are currently in offline mode and LOOT will not update the master list."), - QMessageBox::Yes | QMessageBox::No); - } - else { - r = QMessageBox::question( - topLevelWidget(), tr("Sorting plugins"), - tr("Are you sure you want to sort your plugins list?"), - QMessageBox::Yes | QMessageBox::No); + r = QMessageBox::question(topLevelWidget(), tr("Sorting plugins"), + tr("Are you sure you want to sort your plugins list?") + + "\r\n\r\n" + + tr("Note: You are currently in offline mode and LOOT " + "will not update the master list."), + QMessageBox::Yes | QMessageBox::No); + } else { + r = QMessageBox::question(topLevelWidget(), tr("Sorting plugins"), + tr("Are you sure you want to sort your plugins list?"), + QMessageBox::Yes | QMessageBox::No); } if (r != QMessageBox::Yes) { @@ -148,7 +150,9 @@ void PluginListView::onSortButtonClicked() m_core->savePluginList(); topLevelWidget()->setEnabled(false); - Guard g([=]() { topLevelWidget()->setEnabled(true); }); + Guard g([=]() { + topLevelWidget()->setEnabled(true); + }); // don't try to update the master list in offline mode const bool didUpdateMasterList = offline ? true : m_didUpdateMasterList; @@ -166,22 +170,24 @@ void PluginListView::onSortButtonClicked() std::pair PluginListView::selected() const { - return { indexViewToModel(currentIndex()), indexViewToModel(selectionModel()->selectedRows()) }; + return {indexViewToModel(currentIndex()), + indexViewToModel(selectionModel()->selectedRows())}; } -void PluginListView::setSelected(const QModelIndex& current, const QModelIndexList& selected) +void PluginListView::setSelected(const QModelIndex& current, + const QModelIndexList& selected) { setCurrentIndex(indexModelToView(current)); for (auto idx : selected) { - selectionModel()->select(indexModelToView(idx), QItemSelectionModel::Select | QItemSelectionModel::Rows); + selectionModel()->select(indexModelToView(idx), + QItemSelectionModel::Select | QItemSelectionModel::Rows); } } - void PluginListView::setup(OrganizerCore& core, MainWindow* mw, Ui::MainWindow* mwui) { - m_core = &core; - ui = { mwui->activePluginsCounter, mwui->espFilterEdit }; + m_core = &core; + ui = {mwui->activePluginsCounter, mwui->espFilterEdit}; m_modActions = &mwui->modList->actions(); m_sortProxy = new PluginListSortProxy(&core); @@ -192,28 +198,40 @@ void PluginListView::setup(OrganizerCore& core, MainWindow* mw, Ui::MainWindow* setItemDelegateForColumn(PluginList::COL_FLAGS, new GenericIconDelegate(this)); // counter - connect(core.pluginList(), &PluginList::writePluginsList, [=]{ updatePluginCount(); }); - connect(core.pluginList(), &PluginList::esplist_changed, [=]{ updatePluginCount(); }); + connect(core.pluginList(), &PluginList::writePluginsList, [=] { + updatePluginCount(); + }); + connect(core.pluginList(), &PluginList::esplist_changed, [=] { + updatePluginCount(); + }); // sort - connect(mwui->bossButton, &QPushButton::clicked, [=]{ onSortButtonClicked(); }); + connect(mwui->bossButton, &QPushButton::clicked, [=] { + onSortButtonClicked(); + }); // filter - connect(ui.filter, &QLineEdit::textChanged, m_sortProxy, &PluginListSortProxy::updateFilter); + connect(ui.filter, &QLineEdit::textChanged, m_sortProxy, + &PluginListSortProxy::updateFilter); connect(ui.filter, &QLineEdit::textChanged, this, &PluginListView::onFilterChanged); // highligth mod list when selected - connect(selectionModel(), &QItemSelectionModel::selectionChanged, [=](auto&& selected) { - std::vector pluginIndices; - for (auto& idx : indexViewToModel(selectionModel()->selectedRows())) { - pluginIndices.push_back(idx.row()); - } - mwui->modList->setHighlightedMods(pluginIndices); - }); + connect(selectionModel(), &QItemSelectionModel::selectionChanged, + [=](auto&& selected) { + std::vector pluginIndices; + for (auto& idx : indexViewToModel(selectionModel()->selectedRows())) { + pluginIndices.push_back(idx.row()); + } + mwui->modList->setHighlightedMods(pluginIndices); + }); // using a lambda here to avoid storing the mod list actions - connect(this, &QTreeView::customContextMenuRequested, [=](auto&& pos) { onCustomContextMenuRequested(pos); }); - connect(this, &QTreeView::doubleClicked, [=](auto&& index) { onDoubleClicked(index); }); + connect(this, &QTreeView::customContextMenuRequested, [=](auto&& pos) { + onCustomContextMenuRequested(pos); + }); + connect(this, &QTreeView::doubleClicked, [=](auto&& index) { + onDoubleClicked(index); + }); } void PluginListView::onCustomContextMenuRequested(const QPoint& pos) @@ -221,13 +239,12 @@ void PluginListView::onCustomContextMenuRequested(const QPoint& pos) try { PluginListContextMenu menu(indexViewToModel(indexAt(pos)), *m_core, this); connect(&menu, &PluginListContextMenu::openModInformation, [=](auto&& modIndex) { - m_modActions->displayModInformation(modIndex); }); + m_modActions->displayModInformation(modIndex); + }); menu.exec(viewport()->mapToGlobal(pos)); - } - catch (const std::exception& e) { + } catch (const std::exception& e) { reportError(tr("Exception: ").arg(e.what())); - } - catch (...) { + } catch (...) { reportError(tr("Unknown exception")); } } @@ -238,32 +255,34 @@ void PluginListView::onDoubleClicked(const QModelIndex& index) return; } - if (m_core->pluginList()->timeElapsedSinceLastChecked() <= QApplication::doubleClickInterval()) { + if (m_core->pluginList()->timeElapsedSinceLastChecked() <= + QApplication::doubleClickInterval()) { // don't interpret double click if we only just checked a plugin return; } try { - if (selectionModel()->hasSelection() && selectionModel()->selectedRows().count() == 1) { + if (selectionModel()->hasSelection() && + selectionModel()->selectedRows().count() == 1) { - QModelIndex idx = selectionModel()->currentIndex(); + QModelIndex idx = selectionModel()->currentIndex(); QString fileName = idx.data().toString(); if (ModInfo::getIndex(m_core->pluginList()->origin(fileName)) == UINT_MAX) { return; } - auto modIndex = ModInfo::getIndex(m_core->pluginList()->origin(fileName)); + auto modIndex = ModInfo::getIndex(m_core->pluginList()->origin(fileName)); ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); if (modInfo->isRegular() || modInfo->isOverwrite()) { Qt::KeyboardModifiers modifiers = QApplication::queryKeyboardModifiers(); if (modifiers.testFlag(Qt::ControlModifier)) { - m_modActions->openExplorer({ m_core->modList()->index(modIndex, 0) }); - } - else { - m_modActions->displayModInformation(ModInfo::getIndex(m_core->pluginList()->origin(fileName))); + m_modActions->openExplorer({m_core->modList()->index(modIndex, 0)}); + } else { + m_modActions->displayModInformation( + ModInfo::getIndex(m_core->pluginList()->origin(fileName))); } // workaround to cancel the editor that might have opened because of @@ -271,8 +290,7 @@ void PluginListView::onDoubleClicked(const QModelIndex& index) closePersistentEditor(index); } } - } - catch (const std::exception& e) { + } catch (const std::exception& e) { reportError(e.what()); } } @@ -308,10 +326,11 @@ bool PluginListView::event(QEvent* event) if (event->type() == QEvent::KeyPress) { QKeyEvent* keyEvent = static_cast(event); - if (keyEvent->modifiers() == Qt::ControlModifier - && (keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Enter)) { - if (selectionModel()->hasSelection() && selectionModel()->selectedRows().count() == 1) { - QModelIndex idx = selectionModel()->currentIndex(); + if (keyEvent->modifiers() == Qt::ControlModifier && + (keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Enter)) { + if (selectionModel()->hasSelection() && + selectionModel()->selectedRows().count() == 1) { + QModelIndex idx = selectionModel()->currentIndex(); QString fileName = idx.data().toString(); if (ModInfo::getIndex(m_core->pluginList()->origin(fileName)) == UINT_MAX) { @@ -319,16 +338,15 @@ bool PluginListView::event(QEvent* event) } auto modIndex = ModInfo::getIndex(m_core->pluginList()->origin(fileName)); - m_modActions->openExplorer({ m_core->modList()->index(modIndex, 0) }); + m_modActions->openExplorer({m_core->modList()->index(modIndex, 0)}); return true; } - } - else if (keyEvent->modifiers() == Qt::ControlModifier - && (sortColumn() == PluginList::COL_PRIORITY || sortColumn() == PluginList::COL_MODINDEX) - && (keyEvent->key() == Qt::Key_Up || keyEvent->key() == Qt::Key_Down)) { + } else if (keyEvent->modifiers() == Qt::ControlModifier && + (sortColumn() == PluginList::COL_PRIORITY || + sortColumn() == PluginList::COL_MODINDEX) && + (keyEvent->key() == Qt::Key_Up || keyEvent->key() == Qt::Key_Down)) { return moveSelection(keyEvent->key()); - } - else if (keyEvent->key() == Qt::Key_Space) { + } else if (keyEvent->key() == Qt::Key_Space) { return toggleSelectionState(); } return QTreeView::event(event); diff --git a/src/pluginlistview.h b/src/pluginlistview.h index ed6458daf..642976e3c 100644 --- a/src/pluginlistview.h +++ b/src/pluginlistview.h @@ -1,12 +1,13 @@ #ifndef PLUGINLISTVIEW_H #define PLUGINLISTVIEW_H -#include -#include #include "viewmarkingscrollbar.h" +#include +#include -namespace Ui { - class MainWindow; +namespace Ui +{ +class MainWindow; } class OrganizerCore; @@ -39,7 +40,6 @@ protected slots: void onSortButtonClicked(); protected: - friend class PluginListContextMenu; // map from/to the view indexes to the model @@ -63,7 +63,6 @@ protected slots: bool event(QEvent* event) override; private: - struct PluginListViewUi { // the plguin counter @@ -83,4 +82,4 @@ protected slots: bool m_didUpdateMasterList; }; -#endif // PLUGINLISTVIEW_H +#endif // PLUGINLISTVIEW_H diff --git a/src/previewdialog.cpp b/src/previewdialog.cpp index 91a5f13ea..5d40885d2 100644 --- a/src/previewdialog.cpp +++ b/src/previewdialog.cpp @@ -1,64 +1,65 @@ -#include "previewdialog.h" -#include "ui_previewdialog.h" -#include "settings.h" -#include - -PreviewDialog::PreviewDialog(const QString &fileName, QWidget *parent) : - QDialog(parent), - ui(new Ui::PreviewDialog) -{ - ui->setupUi(this); - ui->nameLabel->setText(QFileInfo(fileName).fileName()); - ui->nextButton->setEnabled(false); - ui->previousButton->setEnabled(false); -} - -PreviewDialog::~PreviewDialog() -{ - delete ui; -} - -int PreviewDialog::exec() -{ - GeometrySaver gs(Settings::instance(), this); - return QDialog::exec(); -} - -void PreviewDialog::addVariant(const QString &modName, QWidget *widget) -{ - widget->setProperty("modName", modName); - ui->variantsStack->addWidget(widget); - if (ui->variantsStack->count() > 1) { - ui->nextButton->setEnabled(true); - ui->previousButton->setEnabled(true); - } -} - -int PreviewDialog::numVariants() const -{ - return ui->variantsStack->count(); -} - -void PreviewDialog::on_variantsStack_currentChanged(int index) -{ - ui->modLabel->setText(ui->variantsStack->widget(index)->property("modName").toString()); -} - -void PreviewDialog::on_closeButton_clicked() -{ - this->accept(); -} - -void PreviewDialog::on_previousButton_clicked() -{ - int i = ui->variantsStack->currentIndex() - 1; - if (i < 0) { - i = ui->variantsStack->count() - 1; - } - ui->variantsStack->setCurrentIndex(i); -} - -void PreviewDialog::on_nextButton_clicked() -{ - ui->variantsStack->setCurrentIndex((ui->variantsStack->currentIndex() + 1) % ui->variantsStack->count()); -} +#include "previewdialog.h" +#include "settings.h" +#include "ui_previewdialog.h" +#include + +PreviewDialog::PreviewDialog(const QString& fileName, QWidget* parent) + : QDialog(parent), ui(new Ui::PreviewDialog) +{ + ui->setupUi(this); + ui->nameLabel->setText(QFileInfo(fileName).fileName()); + ui->nextButton->setEnabled(false); + ui->previousButton->setEnabled(false); +} + +PreviewDialog::~PreviewDialog() +{ + delete ui; +} + +int PreviewDialog::exec() +{ + GeometrySaver gs(Settings::instance(), this); + return QDialog::exec(); +} + +void PreviewDialog::addVariant(const QString& modName, QWidget* widget) +{ + widget->setProperty("modName", modName); + ui->variantsStack->addWidget(widget); + if (ui->variantsStack->count() > 1) { + ui->nextButton->setEnabled(true); + ui->previousButton->setEnabled(true); + } +} + +int PreviewDialog::numVariants() const +{ + return ui->variantsStack->count(); +} + +void PreviewDialog::on_variantsStack_currentChanged(int index) +{ + ui->modLabel->setText( + ui->variantsStack->widget(index)->property("modName").toString()); +} + +void PreviewDialog::on_closeButton_clicked() +{ + this->accept(); +} + +void PreviewDialog::on_previousButton_clicked() +{ + int i = ui->variantsStack->currentIndex() - 1; + if (i < 0) { + i = ui->variantsStack->count() - 1; + } + ui->variantsStack->setCurrentIndex(i); +} + +void PreviewDialog::on_nextButton_clicked() +{ + ui->variantsStack->setCurrentIndex((ui->variantsStack->currentIndex() + 1) % + ui->variantsStack->count()); +} diff --git a/src/previewdialog.h b/src/previewdialog.h index 9525f1276..fd2141886 100644 --- a/src/previewdialog.h +++ b/src/previewdialog.h @@ -1,40 +1,40 @@ -#ifndef PREVIEWDIALOG_H -#define PREVIEWDIALOG_H - -#include - -namespace Ui { -class PreviewDialog; -} - -class PreviewDialog : public QDialog -{ - Q_OBJECT - -public: - explicit PreviewDialog(const QString &fileName, QWidget *parent = 0); - ~PreviewDialog(); - - // also saves and restores geometry - // - int exec() override; - - void addVariant(const QString &modName, QWidget *widget); - int numVariants() const; - -private slots: - - void on_variantsStack_currentChanged(int arg1); - - void on_closeButton_clicked(); - - void on_previousButton_clicked(); - - void on_nextButton_clicked(); - -private: - - Ui::PreviewDialog *ui; -}; - -#endif // PREVIEWDIALOG_H +#ifndef PREVIEWDIALOG_H +#define PREVIEWDIALOG_H + +#include + +namespace Ui +{ +class PreviewDialog; +} + +class PreviewDialog : public QDialog +{ + Q_OBJECT + +public: + explicit PreviewDialog(const QString& fileName, QWidget* parent = 0); + ~PreviewDialog(); + + // also saves and restores geometry + // + int exec() override; + + void addVariant(const QString& modName, QWidget* widget); + int numVariants() const; + +private slots: + + void on_variantsStack_currentChanged(int arg1); + + void on_closeButton_clicked(); + + void on_previousButton_clicked(); + + void on_nextButton_clicked(); + +private: + Ui::PreviewDialog* ui; +}; + +#endif // PREVIEWDIALOG_H diff --git a/src/previewgenerator.cpp b/src/previewgenerator.cpp index 22332af34..ba73c0650 100644 --- a/src/previewgenerator.cpp +++ b/src/previewgenerator.cpp @@ -1,58 +1,60 @@ -/* -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 "previewgenerator.h" - -#include -#include -#include -#include -#include - -#include "plugincontainer.h" - -using namespace MOBase; - -PreviewGenerator::PreviewGenerator(const PluginContainer& pluginContainer) : - m_PluginContainer(pluginContainer) { - m_MaxSize = QGuiApplication::primaryScreen()->size() * 0.8; -} - -bool PreviewGenerator::previewSupported(const QString &fileExtension) const -{ - auto& previews = m_PluginContainer.plugins(); - for (auto* preview : previews) { - if (preview->supportedExtensions().contains(fileExtension)) { - return true; - } - } - return false; -} - -QWidget *PreviewGenerator::genPreview(const QString &fileName) const -{ - const QString ext = QFileInfo(fileName).suffix().toLower(); - auto& previews = m_PluginContainer.plugins(); - for (auto* preview : previews) { - if (m_PluginContainer.isEnabled(preview) && preview->supportedExtensions().contains(ext)) { - return preview->genFilePreview(fileName, m_MaxSize); - } - } - return nullptr; -} +/* +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 "previewgenerator.h" + +#include +#include +#include +#include +#include + +#include "plugincontainer.h" + +using namespace MOBase; + +PreviewGenerator::PreviewGenerator(const PluginContainer& pluginContainer) + : m_PluginContainer(pluginContainer) +{ + m_MaxSize = QGuiApplication::primaryScreen()->size() * 0.8; +} + +bool PreviewGenerator::previewSupported(const QString& fileExtension) const +{ + auto& previews = m_PluginContainer.plugins(); + for (auto* preview : previews) { + if (preview->supportedExtensions().contains(fileExtension)) { + return true; + } + } + return false; +} + +QWidget* PreviewGenerator::genPreview(const QString& fileName) const +{ + const QString ext = QFileInfo(fileName).suffix().toLower(); + auto& previews = m_PluginContainer.plugins(); + for (auto* preview : previews) { + if (m_PluginContainer.isEnabled(preview) && + preview->supportedExtensions().contains(ext)) { + return preview->genFilePreview(fileName, m_MaxSize); + } + } + return nullptr; +} diff --git a/src/previewgenerator.h b/src/previewgenerator.h index 8e491116c..dd0d2cd4f 100644 --- a/src/previewgenerator.h +++ b/src/previewgenerator.h @@ -1,47 +1,45 @@ -/* -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 PREVIEWGENERATOR_H -#define PREVIEWGENERATOR_H - -#include -#include -#include -#include -#include - -class PluginContainer; - -class PreviewGenerator -{ -public: - PreviewGenerator(const PluginContainer& pluginContainer); - - bool previewSupported(const QString &fileExtension) const; - - QWidget *genPreview(const QString &fileName) const; - -private: - - const PluginContainer& m_PluginContainer; - QSize m_MaxSize; - -}; - -#endif // PREVIEWGENERATOR_H +/* +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 PREVIEWGENERATOR_H +#define PREVIEWGENERATOR_H + +#include +#include +#include +#include +#include + +class PluginContainer; + +class PreviewGenerator +{ +public: + PreviewGenerator(const PluginContainer& pluginContainer); + + bool previewSupported(const QString& fileExtension) const; + + QWidget* genPreview(const QString& fileName) const; + +private: + const PluginContainer& m_PluginContainer; + QSize m_MaxSize; +}; + +#endif // PREVIEWGENERATOR_H diff --git a/src/problemsdialog.cpp b/src/problemsdialog.cpp index d72f8c208..f6b23a100 100644 --- a/src/problemsdialog.cpp +++ b/src/problemsdialog.cpp @@ -1,113 +1,116 @@ -#include "problemsdialog.h" -#include "ui_problemsdialog.h" -#include "organizercore.h" -#include -#include -#include -#include -#include - -#include "plugincontainer.h" - -using namespace MOBase; - - -ProblemsDialog::ProblemsDialog(const PluginContainer& pluginContainer, QWidget *parent) : - QDialog(parent), ui(new Ui::ProblemsDialog), m_PluginContainer(pluginContainer), - m_hasProblems(false) -{ - ui->setupUi(this); - - runDiagnosis(); - - connect(ui->problemsWidget, SIGNAL(itemSelectionChanged()), this, SLOT(selectionChanged())); - connect(ui->descriptionText, SIGNAL(anchorClicked(QUrl)), this, SLOT(urlClicked(QUrl))); -} - - -ProblemsDialog::~ProblemsDialog() -{ - delete ui; -} - -int ProblemsDialog::exec() -{ - GeometrySaver gs(Settings::instance(), this); - return QDialog::exec(); -} - -void ProblemsDialog::runDiagnosis() -{ - m_hasProblems = false; - ui->problemsWidget->clear(); - - for (IPluginDiagnose *diagnose : m_PluginContainer.plugins()) { - if (!m_PluginContainer.isEnabled(diagnose)) { - continue; - } - - std::vector activeProblems = diagnose->activeProblems(); - foreach (unsigned int key, activeProblems) { - QTreeWidgetItem *newItem = new QTreeWidgetItem(); - newItem->setText(0, diagnose->shortDescription(key)); - newItem->setData(0, Qt::UserRole, diagnose->fullDescription(key)); - - ui->problemsWidget->addTopLevelItem(newItem); - m_hasProblems = true; - - if (diagnose->hasGuidedFix(key)) { - newItem->setText(1, tr("Fix")); - QPushButton *fixButton = new QPushButton(tr("Fix")); - fixButton->setProperty("fix", QVariant::fromValue(reinterpret_cast(diagnose))); - fixButton->setProperty("key", key); - connect(fixButton, SIGNAL(clicked()), this, SLOT(startFix())); - ui->problemsWidget->setItemWidget(newItem, 1, fixButton); - } else { - newItem->setText(1, tr("No guided fix")); - } - } - } - - if (!m_hasProblems) { - auto* item = new QTreeWidgetItem; - - item->setText(0, tr("(There are no notifications)")); - item->setText(1, ""); - item->setData(0, Qt::UserRole, QString()); - - QFont font = item->font(0); - font.setItalic(true); - item->setFont(0, font); - - ui->problemsWidget->addTopLevelItem(item); - } -} - -bool ProblemsDialog::hasProblems() const -{ - return m_hasProblems; -} - -void ProblemsDialog::selectionChanged() -{ - QString text = ui->problemsWidget->currentItem()->data(0, Qt::UserRole).toString(); - ui->descriptionText->setText(text); - ui->descriptionText->setLineWrapMode(text.contains('\n') ? QTextEdit::NoWrap : QTextEdit::WidgetWidth); -} - -void ProblemsDialog::startFix() -{ - QObject *fixButton = QObject::sender(); - if (fixButton == NULL) { - log::warn("no button"); - return; - } - IPluginDiagnose *plugin = reinterpret_cast(fixButton ->property("fix").value()); - plugin->startGuidedFix(fixButton ->property("key").toUInt()); - runDiagnosis(); -} - -void ProblemsDialog::urlClicked(const QUrl &url) -{ - shell::Open(url); -} +#include "problemsdialog.h" +#include "organizercore.h" +#include "ui_problemsdialog.h" +#include +#include +#include +#include +#include + +#include "plugincontainer.h" + +using namespace MOBase; + +ProblemsDialog::ProblemsDialog(const PluginContainer& pluginContainer, QWidget* parent) + : QDialog(parent), ui(new Ui::ProblemsDialog), m_PluginContainer(pluginContainer), + m_hasProblems(false) +{ + ui->setupUi(this); + + runDiagnosis(); + + connect(ui->problemsWidget, SIGNAL(itemSelectionChanged()), this, + SLOT(selectionChanged())); + connect(ui->descriptionText, SIGNAL(anchorClicked(QUrl)), this, + SLOT(urlClicked(QUrl))); +} + +ProblemsDialog::~ProblemsDialog() +{ + delete ui; +} + +int ProblemsDialog::exec() +{ + GeometrySaver gs(Settings::instance(), this); + return QDialog::exec(); +} + +void ProblemsDialog::runDiagnosis() +{ + m_hasProblems = false; + ui->problemsWidget->clear(); + + for (IPluginDiagnose* diagnose : m_PluginContainer.plugins()) { + if (!m_PluginContainer.isEnabled(diagnose)) { + continue; + } + + std::vector activeProblems = diagnose->activeProblems(); + foreach (unsigned int key, activeProblems) { + QTreeWidgetItem* newItem = new QTreeWidgetItem(); + newItem->setText(0, diagnose->shortDescription(key)); + newItem->setData(0, Qt::UserRole, diagnose->fullDescription(key)); + + ui->problemsWidget->addTopLevelItem(newItem); + m_hasProblems = true; + + if (diagnose->hasGuidedFix(key)) { + newItem->setText(1, tr("Fix")); + QPushButton* fixButton = new QPushButton(tr("Fix")); + fixButton->setProperty("fix", + QVariant::fromValue(reinterpret_cast(diagnose))); + fixButton->setProperty("key", key); + connect(fixButton, SIGNAL(clicked()), this, SLOT(startFix())); + ui->problemsWidget->setItemWidget(newItem, 1, fixButton); + } else { + newItem->setText(1, tr("No guided fix")); + } + } + } + + if (!m_hasProblems) { + auto* item = new QTreeWidgetItem; + + item->setText(0, tr("(There are no notifications)")); + item->setText(1, ""); + item->setData(0, Qt::UserRole, QString()); + + QFont font = item->font(0); + font.setItalic(true); + item->setFont(0, font); + + ui->problemsWidget->addTopLevelItem(item); + } +} + +bool ProblemsDialog::hasProblems() const +{ + return m_hasProblems; +} + +void ProblemsDialog::selectionChanged() +{ + QString text = ui->problemsWidget->currentItem()->data(0, Qt::UserRole).toString(); + ui->descriptionText->setText(text); + ui->descriptionText->setLineWrapMode(text.contains('\n') ? QTextEdit::NoWrap + : QTextEdit::WidgetWidth); +} + +void ProblemsDialog::startFix() +{ + QObject* fixButton = QObject::sender(); + if (fixButton == NULL) { + log::warn("no button"); + return; + } + IPluginDiagnose* plugin = + reinterpret_cast(fixButton->property("fix").value()); + plugin->startGuidedFix(fixButton->property("key").toUInt()); + runDiagnosis(); +} + +void ProblemsDialog::urlClicked(const QUrl& url) +{ + shell::Open(url); +} diff --git a/src/problemsdialog.h b/src/problemsdialog.h index 504a0a10e..288f50491 100644 --- a/src/problemsdialog.h +++ b/src/problemsdialog.h @@ -1,45 +1,44 @@ -#ifndef PROBLEMSDIALOG_H -#define PROBLEMSDIALOG_H - - -#include -#include -#include - - -namespace Ui { -class ProblemsDialog; -} - -class PluginContainer; - -class ProblemsDialog : public QDialog -{ - Q_OBJECT - -public: - explicit ProblemsDialog(PluginContainer const& pluginContainer, QWidget *parent = 0); - ~ProblemsDialog(); - - // also saves and restores geometry - // - int exec() override; - - bool hasProblems() const; - -private: - void runDiagnosis(); - -private slots: - void selectionChanged(); - void urlClicked(const QUrl &url); - - void startFix(); - -private: - Ui::ProblemsDialog *ui; - const PluginContainer& m_PluginContainer; - bool m_hasProblems; -}; - -#endif // PROBLEMSDIALOG_H +#ifndef PROBLEMSDIALOG_H +#define PROBLEMSDIALOG_H + +#include +#include +#include + +namespace Ui +{ +class ProblemsDialog; +} + +class PluginContainer; + +class ProblemsDialog : public QDialog +{ + Q_OBJECT + +public: + explicit ProblemsDialog(PluginContainer const& pluginContainer, QWidget* parent = 0); + ~ProblemsDialog(); + + // also saves and restores geometry + // + int exec() override; + + bool hasProblems() const; + +private: + void runDiagnosis(); + +private slots: + void selectionChanged(); + void urlClicked(const QUrl& url); + + void startFix(); + +private: + Ui::ProblemsDialog* ui; + const PluginContainer& m_PluginContainer; + bool m_hasProblems; +}; + +#endif // PROBLEMSDIALOG_H diff --git a/src/processrunner.cpp b/src/processrunner.cpp index 4a954dee5..f748f4909 100644 --- a/src/processrunner.cpp +++ b/src/processrunner.cpp @@ -1,17 +1,17 @@ #include "processrunner.h" -#include "organizercore.h" +#include "env.h" +#include "envmodule.h" #include "instancemanager.h" #include "iuserinterface.h" -#include "envmodule.h" -#include "env.h" -#include +#include "organizercore.h" #include #include +#include using namespace MOBase; -void adjustForVirtualized( - const IPluginGame* game, spawn::SpawnParameters& sp, const Settings& settings) +void adjustForVirtualized(const IPluginGame* game, spawn::SpawnParameters& sp, + const Settings& settings) { const QString modsPath = settings.paths().mods(); @@ -19,38 +19,36 @@ void adjustForVirtualized( // under our mods folder then will start the process in a virtualized // "environment" with the appropriate paths fixed: // (i.e. mods\FNIS\path\exe => game\data\path\exe) - QString cwdPath = sp.currentDirectory.absolutePath(); + QString cwdPath = sp.currentDirectory.absolutePath(); QString trailedModsPath = modsPath; if (!trailedModsPath.endsWith('/')) { trailedModsPath = trailedModsPath + '/'; } bool virtualizedCwd = cwdPath.startsWith(trailedModsPath, Qt::CaseInsensitive); - QString binPath = sp.binary.absoluteFilePath(); + QString binPath = sp.binary.absoluteFilePath(); bool virtualizedBin = binPath.startsWith(trailedModsPath, Qt::CaseInsensitive); if (virtualizedCwd || virtualizedBin) { if (virtualizedCwd) { - int cwdOffset = cwdPath.indexOf('/', trailedModsPath.length()); + int cwdOffset = cwdPath.indexOf('/', trailedModsPath.length()); QString adjustedCwd = cwdPath.mid(cwdOffset, -1); - cwdPath = game->dataDirectory().absolutePath(); + cwdPath = game->dataDirectory().absolutePath(); if (cwdOffset >= 0) cwdPath += adjustedCwd; - } if (virtualizedBin) { - int binOffset = binPath.indexOf('/', trailedModsPath.length()); + int binOffset = binPath.indexOf('/', trailedModsPath.length()); QString adjustedBin = binPath.mid(binOffset, -1); - binPath = game->dataDirectory().absolutePath(); + binPath = game->dataDirectory().absolutePath(); if (binOffset >= 0) binPath += adjustedBin; } - QString cmdline - = QString("launch \"%1\" \"%2\" %3") - .arg(QDir::toNativeSeparators(cwdPath), - QDir::toNativeSeparators(binPath), sp.arguments); + QString cmdline = QString("launch \"%1\" \"%2\" %3") + .arg(QDir::toNativeSeparators(cwdPath), + QDir::toNativeSeparators(binPath), sp.arguments); - sp.binary = QFileInfo(QCoreApplication::applicationFilePath()); + sp.binary = QFileInfo(QCoreApplication::applicationFilePath()); sp.arguments = cmdline; sp.currentDirectory.setPath(QCoreApplication::applicationDirPath()); } @@ -64,28 +62,24 @@ std::optional singleWait(HANDLE handle, DWORD pid) const auto res = WaitForSingleObject(handle, 50); - switch (res) - { - case WAIT_OBJECT_0: - { - log::debug("process {} completed", pid); - return ProcessRunner::Completed; - } + switch (res) { + case WAIT_OBJECT_0: { + log::debug("process {} completed", pid); + return ProcessRunner::Completed; + } - case WAIT_TIMEOUT: - { - // still running - return {}; - } + case WAIT_TIMEOUT: { + // still running + return {}; + } - case WAIT_FAILED: // fall-through - default: - { - // error - const auto e = ::GetLastError(); - log::error("failed waiting for {}, {}", pid, formatSystemMessage(e)); - return ProcessRunner::Error; - } + case WAIT_FAILED: // fall-through + default: { + // error + const auto e = ::GetLastError(); + log::error("failed waiting for {}, {}", pid, formatSystemMessage(e)); + return ProcessRunner::Error; + } } } @@ -98,21 +92,19 @@ enum class Interest QString toString(Interest i) { - switch (i) - { - case Interest::Weak: - return "weak"; + switch (i) { + case Interest::Weak: + return "weak"; - case Interest::Strong: - return "strong"; + case Interest::Strong: + return "strong"; - case Interest::None: // fall-through - default: - return "no"; + case Interest::None: // fall-through + default: + return "no"; } } - struct InterestingProcess { env::Process p; @@ -120,7 +112,6 @@ struct InterestingProcess env::HandlePtr handle; }; - InterestingProcess findRandomProcess(const env::Process& root) { for (auto&& c : root.children()) { @@ -145,9 +136,7 @@ InterestingProcess findInterestingProcessInTrees(const env::Process& root) { // Certain process names we wish to "hide" for aesthetic reason: static const std::vector hiddenList = { - QFileInfo(QCoreApplication::applicationFilePath()).fileName(), - "conhost.exe" - }; + QFileInfo(QCoreApplication::applicationFilePath()).fileName(), "conhost.exe"}; if (root.children().empty()) { return {}; @@ -163,7 +152,6 @@ InterestingProcess findInterestingProcessInTrees(const env::Process& root) return false; }; - for (auto&& p : root.children()) { if (!isHidden(p)) { env::HandlePtr h = p.openHandleForWait(); @@ -184,9 +172,8 @@ InterestingProcess findInterestingProcessInTrees(const env::Process& root) void dump(const env::Process& p, int indent) { - log::debug( - "{}{}, pid={}, ppid={}", - std::string(indent * 4, ' '), p.name(), p.pid(), p.ppid()); + log::debug("{}{}, pid={}, ppid={}", std::string(indent * 4, ' '), p.name(), p.pid(), + p.ppid()); for (auto&& c : p.children()) { dump(c, indent + 1); @@ -228,9 +215,10 @@ const std::chrono::milliseconds Infinite(-1); // waits for completion, times out after `wait` if not Infinite // -std::optional timedWait( - HANDLE handle, DWORD pid, UILocker::Session* ls, - std::chrono::milliseconds wait, std::atomic& interrupt) +std::optional timedWait(HANDLE handle, DWORD pid, + UILocker::Session* ls, + std::chrono::milliseconds wait, + std::atomic& interrupt) { using namespace std::chrono; @@ -253,35 +241,29 @@ std::optional timedWait( // check the lock widget; the session can be null when running shortcuts // with locking disabled, in which case the user cannot force unlock if (ls) { - switch (ls->result()) - { - case UILocker::StillLocked: - { - break; - } - - case UILocker::ForceUnlocked: - { - log::debug("waiting for {} force unlocked by user", pid); - return ProcessRunner::ForceUnlocked; - } - - case UILocker::Cancelled: - { - log::debug("waiting for {} cancelled by user", pid); - return ProcessRunner::Cancelled; - } - - case UILocker::NoResult: // fall-through - default: - { - // shouldn't happen - log::debug( - "unexpected result {} while waiting for {}", - static_cast(ls->result()), pid); - - return ProcessRunner::Error; - } + switch (ls->result()) { + case UILocker::StillLocked: { + break; + } + + case UILocker::ForceUnlocked: { + log::debug("waiting for {} force unlocked by user", pid); + return ProcessRunner::ForceUnlocked; + } + + case UILocker::Cancelled: { + log::debug("waiting for {} cancelled by user", pid); + return ProcessRunner::Cancelled; + } + + case UILocker::NoResult: // fall-through + default: { + // shouldn't happen + log::debug("unexpected result {} while waiting for {}", + static_cast(ls->result()), pid); + + return ProcessRunner::Error; + } } } @@ -299,8 +281,8 @@ std::optional timedWait( return ProcessRunner::ForceUnlocked; } -ProcessRunner::Results waitForProcessesThreadImpl( - HANDLE job, UILocker::Session* ls, std::atomic& interrupt) +ProcessRunner::Results waitForProcessesThreadImpl(HANDLE job, UILocker::Session* ls, + std::atomic& interrupt) { using namespace std::chrono; @@ -329,9 +311,8 @@ ProcessRunner::Results waitForProcessesThreadImpl( // log any change in the process being waited for currentPID = ip.p.pid(); - log::debug( - "waiting for completion on {} ({}), {} interest", - ip.p.name(), ip.p.pid(), toString(ip.interest)); + log::debug("waiting for completion on {} ({}), {} interest", ip.p.name(), + ip.p.pid(), toString(ip.interest)); } if (ip.interest == Interest::Strong) { @@ -360,9 +341,8 @@ ProcessRunner::Results waitForProcessesThreadImpl( return ProcessRunner::ForceUnlocked; } -void waitForProcessesThread( - ProcessRunner::Results& result, HANDLE job, UILocker::Session* ls, - std::atomic& interrupt) +void waitForProcessesThread(ProcessRunner::Results& result, HANDLE job, + UILocker::Session* ls, std::atomic& interrupt) { result = waitForProcessesThreadImpl(job, ls, interrupt); @@ -372,8 +352,8 @@ void waitForProcessesThread( } } -ProcessRunner::Results waitForProcesses( - const std::vector& initialProcesses, UILocker::Session* ls) +ProcessRunner::Results waitForProcesses(const std::vector& initialProcesses, + UILocker::Session* ls) { if (initialProcesses.empty()) { // nothing to wait for @@ -386,9 +366,8 @@ ProcessRunner::Results waitForProcesses( if (!job) { const auto e = GetLastError(); - log::error( - "failed to create job to wait for processes, {}", - formatSystemMessage(e)); + log::error("failed to create job to wait for processes, {}", + formatSystemMessage(e)); return ProcessRunner::Error; } @@ -404,7 +383,7 @@ ProcessRunner::Results waitForProcesses( // this happens when closing MO while multiple processes are running, // so the logging is disabled until it gets fixed - //log::error( + // log::error( // "can't assign process to job to wait for processes, {}", // formatSystemMessage(e)); @@ -424,12 +403,11 @@ ProcessRunner::Results waitForProcesses( auto results = ProcessRunner::Running; std::atomic interrupt(false); - auto* t = QThread::create( - waitForProcessesThread, - std::ref(results), monitor, ls, std::ref(interrupt)); + auto* t = QThread::create(waitForProcessesThread, std::ref(results), monitor, ls, + std::ref(interrupt)); QEventLoop events; - QObject::connect(t, &QThread::finished, [&]{ + QObject::connect(t, &QThread::finished, [&] { events.quit(); }); @@ -446,8 +424,8 @@ ProcessRunner::Results waitForProcesses( return results; } -ProcessRunner::Results waitForProcess( - HANDLE initialProcess, LPDWORD exitCode, UILocker::Session* ls) +ProcessRunner::Results waitForProcess(HANDLE initialProcess, LPDWORD exitCode, + UILocker::Session* ls) { std::vector processes = {initialProcess}; @@ -457,25 +435,22 @@ ProcessRunner::Results waitForProcess( if (exitCode && r != ProcessRunner::Running) { if (!::GetExitCodeProcess(initialProcess, exitCode)) { const auto e = ::GetLastError(); - log::warn( - "failed to get exit code of process, {}", - formatSystemMessage(e)); + log::warn("failed to get exit code of process, {}", formatSystemMessage(e)); } } return r; } - -ProcessRunner::ProcessRunner(OrganizerCore& core, IUserInterface* ui) : - m_core(core), m_ui(ui), m_lockReason(UILocker::NoReason), - m_waitFlags(NoFlags), m_handle(INVALID_HANDLE_VALUE), m_exitCode(-1) +ProcessRunner::ProcessRunner(OrganizerCore& core, IUserInterface* ui) + : m_core(core), m_ui(ui), m_lockReason(UILocker::NoReason), m_waitFlags(NoFlags), + m_handle(INVALID_HANDLE_VALUE), m_exitCode(-1) { // all processes started in ProcessRunner are hooked by default setHooked(true); } -ProcessRunner& ProcessRunner::setBinary(const QFileInfo &binary) +ProcessRunner& ProcessRunner::setBinary(const QFileInfo& binary) { m_sp.binary = binary; return *this; @@ -517,16 +492,15 @@ ProcessRunner& ProcessRunner::setProfileName(const QString& profileName) return *this; } -ProcessRunner& ProcessRunner::setWaitForCompletion( - WaitFlags flags, UILocker::Reasons reason) +ProcessRunner& ProcessRunner::setWaitForCompletion(WaitFlags flags, + UILocker::Reasons reason) { - m_waitFlags = flags; + m_waitFlags = flags; m_lockReason = reason; if (m_waitFlags.testFlag(WaitForRefresh) && !m_waitFlags.testFlag(TriggerRefresh)) { - log::warn( - "process runner: WaitForRefresh without TriggerRefresh " - "makes no sense, will be ignored"); + log::warn("process runner: WaitForRefresh without TriggerRefresh " + "makes no sense, will be ignored"); } return *this; @@ -538,8 +512,7 @@ ProcessRunner& ProcessRunner::setHooked(bool b) return *this; } -ProcessRunner& ProcessRunner::setFromFile( - QWidget* parent, const QFileInfo& targetInfo) +ProcessRunner& ProcessRunner::setFromFile(QWidget* parent, const QFileInfo& targetInfo) { if (!parent && m_ui) { parent = m_ui->mainWindow(); @@ -550,23 +523,20 @@ ProcessRunner& ProcessRunner::setFromFile( const auto fec = spawn::getFileExecutionContext(parent, targetInfo); - switch (fec.type) - { - case spawn::FileExecutionTypes::Executable: - { - setBinary(fec.binary); - setArguments(fec.arguments); - setCurrentDirectory(targetInfo.absoluteDir()); - break; - } + switch (fec.type) { + case spawn::FileExecutionTypes::Executable: { + setBinary(fec.binary); + setArguments(fec.arguments); + setCurrentDirectory(targetInfo.absoluteDir()); + break; + } - case spawn::FileExecutionTypes::Other: // fall-through - default: - { - m_shellOpen = targetInfo; - setHooked(false); - break; - } + case spawn::FileExecutionTypes::Other: // fall-through + default: { + m_shellOpen = targetInfo; + setHooked(false); + break; + } } return *this; @@ -579,8 +549,8 @@ ProcessRunner& ProcessRunner::setFromExecutable(const Executable& exe) throw MyException(QObject::tr("No profile set")); } - const QString customOverwrite = profile->setting( - "custom_overwrites", exe.title()).toString(); + const QString customOverwrite = + profile->setting("custom_overwrites", exe.title()).toString(); ForcedLibraries forcedLibraries; if (profile->forcedLibrariesEnabled(exe.title())) { @@ -606,29 +576,29 @@ ProcessRunner& ProcessRunner::setFromShortcut(const MOShortcut& shortcut) { const auto currentInstance = InstanceManager::singleton().currentInstance(); - if (currentInstance) - { + if (currentInstance) { if (shortcut.hasInstance() && !shortcut.isForInstance(*currentInstance)) { - MOBase::reportError(QObject::tr( - "This shortcut is for instance '%1' but Mod Organizer is currently " - "running for '%2'. Exit Mod Organizer before running the shortcut or " - "change the active instance.") - .arg(shortcut.instanceDisplayName()) - .arg(currentInstance->displayName())); + MOBase::reportError( + QObject::tr( + "This shortcut is for instance '%1' but Mod Organizer is currently " + "running for '%2'. Exit Mod Organizer before running the shortcut or " + "change the active instance.") + .arg(shortcut.instanceDisplayName()) + .arg(currentInstance->displayName())); throw std::exception(); } } const auto* exes = m_core.executablesList(); - const auto exe = exes->find(shortcut.executableName()); + const auto exe = exes->find(shortcut.executableName()); if (exe != exes->end()) { setFromExecutable(*exe); } else { - MOBase::reportError(QObject::tr( - "Executable '%1' does not exist in instance '%2'.") - .arg(shortcut.executableName()).arg(currentInstance->displayName())); + MOBase::reportError(QObject::tr("Executable '%1' does not exist in instance '%2'.") + .arg(shortcut.executableName()) + .arg(currentInstance->displayName())); throw std::exception(); } @@ -637,12 +607,9 @@ ProcessRunner& ProcessRunner::setFromShortcut(const MOShortcut& shortcut) } ProcessRunner& ProcessRunner::setFromFileOrExecutable( - const QString &executable, - const QStringList &args, - const QString &cwd, - const QString &profileOverride, - const QString &forcedCustomOverwrite, - bool ignoreCustomOverwrite) + const QString& executable, const QStringList& args, const QString& cwd, + const QString& profileOverride, const QString& forcedCustomOverwrite, + bool ignoreCustomOverwrite) { const auto* profile = m_core.currentProfile(); if (!profile) { @@ -659,7 +626,8 @@ ProcessRunner& ProcessRunner::setFromFileOrExecutable( if (m_sp.binary.isRelative()) { // relative path, should be relative to game directory - setBinary(QFileInfo(m_core.managedGame()->gameDirectory().absoluteFilePath(executable))); + setBinary(QFileInfo( + m_core.managedGame()->gameDirectory().absoluteFilePath(executable))); } if (cwd == "") { @@ -675,13 +643,13 @@ ProcessRunner& ProcessRunner::setFromFileOrExecutable( if (profile->forcedLibrariesEnabled(exe.title())) { setForcedLibraries(profile->determineForcedLibraries(exe.title())); } - } catch (const std::runtime_error &) { + } catch (const std::runtime_error&) { // nop } } else { // only a file name, search executables list try { - const Executable &exe = m_core.executablesList()->get(executable); + const Executable& exe = m_core.executablesList()->get(executable); setSteamID(exe.steamAppID()); setCustomOverwrite(profile->setting("custom_overwrites", exe.title()).toString()); @@ -699,7 +667,7 @@ ProcessRunner& ProcessRunner::setFromFileOrExecutable( if (cwd == "") { setCurrentDirectory(exe.workingDirectory()); } - } catch (const std::runtime_error &) { + } catch (const std::runtime_error&) { log::warn("\"{}\" not set up as executable", executable); } } @@ -744,7 +712,6 @@ ProcessRunner::Results ProcessRunner::run() m_shellOpen = m_sp.binary; } - std::optional r; if (shouldRunShell()) { @@ -801,7 +768,8 @@ std::optional ProcessRunner::runBinary() // saves profile, sets up usvfs, notifies plugins, etc.; can return false if // a plugin doesn't want the program to run (such as when checkFNIS fails to // run FNIS and the user clicks cancel) - if (!m_core.beforeRun(m_sp.binary, m_profileName, m_customOverwrite, m_forcedLibraries)) { + if (!m_core.beforeRun(m_sp.binary, m_profileName, m_customOverwrite, + m_forcedLibraries)) { return Error; } @@ -809,7 +777,7 @@ std::optional ProcessRunner::runBinary() QWidget* parent = (m_ui ? m_ui->mainWindow() : nullptr); const auto* game = m_core.managedGame(); - auto& settings = m_core.settings(); + auto& settings = m_core.settings(); // start steam if needed if (!checkSteam(parent, m_sp, game->gameDirectory(), m_sp.steamAppID, settings)) { @@ -852,27 +820,23 @@ bool ProcessRunner::shouldRefresh(Results r) const return false; } - switch (r) - { - case Completed: - { - log::debug("process runner: refreshing because the process completed"); - return true; - } + switch (r) { + case Completed: { + log::debug("process runner: refreshing because the process completed"); + return true; + } - case ForceUnlocked: - { - log::debug("process runner: refreshing because the ui was force unlocked"); - return true; - } + case ForceUnlocked: { + log::debug("process runner: refreshing because the ui was force unlocked"); + return true; + } - case Error: // fall-through - case Cancelled: - case Running: - default: - { - return false; - } + case Error: // fall-through + case Cancelled: + case Running: + default: { + return false; + } } } @@ -887,9 +851,8 @@ ProcessRunner::Results ProcessRunner::postRun() if (mustWait && m_lockReason == UILocker::NoReason) { // never lock the ui without an escape hatch for the user - log::debug( - "the ForceWait flag is set but the lock reason wasn't, " - "defaulting to LockUI"); + log::debug("the ForceWait flag is set but the lock reason wasn't, " + "defaulting to LockUI"); m_lockReason = UILocker::LockUI; } @@ -899,9 +862,8 @@ ProcessRunner::Results ProcessRunner::postRun() if (mustWait) { if (!lockEnabled) { // at least tell the user what's going on - log::debug( - "locking is disabled, but the output of the application is required; " - "overriding this setting and locking the ui"); + log::debug("locking is disabled, but the output of the application is required; " + "overriding this setting and locking the ui"); } } else { // no force wait @@ -913,9 +875,8 @@ ProcessRunner::Results ProcessRunner::postRun() if (!lockEnabled) { // disabling locking is like clicking on unlock immediately - log::debug( - "process runner: not waiting for process because " - "locking is disabled"); + log::debug("process runner: not waiting for process because " + "locking is disabled"); return ForceUnlocked; } @@ -946,10 +907,8 @@ ProcessRunner::Results ProcessRunner::postRun() const bool wait = m_waitFlags.testFlag(WaitForRefresh); if (wait) { - QObject::connect( - &m_core, &OrganizerCore::directoryStructureReady, - &loop, &QEventLoop::quit, - Qt::ConnectionType::QueuedConnection); + QObject::connect(&m_core, &OrganizerCore::directoryStructureReady, &loop, + &QEventLoop::quit, Qt::ConnectionType::QueuedConnection); } m_core.afterRun(m_sp.binary, m_exitCode); @@ -959,7 +918,7 @@ ProcessRunner::Results ProcessRunner::postRun() loop.exec(); log::debug("process runner: refresh is done"); } -} + } return r; } @@ -987,8 +946,8 @@ env::HandlePtr ProcessRunner::stealProcessHandle() return env::HandlePtr(h); } -ProcessRunner::Results ProcessRunner::waitForAllUSVFSProcessesWithLock( - UILocker::Reasons reason) +ProcessRunner::Results +ProcessRunner::waitForAllUSVFSProcessesWithLock(UILocker::Reasons reason) { m_lockReason = reason; @@ -1026,7 +985,7 @@ ProcessRunner::Results ProcessRunner::waitForAllUSVFSProcessesWithLock( return r; } -void ProcessRunner::withLock(std::function f) +void ProcessRunner::withLock(std::function f) { auto ls = UILocker::instance().lock(m_lockReason); f(*ls); diff --git a/src/processrunner.h b/src/processrunner.h index b8c8935c2..c5b6bbe96 100644 --- a/src/processrunner.h +++ b/src/processrunner.h @@ -1,9 +1,9 @@ #ifndef PROCESSRUNNER_H #define PROCESSRUNNER_H +#include "envmodule.h" #include "spawn.h" #include "uilocker.h" -#include "envmodule.h" #include class OrganizerCore; @@ -37,10 +37,10 @@ class ProcessRunner enum WaitFlag { - NoFlags = 0x00, + NoFlags = 0x00, // the directory structure will be refreshed once the process has completed - TriggerRefresh = 0x01, + TriggerRefresh = 0x01, // the process will be waited for even if locking is disabled or the // process is not hooked @@ -65,26 +65,26 @@ class ProcessRunner ForCommandLine = TriggerRefresh | WaitForRefresh | ForceWait }; - using WaitFlags = QFlags; + using WaitFlags = QFlags; using ForcedLibraries = QList; ProcessRunner(OrganizerCore& core, IUserInterface* ui); // move only - ProcessRunner(ProcessRunner&&) = default; + ProcessRunner(ProcessRunner&&) = default; ProcessRunner& operator=(const ProcessRunner&) = delete; - ProcessRunner(const ProcessRunner&) = delete; - ProcessRunner& operator=(ProcessRunner&&) = delete; + ProcessRunner(const ProcessRunner&) = delete; + ProcessRunner& operator=(ProcessRunner&&) = delete; - ProcessRunner& setBinary(const QFileInfo &binary); + ProcessRunner& setBinary(const QFileInfo& binary); ProcessRunner& setArguments(const QString& arguments); ProcessRunner& setCurrentDirectory(const QDir& directory); ProcessRunner& setSteamID(const QString& steamID); ProcessRunner& setCustomOverwrite(const QString& customOverwrite); ProcessRunner& setForcedLibraries(const ForcedLibraries& forcedLibraries); ProcessRunner& setProfileName(const QString& profileName); - ProcessRunner& setWaitForCompletion( - WaitFlags flags=NoFlags, UILocker::Reasons reason=UILocker::LockUI); + ProcessRunner& setWaitForCompletion(WaitFlags flags = NoFlags, + UILocker::Reasons reason = UILocker::LockUI); ProcessRunner& setHooked(bool b); // - if the target is an executable file, runs it hooked @@ -113,13 +113,12 @@ class ProcessRunner // if the executable is not found in the list, the binary is run solely // based on the parameters given // - ProcessRunner& setFromFileOrExecutable( - const QString &executable, - const QStringList &args, - const QString &cwd={}, - const QString &profile={}, - const QString &forcedCustomOverwrite={}, - bool ignoreCustomOverwrite=false); + ProcessRunner& setFromFileOrExecutable(const QString& executable, + const QStringList& args, + const QString& cwd = {}, + const QString& profile = {}, + const QString& forcedCustomOverwrite = {}, + bool ignoreCustomOverwrite = false); // spawns the process and waits for it if required // @@ -172,7 +171,6 @@ class ProcessRunner env::HandlePtr m_handle; DWORD m_exitCode; - bool shouldRunShell() const; bool shouldRefresh(Results r) const; @@ -190,9 +188,9 @@ class ProcessRunner // creates the lock widget and calls f() // - void withLock(std::function f); + void withLock(std::function f); }; Q_DECLARE_OPERATORS_FOR_FLAGS(ProcessRunner::WaitFlags); -#endif // PROCESSRUNNER_H +#endif // PROCESSRUNNER_H diff --git a/src/profile.cpp b/src/profile.cpp index b4b0ddcb4..c5ea5eec8 100644 --- a/src/profile.cpp +++ b/src/profile.cpp @@ -19,44 +19,44 @@ along with Mod Organizer. If not, see . #include "profile.h" +#include "filesystemutilities.h" #include "modinfo.h" +#include "modinfoforeign.h" +#include "registry.h" #include "settings.h" -#include "filesystemutilities.h" #include "shared/appconfig.h" -#include -#include -#include +#include "shared/util.h" #include #include -#include "shared/util.h" -#include "registry.h" -#include "modinfoforeign.h" +#include #include +#include +#include #include -#include // for QFile -#include // for operator|, QFlags -#include // for QIODevice, etc -#include -#include -#include // for QStringList -#include // for qUtf8Printable #include #include +#include // for QFile +#include // for operator|, QFlags +#include // for QIODevice, etc +#include +#include +#include // for QStringList +#include // for qUtf8Printable #include -#include // for assert -#include // for UINT_MAX, INT_MAX, etc -#include // for size_t -#include // for wcslen +#include // for assert +#include // for UINT_MAX, INT_MAX, etc +#include // for size_t +#include // for wcslen -#include // for max, min -#include // for exception +#include // for max, min +#include // for exception #include -#include // for set -#include // for find +#include // for set #include +#include // for find using namespace MOBase; using namespace MOShared; @@ -65,13 +65,17 @@ void Profile::touchFile(QString fileName) { QFile modList(m_Directory.filePath(fileName)); if (!modList.open(QIODevice::ReadWrite)) { - throw std::runtime_error(QObject::tr("failed to create %1").arg(m_Directory.filePath(fileName)).toUtf8().constData()); + throw std::runtime_error(QObject::tr("failed to create %1") + .arg(m_Directory.filePath(fileName)) + .toUtf8() + .constData()); } } -Profile::Profile(const QString &name, IPluginGame const *gamePlugin, bool useDefaultSettings) - : m_ModListWriter(std::bind(&Profile::doWriteModlist, this)) - , m_GamePlugin(gamePlugin) +Profile::Profile(const QString& name, IPluginGame const* gamePlugin, + bool useDefaultSettings) + : m_ModListWriter(std::bind(&Profile::doWriteModlist, this)), + m_GamePlugin(gamePlugin) { QString profilesDir = Settings::instance().paths().profiles(); QDir profileBase(profilesDir); @@ -84,18 +88,17 @@ Profile::Profile(const QString &name, IPluginGame const *gamePlugin, bool useDef throw MyException(tr("failed to create %1").arg(fixedName).toUtf8().constData()); } QString fullPath = profilesDir + "/" + fixedName; - m_Directory = QDir(fullPath); - m_Settings = new QSettings(m_Directory.absoluteFilePath("settings.ini"), - QSettings::IniFormat); + m_Directory = QDir(fullPath); + m_Settings = + new QSettings(m_Directory.absoluteFilePath("settings.ini"), QSettings::IniFormat); try { // create files. Needs to happen after m_Directory was set! touchFile("modlist.txt"); touchFile("archives.txt"); - IPluginGame::ProfileSettings settings = IPluginGame::CONFIGURATION - | IPluginGame::MODS - | IPluginGame::SAVEGAMES; + IPluginGame::ProfileSettings settings = + IPluginGame::CONFIGURATION | IPluginGame::MODS | IPluginGame::SAVEGAMES; if (useDefaultSettings) { settings |= IPluginGame::PREFER_DEFAULTS; @@ -111,16 +114,14 @@ Profile::Profile(const QString &name, IPluginGame const *gamePlugin, bool useDef refreshModStatus(); } - -Profile::Profile(const QDir &directory, IPluginGame const *gamePlugin) - : m_Directory(directory) - , m_GamePlugin(gamePlugin) - , m_ModListWriter(std::bind(&Profile::doWriteModlist, this)) +Profile::Profile(const QDir& directory, IPluginGame const* gamePlugin) + : m_Directory(directory), m_GamePlugin(gamePlugin), + m_ModListWriter(std::bind(&Profile::doWriteModlist, this)) { assert(gamePlugin != nullptr); - m_Settings = new QSettings(directory.absoluteFilePath("settings.ini"), - QSettings::IniFormat); + m_Settings = + new QSettings(directory.absoluteFilePath("settings.ini"), QSettings::IniFormat); findProfileSettings(); if (!QFile::exists(m_Directory.filePath("modlist.txt"))) { @@ -128,27 +129,24 @@ Profile::Profile(const QDir &directory, IPluginGame const *gamePlugin) touchFile(m_Directory.filePath("modlist.txt")); } - IPluginGame::ProfileSettings settings = IPluginGame::MODS - | IPluginGame::SAVEGAMES; + IPluginGame::ProfileSettings settings = IPluginGame::MODS | IPluginGame::SAVEGAMES; gamePlugin->initializeProfile(directory, settings); refreshModStatus(); } - -Profile::Profile(const Profile &reference) - : m_Directory(reference.m_Directory) - , m_ModListWriter(std::bind(&Profile::doWriteModlist, this)) - , m_GamePlugin(reference.m_GamePlugin) +Profile::Profile(const Profile& reference) + : m_Directory(reference.m_Directory), + m_ModListWriter(std::bind(&Profile::doWriteModlist, this)), + m_GamePlugin(reference.m_GamePlugin) { - m_Settings = new QSettings(m_Directory.absoluteFilePath("settings.ini"), - QSettings::IniFormat); + m_Settings = + new QSettings(m_Directory.absoluteFilePath("settings.ini"), QSettings::IniFormat); findProfileSettings(); refreshModStatus(); } - Profile::~Profile() { delete m_Settings; @@ -179,11 +177,11 @@ void Profile::findProfileSettings() } if (setting("", "AutomaticArchiveInvalidation") == QVariant()) { - BSAInvalidation *invalidation = m_GamePlugin->feature(); - DataArchives *dataArchives = m_GamePlugin->feature(); - bool found = false; + BSAInvalidation* invalidation = m_GamePlugin->feature(); + DataArchives* dataArchives = m_GamePlugin->feature(); + bool found = false; if ((invalidation != nullptr) && (dataArchives != nullptr)) { - for (const QString &archive : dataArchives->archives(this)) { + for (const QString& archive : dataArchives->archives(this)) { if (invalidation->isInvalidationBSA(archive)) { storeSetting("", "AutomaticArchiveInvalidation", true); found = true; @@ -219,20 +217,23 @@ void Profile::cancelModlistWrite() void Profile::doWriteModlist() { - if (!m_Directory.exists()) return; + if (!m_Directory.exists()) + return; try { QString fileName = getModlistFileName(); SafeWriteFile file(fileName); - 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; } - for (auto iter = m_ModIndexByPriority.crbegin(); iter != m_ModIndexByPriority.crend(); iter++) { + for (auto iter = m_ModIndexByPriority.crbegin(); + iter != m_ModIndexByPriority.crend(); iter++) { // the priority order was inverted on load so it has to be inverted again - const auto index = iter->second; + const auto index = iter->second; ModInfo::Ptr modInfo = ModInfo::getByIndex(index); if (!modInfo->hasAutomaticPriority()) { if (modInfo->isForeign()) { @@ -248,22 +249,20 @@ void Profile::doWriteModlist() } file.commitIfDifferent(m_LastModlistHash); - } catch (const std::exception &e) { + } catch (const std::exception& e) { reportError(tr("failed to write mod list: %1").arg(e.what())); return; } } - void Profile::createTweakedIniFile() { QString tweakedIni = m_Directory.absoluteFilePath("initweaks.ini"); if (QFile::exists(tweakedIni) && !shellDeleteQuiet(tweakedIni)) { const auto e = GetLastError(); - reportError( - tr("failed to update tweaked ini file, wrong settings may be used: %1") - .arg(QString::fromStdWString(formatSystemMessage(e)))); + reportError(tr("failed to update tweaked ini file, wrong settings may be used: %1") + .arg(QString::fromStdWString(formatSystemMessage(e)))); return; } @@ -277,14 +276,15 @@ void Profile::createTweakedIniFile() mergeTweak(getProfileTweaks(), tweakedIni); bool error = false; - if (!MOBase::WriteRegistryValue(L"Archive", L"bInvalidateOlderFiles", L"1", ToWString(tweakedIni).c_str())) { + if (!MOBase::WriteRegistryValue(L"Archive", L"bInvalidateOlderFiles", L"1", + ToWString(tweakedIni).c_str())) { error = true; } if (error) { const auto e = ::GetLastError(); reportError(tr("failed to create tweaked ini: %1") - .arg(QString::fromStdWString(formatSystemMessage(e)))); + .arg(QString::fromStdWString(formatSystemMessage(e)))); } } @@ -305,7 +305,8 @@ void Profile::renameModInAllProfiles(const QString& oldName, const QString& newN } // static -void Profile::renameModInList(QFile &modList, const QString &oldName, const QString &newName) +void Profile::renameModInList(QFile& modList, const QString& oldName, + const QString& newName) { if (!modList.open(QIODevice::ReadOnly)) { reportError(tr("failed to open %1").arg(modList.fileName())); @@ -357,9 +358,8 @@ void Profile::renameModInList(QFile &modList, const QString &oldName, const QStr } if (renamed) - log::debug( - "Renamed {} \"{}\" mod to \"{}\" in {}", - renamed, oldName, newName, modList.fileName()); + log::debug("Renamed {} \"{}\" mod to \"{}\" in {}", renamed, oldName, newName, + modList.fileName()); } void Profile::refreshModStatus() @@ -396,11 +396,12 @@ void Profile::refreshModStatus() // N-1 overwrite (N = number of mods) // - writeModlistNow(true); // if there are pending changes write them first + writeModlistNow(true); // if there are pending changes write them first QFile file(getModlistFileName()); if (!file.open(QIODevice::ReadOnly)) { - throw MyException(tr("\"%1\" is missing or inaccessible").arg(getModlistFileName())); + throw MyException( + tr("\"%1\" is missing or inaccessible").arg(getModlistFileName())); } bool modStatusModified = false; @@ -450,9 +451,7 @@ void Profile::refreshModStatus() unsigned int modIndex = ModInfo::getIndex(modName); if (modIndex == UINT_MAX) { - log::debug( - "mod not found: \"{}\" (profile \"{}\")", - modName, m_Directory.path()); + log::debug("mod not found: \"{}\" (profile \"{}\")", modName, m_Directory.path()); // need to rewrite the modlist to fix this modStatusModified = true; continue; @@ -469,19 +468,18 @@ void Profile::refreshModStatus() m_ModStatus[modIndex].m_Priority = index++; } } else { - log::warn( - "no mod state for \"{}\" (profile \"{}\")", - modName, m_Directory.path()); + log::warn("no mod state for \"{}\" (profile \"{}\")", modName, + m_Directory.path()); // need to rewrite the modlist to fix this modStatusModified = true; } - } // while (!file.atEnd()) + } // while (!file.atEnd()) file.close(); const int numKnownMods = index; - int topInsert = 0; + int topInsert = 0; // invert priority order to match that of the pluginlist, also // give priorities to mods not referenced in the profile and @@ -521,8 +519,8 @@ void Profile::refreshModStatus() } } - // to support insertion of new mods at the top we may now have mods with negative priority, - // so shift them all up to align priority with 0 + // to support insertion of new mods at the top we may now have mods with negative + // priority, so shift them all up to align priority with 0 if (topInsert < 0) { int offset = topInsert * -1; for (size_t i = 0; i < m_ModStatus.size(); ++i) { @@ -548,7 +546,8 @@ void Profile::refreshModStatus() // User has a mod named some variation of "overwrite". Tell them about it. if (warnAboutOverwrite) { - reportError(tr("A mod named \"overwrite\" was detected, disabled, and moved to the highest priority on the mod list. " + reportError(tr("A mod named \"overwrite\" was detected, disabled, and moved to the " + "highest priority on the mod list. " "You may want to rename this mod and enable it again.")); // also, mark the mod-list as changed modStatusModified = true; @@ -559,36 +558,32 @@ void Profile::refreshModStatus() } } - void Profile::dumpModStatus() const { for (unsigned int i = 0; i < m_ModStatus.size(); ++i) { ModInfo::Ptr info = ModInfo::getByIndex(i); - log::warn( - "{}: {} - {} ({})", - i, info->name(), m_ModStatus[i].m_Priority, - m_ModStatus[i].m_Enabled ? "enabled" : "disabled"); + log::warn("{}: {} - {} ({})", i, info->name(), m_ModStatus[i].m_Priority, + m_ModStatus[i].m_Enabled ? "enabled" : "disabled"); } } - void Profile::updateIndices() { m_ModIndexByPriority.clear(); for (unsigned int i = 0; i < m_ModStatus.size(); ++i) { - int priority = m_ModStatus[i].m_Priority; + int priority = m_ModStatus[i].m_Priority; m_ModIndexByPriority[priority] = i; } } - -std::vector > Profile::getActiveMods() +std::vector> Profile::getActiveMods() { - std::vector > result; + std::vector> result; for (const auto& [priority, index] : m_ModIndexByPriority) { if (m_ModStatus[index].m_Enabled) { ModInfo::Ptr modInfo = ModInfo::getByIndex(index); - result.push_back(std::make_tuple(modInfo->internalName(), modInfo->absolutePath(), m_ModStatus[index].m_Priority)); + result.push_back(std::make_tuple(modInfo->internalName(), modInfo->absolutePath(), + m_ModStatus[index].m_Priority)); } } @@ -618,7 +613,8 @@ void Profile::setModEnabled(unsigned int index, bool enabled) } } -void Profile::setModsEnabled(const QList& modsToEnable, const QList& modsToDisable) +void Profile::setModsEnabled(const QList& modsToEnable, + const QList& modsToDisable) { QList dirtyMods; for (auto idx : modsToEnable) { @@ -661,7 +657,6 @@ bool Profile::modEnabled(unsigned int index) const return m_ModStatus[index].m_Enabled; } - int Profile::getModPriority(unsigned int index) const { if (index >= m_ModStatus.size()) { @@ -671,8 +666,7 @@ int Profile::getModPriority(unsigned int index) const return m_ModStatus[index].m_Priority; } - -bool Profile::setModPriority(unsigned int index, int &newPriority) +bool Profile::setModPriority(unsigned int index, int& newPriority) { if (ModInfo::getByIndex(index)->hasAutomaticPriority()) { // can't change priority of overwrite/backups @@ -681,7 +675,7 @@ bool Profile::setModPriority(unsigned int index, int &newPriority) newPriority = std::clamp(newPriority, 0, static_cast(m_NumRegularMods) - 1); - int oldPriority = m_ModStatus.at(index).m_Priority; + int oldPriority = m_ModStatus.at(index).m_Priority; int lastPriority = INT_MIN; if (newPriority == oldPriority) { @@ -690,16 +684,17 @@ bool Profile::setModPriority(unsigned int index, int &newPriority) } for (const auto& [priority, index] : m_ModIndexByPriority) { - if (newPriority < oldPriority && priority >= newPriority && priority < oldPriority) { + if (newPriority < oldPriority && priority >= newPriority && + priority < oldPriority) { m_ModStatus.at(index).m_Priority += 1; - } - else if (newPriority > oldPriority && priority <= newPriority && priority > oldPriority) { + } else if (newPriority > oldPriority && priority <= newPriority && + priority > oldPriority) { m_ModStatus.at(index).m_Priority -= 1; } lastPriority = std::max(lastPriority, priority); } - newPriority = std::min(newPriority, lastPriority); + newPriority = std::min(newPriority, lastPriority); m_ModStatus.at(index).m_Priority = std::min(newPriority, lastPriority); updateIndices(); @@ -708,23 +703,24 @@ bool Profile::setModPriority(unsigned int index, int &newPriority) return true; } -Profile *Profile::createPtrFrom(const QString &name, const Profile &reference, MOBase::IPluginGame const *gamePlugin) +Profile* Profile::createPtrFrom(const QString& name, const Profile& reference, + MOBase::IPluginGame const* gamePlugin) { QString profileDirectory = Settings::instance().paths().profiles() + "/" + name; reference.copyFilesTo(profileDirectory); return new Profile(QDir(profileDirectory), gamePlugin); } -void Profile::copyFilesTo(QString &target) const +void Profile::copyFilesTo(QString& target) const { copyDir(m_Directory.absolutePath(), target, false); } -std::vector Profile::splitDZString(const wchar_t *buffer) const +std::vector Profile::splitDZString(const wchar_t* buffer) const { std::vector result; - const wchar_t *pos = buffer; - size_t length = wcslen(pos); + const wchar_t* pos = buffer; + size_t length = wcslen(pos); while (length != 0U) { result.push_back(pos); pos += length + 1; @@ -733,7 +729,7 @@ std::vector Profile::splitDZString(const wchar_t *buffer) const return result; } -void Profile::mergeTweak(const QString &tweakName, const QString &tweakedIni) const +void Profile::mergeTweak(const QString& tweakName, const QString& tweakedIni) const { static const int bufferSize = 32768; @@ -742,14 +738,15 @@ void Profile::mergeTweak(const QString &tweakName, const QString &tweakedIni) co QScopedArrayPointer buffer(new wchar_t[bufferSize]); // retrieve a list of sections - DWORD size = ::GetPrivateProfileSectionNamesW( - buffer.data(), bufferSize, tweakNameW.c_str()); + DWORD size = + ::GetPrivateProfileSectionNamesW(buffer.data(), bufferSize, tweakNameW.c_str()); if (size == bufferSize - 2) { // unfortunately there is no good way to find the required size // of the buffer throw MyException(QString("Buffer too small. Please report this as a bug. " - "For now you might want to split up %1").arg(tweakName)); + "For now you might want to split up %1") + .arg(tweakName)); } std::vector sections = splitDZString(buffer.data()); @@ -762,36 +759,37 @@ void Profile::mergeTweak(const QString &tweakName, const QString &tweakedIni) co bufferSize, tweakNameW.c_str()); if (size == bufferSize - 2) { throw MyException(QString("Buffer too small. Please report this as a bug. " - "For now you might want to split up %1").arg(tweakName)); + "For now you might want to split up %1") + .arg(tweakName)); } std::vector keys = splitDZString(buffer.data()); for (std::vector::iterator keyIter = keys.begin(); keyIter != keys.end(); ++keyIter) { - //TODO this treats everything as strings but how could I differentiate the type? - ::GetPrivateProfileStringW(iter->c_str(), keyIter->c_str(), - nullptr, buffer.data(), bufferSize, ToWString(tweakName).c_str()); - MOBase::WriteRegistryValue(iter->c_str(), keyIter->c_str(), - buffer.data(), tweakedIniW.c_str()); + // TODO this treats everything as strings but how could I differentiate the type? + ::GetPrivateProfileStringW(iter->c_str(), keyIter->c_str(), nullptr, + buffer.data(), bufferSize, + ToWString(tweakName).c_str()); + MOBase::WriteRegistryValue(iter->c_str(), keyIter->c_str(), buffer.data(), + tweakedIniW.c_str()); } } } -void Profile::mergeTweaks(ModInfo::Ptr modInfo, const QString &tweakedIni) const +void Profile::mergeTweaks(ModInfo::Ptr modInfo, const QString& tweakedIni) const { std::vector iniTweaks = modInfo->getIniTweaks(); - for (std::vector::iterator iter = iniTweaks.begin(); - iter != iniTweaks.end(); ++iter) { + for (std::vector::iterator iter = iniTweaks.begin(); iter != iniTweaks.end(); + ++iter) { mergeTweak(*iter, tweakedIni); } } - -bool Profile::invalidationActive(bool *supported) const +bool Profile::invalidationActive(bool* supported) const { - BSAInvalidation *invalidation = m_GamePlugin->feature(); - DataArchives *dataArchives = m_GamePlugin->feature(); + BSAInvalidation* invalidation = m_GamePlugin->feature(); + DataArchives* dataArchives = m_GamePlugin->feature(); if (supported != nullptr) { *supported = ((invalidation != nullptr) && (dataArchives != nullptr)); @@ -800,10 +798,9 @@ bool Profile::invalidationActive(bool *supported) const return setting("", "AutomaticArchiveInvalidation", false).toBool(); } - void Profile::deactivateInvalidation() { - BSAInvalidation *invalidation = m_GamePlugin->feature(); + BSAInvalidation* invalidation = m_GamePlugin->feature(); if (invalidation != nullptr) { invalidation->deactivate(this); @@ -812,10 +809,9 @@ void Profile::deactivateInvalidation() storeSetting("", "AutomaticArchiveInvalidation", false); } - void Profile::activateInvalidation() { - BSAInvalidation *invalidation = m_GamePlugin->feature(); + BSAInvalidation* invalidation = m_GamePlugin->feature(); if (invalidation != nullptr) { invalidation->activate(this); @@ -824,26 +820,28 @@ void Profile::activateInvalidation() storeSetting("", "AutomaticArchiveInvalidation", true); } - bool Profile::localSavesEnabled() const { return setting("", "LocalSaves", false).toBool(); } - bool Profile::enableLocalSaves(bool enable) { if (enable) { if (!m_Directory.exists("saves")) { m_Directory.mkdir("saves"); } - } else { + } else { QDialogButtonBox::StandardButton res; - res = QuestionBoxMemory::query(QApplication::activeModalWidget(), "deleteSavesQuery", - tr("Delete profile-specific save games?"), - tr("Do you want to delete the profile-specific save games? (If you select \"No\", the " - "save games will show up again if you re-enable profile-specific save games)"), - QDialogButtonBox::No | QDialogButtonBox::Yes | QDialogButtonBox::Cancel, QDialogButtonBox::No); + res = QuestionBoxMemory::query( + QApplication::activeModalWidget(), "deleteSavesQuery", + tr("Delete profile-specific save games?"), + tr("Do you want to delete the profile-specific save games? (If you select " + "\"No\", the " + "save games will show up again if you re-enable profile-specific save " + "games)"), + QDialogButtonBox::No | QDialogButtonBox::Yes | QDialogButtonBox::Cancel, + QDialogButtonBox::No); if (res == QMessageBox::Yes) { shellDelete(QStringList(m_Directory.absoluteFilePath("saves"))); } else if (res == QMessageBox::No) { @@ -854,7 +852,6 @@ bool Profile::enableLocalSaves(bool enable) } storeSetting("", "LocalSaves", enable); return true; - } bool Profile::localSettingsEnabled() const @@ -871,12 +868,15 @@ bool Profile::localSettingsEnabled() const } if (!missingFiles.empty()) { m_GamePlugin->initializeProfile(m_Directory, IPluginGame::CONFIGURATION); - QMessageBox::StandardButton res = QMessageBox::warning( - QApplication::activeModalWidget(), tr("Missing profile-specific game INI files!"), - tr("Some of your profile-specific game INI files were missing. They will now be copied " - "from the vanilla game folder. You might want to double-check your settings.\n\n" - "Missing files:\n") + missingFiles.join("\n") - ); + QMessageBox::StandardButton res = + QMessageBox::warning(QApplication::activeModalWidget(), + tr("Missing profile-specific game INI files!"), + tr("Some of your profile-specific game INI files were " + "missing. They will now be copied " + "from the vanilla game folder. You might want to " + "double-check your settings.\n\n" + "Missing files:\n") + + missingFiles.join("\n")); } } return enabled; @@ -885,14 +885,19 @@ bool Profile::localSettingsEnabled() const bool Profile::enableLocalSettings(bool enable) { if (enable) { - m_GamePlugin->initializeProfile(m_Directory.absolutePath(), IPluginGame::CONFIGURATION); + m_GamePlugin->initializeProfile(m_Directory.absolutePath(), + IPluginGame::CONFIGURATION); } else { QDialogButtonBox::StandardButton res; res = QuestionBoxMemory::query(QApplication::activeModalWidget(), "deleteINIQuery", - tr("Delete profile-specific game INI files?"), - tr("Do you want to delete the profile-specific game INI files? (If you select \"No\", the " - "INI files will be used again if you re-enable profile-specific game INI files.)"), - QDialogButtonBox::No | QDialogButtonBox::Yes | QDialogButtonBox::Cancel, QDialogButtonBox::No); + tr("Delete profile-specific game INI files?"), + tr("Do you want to delete the profile-specific game " + "INI files? (If you select \"No\", the " + "INI files will be used again if you re-enable " + "profile-specific game INI files.)"), + QDialogButtonBox::No | QDialogButtonBox::Yes | + QDialogButtonBox::Cancel, + QDialogButtonBox::No); if (res == QMessageBox::Yes) { QStringList filesToDelete; for (QString file : m_GamePlugin->iniFiles()) { @@ -970,7 +975,8 @@ QString Profile::absoluteIniFilePath(QString iniFile) const QString Profile::getProfileTweaks() const { - return QDir::cleanPath(m_Directory.absoluteFilePath(ToQString(AppConfig::profileTweakIni()))); + return QDir::cleanPath( + m_Directory.absoluteFilePath(ToQString(AppConfig::profileTweakIni()))); } QString Profile::absolutePath() const @@ -981,17 +987,16 @@ QString Profile::absolutePath() const QString Profile::savePath() const { return QDir::cleanPath(m_Directory.absoluteFilePath("saves")); - } -void Profile::rename(const QString &newName) +void Profile::rename(const QString& newName) { QDir profileDir(Settings::instance().paths().profiles()); profileDir.rename(name(), newName); m_Directory.setPath(profileDir.absoluteFilePath(newName)); } -QString keyName(const QString §ion, const QString &name) +QString keyName(const QString& section, const QString& name) { QString key = section; @@ -1006,24 +1011,24 @@ QString keyName(const QString §ion, const QString &name) return key; } -QVariant Profile::setting(const QString §ion, const QString &name, - const QVariant &fallback) const +QVariant Profile::setting(const QString& section, const QString& name, + const QVariant& fallback) const { return m_Settings->value(keyName(section, name), fallback); } -void Profile::storeSetting(const QString §ion, const QString &name, - const QVariant &value) +void Profile::storeSetting(const QString& section, const QString& name, + const QVariant& value) { m_Settings->setValue(keyName(section, name), value); } -void Profile::removeSetting(const QString §ion, const QString &name) +void Profile::removeSetting(const QString& section, const QString& name) { - m_Settings->remove(keyName(section, name)); + m_Settings->remove(keyName(section, name)); } -QVariantMap Profile::settingsByGroup(const QString §ion) const +QVariantMap Profile::settingsByGroup(const QString& section) const { QVariantMap results; m_Settings->beginGroup(section); @@ -1034,7 +1039,7 @@ QVariantMap Profile::settingsByGroup(const QString §ion) const return results; } -void Profile::storeSettingsByGroup(const QString §ion, const QVariantMap &values) +void Profile::storeSettingsByGroup(const QString& section, const QVariantMap& values) { m_Settings->beginGroup(section); for (auto key : values.keys()) { @@ -1043,7 +1048,7 @@ void Profile::storeSettingsByGroup(const QString §ion, const QVariantMap &va m_Settings->endGroup(); } -QList Profile::settingsByArray(const QString &prefix) const +QList Profile::settingsByArray(const QString& prefix) const { QList results; int size = m_Settings->beginReadArray(prefix); @@ -1059,7 +1064,8 @@ QList Profile::settingsByArray(const QString &prefix) const return results; } -void Profile::storeSettingsByArray(const QString &prefix, const QList &values) +void Profile::storeSettingsByArray(const QString& prefix, + const QList& values) { m_Settings->beginWriteArray(prefix); for (int i = 0; i < values.length(); i++) { @@ -1071,17 +1077,18 @@ void Profile::storeSettingsByArray(const QString &prefix, const QListendArray(); } -bool Profile::forcedLibrariesEnabled(const QString &executable) const +bool Profile::forcedLibrariesEnabled(const QString& executable) const { return setting("forced_libraries", executable + "/enabled", true).toBool(); } -void Profile::setForcedLibrariesEnabled(const QString &executable, bool enabled) +void Profile::setForcedLibrariesEnabled(const QString& executable, bool enabled) { storeSetting("forced_libraries", executable + "/enabled", enabled); } -QList Profile::determineForcedLibraries(const QString &executable) const +QList +Profile::determineForcedLibraries(const QString& executable) const { QList results; @@ -1092,10 +1099,12 @@ QList Profile::determineForcedLibraries(const QStri for (auto forcedLoad : forcedLoads) { bool found = false; for (auto rawSetting : rawSettings) { - if ((rawSetting.value("process").toString().compare(forcedLoad.process(), Qt::CaseInsensitive) == 0) - && (rawSetting.value("library").toString().compare(forcedLoad.library(), Qt::CaseInsensitive) == 0)) - { - results.append(forcedLoad.withEnabled(rawSetting.value("enabled", false).toBool())); + if ((rawSetting.value("process").toString().compare(forcedLoad.process(), + Qt::CaseInsensitive) == 0) && + (rawSetting.value("library").toString().compare(forcedLoad.library(), + Qt::CaseInsensitive) == 0)) { + results.append( + forcedLoad.withEnabled(rawSetting.value("enabled", false).toBool())); found = true; } } @@ -1108,26 +1117,25 @@ QList Profile::determineForcedLibraries(const QStri for (auto rawSetting : rawSettings) { bool add = true; for (auto forcedLoad : forcedLoads) { - if ((rawSetting.value("process").toString().compare(forcedLoad.process(), Qt::CaseInsensitive) == 0) - && (rawSetting.value("library").toString().compare(forcedLoad.library(), Qt::CaseInsensitive) == 0)) - { + if ((rawSetting.value("process").toString().compare(forcedLoad.process(), + Qt::CaseInsensitive) == 0) && + (rawSetting.value("library").toString().compare(forcedLoad.library(), + Qt::CaseInsensitive) == 0)) { add = false; } } if (add) { - results.append( - ExecutableForcedLoadSetting( - rawSetting.value("process").toString(), - rawSetting.value("library").toString() - ).withEnabled(rawSetting.value("enabled", false).toBool()) - ); + results.append(ExecutableForcedLoadSetting(rawSetting.value("process").toString(), + rawSetting.value("library").toString()) + .withEnabled(rawSetting.value("enabled", false).toBool())); } } return results; } -void Profile::storeForcedLibraries(const QString &executable, const QList &values) +void Profile::storeForcedLibraries(const QString& executable, + const QList& values) { QList rawSettings; for (auto setting : values) { @@ -1140,7 +1148,7 @@ void Profile::storeForcedLibraries(const QString &executable, const QListremove("forced_libraries/" + executable); } @@ -1149,8 +1157,8 @@ void Profile::debugDump() const { struct Pair { - std::size_t enabled=0; - std::size_t total=0; + std::size_t enabled = 0; + std::size_t total = 0; }; Pair total; @@ -1169,7 +1177,6 @@ void Profile::debugDump() const } }; - for (const auto& status : m_ModStatus) { auto index = m_ModIndexByPriority.find(status.m_Priority); if (index == m_ModIndexByPriority.end()) { @@ -1179,7 +1186,8 @@ void Profile::debugDump() const auto m = ModInfo::getByIndex(index->second); if (!m) { - log::error("mod index {} with priority {} not found", index->second, status.m_Priority); + log::error("mod index {} with priority {} not found", index->second, + status.m_Priority); continue; } @@ -1199,43 +1207,34 @@ void Profile::debugDump() const if (m->hasFlag(ModInfo::FLAG_FOREIGN)) { if (auto* f = dynamic_cast(m.get())) { - switch (f->modType()) - { - case ModInfo::MOD_DLC: - add(dlc, status); - break; - - case ModInfo::MOD_CC: - add(cc, status); - break; - - default: - add(unmanaged, status); - break; + switch (f->modType()) { + case ModInfo::MOD_DLC: + add(dlc, status); + break; + + case ModInfo::MOD_CC: + add(cc, status); + break; + + default: + add(unmanaged, status); + break; } } } - if (!m->hasAnyOfTheseFlags({ - ModInfo::FLAG_BACKUP, ModInfo::FLAG_FOREIGN, - ModInfo::FLAG_SEPARATOR, ModInfo::FLAG_OVERWRITE })) - { + if (!m->hasAnyOfTheseFlags({ModInfo::FLAG_BACKUP, ModInfo::FLAG_FOREIGN, + ModInfo::FLAG_SEPARATOR, ModInfo::FLAG_OVERWRITE})) { add(real, status); } } - log::debug( - "profile '{}' in '{}': " - "mods={}/{} backup={}/{} separators={}/{} real={}/{} dlc={}/{} " - "cc={}/{} unmanaged={}/{} localsaves={}, localsettings={}", - name(), absolutePath(), - total.enabled, total.total, - backup.enabled, backup.total, - separators.enabled, separators.total, - real.enabled, real.total, - dlc.enabled, dlc.total, - cc.enabled, cc.total, - unmanaged.enabled, unmanaged.total, - localSavesEnabled() ? "yes" : "no", - localSettingsEnabled() ? "yes" : "no"); + log::debug("profile '{}' in '{}': " + "mods={}/{} backup={}/{} separators={}/{} real={}/{} dlc={}/{} " + "cc={}/{} unmanaged={}/{} localsaves={}, localsettings={}", + name(), absolutePath(), total.enabled, total.total, backup.enabled, + backup.total, separators.enabled, separators.total, real.enabled, + real.total, dlc.enabled, dlc.total, cc.enabled, cc.total, + unmanaged.enabled, unmanaged.total, localSavesEnabled() ? "yes" : "no", + localSettingsEnabled() ? "yes" : "no"); } diff --git a/src/profile.h b/src/profile.h index 457d8015d..0adb4d0fe 100644 --- a/src/profile.h +++ b/src/profile.h @@ -20,18 +20,17 @@ along with Mod Organizer. If not, see . #ifndef PROFILE_H #define PROFILE_H - +#include "executableinfo.h" #include "modinfo.h" -#include #include -#include "executableinfo.h" +#include #include #include +#include #include -#include #include -#include +#include #include @@ -39,8 +38,10 @@ along with Mod Organizer. If not, see . #include #include - -namespace MOBase { class IPluginGame; } +namespace MOBase +{ +class IPluginGame; +} /** * @brief represents a profile @@ -51,18 +52,15 @@ class Profile : public QObject, public MOBase::IProfile Q_OBJECT public: - using Ptr = boost::shared_ptr; public: - // the minimum and maximum priority achievable by mods // static constexpr int MinimumPriority = 0; static constexpr int MaximumPriority = std::numeric_limits::max(); public: - /** * @brief constructor * @@ -71,25 +69,27 @@ class Profile : public QObject, public MOBase::IProfile * @param name name of the new profile * @param filter save game filter. Defaults to <no filter>. **/ - Profile(const QString &name, MOBase::IPluginGame const *gamePlugin, bool useDefaultSettings); + Profile(const QString& name, MOBase::IPluginGame const* gamePlugin, + bool useDefaultSettings); /** * @brief constructor * - * This constructor is used to open an existing profile though it will also try to repair - * the profile if important files are missing (including the directory itself) so technically, - * invoking this should always produce a working profile + * This constructor is used to open an existing profile though it will also try to + *repair the profile if important files are missing (including the directory itself) + *so technically, invoking this should always produce a working profile * @param directory directory to read the profile from **/ - Profile(const QDir &directory, MOBase::IPluginGame const *gamePlugin); + Profile(const QDir& directory, MOBase::IPluginGame const* gamePlugin); - Profile(const Profile &reference); + Profile(const Profile& reference); ~Profile(); /** - * Determines the default settings for the profile based on the current state of the profile's - * files. This function should remain backwards compatible as much as possible. + * Determines the default settings for the profile based on the current state of the + *profile's files. This function should remain backwards compatible as much as + *possible. **/ void findProfileSettings(void); @@ -103,25 +103,27 @@ class Profile : public QObject, public MOBase::IProfile * @param name of the new profile * @param reference profile to copy from **/ - static Profile *createPtrFrom(const QString &name, const Profile &reference, MOBase::IPluginGame const *gamePlugin); - + static Profile* createPtrFrom(const QString& name, const Profile& reference, + MOBase::IPluginGame const* gamePlugin); static void renameModInAllProfiles(const QString& oldName, const QString& newName); void writeModlist(); - void writeModlistNow(bool onlyIfPending=false); + void writeModlistNow(bool onlyIfPending = false); void cancelModlistWrite(); /** * @brief test if this profile uses archive invalidation * - * @param supported if this is not null, the parameter will be set to false if invalidation is not supported in this profile + * @param supported if this is not null, the parameter will be set to false if + *invalidation is not supported in this profile * @return true if archive invalidation is active - * @note currently, invalidation is not supported if the relevant entry in the ini file does not exist + * @note currently, invalidation is not supported if the relevant entry in the ini + *file does not exist **/ - bool invalidationActive(bool *supported) const; + bool invalidationActive(bool* supported) const; /** * @brief deactivate archive invalidation if it was active @@ -165,7 +167,8 @@ class Profile : public QObject, public MOBase::IProfile /** * @return the path of the plugins file in this profile - * @todo is this required? can the functionality using this function be moved to the Profile-class? + * @todo is this required? can the functionality using this function be moved to the + *Profile-class? **/ QString getPluginsFileName() const; @@ -191,8 +194,8 @@ class Profile : public QObject, public MOBase::IProfile /** * @return the path of the ini file in this profile - * @todo since the game can contain multiple ini files (i.e. skyrim.ini skyrimprefs.ini) - * the concept of this function is somewhat broken + * @todo since the game can contain multiple ini files (i.e. skyrim.ini + *skyrimprefs.ini) the concept of this function is somewhat broken **/ QString getIniFileName() const; @@ -225,7 +228,7 @@ class Profile : public QObject, public MOBase::IProfile * @brief rename profile * @param newName new name of profile */ - void rename(const QString &newName); + void rename(const QString& newName); /** * @brief create the ini file to be used by the game @@ -242,7 +245,8 @@ class Profile : public QObject, public MOBase::IProfile /** * @brief retrieve a list of mods that are enabled in this profile * - * @return list of active mods sorted by priority (ascending). "first" is the mod name, "second" is its path + * @return list of active mods sorted by priority (ascending). "first" is the mod + *name, "second" is its path **/ std::vector> getActiveMods(); @@ -251,7 +255,10 @@ class Profile : public QObject, public MOBase::IProfile * * @return map of indexes by priority **/ - const std::map& getAllIndexesByPriority() { return m_ModIndexByPriority; } + const std::map& getAllIndexesByPriority() + { + return m_ModIndexByPriority; + } /** * retrieve the number of mods for which this object has status information. @@ -278,7 +285,8 @@ class Profile : public QObject, public MOBase::IProfile * @param modsToEnable list of mod indicies to enable * @param modsToDisable list of mod indicies to disable **/ - void setModsEnabled(const QList &modsToEnable, const QList &modsToDisable); + void setModsEnabled(const QList& modsToEnable, + const QList& modsToDisable); // set the priority of a mod, and the priority of other mods in the range // [old priority, new priority] such that no gaps are possible @@ -311,30 +319,30 @@ class Profile : public QObject, public MOBase::IProfile void dumpModStatus() const; - QVariant setting( - const QString §ion, const QString &name, - const QVariant &fallback={}) const; + QVariant setting(const QString& section, const QString& name, + const QVariant& fallback = {}) const; - void storeSetting( - const QString §ion, const QString &name, const QVariant &value={}); + void storeSetting(const QString& section, const QString& name, + const QVariant& value = {}); - void removeSetting(const QString §ion, const QString &name); + void removeSetting(const QString& section, const QString& name); - QVariantMap settingsByGroup(const QString §ion) const; - void storeSettingsByGroup(const QString §ion, const QVariantMap &values); + QVariantMap settingsByGroup(const QString& section) const; + void storeSettingsByGroup(const QString& section, const QVariantMap& values); - QList settingsByArray(const QString &prefix) const; - void storeSettingsByArray(const QString &prefix, const QList &values); + QList settingsByArray(const QString& prefix) const; + void storeSettingsByArray(const QString& prefix, const QList& values); - bool forcedLibrariesEnabled(const QString &executable) const; - void setForcedLibrariesEnabled(const QString &executable, bool enabled); - QList determineForcedLibraries(const QString &executable) const; - void storeForcedLibraries(const QString &executable, const QList &values); - void removeForcedLibraries(const QString &executable); + bool forcedLibrariesEnabled(const QString& executable) const; + void setForcedLibrariesEnabled(const QString& executable, bool enabled); + QList + determineForcedLibraries(const QString& executable) const; + void storeForcedLibraries(const QString& executable, + const QList& values); + void removeForcedLibraries(const QString& executable); void debugDump() const; - Profile& operator=(const Profile& reference) = delete; signals: @@ -355,40 +363,42 @@ class Profile : public QObject, public MOBase::IProfile protected slots: - // should only be called by DelayedFileWriter, use writeModlist() and writeModlistNow() instead + // should only be called by DelayedFileWriter, use writeModlist() and + // writeModlistNow() instead void doWriteModlist(); private: - - class ModStatus { + class ModStatus + { friend class Profile; + public: ModStatus() : m_Enabled(false), m_Priority(-1) {} + private: bool m_Enabled; int m_Priority; }; private: - void updateIndices(); - void copyFilesTo(QString &target) const; + void copyFilesTo(QString& target) const; - std::vector splitDZString(const wchar_t *buffer) const; - void mergeTweak(const QString &tweakName, const QString &tweakedIni) const; - void mergeTweaks(ModInfo::Ptr modInfo, const QString &tweakedIni) const; + std::vector splitDZString(const wchar_t* buffer) const; + void mergeTweak(const QString& tweakName, const QString& tweakedIni) const; + void mergeTweaks(ModInfo::Ptr modInfo, const QString& tweakedIni) const; void touchFile(QString fileName); - static void renameModInList(QFile &modList, const QString &oldName, const QString &newName); + static void renameModInList(QFile& modList, const QString& oldName, + const QString& newName); private: - QDir m_Directory; - QSettings *m_Settings; + QSettings* m_Settings; - const MOBase::IPluginGame *m_GamePlugin; + const MOBase::IPluginGame* m_GamePlugin; std::vector m_ModStatus; std::map m_ModIndexByPriority; @@ -399,8 +409,6 @@ protected slots: mutable QByteArray m_LastModlistHash; MOBase::DelayedFileWriter m_ModListWriter; - }; - -#endif // PROFILE_H +#endif // PROFILE_H diff --git a/src/profileinputdialog.cpp b/src/profileinputdialog.cpp index 48b35b82f..311558199 100644 --- a/src/profileinputdialog.cpp +++ b/src/profileinputdialog.cpp @@ -1,48 +1,45 @@ -/* -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 "profileinputdialog.h" -#include "ui_profileinputdialog.h" -#include "filesystemutilities.h" - -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; -} - +/* +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 "profileinputdialog.h" +#include "filesystemutilities.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 +{ + QString result = ui->nameEdit->text(); + MOBase::fixDirectoryName(result); + return result; +} + +bool ProfileInputDialog::getPreferDefaultSettings() const +{ + return ui->defaultSettingsBox->checkState() == Qt::Checked; +} diff --git a/src/profileinputdialog.h b/src/profileinputdialog.h index 8a132666b..d75e11b84 100644 --- a/src/profileinputdialog.h +++ b/src/profileinputdialog.h @@ -1,44 +1,45 @@ -/* -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 PROFILEINPUTDIALOG_H -#define PROFILEINPUTDIALOG_H - -#include - -namespace Ui { -class ProfileInputDialog; -} - -class ProfileInputDialog : public QDialog -{ - Q_OBJECT - -public: - explicit ProfileInputDialog(QWidget *parent = 0); - ~ProfileInputDialog(); - - QString getName() const; - bool getPreferDefaultSettings() const; - -private: - Ui::ProfileInputDialog *ui; -}; - -#endif // PROFILEINPUTDIALOG_H +/* +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 PROFILEINPUTDIALOG_H +#define PROFILEINPUTDIALOG_H + +#include + +namespace Ui +{ +class ProfileInputDialog; +} + +class ProfileInputDialog : public QDialog +{ + Q_OBJECT + +public: + explicit ProfileInputDialog(QWidget* parent = 0); + ~ProfileInputDialog(); + + QString getName() const; + bool getPreferDefaultSettings() const; + +private: + Ui::ProfileInputDialog* ui; +}; + +#endif // PROFILEINPUTDIALOG_H diff --git a/src/profilesdialog.cpp b/src/profilesdialog.cpp index 3ab709367..674a77800 100644 --- a/src/profilesdialog.cpp +++ b/src/profilesdialog.cpp @@ -1,403 +1,423 @@ -/* -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 "profilesdialog.h" -#include "ui_profilesdialog.h" - -#include "shared/appconfig.h" -#include "bsainvalidation.h" -#include "iplugingame.h" -#include "organizercore.h" -#include "profile.h" -#include "profileinputdialog.h" -#include "report.h" -#include "transfersavesdialog.h" -#include "filesystemutilities.h" -#include "settings.h" -#include "localsavegames.h" - -#include -#include -#include -#include -#include -#include -#include - -#include - -#include - -using namespace MOBase; -using namespace MOShared; - -Q_DECLARE_METATYPE(Profile::Ptr) - - -ProfilesDialog::ProfilesDialog(const QString &profileName, OrganizerCore &organizer, QWidget *parent) - : TutorableDialog("Profiles", parent) - , ui(new Ui::ProfilesDialog) - , m_FailState(false) - , m_Game(organizer.managedGame()) - , m_ActiveProfileName("") -{ - ui->setupUi(this); - - QDir profilesDir(Settings::instance().paths().profiles()); - profilesDir.setFilter(QDir::AllDirs | QDir::NoDotAndDotDot); - - QDirIterator profileIter(profilesDir); - - while (profileIter.hasNext()) { - profileIter.next(); - QListWidgetItem *item = addItem(profileIter.filePath()); - if (profileName == profileIter.fileName()) { - ui->profilesList->setCurrentItem(item); - m_ActiveProfileName = profileName; - } - } - - BSAInvalidation *invalidation = m_Game->feature(); - - if (invalidation == nullptr) { - ui->invalidationBox->setToolTip(tr("Archive invalidation isn't required for this game.")); - ui->invalidationBox->setEnabled(false); - } - - if (!m_Game->feature()) { - ui->localSavesBox->setToolTip(tr("This game does not support profile-specific game saves.")); - ui->localSavesBox->setEnabled(false); - } - - connect(this, &ProfilesDialog::profileCreated, &organizer, &OrganizerCore::profileCreated); - connect(this, &ProfilesDialog::profileRenamed, &organizer, &OrganizerCore::profileRenamed); - connect(this, &ProfilesDialog::profileRemoved, &organizer, &OrganizerCore::profileRemoved); -} - -ProfilesDialog::~ProfilesDialog() -{ - delete ui; -} - -int ProfilesDialog::exec() -{ - GeometrySaver gs(Settings::instance(), this); - return QDialog::exec(); -} - -void ProfilesDialog::showEvent(QShowEvent *event) -{ - TutorableDialog::showEvent(event); - - if (ui->profilesList->count() == 0) { - QPoint pos = ui->profilesList->mapToGlobal(QPoint(0, 0)); - pos.rx() += ui->profilesList->width() / 2; - pos.ry() += (ui->profilesList->height() / 2) - 20; - QWhatsThis::showText(pos, - QObject::tr("Before you can use ModOrganizer, you need to create at least one profile. " - "ATTENTION: Run the game at least once before creating a profile!"), ui->profilesList); - } -} - -void ProfilesDialog::on_close_clicked() -{ - close(); -} - -void ProfilesDialog::on_select_clicked() -{ - const Profile::Ptr currentProfile = ui->profilesList->currentItem() - ->data(Qt::UserRole) - .value(); - - if (!currentProfile) { - return; - } - - m_Selected = currentProfile->name(); - close(); -} - -std::optional ProfilesDialog::selectedProfile() const -{ - return m_Selected; -} - -QListWidgetItem *ProfilesDialog::addItem(const QString &name) -{ - QDir profileDir(name); - QListWidgetItem *newItem = new QListWidgetItem(profileDir.dirName(), ui->profilesList); - try { - newItem->setData(Qt::UserRole, QVariant::fromValue(Profile::Ptr(new Profile(profileDir, m_Game)))); - m_FailState = false; - } catch (const std::exception& e) { - reportError(tr("failed to create profile: %1").arg(e.what())); - } - return newItem; -} - -void ProfilesDialog::createProfile(const QString &name, bool useDefaultSettings) -{ - try { - QListWidgetItem *newItem = new QListWidgetItem(name, ui->profilesList); - auto profile = Profile::Ptr(new Profile(name, m_Game, useDefaultSettings)); - newItem->setData(Qt::UserRole, QVariant::fromValue(profile)); - ui->profilesList->addItem(newItem); - m_FailState = false; - ui->profilesList->setCurrentItem(newItem); - emit profileCreated(profile.get()); - } catch (const std::exception&) { - m_FailState = true; - throw; - } -} - -void ProfilesDialog::createProfile(const QString &name, const Profile &reference) -{ - try { - QListWidgetItem *newItem = new QListWidgetItem(name, ui->profilesList); - auto profile = Profile::Ptr(Profile::createPtrFrom(name, reference, m_Game)); - newItem->setData(Qt::UserRole, QVariant::fromValue(profile)); - ui->profilesList->addItem(newItem); - m_FailState = false; - ui->profilesList->setCurrentItem(newItem); - emit profileCreated(profile.get()); - } catch (const std::exception&) { - m_FailState = true; - throw; - } -} - -void ProfilesDialog::on_addProfileButton_clicked() -{ - ProfileInputDialog dialog(this); - bool okClicked = dialog.exec(); - QString name = dialog.getName(); - - if (okClicked && (name.size() > 0)) { - try { - createProfile(name, dialog.getPreferDefaultSettings()); - } catch (const std::exception &e) { - reportError(tr("failed to create profile: %1").arg(e.what())); - } - } -} - -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); - fixDirectoryName(name); - if (okClicked) { - if (name.size() > 0) { - try { - const Profile::Ptr currentProfile = ui->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() -{ - Profile::Ptr profileToDelete = ui->profilesList->currentItem()->data(Qt::UserRole).value(); - if (profileToDelete->name() == m_ActiveProfileName) { - QMessageBox::warning(this, tr("Deleting active profile"), - tr("Unable to delete active profile. Please change to a different profile first.")); - return; - } - - QMessageBox confirmBox(QMessageBox::Question, tr("Confirm"), tr("Are you sure you want to remove this profile (including profile-specific save games, if any)?"), - QMessageBox::Yes | QMessageBox::No, this); - - if (confirmBox.exec() == QMessageBox::Yes) { - QString profilePath; - if (profileToDelete.get() == nullptr) { - profilePath = Settings::instance().paths().profiles() - + "/" + ui->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 = profileToDelete->absolutePath(); - } - QListWidgetItem* item = ui->profilesList->takeItem(ui->profilesList->currentRow()); - if (item != nullptr) { - delete item; - } - if (!shellDelete(QStringList(profilePath))) { - log::warn("Failed to shell-delete \"{}\" (errorcode {}), trying regular delete", profilePath, ::GetLastError()); - if (!removeDir(profilePath)) { - log::warn("regular delete failed too"); - } - } - - emit profileRemoved(profileToDelete->name()); - } -} - - -void ProfilesDialog::on_renameButton_clicked() -{ - Profile::Ptr currentProfile = ui->profilesList->currentItem()->data(Qt::UserRole).value(); - - if (currentProfile->name() == m_ActiveProfileName) { - QMessageBox::warning(this, tr("Renaming active profile"), - tr("The active profile cannot be renamed. Please change to a different profile first.")); - return; - } - - bool valid = false; - QString name; - - while (!valid) { - bool ok = false; - name = QInputDialog::getText(this, tr("Rename Profile"), tr("New Name"), - QLineEdit::Normal, currentProfile->name(), - &ok); - valid = fixDirectoryName(name); - if (!ok) { - return; - } - } - - ui->profilesList->currentItem()->setText(name); - - QString oldName = currentProfile->name(); - currentProfile->rename(name); - - emit profileRenamed(currentProfile.get(), oldName, name); -} - - -void ProfilesDialog::on_invalidationBox_stateChanged(int state) -{ - QListWidgetItem *currentItem = ui->profilesList->currentItem(); - if (currentItem == nullptr) { - return; - } - if (!ui->invalidationBox->isEnabled()) { - return; - } - try { - QVariant currentProfileVariant = currentItem->data(Qt::UserRole); - if (!currentProfileVariant.isValid() || currentProfileVariant.isNull()) { - return; - } - const Profile::Ptr currentProfile = currentItem->data(Qt::UserRole).value(); - if (state == Qt::Unchecked) { - currentProfile->deactivateInvalidation(); - } else { - currentProfile->activateInvalidation(); - } - } catch (const std::exception &e) { - reportError(tr("failed to change archive invalidation state: %1").arg(e.what())); - } -} - - -void ProfilesDialog::on_profilesList_currentItemChanged(QListWidgetItem *current, QListWidgetItem*) -{ - if (current != nullptr) { - if (!current->data(Qt::UserRole).isValid()) return; - const Profile::Ptr currentProfile = current->data(Qt::UserRole).value(); - - try { - bool invalidationSupported = false; - ui->invalidationBox->blockSignals(true); - ui->invalidationBox->setChecked(currentProfile->invalidationActive(&invalidationSupported)); - ui->invalidationBox->setEnabled(invalidationSupported); - ui->invalidationBox->blockSignals(false); - - bool localSaves = currentProfile->localSavesEnabled(); - ui->transferButton->setEnabled(localSaves); - // prevent the stateChanged-event for the saves-box from triggering, otherwise it may think local saves - // were disabled and delete the files/rename the dir - ui->localSavesBox->blockSignals(true); - ui->localSavesBox->setChecked(localSaves); - ui->localSavesBox->blockSignals(false); - - ui->copyProfileButton->setEnabled(true); - ui->removeProfileButton->setEnabled(true); - ui->renameButton->setEnabled(true); - - ui->localIniFilesBox->blockSignals(true); - ui->localIniFilesBox->setChecked(currentProfile->localSettingsEnabled()); - ui->localIniFilesBox->blockSignals(false); - } catch (const std::exception& E) { - reportError(tr("failed to determine if invalidation is active: %1").arg(E.what())); - ui->copyProfileButton->setEnabled(false); - ui->removeProfileButton->setEnabled(false); - ui->renameButton->setEnabled(false); - ui->invalidationBox->setChecked(false); - } - } else { - ui->invalidationBox->setChecked(false); - ui->copyProfileButton->setEnabled(false); - ui->removeProfileButton->setEnabled(false); - ui->renameButton->setEnabled(false); - } -} - -void ProfilesDialog::on_profilesList_itemActivated(QListWidgetItem* item) -{ - on_select_clicked(); -} - -void ProfilesDialog::on_localSavesBox_stateChanged(int state) -{ - Profile::Ptr currentProfile = ui->profilesList->currentItem()->data(Qt::UserRole).value(); - - if (currentProfile->enableLocalSaves(state == Qt::Checked)) { - ui->transferButton->setEnabled(state == Qt::Checked); - } else { - // revert checkbox-state - ui->localSavesBox->setChecked(state != Qt::Checked); - } -} - -void ProfilesDialog::on_transferButton_clicked() -{ - const Profile::Ptr currentProfile = ui->profilesList->currentItem()->data(Qt::UserRole).value(); - TransferSavesDialog transferDialog(*currentProfile, m_Game, this); - transferDialog.exec(); -} - -void ProfilesDialog::on_localIniFilesBox_stateChanged(int state) -{ - Profile::Ptr currentProfile = ui->profilesList->currentItem()->data(Qt::UserRole).value(); - - if (!currentProfile->enableLocalSettings(state == Qt::Checked)) { - // revert checkbox-state - ui->localIniFilesBox->setChecked(state != Qt::Checked); - } -} +/* +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 "profilesdialog.h" +#include "ui_profilesdialog.h" + +#include "bsainvalidation.h" +#include "filesystemutilities.h" +#include "iplugingame.h" +#include "localsavegames.h" +#include "organizercore.h" +#include "profile.h" +#include "profileinputdialog.h" +#include "report.h" +#include "settings.h" +#include "shared/appconfig.h" +#include "transfersavesdialog.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +using namespace MOBase; +using namespace MOShared; + +Q_DECLARE_METATYPE(Profile::Ptr) + +ProfilesDialog::ProfilesDialog(const QString& profileName, OrganizerCore& organizer, + QWidget* parent) + : TutorableDialog("Profiles", parent), ui(new Ui::ProfilesDialog), + m_FailState(false), m_Game(organizer.managedGame()), m_ActiveProfileName("") +{ + ui->setupUi(this); + + QDir profilesDir(Settings::instance().paths().profiles()); + profilesDir.setFilter(QDir::AllDirs | QDir::NoDotAndDotDot); + + QDirIterator profileIter(profilesDir); + + while (profileIter.hasNext()) { + profileIter.next(); + QListWidgetItem* item = addItem(profileIter.filePath()); + if (profileName == profileIter.fileName()) { + ui->profilesList->setCurrentItem(item); + m_ActiveProfileName = profileName; + } + } + + BSAInvalidation* invalidation = m_Game->feature(); + + if (invalidation == nullptr) { + ui->invalidationBox->setToolTip( + tr("Archive invalidation isn't required for this game.")); + ui->invalidationBox->setEnabled(false); + } + + if (!m_Game->feature()) { + ui->localSavesBox->setToolTip( + tr("This game does not support profile-specific game saves.")); + ui->localSavesBox->setEnabled(false); + } + + connect(this, &ProfilesDialog::profileCreated, &organizer, + &OrganizerCore::profileCreated); + connect(this, &ProfilesDialog::profileRenamed, &organizer, + &OrganizerCore::profileRenamed); + connect(this, &ProfilesDialog::profileRemoved, &organizer, + &OrganizerCore::profileRemoved); +} + +ProfilesDialog::~ProfilesDialog() +{ + delete ui; +} + +int ProfilesDialog::exec() +{ + GeometrySaver gs(Settings::instance(), this); + return QDialog::exec(); +} + +void ProfilesDialog::showEvent(QShowEvent* event) +{ + TutorableDialog::showEvent(event); + + if (ui->profilesList->count() == 0) { + QPoint pos = ui->profilesList->mapToGlobal(QPoint(0, 0)); + pos.rx() += ui->profilesList->width() / 2; + pos.ry() += (ui->profilesList->height() / 2) - 20; + QWhatsThis::showText( + pos, + QObject::tr( + "Before you can use ModOrganizer, you need to create at least one profile. " + "ATTENTION: Run the game at least once before creating a profile!"), + ui->profilesList); + } +} + +void ProfilesDialog::on_close_clicked() +{ + close(); +} + +void ProfilesDialog::on_select_clicked() +{ + const Profile::Ptr currentProfile = + ui->profilesList->currentItem()->data(Qt::UserRole).value(); + + if (!currentProfile) { + return; + } + + m_Selected = currentProfile->name(); + close(); +} + +std::optional ProfilesDialog::selectedProfile() const +{ + return m_Selected; +} + +QListWidgetItem* ProfilesDialog::addItem(const QString& name) +{ + QDir profileDir(name); + QListWidgetItem* newItem = + new QListWidgetItem(profileDir.dirName(), ui->profilesList); + try { + newItem->setData(Qt::UserRole, QVariant::fromValue( + Profile::Ptr(new Profile(profileDir, m_Game)))); + m_FailState = false; + } catch (const std::exception& e) { + reportError(tr("failed to create profile: %1").arg(e.what())); + } + return newItem; +} + +void ProfilesDialog::createProfile(const QString& name, bool useDefaultSettings) +{ + try { + QListWidgetItem* newItem = new QListWidgetItem(name, ui->profilesList); + auto profile = Profile::Ptr(new Profile(name, m_Game, useDefaultSettings)); + newItem->setData(Qt::UserRole, QVariant::fromValue(profile)); + ui->profilesList->addItem(newItem); + m_FailState = false; + ui->profilesList->setCurrentItem(newItem); + emit profileCreated(profile.get()); + } catch (const std::exception&) { + m_FailState = true; + throw; + } +} + +void ProfilesDialog::createProfile(const QString& name, const Profile& reference) +{ + try { + QListWidgetItem* newItem = new QListWidgetItem(name, ui->profilesList); + auto profile = Profile::Ptr(Profile::createPtrFrom(name, reference, m_Game)); + newItem->setData(Qt::UserRole, QVariant::fromValue(profile)); + ui->profilesList->addItem(newItem); + m_FailState = false; + ui->profilesList->setCurrentItem(newItem); + emit profileCreated(profile.get()); + } catch (const std::exception&) { + m_FailState = true; + throw; + } +} + +void ProfilesDialog::on_addProfileButton_clicked() +{ + ProfileInputDialog dialog(this); + bool okClicked = dialog.exec(); + QString name = dialog.getName(); + + if (okClicked && (name.size() > 0)) { + try { + createProfile(name, dialog.getPreferDefaultSettings()); + } catch (const std::exception& e) { + reportError(tr("failed to create profile: %1").arg(e.what())); + } + } +} + +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); + fixDirectoryName(name); + if (okClicked) { + if (name.size() > 0) { + try { + const Profile::Ptr currentProfile = + ui->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() +{ + Profile::Ptr profileToDelete = + ui->profilesList->currentItem()->data(Qt::UserRole).value(); + if (profileToDelete->name() == m_ActiveProfileName) { + QMessageBox::warning(this, tr("Deleting active profile"), + tr("Unable to delete active profile. Please change to a " + "different profile first.")); + return; + } + + QMessageBox confirmBox(QMessageBox::Question, tr("Confirm"), + tr("Are you sure you want to remove this profile (including " + "profile-specific save games, if any)?"), + QMessageBox::Yes | QMessageBox::No, this); + + if (confirmBox.exec() == QMessageBox::Yes) { + QString profilePath; + if (profileToDelete.get() == nullptr) { + profilePath = Settings::instance().paths().profiles() + "/" + + ui->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 = profileToDelete->absolutePath(); + } + QListWidgetItem* item = ui->profilesList->takeItem(ui->profilesList->currentRow()); + if (item != nullptr) { + delete item; + } + if (!shellDelete(QStringList(profilePath))) { + log::warn("Failed to shell-delete \"{}\" (errorcode {}), trying regular delete", + profilePath, ::GetLastError()); + if (!removeDir(profilePath)) { + log::warn("regular delete failed too"); + } + } + + emit profileRemoved(profileToDelete->name()); + } +} + +void ProfilesDialog::on_renameButton_clicked() +{ + Profile::Ptr currentProfile = + ui->profilesList->currentItem()->data(Qt::UserRole).value(); + + if (currentProfile->name() == m_ActiveProfileName) { + QMessageBox::warning(this, tr("Renaming active profile"), + tr("The active profile cannot be renamed. Please change to a " + "different profile first.")); + return; + } + + bool valid = false; + QString name; + + while (!valid) { + bool ok = false; + name = QInputDialog::getText(this, tr("Rename Profile"), tr("New Name"), + QLineEdit::Normal, currentProfile->name(), &ok); + valid = fixDirectoryName(name); + if (!ok) { + return; + } + } + + ui->profilesList->currentItem()->setText(name); + + QString oldName = currentProfile->name(); + currentProfile->rename(name); + + emit profileRenamed(currentProfile.get(), oldName, name); +} + +void ProfilesDialog::on_invalidationBox_stateChanged(int state) +{ + QListWidgetItem* currentItem = ui->profilesList->currentItem(); + if (currentItem == nullptr) { + return; + } + if (!ui->invalidationBox->isEnabled()) { + return; + } + try { + QVariant currentProfileVariant = currentItem->data(Qt::UserRole); + if (!currentProfileVariant.isValid() || currentProfileVariant.isNull()) { + return; + } + const Profile::Ptr currentProfile = + currentItem->data(Qt::UserRole).value(); + if (state == Qt::Unchecked) { + currentProfile->deactivateInvalidation(); + } else { + currentProfile->activateInvalidation(); + } + } catch (const std::exception& e) { + reportError(tr("failed to change archive invalidation state: %1").arg(e.what())); + } +} + +void ProfilesDialog::on_profilesList_currentItemChanged(QListWidgetItem* current, + QListWidgetItem*) +{ + if (current != nullptr) { + if (!current->data(Qt::UserRole).isValid()) + return; + const Profile::Ptr currentProfile = + current->data(Qt::UserRole).value(); + + try { + bool invalidationSupported = false; + ui->invalidationBox->blockSignals(true); + ui->invalidationBox->setChecked( + currentProfile->invalidationActive(&invalidationSupported)); + ui->invalidationBox->setEnabled(invalidationSupported); + ui->invalidationBox->blockSignals(false); + + bool localSaves = currentProfile->localSavesEnabled(); + ui->transferButton->setEnabled(localSaves); + // prevent the stateChanged-event for the saves-box from triggering, otherwise it + // may think local saves were disabled and delete the files/rename the dir + ui->localSavesBox->blockSignals(true); + ui->localSavesBox->setChecked(localSaves); + ui->localSavesBox->blockSignals(false); + + ui->copyProfileButton->setEnabled(true); + ui->removeProfileButton->setEnabled(true); + ui->renameButton->setEnabled(true); + + ui->localIniFilesBox->blockSignals(true); + ui->localIniFilesBox->setChecked(currentProfile->localSettingsEnabled()); + ui->localIniFilesBox->blockSignals(false); + } catch (const std::exception& E) { + reportError( + tr("failed to determine if invalidation is active: %1").arg(E.what())); + ui->copyProfileButton->setEnabled(false); + ui->removeProfileButton->setEnabled(false); + ui->renameButton->setEnabled(false); + ui->invalidationBox->setChecked(false); + } + } else { + ui->invalidationBox->setChecked(false); + ui->copyProfileButton->setEnabled(false); + ui->removeProfileButton->setEnabled(false); + ui->renameButton->setEnabled(false); + } +} + +void ProfilesDialog::on_profilesList_itemActivated(QListWidgetItem* item) +{ + on_select_clicked(); +} + +void ProfilesDialog::on_localSavesBox_stateChanged(int state) +{ + Profile::Ptr currentProfile = + ui->profilesList->currentItem()->data(Qt::UserRole).value(); + + if (currentProfile->enableLocalSaves(state == Qt::Checked)) { + ui->transferButton->setEnabled(state == Qt::Checked); + } else { + // revert checkbox-state + ui->localSavesBox->setChecked(state != Qt::Checked); + } +} + +void ProfilesDialog::on_transferButton_clicked() +{ + const Profile::Ptr currentProfile = + ui->profilesList->currentItem()->data(Qt::UserRole).value(); + TransferSavesDialog transferDialog(*currentProfile, m_Game, this); + transferDialog.exec(); +} + +void ProfilesDialog::on_localIniFilesBox_stateChanged(int state) +{ + Profile::Ptr currentProfile = + ui->profilesList->currentItem()->data(Qt::UserRole).value(); + + if (!currentProfile->enableLocalSettings(state == Qt::Checked)) { + // revert checkbox-state + ui->localIniFilesBox->setChecked(state != Qt::Checked); + } +} diff --git a/src/profilesdialog.h b/src/profilesdialog.h index 005bc33e9..dc8329a26 100644 --- a/src/profilesdialog.h +++ b/src/profilesdialog.h @@ -1,132 +1,136 @@ -/* -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 PROFILESDIALOG_H -#define PROFILESDIALOG_H - -#include "tutorabledialog.h" -class Profile; -class OrganizerCore; - -class QListWidget; -class QListWidgetItem; -#include -class QString; - -namespace Ui { class ProfilesDialog; } - -namespace MOBase { class IPluginGame; } - - -/** - * @brief Dialog that can be used to create/delete/modify profiles - **/ -class ProfilesDialog : public MOBase::TutorableDialog -{ - Q_OBJECT - -public: - - /** - * @brief constructor - * - * @param profileName currently enabled profile - * @param organizer - * @param parent parent widget - **/ - explicit ProfilesDialog(const QString &profileName, OrganizerCore &organizer, QWidget *parent = 0); - ~ProfilesDialog(); - - // also saves and restores geometry - // - int exec() override; - - /** - * @return true if creation of a new profile failed - * @todo the notion of a fail state makes little sense in the current dialog - **/ - bool failed() const { return m_FailState; } - - // if the dialog was closed with the 'select' button, returns the name of the - // selected profile; if the dialog was closed with 'cancel', returns empty - // - std::optional selectedProfile() const; - -signals: - - /** - * @brief Signal emitted when a profile is created. - */ - void profileCreated(Profile* profile); - - /** - * @brief Signal emitted when a profile is renamed. - */ - void profileRenamed(Profile* profile, QString const& oldName, QString const& newName); - - /** - * @brief Signal emitted when a profile has been removed. - */ - void profileRemoved(QString const& profileName); - -protected: - - virtual void showEvent(QShowEvent *event); - -private slots: - void on_localIniFilesBox_stateChanged(int state); - -private: - - QListWidgetItem *addItem(const QString &name); - void createProfile(const QString &name, bool useDefaultSettings); - void createProfile(const QString &name, const Profile &reference); - -private slots: - - void on_close_clicked(); - void on_select_clicked(); - - void on_addProfileButton_clicked(); - - void on_invalidationBox_stateChanged(int arg1); - - void on_copyProfileButton_clicked(); - - void on_profilesList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous); - void on_profilesList_itemActivated(QListWidgetItem* item); - - void on_removeProfileButton_clicked(); - - void on_localSavesBox_stateChanged(int arg1); - - void on_transferButton_clicked(); - - void on_renameButton_clicked(); - -private: - Ui::ProfilesDialog *ui; - QListWidget *m_ProfilesList; - bool m_FailState; - MOBase::IPluginGame const *m_Game; - QString m_ActiveProfileName; - std::optional m_Selected; -}; - -#endif // PROFILESDIALOG_H +/* +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 PROFILESDIALOG_H +#define PROFILESDIALOG_H + +#include "tutorabledialog.h" +class Profile; +class OrganizerCore; + +class QListWidget; +class QListWidgetItem; +#include +class QString; + +namespace Ui +{ +class ProfilesDialog; +} + +namespace MOBase +{ +class IPluginGame; +} + +/** + * @brief Dialog that can be used to create/delete/modify profiles + **/ +class ProfilesDialog : public MOBase::TutorableDialog +{ + Q_OBJECT + +public: + /** + * @brief constructor + * + * @param profileName currently enabled profile + * @param organizer + * @param parent parent widget + **/ + explicit ProfilesDialog(const QString& profileName, OrganizerCore& organizer, + QWidget* parent = 0); + ~ProfilesDialog(); + + // also saves and restores geometry + // + int exec() override; + + /** + * @return true if creation of a new profile failed + * @todo the notion of a fail state makes little sense in the current dialog + **/ + bool failed() const { return m_FailState; } + + // if the dialog was closed with the 'select' button, returns the name of the + // selected profile; if the dialog was closed with 'cancel', returns empty + // + std::optional selectedProfile() const; + +signals: + + /** + * @brief Signal emitted when a profile is created. + */ + void profileCreated(Profile* profile); + + /** + * @brief Signal emitted when a profile is renamed. + */ + void profileRenamed(Profile* profile, QString const& oldName, QString const& newName); + + /** + * @brief Signal emitted when a profile has been removed. + */ + void profileRemoved(QString const& profileName); + +protected: + virtual void showEvent(QShowEvent* event); + +private slots: + void on_localIniFilesBox_stateChanged(int state); + +private: + QListWidgetItem* addItem(const QString& name); + void createProfile(const QString& name, bool useDefaultSettings); + void createProfile(const QString& name, const Profile& reference); + +private slots: + + void on_close_clicked(); + void on_select_clicked(); + + void on_addProfileButton_clicked(); + + void on_invalidationBox_stateChanged(int arg1); + + void on_copyProfileButton_clicked(); + + void on_profilesList_currentItemChanged(QListWidgetItem* current, + QListWidgetItem* previous); + void on_profilesList_itemActivated(QListWidgetItem* item); + + void on_removeProfileButton_clicked(); + + void on_localSavesBox_stateChanged(int arg1); + + void on_transferButton_clicked(); + + void on_renameButton_clicked(); + +private: + Ui::ProfilesDialog* ui; + QListWidget* m_ProfilesList; + bool m_FailState; + MOBase::IPluginGame const* m_Game; + QString m_ActiveProfileName; + std::optional m_Selected; +}; + +#endif // PROFILESDIALOG_H diff --git a/src/proxyutils.h b/src/proxyutils.h index 6c85ba14d..7aebf3157 100644 --- a/src/proxyutils.h +++ b/src/proxyutils.h @@ -5,37 +5,46 @@ #include "organizerproxy.h" -namespace MOShared { - - template - auto callIfPluginActive(OrganizerProxy* proxy, Fn&& callback, T defaultReturn = T{}) { - return [fn = std::forward(callback), proxy, defaultReturn](auto&& ...args) { - if (proxy->isPluginEnabled(proxy->plugin())) { - return fn(std::forward(args)...); - } - else { - if constexpr (!std::is_same_v, decltype(args)... >, void>) { - return defaultReturn; - } +namespace MOShared +{ + +template +auto callIfPluginActive(OrganizerProxy* proxy, Fn&& callback, T defaultReturn = T{}) +{ + return [fn = std::forward(callback), proxy, defaultReturn](auto&&... args) { + if (proxy->isPluginEnabled(proxy->plugin())) { + return fn(std::forward(args)...); + } else { + if constexpr (!std::is_same_v< + std::invoke_result_t, decltype(args)...>, + void>) { + return defaultReturn; } - }; - } - - // We need to connect to the organizer. - template - auto callSignalIfPluginActive(OrganizerProxy* proxy, const Signal& signal, T defaultReturn = T{}) { - return callIfPluginActive(proxy, [&signal](auto&&... args) { - return signal(std::forward(args)...); - }, defaultReturn); - } - - template - auto callSignalAlways(const Signal& signal) { - return [&signal](auto&&... args) { - return signal(std::forward(args)...); - }; - } + } + }; +} +// We need to connect to the organizer. +template +auto callSignalIfPluginActive(OrganizerProxy* proxy, const Signal& signal, + T defaultReturn = T{}) +{ + return callIfPluginActive( + proxy, + [&signal](auto&&... args) { + return signal(std::forward(args)...); + }, + defaultReturn); } +template +auto callSignalAlways(const Signal& signal) +{ + return [&signal](auto&&... args) { + return signal(std::forward(args)...); + }; +} + +} // namespace MOShared + #endif diff --git a/src/qdirfiletree.cpp b/src/qdirfiletree.cpp index 6fd370bf8..f7bb11a71 100644 --- a/src/qdirfiletree.cpp +++ b/src/qdirfiletree.cpp @@ -4,31 +4,51 @@ using namespace MOBase; -class QDirFileTreeImpl : public QDirFileTree { +class QDirFileTreeImpl : public QDirFileTree +{ public: - - QDirFileTreeImpl(std::shared_ptr parent, QDir dir) : - FileTreeEntry(parent, dir.dirName()), QDirFileTree(), qDir(dir) { } + QDirFileTreeImpl(std::shared_ptr parent, QDir dir) + : FileTreeEntry(parent, dir.dirName()), QDirFileTree(), qDir(dir) + {} protected: - /** * No mutable operations allowed. */ - bool beforeReplace(IFileTree const* dstTree, FileTreeEntry const* destination, FileTreeEntry const* source) override { return false; } - bool beforeInsert(IFileTree const* entry, FileTreeEntry const* name) override { return false; } - bool beforeRemove(IFileTree const* entry, FileTreeEntry const* name) override { return false; } - std::shared_ptr makeFile(std::shared_ptr parent, QString name) const override { return nullptr; } - std::shared_ptr makeDirectory(std::shared_ptr parent, QString name) const override { return nullptr; } + bool beforeReplace(IFileTree const* dstTree, FileTreeEntry const* destination, + FileTreeEntry const* source) override + { + return false; + } + bool beforeInsert(IFileTree const* entry, FileTreeEntry const* name) override + { + return false; + } + bool beforeRemove(IFileTree const* entry, FileTreeEntry const* name) override + { + return false; + } + std::shared_ptr makeFile(std::shared_ptr parent, + QString name) const override + { + return nullptr; + } + std::shared_ptr makeDirectory(std::shared_ptr parent, + QString name) const override + { + return nullptr; + } - bool doPopulate(std::shared_ptr parent, std::vector>& entries) const override + bool doPopulate(std::shared_ptr parent, + std::vector>& entries) const override { - auto infoList = qDir.entryInfoList(qDir.filter() | QDir::NoDotAndDotDot, QDir::Name | QDir::DirsFirst | QDir::IgnoreCase); + auto infoList = qDir.entryInfoList(qDir.filter() | QDir::NoDotAndDotDot, + QDir::Name | QDir::DirsFirst | QDir::IgnoreCase); for (auto& info : infoList) { if (info.isDir()) { - entries.push_back(std::make_shared(parent, QDir(info.absoluteFilePath()))); - } - else { + entries.push_back( + std::make_shared(parent, QDir(info.absoluteFilePath()))); + } else { entries.push_back(createFileEntry(parent, info.fileName())); } } @@ -44,28 +64,29 @@ class QDirFileTreeImpl : public QDirFileTree { protected: QDir qDir; - }; // subclass of QDirFileTreeImpl that ignores meta.ini // // only used for the root folder, subdirectories are actually QDirFileTreeImpl -class QDirRootFileTreeImpl : public QDirFileTreeImpl { +class QDirRootFileTreeImpl : public QDirFileTreeImpl +{ public: - - - QDirRootFileTreeImpl(QDir dir) : FileTreeEntry(nullptr, dir.dirName()), QDirFileTreeImpl(nullptr, dir) { } + QDirRootFileTreeImpl(QDir dir) + : FileTreeEntry(nullptr, dir.dirName()), QDirFileTreeImpl(nullptr, dir) + {} protected: - - bool doPopulate(std::shared_ptr parent, std::vector>& entries) const override + bool doPopulate(std::shared_ptr parent, + std::vector>& entries) const override { - auto infoList = qDir.entryInfoList(qDir.filter() | QDir::NoDotAndDotDot, QDir::Name | QDir::DirsFirst | QDir::IgnoreCase); + auto infoList = qDir.entryInfoList(qDir.filter() | QDir::NoDotAndDotDot, + QDir::Name | QDir::DirsFirst | QDir::IgnoreCase); for (auto& info : infoList) { if (info.isDir()) { - entries.push_back(std::make_shared(parent, QDir(info.absoluteFilePath()))); - } - else if (info.fileName().compare("meta.ini", Qt::CaseInsensitive) != 0) { + entries.push_back( + std::make_shared(parent, QDir(info.absoluteFilePath()))); + } else if (info.fileName().compare("meta.ini", Qt::CaseInsensitive) != 0) { entries.push_back(createFileEntry(parent, info.fileName())); } } @@ -78,18 +99,17 @@ class QDirRootFileTreeImpl : public QDirFileTreeImpl { { return std::make_shared(qDir); } - }; /** * */ -std::shared_ptr QDirFileTree::makeTree(QDir directory, bool ignoreRootMeta) +std::shared_ptr QDirFileTree::makeTree(QDir directory, + bool ignoreRootMeta) { if (ignoreRootMeta) { return std::make_shared(directory); - } - else { + } else { return std::make_shared(nullptr, directory); } } diff --git a/src/qdirfiletree.h b/src/qdirfiletree.h index e9229bf57..1b3f7294e 100644 --- a/src/qdirfiletree.h +++ b/src/qdirfiletree.h @@ -24,19 +24,19 @@ along with Mod Organizer. If not, see . #include "ifiletree.h" - /** - * @brief Class that expose a directory on the drive, using QDir, as a `MOBase::IFileTree`. + * @brief Class that expose a directory on the drive, using QDir, as a + * `MOBase::IFileTree`. * - * The tree is lazily populated: each subtree is only populated (from the disk) when needed, - * as specified by IFileTree. + * The tree is lazily populated: each subtree is only populated (from the disk) when + * needed, as specified by IFileTree. * * This class does not expose mutable operations, so any mutable operations will * fail. */ -class QDirFileTree : public MOBase::IFileTree { +class QDirFileTree : public MOBase::IFileTree +{ public: - /** * @brief Create a new file tree representing the given directory. * @@ -46,13 +46,15 @@ class QDirFileTree : public MOBase::IFileTree { * * @return a file tree representing the given directory. */ - static std::shared_ptr makeTree(QDir directory, bool ignoreRootMeta = true); + static std::shared_ptr makeTree(QDir directory, + bool ignoreRootMeta = true); protected: - using IFileTree::IFileTree; - virtual bool doPopulate(std::shared_ptr parent, std::vector>& entries) const = 0; + virtual bool + doPopulate(std::shared_ptr parent, + std::vector>& entries) const = 0; }; #endif diff --git a/src/qtgroupingproxy.cpp b/src/qtgroupingproxy.cpp index d00bd2880..54f8b66e5 100644 --- a/src/qtgroupingproxy.cpp +++ b/src/qtgroupingproxy.cpp @@ -1,1038 +1,948 @@ -/**************************************************************************************** - * Copyright (c) 2007-2011 Bart Cerneels * - * * - * This program 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 2 of the License, or (at your option) any later * - * version. * - * * - * This program 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 * - * this program. If not, see . * - ****************************************************************************************/ - -// Modifications 2013-03-27 to 2013-03-29 by Sebastian Herbord - - -#include "qtgroupingproxy.h" -#include - -#include -#include -#include - -using namespace MOBase; - -/*! - \class QtGroupingProxy - \brief The QtGroupingProxy class will group source model rows by adding a new top tree-level. - The source model can be flat or tree organized, but only the original top level rows are used - for determining the grouping. - \ingroup model-view -*/ - -QtGroupingProxy::QtGroupingProxy(QModelIndex rootNode, int groupedColumn, int groupedRole, unsigned int flags, int aggregateRole) - : QAbstractProxyModel() - , m_rootNode(rootNode) - , m_groupedColumn(0) - , m_groupedRole(groupedRole) - , m_aggregateRole(aggregateRole) - , m_flags(flags) -{ - if (groupedColumn != -1) { - setGroupedColumn(groupedColumn); - } -} - -QtGroupingProxy::~QtGroupingProxy() -{ -} - -void QtGroupingProxy::setSourceModel(QAbstractItemModel* model) -{ - if (sourceModel()) { - disconnect(sourceModel(), nullptr, this, nullptr); - } - - QAbstractProxyModel::setSourceModel(model); - - if (sourceModel()) { - // signal proxies - connect(sourceModel(), SIGNAL(rowsInserted(const QModelIndex&, int, int)), - SLOT(modelRowsInserted(const QModelIndex&, int, int))); - connect(sourceModel(), SIGNAL(rowsAboutToBeInserted(const QModelIndex&, int, int)), - SLOT(modelRowsAboutToBeInserted(const QModelIndex&, int, int))); - connect(sourceModel(), SIGNAL(rowsRemoved(const QModelIndex&, int, int)), - SLOT(modelRowsRemoved(const QModelIndex&, int, int))); - connect(sourceModel(), SIGNAL(rowsAboutToBeRemoved(const QModelIndex&, int, int)), - SLOT(modelRowsAboutToBeRemoved(QModelIndex, int, int))); - connect(sourceModel(), SIGNAL(layoutChanged()), SLOT(buildTree())); - connect(sourceModel(), SIGNAL(dataChanged(QModelIndex, QModelIndex)), - SLOT(modelDataChanged(QModelIndex, QModelIndex))); - connect(sourceModel(), SIGNAL(modelReset()), this, SLOT(resetModel())); - - buildTree(); - } -} - -void -QtGroupingProxy::setGroupedColumn( int groupedColumn ) -{ - m_groupedColumn = groupedColumn; - buildTree(); -} - -/** Maps to what groups the source row belongs by returning the data of those groups. - * - * @returns a list of data for the rows the argument belongs to. In common cases this list will - * contain only one entry. An empty list means that the source item will be placed in the root of - * this proxyModel. There is no support for hiding source items. - * - * Group data can be pre-loaded in the return value so it's added to the cache maintained by this - * class. This is required if you want to have data that is not present in the source model. - */ -QList -QtGroupingProxy::belongsTo( const QModelIndex &idx ) -{ - QList rowDataList; - - //get all the data for this index from the model - ItemData itemData = sourceModel()->itemData( idx ); - if (m_groupedRole != Qt::DisplayRole) { - itemData[Qt::DisplayRole] = itemData[m_groupedRole]; - } - - // invalid value in grouped role -> ungrouped - if (!itemData[Qt::DisplayRole].isValid()) { - return rowDataList; - } - - QMapIterator i( itemData ); - while( i.hasNext() ) - { - i.next(); - int role = i.key(); - QVariant variant = i.value(); - - if ( variant.type() == QVariant::List ) - { - //a list of variants get's expanded to multiple rows - QVariantList list = variant.toList(); - for( int i = 0; i < list.length(); i++ ) - { - //take an existing row data or create a new one - RowData rowData = (rowDataList.count() > i) ? rowDataList.takeAt( i ) - : RowData(); - - //we only gather data for the first column - ItemData indexData = rowData.contains( 0 ) ? rowData.take( 0 ) : ItemData(); - indexData.insert( role, list.value( i ) ); - rowData.insert( 0, indexData ); - //for the grouped column the data should not be gathered from the children - //this will allow filtering on the content of this column with a - //QSortFilterProxyModel - rowData.insert( m_groupedColumn, indexData ); - rowDataList.insert( i, rowData ); - } - break; - } - else if( !variant.isNull() ) - { - //it's just a normal item. Copy all the data and break this loop. - RowData rowData; - rowData.insert( 0, itemData ); - rowDataList << rowData; - break; - } - } - - return rowDataList; -} - -/* m_groupMap layout -* key : index of the group in m_groupMaps -* value : a QList of the original rows in sourceModel() for the children of this group -* -* key = -1 contains a QList of the non-grouped indexes -* -* TODO: sub-groups -*/ -void -QtGroupingProxy::buildTree() -{ - if( !sourceModel() ) - return; - beginResetModel(); - - m_groupMap.clear(); - //don't clear the data maps since most of it will probably be needed again. - m_parentCreateList.clear(); - - int max = sourceModel()->rowCount( m_rootNode ); - - //WARNING: these have to be added in order because the addToGroups function is optimized for - //modelRowsInserted(). Failure to do so will result in wrong data shown in the view at best. - for( int row = 0; row < max; row++ ) - { - QModelIndex idx = sourceModel()->index( row, m_groupedColumn, m_rootNode ); - addSourceRow( idx ); - } - //dumpGroups(); - - if (m_flags & FLAG_NOSINGLE) { - // awkward: flatten single-item groups as a post-processing steps. - - int currentKey = 0; - quint32 quint32max = std::numeric_limits::max(); - std::vector rmgroups; - - QMap > temp; - - for (auto iter = m_groupMap.begin(); iter != m_groupMap.end(); ++iter) { - if ((iter.key() == quint32max) || - (iter->count() < 2)) { - temp[quint32max].append(iter.value()); - if (iter.key() != quint32max) { - rmgroups.push_back(iter.key()); - } - } else { - temp[currentKey++] = *iter; - } - } - m_groupMap = temp; - - // second loop is necessary because qt containers can't be iterated from end to front - // and removing by index from begin to end is ugly - std::sort(rmgroups.begin(), rmgroups.end(), [] (int lhs, int rhs) { return rhs < lhs; }); - for (auto iter = rmgroups.begin(); iter != rmgroups.end(); ++iter) { - m_groupMaps.removeAt(*iter); - } - } - - endResetModel(); -} - -QList -QtGroupingProxy::addSourceRow( const QModelIndex &idx ) -{ - QList updatedGroups; - QList groupData = belongsTo( idx ); - - //an empty list here means it's supposed to go in root. - if( groupData.isEmpty() ) - { - updatedGroups << -1; - if( !m_groupMap.keys().contains( std::numeric_limits::max() ) ) - m_groupMap.insert( std::numeric_limits::max(), QList() ); //add an empty placeholder - } - - //an item can be in multiple groups - foreach( RowData data, groupData ) - { - int updatedGroup = -1; - if( !data.isEmpty() ) - { - foreach( const RowData &cachedData, m_groupMaps ) - { - //when this matches the index belongs to an existing group - if( data[0][Qt::DisplayRole] == cachedData[0][Qt::DisplayRole] ) - { - data = cachedData; - break; - } - } - - updatedGroup = m_groupMaps.indexOf( data ); - //-1 means not found - if( updatedGroup == -1 ) - { - //new groups are added to the end of the existing list - m_groupMaps << data; - updatedGroup = m_groupMaps.count() - 1; - } - - if( !m_groupMap.keys().contains( updatedGroup ) ) - m_groupMap.insert( updatedGroup, QList() ); //add an empty placeholder - } - - if( !updatedGroups.contains( updatedGroup ) ) - updatedGroups << updatedGroup; - } - - //update m_groupMap to the new source-model layout (one row added) - QMutableMapIterator > i( m_groupMap ); - while( i.hasNext() ) - { - i.next(); - QList &groupList = i.value(); - int insertedProxyRow = groupList.count(); - for( ; insertedProxyRow > 0 ; insertedProxyRow-- ) - { - int &rowValue = groupList[insertedProxyRow-1]; - if( idx.row() <= rowValue ) - { - //increment the rows that come after the new row since they moved one place up. - rowValue++; - } - else - { - break; - } - } - - if( updatedGroups.contains( i.key() ) ) - { - //the row needs to be added to this group - groupList.insert( insertedProxyRow, idx.row() ); - } - } - - return updatedGroups; -} - -/** Each ModelIndex has in it's internalId a position in the parentCreateList. - * struct ParentCreate are the instructions to recreate the parent index. - * It contains the proxy row number of the parent and the postion in this list of the grandfather. - * This function creates the ParentCreate structs and saves them in a list. - */ -int -QtGroupingProxy::indexOfParentCreate( const QModelIndex &parent ) const -{ - if( !parent.isValid() ) - return -1; - - struct ParentCreate pc; - for( int i = 0 ; i < m_parentCreateList.size() ; i++ ) - { - pc = m_parentCreateList[i]; - if( pc.parentCreateIndex == parent.internalId() && pc.row == parent.row() ) - return i; - } - //there is no parentCreate yet for this index, so let's create one. - pc.parentCreateIndex = parent.internalId(); - pc.row = parent.row(); - m_parentCreateList << pc; - - return m_parentCreateList.size() - 1; -} - -QModelIndex -QtGroupingProxy::index( int row, int column, const QModelIndex &parent ) const -{ - if( !hasIndex(row, column, parent) ) { - return QModelIndex(); - } - - if( parent.column() > 0 ) { - return QModelIndex(); - } - - /* We save the instructions to make the parent of the index in a struct. - * The place of the struct in the list is stored in the internalId - */ - int parentCreateIndex = indexOfParentCreate( parent ); - - return createIndex( row, column, parentCreateIndex ); -} - -QModelIndex -QtGroupingProxy::parent( const QModelIndex &index ) const -{ - if( !index.isValid() ) - return QModelIndex(); - - int parentCreateIndex = index.internalId(); - if( parentCreateIndex == -1 || parentCreateIndex >= m_parentCreateList.count() ) - return QModelIndex(); - - struct ParentCreate pc = m_parentCreateList[parentCreateIndex]; - - //only items at column 0 have children - return createIndex( pc.row, 0, pc.parentCreateIndex ); -} - -int -QtGroupingProxy::rowCount( const QModelIndex &index ) const -{ - if( !index.isValid() ) - { - //the number of top level groups + the number of non-grouped items - int rows = m_groupMaps.count() + m_groupMap.value( std::numeric_limits::max() ).count(); - return rows; - } - - //TODO:group in group support. - if( isGroup( index ) ) - { - qint64 groupIndex = index.row(); - int rows = m_groupMap.value( groupIndex ).count(); - return rows; - } else { - QModelIndex originalIndex = mapToSource( index ); - int rowCount = sourceModel()->rowCount( originalIndex ); - return rowCount; - } -} - -int -QtGroupingProxy::columnCount( const QModelIndex &index ) const -{ - if( !index.isValid() ) - return sourceModel()->columnCount( m_rootNode ); - - if( index.column() != 0 ) - return 0; - - return sourceModel()->columnCount( mapToSource( index ) ); -} - - -static bool variantLess(const QVariant &LHS, const QVariant &RHS) -{ - if ((LHS.type() == RHS.type()) && - ((LHS.type() == QVariant::Int) || (LHS.type() == QVariant::UInt))) { - return LHS.toInt() < RHS.toInt(); - } - - // this should always work (comparing empty strings in the worst case) but - // the results may be wrong - return LHS.toString() < RHS.toString(); -} - - -static QVariant variantMax(const QVariantList &variants) -{ - QVariant result = variants.first(); - foreach (const QVariant &iter, variants) { - if (variantLess(result, iter)) { - result = iter; - } - } - return result; -} - - -static QVariant variantMin(const QVariantList &variants) -{ - QVariant result = variants.first(); - foreach (const QVariant &iter, variants) { - if (variantLess(iter, result)) { - result = iter; - } - } - return result; -} - - -QVariant -QtGroupingProxy::data( const QModelIndex &index, int role ) const -{ - if( !index.isValid() ) - return QVariant(); - - int row = index.row(); - int column = index.column(); - if( isGroup( index ) ) - { - if ((role != Qt::DisplayRole) && (role != Qt::EditRole)) { - switch (role) { - case Qt::ForegroundRole: { - return QBrush(Qt::gray); - } break; - case Qt::FontRole: { - QFont font(m_groupMaps[row][column].value(Qt::FontRole).value()); - font.setItalic(true); - return font; - } break; - case Qt::TextAlignmentRole: { - return Qt::AlignHCenter; - } break; - case Qt::UserRole: { - return m_groupMaps[row][column].value( Qt::DisplayRole ).toString(); - } break; - case Qt::CheckStateRole: { - if (column != 0) return QVariant(); - int childCount = m_groupMap.value( row ).count(); - int checked = 0; - QModelIndex parentIndex = this->index( row, 0, index.parent() ); - for( int childRow = 0; childRow < childCount; ++childRow ) - { - QModelIndex childIndex = this->index( childRow, 0, parentIndex ); - QVariant data = mapToSource( childIndex ).data( Qt::CheckStateRole ); - if (data.toInt() == 2) ++checked; - } - if (checked == childCount) return Qt::Checked; - else if (checked == 0) return Qt::Unchecked; - else return Qt::PartiallyChecked; - } break; - default: { - QModelIndex parentIndex = this->index( row, 0, index.parent() ); - if (m_groupMap.value( row ).count() > 0) { - return this->index(0, column, parentIndex).data(role); - } else { - return QVariant(); - } - // return m_groupMaps[row][column].value( role ); - } break; - } - } - - //use cached or precalculated data - if( m_groupMaps[row][column].contains( Qt::DisplayRole ) ) - { - if ((m_flags & FLAG_NOGROUPNAME) != 0) { - QModelIndex parentIndex = this->index( row, 0, index.parent() ); - QModelIndex childIndex = this->index( 0, column, parentIndex ); - return childIndex.data(role).toString(); - } else { - return m_groupMaps[row][column].value( role ).toString(); - } - } - - //for column 0 we gather data from the grouped column instead - if( column == 0 ) - column = m_groupedColumn; - - //map all data from children to columns of group to allow grouping one level up - QVariantList variantsOfChildren; - int childCount = m_groupMap.value( row ).count(); - if( childCount == 0 ) - return QVariant(); - - int function = AGGR_NONE; - if (m_aggregateRole >= Qt::UserRole) { - QModelIndex parentIndex = this->index( row, 0, index.parent() ); - QModelIndex childIndex = this->index( 0, column, parentIndex ); - function = mapToSource(childIndex).data(m_aggregateRole).toInt(); - } - - //Need a parentIndex with column == 0 because only those have children. - QModelIndex parentIndex = this->index( row, 0, index.parent() ); - for( int childRow = 0; childRow < childCount; childRow++ ) - { - QModelIndex childIndex = this->index( childRow, column, parentIndex ); - QVariant data = mapToSource( childIndex ).data( role ); - - if( data.isValid() && !variantsOfChildren.contains( data ) ) - variantsOfChildren << data; - } - - //saving in cache - ItemData roleMap = m_groupMaps[row].value( column ); - foreach( const QVariant &variant, variantsOfChildren ) - { - if( roleMap[ role ] != variant ) { - roleMap.insert( role, variantsOfChildren ); - } - } - - if( variantsOfChildren.count() == 0 ) - return QVariant(); - - //only one unique variant? No need to return a list - switch (function) { - case AGGR_EMPTY: return QVariant(); - case AGGR_FIRST: return variantsOfChildren.first(); - case AGGR_MAX: return variantMax(variantsOfChildren); - case AGGR_MIN: return variantMin(variantsOfChildren); - default: { - if( variantsOfChildren.count() == 1 ) - return variantsOfChildren.first(); - - return variantsOfChildren; - } break; - } - } - - return mapToSource( index ).data( role ); -} - -bool -QtGroupingProxy::setData( const QModelIndex &idx, const QVariant &value, int role ) -{ - if( !idx.isValid() ) - return false; - - //no need to set data to exactly the same value - if( idx.data( role ) == value ) - return false; - - if( isGroup( idx ) ) - { - ItemData columnData = m_groupMaps[idx.row()][idx.column()]; - - columnData.insert( role, value ); - //QItemDelegate will always use Qt::EditRole - if( role == Qt::EditRole ) - columnData.insert( Qt::DisplayRole, value ); - - //and make sure it's stored in the map - m_groupMaps[idx.row()].insert( idx.column(), columnData ); - - int columnToChange = idx.column() ? idx.column() : m_groupedColumn; - foreach( int originalRow, m_groupMap.value( idx.row() ) ) - { - QModelIndex childIdx = sourceModel()->index( originalRow, columnToChange, - m_rootNode ); - if( childIdx.isValid() ) - sourceModel()->setData( childIdx, value, role ); - } - //TODO: we might need to reload the data from the children at this point - - emit dataChanged( idx, idx ); - return true; - } - - return sourceModel()->setData( mapToSource( idx ), value, role ); -} - -bool -QtGroupingProxy::isGroup( const QModelIndex &index ) const -{ - int parentCreateIndex = index.internalId(); - if( parentCreateIndex == -1 && index.row() < m_groupMaps.count() ) - return true; - return false; -} - -QModelIndex -QtGroupingProxy::mapToSource( const QModelIndex &index ) const -{ - if( !index.isValid() ) { - return m_rootNode; - } - - if( isGroup( index ) ) - { - return m_rootNode; - } - - QModelIndex proxyParent = index.parent(); - QModelIndex originalParent = mapToSource( proxyParent ); - - int originalRow = index.row(); - if( originalParent == m_rootNode ) - { - int indexInGroup = index.row(); - if( !proxyParent.isValid() ) - indexInGroup -= m_groupMaps.count(); - - QList childRows = m_groupMap.value( proxyParent.row() ); - if( childRows.isEmpty() || indexInGroup >= childRows.count() || indexInGroup < 0 ) - return QModelIndex(); - - originalRow = childRows.at( indexInGroup ); - } - return sourceModel()->index( originalRow, index.column(), originalParent ); -} - -QModelIndexList -QtGroupingProxy::mapToSource( const QModelIndexList& list ) const -{ - QModelIndexList originalList; - foreach( const QModelIndex &index, list ) - { - QModelIndex originalIndex = mapToSource( index ); - if( originalIndex.isValid() ) - originalList << originalIndex; - } - return originalList; -} - -QModelIndex -QtGroupingProxy::mapFromSource( const QModelIndex &idx ) const -{ - if( !idx.isValid() ) - return QModelIndex(); - - QModelIndex proxyParent; - QModelIndex sourceParent = idx.parent(); - - int proxyRow = idx.row(); - int sourceRow = idx.row(); - - if( sourceParent.isValid() && ( sourceParent != m_rootNode ) ) - { - //idx is a child of one of the items in the source model - proxyParent = mapFromSource( sourceParent ); - } - else - { - //idx is an item in the top level of the source model (child of the rootnode) - int groupRow = -1; - QMapIterator > iterator( m_groupMap ); - while( iterator.hasNext() ) - { - iterator.next(); - if( iterator.value().contains( sourceRow ) ) - { - groupRow = iterator.key(); - break; - } - } - - if( groupRow != -1 ) //it's in a group, let's find the correct row. - { - proxyParent = this->index( groupRow, 0, QModelIndex() ); - proxyRow = m_groupMap.value( groupRow ).indexOf( sourceRow ); - } - else - { - proxyParent = QModelIndex(); - // if the proxy item is not in a group it will be below the groups. - int groupLength = m_groupMaps.count(); - int i = m_groupMap.value( std::numeric_limits::max() ).indexOf( sourceRow ); - - proxyRow = groupLength + i; - } - } - - return this->index( proxyRow, idx.column(), proxyParent ); -} - -Qt::ItemFlags -QtGroupingProxy::flags( const QModelIndex &idx ) const -{ - if( !idx.isValid() ) - { - Qt::ItemFlags rootFlags = sourceModel()->flags( m_rootNode ); - if( rootFlags.testFlag( Qt::ItemIsDropEnabled ) ) - return Qt::ItemFlags( Qt::ItemIsDropEnabled ); - - return Qt::ItemFlags(0); - } - - //only if the grouped column has the editable flag set allow the - //actions leading to setData on the source (edit & drop) - if( isGroup( idx ) ) - { - // dumpGroups(); - Qt::ItemFlags defaultFlags( Qt::ItemIsEnabled | Qt::ItemIsSelectable ); - //Qt::ItemFlags defaultFlags(Qt::ItemIsEnabled); - bool groupIsEditable = true; - - if (idx.column() == 0) { - bool checkable = true; - foreach ( int originalRow, m_groupMap.value( idx.row() ) ) - { - QModelIndex originalIdx = sourceModel()->index( originalRow, 0, - m_rootNode.parent() ); - if ( (originalIdx.flags() & Qt::ItemIsUserCheckable) == 0 ) - { - checkable = false; - } - } - - if ( checkable ) { - defaultFlags |= Qt::ItemIsUserCheckable; - } - } - - //it's possible to have empty groups - if( m_groupMap.value( idx.row() ).count() == 0 ) - { - //check the flags of this column with the root node - QModelIndex originalRootNode = sourceModel()->index( m_rootNode.row(), m_groupedColumn, - m_rootNode.parent() ); - groupIsEditable = originalRootNode.flags().testFlag( Qt::ItemIsEditable ); - } - else - { - foreach( int originalRow, m_groupMap.value( idx.row() ) ) - { - QModelIndex originalIdx = sourceModel()->index( originalRow, m_groupedColumn, - m_rootNode ); - - groupIsEditable = groupIsEditable - ? originalIdx.flags().testFlag( Qt::ItemIsEditable ) - : false; - if( !groupIsEditable ) //all children need to have an editable grouped column - break; - } - } - if( groupIsEditable ) - return ( defaultFlags | Qt::ItemIsEditable | Qt::ItemIsDropEnabled ); - return defaultFlags; - } - - QModelIndex originalIdx = mapToSource( idx ); - Qt::ItemFlags originalItemFlags = sourceModel()->flags( originalIdx ); - - //check the source model to see if the grouped column is editable; - QModelIndex groupedColumnIndex = - sourceModel()->index( originalIdx.row(), m_groupedColumn, originalIdx.parent() ); - bool groupIsEditable = sourceModel()->flags( groupedColumnIndex ).testFlag( Qt::ItemIsEditable ); - - if( groupIsEditable ) - return originalItemFlags | Qt::ItemIsDragEnabled; - return originalItemFlags; -} - -QVariant -QtGroupingProxy::headerData( int section, Qt::Orientation orientation, int role ) const -{ - return sourceModel()->headerData( section, orientation, role ); -} - -bool -QtGroupingProxy::canFetchMore( const QModelIndex &parent ) const -{ - if( !parent.isValid() ) - return false; - - if( isGroup( parent ) ) - return false; - - return sourceModel()->canFetchMore( mapToSource( parent ) ); -} - -void -QtGroupingProxy::fetchMore ( const QModelIndex & parent ) -{ - if( !parent.isValid() ) - return; - - if( isGroup( parent ) ) - return; - - return sourceModel()->fetchMore( mapToSource( parent ) ); -} - -QModelIndex -QtGroupingProxy::addEmptyGroup( const RowData &data ) -{ - int newRow = m_groupMaps.count(); - beginInsertRows( QModelIndex(), newRow, newRow ); - m_groupMaps << data; - endInsertRows(); - return index( newRow, 0, QModelIndex() ); -} - -bool -QtGroupingProxy::removeGroup( const QModelIndex &idx ) -{ - beginRemoveRows( idx.parent(), idx.row(), idx.row() ); - m_groupMap.remove( idx.row() ); - m_groupMaps.removeAt( idx.row() ); - m_parentCreateList.removeAt( idx.internalId() ); - endRemoveRows(); - - //TODO: only true if all data could be unset. - return true; -} - -bool -QtGroupingProxy::hasChildren( const QModelIndex &parent ) const -{ - if( !parent.isValid() ) { - return true; - } - - if( isGroup( parent ) ) { - return !m_groupMap.value( parent.row() ).isEmpty(); - } - - return sourceModel()->hasChildren( mapToSource( parent ) ); -} - -bool -QtGroupingProxy::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) -{ - QModelIndex idx = index(row, column, parent); - if (isGroup(idx)) { - QList childRows = m_groupMap.value(idx.row()); - int max = *std::max_element(childRows.begin(), childRows.end()); - - QModelIndex newIdx = mapToSource(index(max, column, idx)); - return sourceModel()->dropMimeData(data, action, max, column, newIdx); - } else { - if (row == -1) { - return sourceModel()->dropMimeData(data, action, -1, -1, mapToSource(parent)); - } else { - QModelIndex idx = mapToSource(index(row, column, parent)); - return sourceModel()->dropMimeData(data, action, idx.row(), idx.column(), idx.parent()); - } - } -} - -void -QtGroupingProxy::modelRowsAboutToBeInserted( const QModelIndex &parent, int start, int end ) -{ - if( parent != m_rootNode ) - { - //an item will be added to an original index, remap and pass it on - QModelIndex proxyParent = mapFromSource( parent ); - beginInsertRows( proxyParent, start, end ); - } -} - -void -QtGroupingProxy::modelRowsInserted( const QModelIndex &parent, int start, int end ) -{ - if( parent == m_rootNode ) - { - //top level of the model changed, these new rows need to be put in groups - for( int modelRow = start; modelRow <= end ; modelRow++ ) - { - addSourceRow( sourceModel()->index( modelRow, m_groupedColumn, m_rootNode ) ); - } - } - else - { - //an item was added to an original index, remap and pass it on - QModelIndex proxyParent = mapFromSource( parent ); - - QString s; - QDebug debug(&s); - debug << proxyParent; - log::debug("{}", s); - - //beginInsertRows had to be called in modelRowsAboutToBeInserted() - endInsertRows(); - } -} - -void -QtGroupingProxy::modelRowsAboutToBeRemoved( const QModelIndex &parent, int start, int end ) -{ - if( parent == m_rootNode ) - { - QMap >::const_iterator i; - //HACK, we are going to call beginRemoveRows() multiple times without - // endRemoveRows() if a source index is in multiple groups. - // This can be a problem for some views/proxies, but Q*Views can handle it. - // TODO: investigate a queue for applying proxy model changes in the correct order - for( i = m_groupMap.constBegin(); i != m_groupMap.constEnd(); ++i ) - { - int groupIndex = i.key(); - const QList &groupList = i.value(); - QModelIndex proxyParent = index( groupIndex, 0 ); - foreach( int originalRow, groupList ) - { - if( originalRow >= start && originalRow <= end ) - { - int proxyRow = groupList.indexOf( originalRow ); - if( groupIndex == -1 ) //adjust for non-grouped (root level) original items - proxyRow += m_groupMaps.count(); - //TODO: optimize for continues original rows in the same group - beginRemoveRows( proxyParent, proxyRow, proxyRow ); - } - } - } - } - else - { - //child item(s) of an original item will be removed, remap and pass it on - QModelIndex proxyParent = mapFromSource( parent ); - beginRemoveRows( proxyParent, start, end ); - } -} - -void -QtGroupingProxy::modelRowsRemoved( const QModelIndex &parent, int start, int end ) -{ - if( parent == m_rootNode ) - { - //TODO: can be optimised by iterating over m_groupMap and checking start <= r < end - - //rather than increasing i we change the stored sourceRows in-place and reuse argument start - //X-times (where X = end - start). - for( int i = start; i <= end; i++ ) - { - //HACK: we are going to iterate the hash in reverse so calls to endRemoveRows() - // are matched up with the beginRemoveRows() in modelRowsAboutToBeRemoved() - //NOTE: easier to do reverse with java style iterator - QMutableMapIterator > iter( m_groupMap ); - iter.toBack(); - while( iter.hasPrevious() ) - { - iter.previous(); - int groupIndex = iter.key(); - //has to be a modifiable reference for remove and replace operations - QList &groupList = iter.value(); - int rowIndex = groupList.indexOf( start ); - if( rowIndex != -1 ) - { - QModelIndex proxyParent = index( groupIndex, 0 ); - groupList.removeAt( rowIndex ); - } - //Now decrement all source rows that are after the removed row - for( int j = 0; j < groupList.count(); j++ ) - { - int sourceRow = groupList.at( j ); - if( sourceRow > start ) - groupList.replace( j, sourceRow-1 ); - } - if( rowIndex != -1) - endRemoveRows(); //end remove operation only after group was updated. - } - } - - return; - } - - //beginRemoveRows had to be called in modelRowsAboutToBeRemoved(); - endRemoveRows(); -} - -void -QtGroupingProxy::resetModel() -{ - buildTree(); -} - -void -QtGroupingProxy::modelDataChanged( const QModelIndex &topLeft, const QModelIndex &bottomRight ) -{ - //TODO: need to look in the groupedColumn and see if it changed and changed grouping accordingly - QModelIndex proxyTopLeft = mapFromSource( topLeft ); - if( !proxyTopLeft.isValid() ) - return; - - if( topLeft == bottomRight ) - { - emit dataChanged( proxyTopLeft, proxyTopLeft ); - } - else - { - QModelIndex proxyBottomRight = mapFromSource( bottomRight ); - emit dataChanged( proxyTopLeft, proxyBottomRight ); - } -} - -bool -QtGroupingProxy::isAGroupSelected( const QModelIndexList& list ) const -{ - foreach( const QModelIndex &index, list ) - { - if( isGroup( index ) ) - return true; - } - return false; -} - -void -QtGroupingProxy::dumpGroups() const -{ - QString s; - QDebug debug(&s); - - debug << "m_groupMap:\n"; - for( int groupIndex = -1; groupIndex < m_groupMap.keys().count() - 1; groupIndex++ ) - { - debug << groupIndex << " : " << m_groupMap.value( groupIndex ) << "\n"; - } - - debug << "m_groupMaps:\n"; - for( int groupIndex = 0; groupIndex < m_groupMaps.count(); groupIndex++ ) - { - debug << m_groupMaps[groupIndex] << ": " << m_groupMap.value( groupIndex ) << "\n"; - } - - debug << m_groupMap.value( std::numeric_limits::max() ); - - log::debug("{}", s); -} +/**************************************************************************************** + * Copyright (c) 2007-2011 Bart Cerneels * + * * + * This program 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 2 of the License, or (at your option) any later * version. + ** + * * + * This program 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 * this + *program. If not, see . * + ****************************************************************************************/ + +// Modifications 2013-03-27 to 2013-03-29 by Sebastian Herbord + +#include "qtgroupingproxy.h" +#include + +#include +#include +#include + +using namespace MOBase; + +/*! + \class QtGroupingProxy + \brief The QtGroupingProxy class will group source model rows by adding a new top + tree-level. The source model can be flat or tree organized, but only the original top + level rows are used for determining the grouping. \ingroup model-view +*/ + +QtGroupingProxy::QtGroupingProxy(QModelIndex rootNode, int groupedColumn, + int groupedRole, unsigned int flags, int aggregateRole) + : QAbstractProxyModel(), m_rootNode(rootNode), m_groupedColumn(0), + m_groupedRole(groupedRole), m_aggregateRole(aggregateRole), m_flags(flags) +{ + if (groupedColumn != -1) { + setGroupedColumn(groupedColumn); + } +} + +QtGroupingProxy::~QtGroupingProxy() {} + +void QtGroupingProxy::setSourceModel(QAbstractItemModel* model) +{ + if (sourceModel()) { + disconnect(sourceModel(), nullptr, this, nullptr); + } + + QAbstractProxyModel::setSourceModel(model); + + if (sourceModel()) { + // signal proxies + connect(sourceModel(), SIGNAL(rowsInserted(const QModelIndex&, int, int)), + SLOT(modelRowsInserted(const QModelIndex&, int, int))); + connect(sourceModel(), SIGNAL(rowsAboutToBeInserted(const QModelIndex&, int, int)), + SLOT(modelRowsAboutToBeInserted(const QModelIndex&, int, int))); + connect(sourceModel(), SIGNAL(rowsRemoved(const QModelIndex&, int, int)), + SLOT(modelRowsRemoved(const QModelIndex&, int, int))); + connect(sourceModel(), SIGNAL(rowsAboutToBeRemoved(const QModelIndex&, int, int)), + SLOT(modelRowsAboutToBeRemoved(QModelIndex, int, int))); + connect(sourceModel(), SIGNAL(layoutChanged()), SLOT(buildTree())); + connect(sourceModel(), SIGNAL(dataChanged(QModelIndex, QModelIndex)), + SLOT(modelDataChanged(QModelIndex, QModelIndex))); + connect(sourceModel(), SIGNAL(modelReset()), this, SLOT(resetModel())); + + buildTree(); + } +} + +void QtGroupingProxy::setGroupedColumn(int groupedColumn) +{ + m_groupedColumn = groupedColumn; + buildTree(); +} + +/** Maps to what groups the source row belongs by returning the data of those groups. + * + * @returns a list of data for the rows the argument belongs to. In common cases this + * list will contain only one entry. An empty list means that the source item will be + * placed in the root of this proxyModel. There is no support for hiding source items. + * + * Group data can be pre-loaded in the return value so it's added to the cache + * maintained by this class. This is required if you want to have data that is not + * present in the source model. + */ +QList QtGroupingProxy::belongsTo(const QModelIndex& idx) +{ + QList rowDataList; + + // get all the data for this index from the model + ItemData itemData = sourceModel()->itemData(idx); + if (m_groupedRole != Qt::DisplayRole) { + itemData[Qt::DisplayRole] = itemData[m_groupedRole]; + } + + // invalid value in grouped role -> ungrouped + if (!itemData[Qt::DisplayRole].isValid()) { + return rowDataList; + } + + QMapIterator i(itemData); + while (i.hasNext()) { + i.next(); + int role = i.key(); + QVariant variant = i.value(); + + if (variant.type() == QVariant::List) { + // a list of variants get's expanded to multiple rows + QVariantList list = variant.toList(); + for (int i = 0; i < list.length(); i++) { + // take an existing row data or create a new one + RowData rowData = (rowDataList.count() > i) ? rowDataList.takeAt(i) : RowData(); + + // we only gather data for the first column + ItemData indexData = rowData.contains(0) ? rowData.take(0) : ItemData(); + indexData.insert(role, list.value(i)); + rowData.insert(0, indexData); + // for the grouped column the data should not be gathered from the children + // this will allow filtering on the content of this column with a + // QSortFilterProxyModel + rowData.insert(m_groupedColumn, indexData); + rowDataList.insert(i, rowData); + } + break; + } else if (!variant.isNull()) { + // it's just a normal item. Copy all the data and break this loop. + RowData rowData; + rowData.insert(0, itemData); + rowDataList << rowData; + break; + } + } + + return rowDataList; +} + +/* m_groupMap layout + * key : index of the group in m_groupMaps + * value : a QList of the original rows in sourceModel() for the children of this group + * + * key = -1 contains a QList of the non-grouped indexes + * + * TODO: sub-groups + */ +void QtGroupingProxy::buildTree() +{ + if (!sourceModel()) + return; + beginResetModel(); + + m_groupMap.clear(); + // don't clear the data maps since most of it will probably be needed again. + m_parentCreateList.clear(); + + int max = sourceModel()->rowCount(m_rootNode); + + // WARNING: these have to be added in order because the addToGroups function is + // optimized for modelRowsInserted(). Failure to do so will result in wrong data shown + // in the view at best. + for (int row = 0; row < max; row++) { + QModelIndex idx = sourceModel()->index(row, m_groupedColumn, m_rootNode); + addSourceRow(idx); + } + // dumpGroups(); + + if (m_flags & FLAG_NOSINGLE) { + // awkward: flatten single-item groups as a post-processing steps. + + int currentKey = 0; + quint32 quint32max = std::numeric_limits::max(); + std::vector rmgroups; + + QMap> temp; + + for (auto iter = m_groupMap.begin(); iter != m_groupMap.end(); ++iter) { + if ((iter.key() == quint32max) || (iter->count() < 2)) { + temp[quint32max].append(iter.value()); + if (iter.key() != quint32max) { + rmgroups.push_back(iter.key()); + } + } else { + temp[currentKey++] = *iter; + } + } + m_groupMap = temp; + + // second loop is necessary because qt containers can't be iterated from end to + // front and removing by index from begin to end is ugly + std::sort(rmgroups.begin(), rmgroups.end(), [](int lhs, int rhs) { + return rhs < lhs; + }); + for (auto iter = rmgroups.begin(); iter != rmgroups.end(); ++iter) { + m_groupMaps.removeAt(*iter); + } + } + + endResetModel(); +} + +QList QtGroupingProxy::addSourceRow(const QModelIndex& idx) +{ + QList updatedGroups; + QList groupData = belongsTo(idx); + + // an empty list here means it's supposed to go in root. + if (groupData.isEmpty()) { + updatedGroups << -1; + if (!m_groupMap.keys().contains(std::numeric_limits::max())) + m_groupMap.insert(std::numeric_limits::max(), + QList()); // add an empty placeholder + } + + // an item can be in multiple groups + foreach (RowData data, groupData) { + int updatedGroup = -1; + if (!data.isEmpty()) { + foreach (const RowData& cachedData, m_groupMaps) { + // when this matches the index belongs to an existing group + if (data[0][Qt::DisplayRole] == cachedData[0][Qt::DisplayRole]) { + data = cachedData; + break; + } + } + + updatedGroup = m_groupMaps.indexOf(data); + //-1 means not found + if (updatedGroup == -1) { + // new groups are added to the end of the existing list + m_groupMaps << data; + updatedGroup = m_groupMaps.count() - 1; + } + + if (!m_groupMap.keys().contains(updatedGroup)) + m_groupMap.insert(updatedGroup, QList()); // add an empty placeholder + } + + if (!updatedGroups.contains(updatedGroup)) + updatedGroups << updatedGroup; + } + + // update m_groupMap to the new source-model layout (one row added) + QMutableMapIterator> i(m_groupMap); + while (i.hasNext()) { + i.next(); + QList& groupList = i.value(); + int insertedProxyRow = groupList.count(); + for (; insertedProxyRow > 0; insertedProxyRow--) { + int& rowValue = groupList[insertedProxyRow - 1]; + if (idx.row() <= rowValue) { + // increment the rows that come after the new row since they moved one place up. + rowValue++; + } else { + break; + } + } + + if (updatedGroups.contains(i.key())) { + // the row needs to be added to this group + groupList.insert(insertedProxyRow, idx.row()); + } + } + + return updatedGroups; +} + +/** Each ModelIndex has in it's internalId a position in the parentCreateList. + * struct ParentCreate are the instructions to recreate the parent index. + * It contains the proxy row number of the parent and the postion in this list of the + * grandfather. This function creates the ParentCreate structs and saves them in a list. + */ +int QtGroupingProxy::indexOfParentCreate(const QModelIndex& parent) const +{ + if (!parent.isValid()) + return -1; + + struct ParentCreate pc; + for (int i = 0; i < m_parentCreateList.size(); i++) { + pc = m_parentCreateList[i]; + if (pc.parentCreateIndex == parent.internalId() && pc.row == parent.row()) + return i; + } + // there is no parentCreate yet for this index, so let's create one. + pc.parentCreateIndex = parent.internalId(); + pc.row = parent.row(); + m_parentCreateList << pc; + + return m_parentCreateList.size() - 1; +} + +QModelIndex QtGroupingProxy::index(int row, int column, const QModelIndex& parent) const +{ + if (!hasIndex(row, column, parent)) { + return QModelIndex(); + } + + if (parent.column() > 0) { + return QModelIndex(); + } + + /* We save the instructions to make the parent of the index in a struct. + * The place of the struct in the list is stored in the internalId + */ + int parentCreateIndex = indexOfParentCreate(parent); + + return createIndex(row, column, parentCreateIndex); +} + +QModelIndex QtGroupingProxy::parent(const QModelIndex& index) const +{ + if (!index.isValid()) + return QModelIndex(); + + int parentCreateIndex = index.internalId(); + if (parentCreateIndex == -1 || parentCreateIndex >= m_parentCreateList.count()) + return QModelIndex(); + + struct ParentCreate pc = m_parentCreateList[parentCreateIndex]; + + // only items at column 0 have children + return createIndex(pc.row, 0, pc.parentCreateIndex); +} + +int QtGroupingProxy::rowCount(const QModelIndex& index) const +{ + if (!index.isValid()) { + // the number of top level groups + the number of non-grouped items + int rows = m_groupMaps.count() + + m_groupMap.value(std::numeric_limits::max()).count(); + return rows; + } + + // TODO:group in group support. + if (isGroup(index)) { + qint64 groupIndex = index.row(); + int rows = m_groupMap.value(groupIndex).count(); + return rows; + } else { + QModelIndex originalIndex = mapToSource(index); + int rowCount = sourceModel()->rowCount(originalIndex); + return rowCount; + } +} + +int QtGroupingProxy::columnCount(const QModelIndex& index) const +{ + if (!index.isValid()) + return sourceModel()->columnCount(m_rootNode); + + if (index.column() != 0) + return 0; + + return sourceModel()->columnCount(mapToSource(index)); +} + +static bool variantLess(const QVariant& LHS, const QVariant& RHS) +{ + if ((LHS.type() == RHS.type()) && + ((LHS.type() == QVariant::Int) || (LHS.type() == QVariant::UInt))) { + return LHS.toInt() < RHS.toInt(); + } + + // this should always work (comparing empty strings in the worst case) but + // the results may be wrong + return LHS.toString() < RHS.toString(); +} + +static QVariant variantMax(const QVariantList& variants) +{ + QVariant result = variants.first(); + foreach (const QVariant& iter, variants) { + if (variantLess(result, iter)) { + result = iter; + } + } + return result; +} + +static QVariant variantMin(const QVariantList& variants) +{ + QVariant result = variants.first(); + foreach (const QVariant& iter, variants) { + if (variantLess(iter, result)) { + result = iter; + } + } + return result; +} + +QVariant QtGroupingProxy::data(const QModelIndex& index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + int row = index.row(); + int column = index.column(); + if (isGroup(index)) { + if ((role != Qt::DisplayRole) && (role != Qt::EditRole)) { + switch (role) { + case Qt::ForegroundRole: { + return QBrush(Qt::gray); + } break; + case Qt::FontRole: { + QFont font(m_groupMaps[row][column].value(Qt::FontRole).value()); + font.setItalic(true); + return font; + } break; + case Qt::TextAlignmentRole: { + return Qt::AlignHCenter; + } break; + case Qt::UserRole: { + return m_groupMaps[row][column].value(Qt::DisplayRole).toString(); + } break; + case Qt::CheckStateRole: { + if (column != 0) + return QVariant(); + int childCount = m_groupMap.value(row).count(); + int checked = 0; + QModelIndex parentIndex = this->index(row, 0, index.parent()); + for (int childRow = 0; childRow < childCount; ++childRow) { + QModelIndex childIndex = this->index(childRow, 0, parentIndex); + QVariant data = mapToSource(childIndex).data(Qt::CheckStateRole); + if (data.toInt() == 2) + ++checked; + } + if (checked == childCount) + return Qt::Checked; + else if (checked == 0) + return Qt::Unchecked; + else + return Qt::PartiallyChecked; + } break; + default: { + QModelIndex parentIndex = this->index(row, 0, index.parent()); + if (m_groupMap.value(row).count() > 0) { + return this->index(0, column, parentIndex).data(role); + } else { + return QVariant(); + } + // return m_groupMaps[row][column].value( role ); + } break; + } + } + + // use cached or precalculated data + if (m_groupMaps[row][column].contains(Qt::DisplayRole)) { + if ((m_flags & FLAG_NOGROUPNAME) != 0) { + QModelIndex parentIndex = this->index(row, 0, index.parent()); + QModelIndex childIndex = this->index(0, column, parentIndex); + return childIndex.data(role).toString(); + } else { + return m_groupMaps[row][column].value(role).toString(); + } + } + + // for column 0 we gather data from the grouped column instead + if (column == 0) + column = m_groupedColumn; + + // map all data from children to columns of group to allow grouping one level up + QVariantList variantsOfChildren; + int childCount = m_groupMap.value(row).count(); + if (childCount == 0) + return QVariant(); + + int function = AGGR_NONE; + if (m_aggregateRole >= Qt::UserRole) { + QModelIndex parentIndex = this->index(row, 0, index.parent()); + QModelIndex childIndex = this->index(0, column, parentIndex); + function = mapToSource(childIndex).data(m_aggregateRole).toInt(); + } + + // Need a parentIndex with column == 0 because only those have children. + QModelIndex parentIndex = this->index(row, 0, index.parent()); + for (int childRow = 0; childRow < childCount; childRow++) { + QModelIndex childIndex = this->index(childRow, column, parentIndex); + QVariant data = mapToSource(childIndex).data(role); + + if (data.isValid() && !variantsOfChildren.contains(data)) + variantsOfChildren << data; + } + + // saving in cache + ItemData roleMap = m_groupMaps[row].value(column); + foreach (const QVariant& variant, variantsOfChildren) { + if (roleMap[role] != variant) { + roleMap.insert(role, variantsOfChildren); + } + } + + if (variantsOfChildren.count() == 0) + return QVariant(); + + // only one unique variant? No need to return a list + switch (function) { + case AGGR_EMPTY: + return QVariant(); + case AGGR_FIRST: + return variantsOfChildren.first(); + case AGGR_MAX: + return variantMax(variantsOfChildren); + case AGGR_MIN: + return variantMin(variantsOfChildren); + default: { + if (variantsOfChildren.count() == 1) + return variantsOfChildren.first(); + + return variantsOfChildren; + } break; + } + } + + return mapToSource(index).data(role); +} + +bool QtGroupingProxy::setData(const QModelIndex& idx, const QVariant& value, int role) +{ + if (!idx.isValid()) + return false; + + // no need to set data to exactly the same value + if (idx.data(role) == value) + return false; + + if (isGroup(idx)) { + ItemData columnData = m_groupMaps[idx.row()][idx.column()]; + + columnData.insert(role, value); + // QItemDelegate will always use Qt::EditRole + if (role == Qt::EditRole) + columnData.insert(Qt::DisplayRole, value); + + // and make sure it's stored in the map + m_groupMaps[idx.row()].insert(idx.column(), columnData); + + int columnToChange = idx.column() ? idx.column() : m_groupedColumn; + foreach (int originalRow, m_groupMap.value(idx.row())) { + QModelIndex childIdx = + sourceModel()->index(originalRow, columnToChange, m_rootNode); + if (childIdx.isValid()) + sourceModel()->setData(childIdx, value, role); + } + // TODO: we might need to reload the data from the children at this point + + emit dataChanged(idx, idx); + return true; + } + + return sourceModel()->setData(mapToSource(idx), value, role); +} + +bool QtGroupingProxy::isGroup(const QModelIndex& index) const +{ + int parentCreateIndex = index.internalId(); + if (parentCreateIndex == -1 && index.row() < m_groupMaps.count()) + return true; + return false; +} + +QModelIndex QtGroupingProxy::mapToSource(const QModelIndex& index) const +{ + if (!index.isValid()) { + return m_rootNode; + } + + if (isGroup(index)) { + return m_rootNode; + } + + QModelIndex proxyParent = index.parent(); + QModelIndex originalParent = mapToSource(proxyParent); + + int originalRow = index.row(); + if (originalParent == m_rootNode) { + int indexInGroup = index.row(); + if (!proxyParent.isValid()) + indexInGroup -= m_groupMaps.count(); + + QList childRows = m_groupMap.value(proxyParent.row()); + if (childRows.isEmpty() || indexInGroup >= childRows.count() || indexInGroup < 0) + return QModelIndex(); + + originalRow = childRows.at(indexInGroup); + } + return sourceModel()->index(originalRow, index.column(), originalParent); +} + +QModelIndexList QtGroupingProxy::mapToSource(const QModelIndexList& list) const +{ + QModelIndexList originalList; + foreach (const QModelIndex& index, list) { + QModelIndex originalIndex = mapToSource(index); + if (originalIndex.isValid()) + originalList << originalIndex; + } + return originalList; +} + +QModelIndex QtGroupingProxy::mapFromSource(const QModelIndex& idx) const +{ + if (!idx.isValid()) + return QModelIndex(); + + QModelIndex proxyParent; + QModelIndex sourceParent = idx.parent(); + + int proxyRow = idx.row(); + int sourceRow = idx.row(); + + if (sourceParent.isValid() && (sourceParent != m_rootNode)) { + // idx is a child of one of the items in the source model + proxyParent = mapFromSource(sourceParent); + } else { + // idx is an item in the top level of the source model (child of the rootnode) + int groupRow = -1; + QMapIterator> iterator(m_groupMap); + while (iterator.hasNext()) { + iterator.next(); + if (iterator.value().contains(sourceRow)) { + groupRow = iterator.key(); + break; + } + } + + if (groupRow != -1) // it's in a group, let's find the correct row. + { + proxyParent = this->index(groupRow, 0, QModelIndex()); + proxyRow = m_groupMap.value(groupRow).indexOf(sourceRow); + } else { + proxyParent = QModelIndex(); + // if the proxy item is not in a group it will be below the groups. + int groupLength = m_groupMaps.count(); + int i = m_groupMap.value(std::numeric_limits::max()).indexOf(sourceRow); + + proxyRow = groupLength + i; + } + } + + return this->index(proxyRow, idx.column(), proxyParent); +} + +Qt::ItemFlags QtGroupingProxy::flags(const QModelIndex& idx) const +{ + if (!idx.isValid()) { + Qt::ItemFlags rootFlags = sourceModel()->flags(m_rootNode); + if (rootFlags.testFlag(Qt::ItemIsDropEnabled)) + return Qt::ItemFlags(Qt::ItemIsDropEnabled); + + return Qt::ItemFlags(0); + } + + // only if the grouped column has the editable flag set allow the + // actions leading to setData on the source (edit & drop) + if (isGroup(idx)) { + // dumpGroups(); + Qt::ItemFlags defaultFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); + // Qt::ItemFlags defaultFlags(Qt::ItemIsEnabled); + bool groupIsEditable = true; + + if (idx.column() == 0) { + bool checkable = true; + foreach (int originalRow, m_groupMap.value(idx.row())) { + QModelIndex originalIdx = + sourceModel()->index(originalRow, 0, m_rootNode.parent()); + if ((originalIdx.flags() & Qt::ItemIsUserCheckable) == 0) { + checkable = false; + } + } + + if (checkable) { + defaultFlags |= Qt::ItemIsUserCheckable; + } + } + + // it's possible to have empty groups + if (m_groupMap.value(idx.row()).count() == 0) { + // check the flags of this column with the root node + QModelIndex originalRootNode = + sourceModel()->index(m_rootNode.row(), m_groupedColumn, m_rootNode.parent()); + groupIsEditable = originalRootNode.flags().testFlag(Qt::ItemIsEditable); + } else { + foreach (int originalRow, m_groupMap.value(idx.row())) { + QModelIndex originalIdx = + sourceModel()->index(originalRow, m_groupedColumn, m_rootNode); + + groupIsEditable = + groupIsEditable ? originalIdx.flags().testFlag(Qt::ItemIsEditable) : false; + if (!groupIsEditable) // all children need to have an editable grouped column + break; + } + } + if (groupIsEditable) + return (defaultFlags | Qt::ItemIsEditable | Qt::ItemIsDropEnabled); + return defaultFlags; + } + + QModelIndex originalIdx = mapToSource(idx); + Qt::ItemFlags originalItemFlags = sourceModel()->flags(originalIdx); + + // check the source model to see if the grouped column is editable; + QModelIndex groupedColumnIndex = + sourceModel()->index(originalIdx.row(), m_groupedColumn, originalIdx.parent()); + bool groupIsEditable = + sourceModel()->flags(groupedColumnIndex).testFlag(Qt::ItemIsEditable); + + if (groupIsEditable) + return originalItemFlags | Qt::ItemIsDragEnabled; + return originalItemFlags; +} + +QVariant QtGroupingProxy::headerData(int section, Qt::Orientation orientation, + int role) const +{ + return sourceModel()->headerData(section, orientation, role); +} + +bool QtGroupingProxy::canFetchMore(const QModelIndex& parent) const +{ + if (!parent.isValid()) + return false; + + if (isGroup(parent)) + return false; + + return sourceModel()->canFetchMore(mapToSource(parent)); +} + +void QtGroupingProxy::fetchMore(const QModelIndex& parent) +{ + if (!parent.isValid()) + return; + + if (isGroup(parent)) + return; + + return sourceModel()->fetchMore(mapToSource(parent)); +} + +QModelIndex QtGroupingProxy::addEmptyGroup(const RowData& data) +{ + int newRow = m_groupMaps.count(); + beginInsertRows(QModelIndex(), newRow, newRow); + m_groupMaps << data; + endInsertRows(); + return index(newRow, 0, QModelIndex()); +} + +bool QtGroupingProxy::removeGroup(const QModelIndex& idx) +{ + beginRemoveRows(idx.parent(), idx.row(), idx.row()); + m_groupMap.remove(idx.row()); + m_groupMaps.removeAt(idx.row()); + m_parentCreateList.removeAt(idx.internalId()); + endRemoveRows(); + + // TODO: only true if all data could be unset. + return true; +} + +bool QtGroupingProxy::hasChildren(const QModelIndex& parent) const +{ + if (!parent.isValid()) { + return true; + } + + if (isGroup(parent)) { + return !m_groupMap.value(parent.row()).isEmpty(); + } + + return sourceModel()->hasChildren(mapToSource(parent)); +} + +bool QtGroupingProxy::dropMimeData(const QMimeData* data, Qt::DropAction action, + int row, int column, const QModelIndex& parent) +{ + QModelIndex idx = index(row, column, parent); + if (isGroup(idx)) { + QList childRows = m_groupMap.value(idx.row()); + int max = *std::max_element(childRows.begin(), childRows.end()); + + QModelIndex newIdx = mapToSource(index(max, column, idx)); + return sourceModel()->dropMimeData(data, action, max, column, newIdx); + } else { + if (row == -1) { + return sourceModel()->dropMimeData(data, action, -1, -1, mapToSource(parent)); + } else { + QModelIndex idx = mapToSource(index(row, column, parent)); + return sourceModel()->dropMimeData(data, action, idx.row(), idx.column(), + idx.parent()); + } + } +} + +void QtGroupingProxy::modelRowsAboutToBeInserted(const QModelIndex& parent, int start, + int end) +{ + if (parent != m_rootNode) { + // an item will be added to an original index, remap and pass it on + QModelIndex proxyParent = mapFromSource(parent); + beginInsertRows(proxyParent, start, end); + } +} + +void QtGroupingProxy::modelRowsInserted(const QModelIndex& parent, int start, int end) +{ + if (parent == m_rootNode) { + // top level of the model changed, these new rows need to be put in groups + for (int modelRow = start; modelRow <= end; modelRow++) { + addSourceRow(sourceModel()->index(modelRow, m_groupedColumn, m_rootNode)); + } + } else { + // an item was added to an original index, remap and pass it on + QModelIndex proxyParent = mapFromSource(parent); + + QString s; + QDebug debug(&s); + debug << proxyParent; + log::debug("{}", s); + + // beginInsertRows had to be called in modelRowsAboutToBeInserted() + endInsertRows(); + } +} + +void QtGroupingProxy::modelRowsAboutToBeRemoved(const QModelIndex& parent, int start, + int end) +{ + if (parent == m_rootNode) { + QMap>::const_iterator i; + // HACK, we are going to call beginRemoveRows() multiple times without + // endRemoveRows() if a source index is in multiple groups. + // This can be a problem for some views/proxies, but Q*Views can handle it. + // TODO: investigate a queue for applying proxy model changes in the correct order + for (i = m_groupMap.constBegin(); i != m_groupMap.constEnd(); ++i) { + int groupIndex = i.key(); + const QList& groupList = i.value(); + QModelIndex proxyParent = index(groupIndex, 0); + foreach (int originalRow, groupList) { + if (originalRow >= start && originalRow <= end) { + int proxyRow = groupList.indexOf(originalRow); + if (groupIndex == -1) // adjust for non-grouped (root level) original items + proxyRow += m_groupMaps.count(); + // TODO: optimize for continues original rows in the same group + beginRemoveRows(proxyParent, proxyRow, proxyRow); + } + } + } + } else { + // child item(s) of an original item will be removed, remap and pass it on + QModelIndex proxyParent = mapFromSource(parent); + beginRemoveRows(proxyParent, start, end); + } +} + +void QtGroupingProxy::modelRowsRemoved(const QModelIndex& parent, int start, int end) +{ + if (parent == m_rootNode) { + // TODO: can be optimised by iterating over m_groupMap and checking start <= r < end + + // rather than increasing i we change the stored sourceRows in-place and reuse + // argument start X-times (where X = end - start). + for (int i = start; i <= end; i++) { + // HACK: we are going to iterate the hash in reverse so calls to endRemoveRows() + // are matched up with the beginRemoveRows() in modelRowsAboutToBeRemoved() + // NOTE: easier to do reverse with java style iterator + QMutableMapIterator> iter(m_groupMap); + iter.toBack(); + while (iter.hasPrevious()) { + iter.previous(); + int groupIndex = iter.key(); + // has to be a modifiable reference for remove and replace operations + QList& groupList = iter.value(); + int rowIndex = groupList.indexOf(start); + if (rowIndex != -1) { + QModelIndex proxyParent = index(groupIndex, 0); + groupList.removeAt(rowIndex); + } + // Now decrement all source rows that are after the removed row + for (int j = 0; j < groupList.count(); j++) { + int sourceRow = groupList.at(j); + if (sourceRow > start) + groupList.replace(j, sourceRow - 1); + } + if (rowIndex != -1) + endRemoveRows(); // end remove operation only after group was updated. + } + } + + return; + } + + // beginRemoveRows had to be called in modelRowsAboutToBeRemoved(); + endRemoveRows(); +} + +void QtGroupingProxy::resetModel() +{ + buildTree(); +} + +void QtGroupingProxy::modelDataChanged(const QModelIndex& topLeft, + const QModelIndex& bottomRight) +{ + // TODO: need to look in the groupedColumn and see if it changed and changed grouping + // accordingly + QModelIndex proxyTopLeft = mapFromSource(topLeft); + if (!proxyTopLeft.isValid()) + return; + + if (topLeft == bottomRight) { + emit dataChanged(proxyTopLeft, proxyTopLeft); + } else { + QModelIndex proxyBottomRight = mapFromSource(bottomRight); + emit dataChanged(proxyTopLeft, proxyBottomRight); + } +} + +bool QtGroupingProxy::isAGroupSelected(const QModelIndexList& list) const +{ + foreach (const QModelIndex& index, list) { + if (isGroup(index)) + return true; + } + return false; +} + +void QtGroupingProxy::dumpGroups() const +{ + QString s; + QDebug debug(&s); + + debug << "m_groupMap:\n"; + for (int groupIndex = -1; groupIndex < m_groupMap.keys().count() - 1; groupIndex++) { + debug << groupIndex << " : " << m_groupMap.value(groupIndex) << "\n"; + } + + debug << "m_groupMaps:\n"; + for (int groupIndex = 0; groupIndex < m_groupMaps.count(); groupIndex++) { + debug << m_groupMaps[groupIndex] << ": " << m_groupMap.value(groupIndex) << "\n"; + } + + debug << m_groupMap.value(std::numeric_limits::max()); + + log::debug("{}", s); +} diff --git a/src/qtgroupingproxy.h b/src/qtgroupingproxy.h index c6116d8ac..aeef9f4b9 100644 --- a/src/qtgroupingproxy.h +++ b/src/qtgroupingproxy.h @@ -1,153 +1,153 @@ -/**************************************************************************************** - * Copyright (c) 2007-2010 Bart Cerneels * - * * - * This program 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 2 of the License, or (at your option) any later * - * version. * - * * - * This program 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 * - * this program. If not, see . * - ****************************************************************************************/ - -// Modifications 2013-03-27 to 2013-03-28 by Sebastian Herbord - -#ifndef GROUPINGPROXY_H -#define GROUPINGPROXY_H - -#include -#include -#include -#include -#include -#include - -typedef QMap ItemData; -typedef QMap RowData; - -class QtGroupingProxy : public QAbstractProxyModel -{ - Q_OBJECT - -public: - - static const unsigned int FLAG_NOSINGLE = 1; - static const unsigned int FLAG_NOGROUPNAME = 2; - - enum EAggregateFunction { - AGGR_NONE, // no aggregation, return child elements as list - AGGR_EMPTY, // display nothing - AGGR_FIRST, // return value of the topmost item - AGGR_MAX, // return maximum value - AGGR_MIN // return minimum value - }; - -public: - explicit QtGroupingProxy( QModelIndex rootNode = QModelIndex(), - int groupedColumn = -1, int groupedRole = Qt::DisplayRole, - unsigned int flags = 0, - int aggregateRole = Qt::DisplayRole); - ~QtGroupingProxy(); - - void setSourceModel(QAbstractItemModel* model) override; - void setGroupedColumn( int groupedColumn ); - - /* QAbstractProxyModel methods */ - virtual QModelIndex index( int, int c = 0, - const QModelIndex& parent = QModelIndex() ) const; - virtual Qt::ItemFlags flags( const QModelIndex &idx ) const; - virtual QModelIndex parent( const QModelIndex &idx ) const; - virtual int rowCount( const QModelIndex &idx = QModelIndex() ) const; - virtual int columnCount( const QModelIndex &idx ) const; - virtual QModelIndex mapToSource( const QModelIndex &idx ) const; - virtual QModelIndexList mapToSource( const QModelIndexList &list ) const; - virtual QModelIndex mapFromSource( const QModelIndex &idx ) const; - virtual QVariant data( const QModelIndex &idx, int role ) const; - virtual bool setData( const QModelIndex &index, const QVariant &value, - int role = Qt::EditRole ); - virtual QVariant headerData ( int section, Qt::Orientation orientation, - int role ) const; - virtual bool canFetchMore( const QModelIndex &parent ) const; - virtual void fetchMore( const QModelIndex &parent ); - virtual bool hasChildren( const QModelIndex &parent = QModelIndex() ) const; - virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent); - - /* QtGroupingProxy methods */ - virtual QModelIndex addEmptyGroup( const RowData &data ); - virtual bool removeGroup( const QModelIndex &idx ); - -protected slots: - virtual void buildTree(); - -private slots: - void modelDataChanged( const QModelIndex &, const QModelIndex & ); - void modelRowsAboutToBeInserted( const QModelIndex &, int ,int ); - void modelRowsInserted( const QModelIndex &, int, int ); - void modelRowsAboutToBeRemoved( const QModelIndex &, int ,int ); - void modelRowsRemoved( const QModelIndex &, int, int ); - void resetModel(); - -protected: - /** Maps an item to a group. - * The return value is a list because an item can put in multiple groups. - * Inside the list is a 2 dimensional map. - * Mapped to column-number is another map of role-number to QVariant. - * This data prepolulates the group-data cache. The rest is gathered on demand - * from the children of the group. - */ - virtual QList belongsTo( const QModelIndex &idx ); - - /** - * calls belongsTo(), checks cached data and adds the index to existing or new groups. - * @returns the groups this index was added to where -1 means it was added to the root. - */ - QList addSourceRow( const QModelIndex &idx ); - - bool isGroup( const QModelIndex &index ) const; - bool isAGroupSelected( const QModelIndexList &list ) const; - - /** Maintains the group -> sourcemodel row mapping - * The reason a QList is use instead of a QMultiHash is that the values have to be - * reordered when rows are inserted or removed. - * TODO:use some auto-incrementing container class (steveire's?) for the list - */ - QMap > m_groupMap; - /** The data cache of the groups. - * This can be pre-loaded with data in belongsTo() - */ - QList m_groupMaps; - - /** "instuctions" how to create an item in the tree. - * This is used by parent( QModelIndex ) - */ - struct ParentCreate - { - int parentCreateIndex; - int row; - }; - mutable QList m_parentCreateList; - /** @returns index of the "instructions" to recreate the parent. Will create new if it doesn't exist yet. - */ - int indexOfParentCreate( const QModelIndex &parent ) const; - - QModelIndexList m_selectedGroups; - - QModelIndex m_rootNode; - int m_groupedColumn; - - /* debug function */ - void dumpGroups() const; - -private: - unsigned int m_flags; - int m_groupedRole; - - int m_aggregateRole; - -}; - -#endif //GROUPINGPROXY_H +/**************************************************************************************** + * Copyright (c) 2007-2010 Bart Cerneels * + * * + * This program 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 2 of the License, or (at your option) any later * version. + ** + * * + * This program 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 * this + *program. If not, see . * + ****************************************************************************************/ + +// Modifications 2013-03-27 to 2013-03-28 by Sebastian Herbord + +#ifndef GROUPINGPROXY_H +#define GROUPINGPROXY_H + +#include +#include +#include +#include +#include +#include + +typedef QMap ItemData; +typedef QMap RowData; + +class QtGroupingProxy : public QAbstractProxyModel +{ + Q_OBJECT + +public: + static const unsigned int FLAG_NOSINGLE = 1; + static const unsigned int FLAG_NOGROUPNAME = 2; + + enum EAggregateFunction + { + AGGR_NONE, // no aggregation, return child elements as list + AGGR_EMPTY, // display nothing + AGGR_FIRST, // return value of the topmost item + AGGR_MAX, // return maximum value + AGGR_MIN // return minimum value + }; + +public: + explicit QtGroupingProxy(QModelIndex rootNode = QModelIndex(), int groupedColumn = -1, + int groupedRole = Qt::DisplayRole, unsigned int flags = 0, + int aggregateRole = Qt::DisplayRole); + ~QtGroupingProxy(); + + void setSourceModel(QAbstractItemModel* model) override; + void setGroupedColumn(int groupedColumn); + + /* QAbstractProxyModel methods */ + virtual QModelIndex index(int, int c = 0, + const QModelIndex& parent = QModelIndex()) const; + virtual Qt::ItemFlags flags(const QModelIndex& idx) const; + virtual QModelIndex parent(const QModelIndex& idx) const; + virtual int rowCount(const QModelIndex& idx = QModelIndex()) const; + virtual int columnCount(const QModelIndex& idx) const; + virtual QModelIndex mapToSource(const QModelIndex& idx) const; + virtual QModelIndexList mapToSource(const QModelIndexList& list) const; + virtual QModelIndex mapFromSource(const QModelIndex& idx) const; + virtual QVariant data(const QModelIndex& idx, int role) const; + virtual bool setData(const QModelIndex& index, const QVariant& value, + int role = Qt::EditRole); + virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const; + virtual bool canFetchMore(const QModelIndex& parent) const; + virtual void fetchMore(const QModelIndex& parent); + virtual bool hasChildren(const QModelIndex& parent = QModelIndex()) const; + virtual bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, + int column, const QModelIndex& parent); + + /* QtGroupingProxy methods */ + virtual QModelIndex addEmptyGroup(const RowData& data); + virtual bool removeGroup(const QModelIndex& idx); + +protected slots: + virtual void buildTree(); + +private slots: + void modelDataChanged(const QModelIndex&, const QModelIndex&); + void modelRowsAboutToBeInserted(const QModelIndex&, int, int); + void modelRowsInserted(const QModelIndex&, int, int); + void modelRowsAboutToBeRemoved(const QModelIndex&, int, int); + void modelRowsRemoved(const QModelIndex&, int, int); + void resetModel(); + +protected: + /** Maps an item to a group. + * The return value is a list because an item can put in multiple groups. + * Inside the list is a 2 dimensional map. + * Mapped to column-number is another map of role-number to QVariant. + * This data prepolulates the group-data cache. The rest is gathered on demand + * from the children of the group. + */ + virtual QList belongsTo(const QModelIndex& idx); + + /** + * calls belongsTo(), checks cached data and adds the index to existing or new groups. + * @returns the groups this index was added to where -1 means it was added to the + * root. + */ + QList addSourceRow(const QModelIndex& idx); + + bool isGroup(const QModelIndex& index) const; + bool isAGroupSelected(const QModelIndexList& list) const; + + /** Maintains the group -> sourcemodel row mapping + * The reason a QList is use instead of a QMultiHash is that the values have to + * be reordered when rows are inserted or removed. + * TODO:use some auto-incrementing container class (steveire's?) for the list + */ + QMap> m_groupMap; + /** The data cache of the groups. + * This can be pre-loaded with data in belongsTo() + */ + QList m_groupMaps; + + /** "instuctions" how to create an item in the tree. + * This is used by parent( QModelIndex ) + */ + struct ParentCreate + { + int parentCreateIndex; + int row; + }; + mutable QList m_parentCreateList; + /** @returns index of the "instructions" to recreate the parent. Will create new if it + * doesn't exist yet. + */ + int indexOfParentCreate(const QModelIndex& parent) const; + + QModelIndexList m_selectedGroups; + + QModelIndex m_rootNode; + int m_groupedColumn; + + /* debug function */ + void dumpGroups() const; + +private: + unsigned int m_flags; + int m_groupedRole; + + int m_aggregateRole; +}; + +#endif // GROUPINGPROXY_H diff --git a/src/queryoverwritedialog.cpp b/src/queryoverwritedialog.cpp index eb67719dc..2f1a24d5a 100644 --- a/src/queryoverwritedialog.cpp +++ b/src/queryoverwritedialog.cpp @@ -1,67 +1,65 @@ -/* -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 "queryoverwritedialog.h" -#include "ui_queryoverwritedialog.h" - -#include - -QueryOverwriteDialog::QueryOverwriteDialog(QWidget *parent, Backup b) - : QDialog(parent), ui(new Ui::QueryOverwriteDialog), - m_Action(ACT_NONE) -{ - ui->setupUi(this); - ui->backupBox->setChecked(b == BACKUP_YES); - QIcon icon = QApplication::style()->standardIcon(QStyle::SP_MessageBoxQuestion); - ui->iconLabel->setPixmap(icon.pixmap(128)); - -} - -QueryOverwriteDialog::~QueryOverwriteDialog() -{ - delete ui; -} - -bool QueryOverwriteDialog::backup() const -{ - return ui->backupBox->isChecked(); -} - -void QueryOverwriteDialog::on_mergeBtn_clicked() -{ - this->m_Action = ACT_MERGE; - this->accept(); -} - -void QueryOverwriteDialog::on_replaceBtn_clicked() -{ - this->m_Action = ACT_REPLACE; - this->accept(); -} - -void QueryOverwriteDialog::on_renameBtn_clicked() -{ - this->m_Action = ACT_RENAME; - this->accept(); -} - -void QueryOverwriteDialog::on_cancelBtn_clicked() -{ - this->reject(); -} +/* +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 "queryoverwritedialog.h" +#include "ui_queryoverwritedialog.h" + +#include + +QueryOverwriteDialog::QueryOverwriteDialog(QWidget* parent, Backup b) + : QDialog(parent), ui(new Ui::QueryOverwriteDialog), m_Action(ACT_NONE) +{ + ui->setupUi(this); + ui->backupBox->setChecked(b == BACKUP_YES); + QIcon icon = QApplication::style()->standardIcon(QStyle::SP_MessageBoxQuestion); + ui->iconLabel->setPixmap(icon.pixmap(128)); +} + +QueryOverwriteDialog::~QueryOverwriteDialog() +{ + delete ui; +} + +bool QueryOverwriteDialog::backup() const +{ + return ui->backupBox->isChecked(); +} + +void QueryOverwriteDialog::on_mergeBtn_clicked() +{ + this->m_Action = ACT_MERGE; + this->accept(); +} + +void QueryOverwriteDialog::on_replaceBtn_clicked() +{ + this->m_Action = ACT_REPLACE; + this->accept(); +} + +void QueryOverwriteDialog::on_renameBtn_clicked() +{ + this->m_Action = ACT_RENAME; + this->accept(); +} + +void QueryOverwriteDialog::on_cancelBtn_clicked() +{ + this->reject(); +} diff --git a/src/queryoverwritedialog.h b/src/queryoverwritedialog.h index 3f7b31940..a8769ea80 100644 --- a/src/queryoverwritedialog.h +++ b/src/queryoverwritedialog.h @@ -1,61 +1,64 @@ -/* -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 QUERYOVERWRITEDIALOG_H -#define QUERYOVERWRITEDIALOG_H - -#include - -namespace Ui { -class QueryOverwriteDialog; -} - -class QueryOverwriteDialog : public QDialog -{ - Q_OBJECT -public: - enum Action { - ACT_NONE, - ACT_MERGE, - ACT_REPLACE, - ACT_RENAME - }; - - enum Backup { - BACKUP_NO, - BACKUP_YES - }; - -public: - QueryOverwriteDialog(QWidget *parent, Backup b); - ~QueryOverwriteDialog(); - bool backup() const; - Action action() const { return m_Action; } -private slots: - void on_mergeBtn_clicked(); - void on_replaceBtn_clicked(); - void on_renameBtn_clicked(); - void on_cancelBtn_clicked(); - -private: - Ui::QueryOverwriteDialog *ui; - Action m_Action; -}; - -#endif // QUERYOVERWRITEDIALOG_H +/* +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 QUERYOVERWRITEDIALOG_H +#define QUERYOVERWRITEDIALOG_H + +#include + +namespace Ui +{ +class QueryOverwriteDialog; +} + +class QueryOverwriteDialog : public QDialog +{ + Q_OBJECT +public: + enum Action + { + ACT_NONE, + ACT_MERGE, + ACT_REPLACE, + ACT_RENAME + }; + + enum Backup + { + BACKUP_NO, + BACKUP_YES + }; + +public: + QueryOverwriteDialog(QWidget* parent, Backup b); + ~QueryOverwriteDialog(); + bool backup() const; + Action action() const { return m_Action; } +private slots: + void on_mergeBtn_clicked(); + void on_replaceBtn_clicked(); + void on_renameBtn_clicked(); + void on_cancelBtn_clicked(); + +private: + Ui::QueryOverwriteDialog* ui; + Action m_Action; +}; + +#endif // QUERYOVERWRITEDIALOG_H diff --git a/src/sanitychecks.cpp b/src/sanitychecks.cpp index 2f7126ead..4aa5dc7c9 100644 --- a/src/sanitychecks.cpp +++ b/src/sanitychecks.cpp @@ -13,51 +13,54 @@ using namespace MOBase; enum class SecurityZone { - NoZone = -1, + NoZone = -1, MyComputer = 0, - Intranet = 1, - Trusted = 2, - Internet = 3, - Untrusted = 4, + Intranet = 1, + Trusted = 2, + Internet = 3, + Untrusted = 4, }; QString toCodeName(SecurityZone z) { - switch (z) - { - case SecurityZone::NoZone: return "NoZone"; - case SecurityZone::MyComputer: return "MyComputer"; - case SecurityZone::Intranet: return "Intranet"; - case SecurityZone::Trusted: return "Trusted"; - case SecurityZone::Internet: return "Internet"; - case SecurityZone::Untrusted: return "Untrusted"; - default: return "Unknown zone"; + switch (z) { + case SecurityZone::NoZone: + return "NoZone"; + case SecurityZone::MyComputer: + return "MyComputer"; + case SecurityZone::Intranet: + return "Intranet"; + case SecurityZone::Trusted: + return "Trusted"; + case SecurityZone::Internet: + return "Internet"; + case SecurityZone::Untrusted: + return "Untrusted"; + default: + return "Unknown zone"; } } QString toString(SecurityZone z) { - return QString("%1 (%2)") - .arg(toCodeName(z)) - .arg(static_cast(z)); + return QString("%1 (%2)").arg(toCodeName(z)).arg(static_cast(z)); } // whether the given zone is considered blocked // bool isZoneBlocked(SecurityZone z) { - switch (z) - { - case SecurityZone::Internet: - case SecurityZone::Untrusted: - return true; - - case SecurityZone::NoZone: - case SecurityZone::MyComputer: - case SecurityZone::Intranet: - case SecurityZone::Trusted: - default: - return false; + switch (z) { + case SecurityZone::Internet: + case SecurityZone::Untrusted: + return true; + + case SecurityZone::NoZone: + case SecurityZone::MyComputer: + case SecurityZone::Intranet: + case SecurityZone::Trusted: + default: + return false; } } @@ -72,7 +75,7 @@ bool isFileBlocked(const QFileInfo& fi) const auto key = "ZoneTransfer/ZoneId"; // the path to the ADS is always `filename:Zone.Identifier` - const auto path = fi.absoluteFilePath(); + const auto path = fi.absoluteFilePath(); const auto adsPath = path + ":" + ads; QFile f(adsPath); @@ -99,7 +102,7 @@ bool isFileBlocked(const QFileInfo& fi) } // should be an int - bool ok = false; + bool ok = false; const auto z = static_cast(v.toInt(&ok)); if (!ok) { @@ -114,10 +117,7 @@ bool isFileBlocked(const QFileInfo& fi) } // file is blocked - log::warn("{}", QObject::tr( - "'%1': file is blocked (%2)") - .arg(path) - .arg(toString(z))); + log::warn("{}", QObject::tr("'%1': file is blocked (%2)").arg(path).arg(toString(z))); return true; } @@ -129,9 +129,8 @@ int checkBlockedFiles(const QDir& dir) if (!dir.exists()) { // shouldn't happen - log::error( - "while checking for blocked files, directory '{}' not found", - dir.absolutePath()); + log::error("while checking for blocked files, directory '{}' not found", + dir.absolutePath()); return 1; } @@ -139,9 +138,8 @@ int checkBlockedFiles(const QDir& dir) const auto files = dir.entryInfoList(FileTypes, QDir::Files); if (files.empty()) { // shouldn't happen - log::error( - "while checking for blocked files, directory '{}' is empty", - dir.absolutePath()); + log::error("while checking for blocked files, directory '{}' is empty", + dir.absolutePath()); return 1; } @@ -162,14 +160,7 @@ int checkBlocked() { // directories that contain executables; these need to be explicit because // portable instances might add billions of files in MO's directory - const QString dirs[] = { - ".", - "/dlls", - "/loot", - "/NCC", - "/platforms", - "/plugins" - }; + const QString dirs[] = {".", "/dlls", "/loot", "/NCC", "/platforms", "/plugins"}; log::debug(" . blocked files"); const QString appDir = QCoreApplication::applicationDirPath(); @@ -187,16 +178,9 @@ int checkBlocked() int checkMissingFiles() { // files that are likely to be eaten - static const QStringList files({ - "helper.exe", - "nxmhandler.exe", - "usvfs_proxy_x64.exe", - "usvfs_proxy_x86.exe", - "usvfs_x64.dll", - "usvfs_x86.dll", - "loot/loot.dll", - "loot/lootcli.exe" - }); + static const QStringList files( + {"helper.exe", "nxmhandler.exe", "usvfs_proxy_x64.exe", "usvfs_proxy_x86.exe", + "usvfs_x64.dll", "usvfs_x86.dll", "loot/loot.dll", "loot/lootcli.exe"}); log::debug(" . missing files"); const auto dir = QCoreApplication::applicationDirPath(); @@ -208,8 +192,8 @@ int checkMissingFiles() if (!file.exists()) { log::warn("{}", QObject::tr( - "'%1' seems to be missing, an antivirus may have deleted it") - .arg(file.absoluteFilePath())); + "'%1' seems to be missing, an antivirus may have deleted it") + .arg(file.absoluteFilePath())); ++n; } @@ -228,29 +212,27 @@ int checkBadOSDs(const env::Module& m) // where they got loaded later, so this is also called every time a new module // is loaded into this process - const std::string nahimic = - "Nahimic (also known as SonicSuite, SonicRadar, " - "SteelSeries, A-Volute, etc.)"; + const std::string nahimic = "Nahimic (also known as SonicSuite, SonicRadar, " + "SteelSeries, A-Volute, etc.)"; auto p = [](std::string re, std::string s) { return std::make_pair(std::regex(re, std::regex::icase), s); }; static const std::vector> list = { - p("nahimic(.*)osd\\.dll", nahimic), - p("cassini(.*)osd\\.dll", nahimic), - p(".+devprops.*.dll", nahimic), - p("ss2osd\\.dll", nahimic), - p("RTSSHooks64\\.dll", "RivaTuner Statistics Server"), - p("SSAudioOSD\\.dll", "SteelSeries Audio"), - p("specialk64\\.dll", "SpecialK"), - p("corsairosdhook\\.x64\\.dll", "Corsair Utility Engine"), - p("gtii-osd64-vk\\.dll", "ASUS GPU Tweak 2"), - p("easyhook64\\.dll", "Razer Cortex"), - p("k_fps64\\.dll", "Razer Cortex"), - p("fw1fontwrapper\\.dll", "Gigabyte 3D OSD"), - p("gfxhook64\\.dll", "Gigabyte 3D OSD") - }; + p("nahimic(.*)osd\\.dll", nahimic), + p("cassini(.*)osd\\.dll", nahimic), + p(".+devprops.*.dll", nahimic), + p("ss2osd\\.dll", nahimic), + p("RTSSHooks64\\.dll", "RivaTuner Statistics Server"), + p("SSAudioOSD\\.dll", "SteelSeries Audio"), + p("specialk64\\.dll", "SpecialK"), + p("corsairosdhook\\.x64\\.dll", "Corsair Utility Engine"), + p("gtii-osd64-vk\\.dll", "ASUS GPU Tweak 2"), + p("easyhook64\\.dll", "Razer Cortex"), + p("k_fps64\\.dll", "Razer Cortex"), + p("fw1fontwrapper\\.dll", "Gigabyte 3D OSD"), + p("gfxhook64\\.dll", "Gigabyte 3D OSD")}; const QFileInfo file(m.path()); int n = 0; @@ -261,10 +243,10 @@ int checkBadOSDs(const env::Module& m) if (std::regex_match(filename, m, p.first)) { log::warn("{}", QObject::tr( - "%1 is loaded.\nThis program is known to cause issues with " - "Mod Organizer, such as freezing or blank windows. Consider " - "uninstalling it.") - .arg(QString::fromStdString(p.second))); + "%1 is loaded.\nThis program is known to cause issues with " + "Mod Organizer, such as freezing or blank windows. Consider " + "uninstalling it.") + .arg(QString::fromStdString(p.second))); log::warn("{}", file.absoluteFilePath()); ++n; @@ -279,20 +261,19 @@ int checkUsvfsIncompatibilites(const env::Module& m) // these dlls seems to interfere with usvfs static const std::map names = { - {"mactype64.dll", "Mactype"}, - {"epclient64.dll", "Citrix ICA Client"} - }; + {"mactype64.dll", "Mactype"}, {"epclient64.dll", "Citrix ICA Client"}}; const QFileInfo file(m.path()); int n = 0; for (auto&& p : names) { if (file.fileName().compare(p.first, Qt::CaseInsensitive) == 0) { - log::warn("{}", QObject::tr( - "%1 is loaded. This program is known to cause issues with " - "Mod Organizer and its virtual filesystem, such script extenders " - "or others programs refusing to run. Consider uninstalling it.") - .arg(p.second)); + log::warn( + "{}", + QObject::tr("%1 is loaded. This program is known to cause issues with " + "Mod Organizer and its virtual filesystem, such script extenders " + "or others programs refusing to run. Consider uninstalling it.") + .arg(p.second)); log::warn("{}", file.absoluteFilePath()); @@ -330,13 +311,12 @@ std::vector> getSystemDirectories() { // folder ids and display names for logging const std::vector> systemFolderIDs = { - {FOLDERID_ProgramFiles, "in Program Files"}, - {FOLDERID_ProgramFilesX86, "in Program Files"}, - {FOLDERID_Desktop, "on the desktop"}, - {FOLDERID_OneDrive, "in OneDrive"}, - {FOLDERID_Documents, "in Documents"}, - {FOLDERID_Downloads, "in Downloads"} - }; + {FOLDERID_ProgramFiles, "in Program Files"}, + {FOLDERID_ProgramFilesX86, "in Program Files"}, + {FOLDERID_Desktop, "on the desktop"}, + {FOLDERID_OneDrive, "in OneDrive"}, + {FOLDERID_Documents, "in Documents"}, + {FOLDERID_Downloads, "in Downloads"}}; std::vector> systemDirs; @@ -366,10 +346,9 @@ int checkProtected(const QDir& d, const QString& what) for (auto&& sd : systemDirs) { if (path.startsWith(sd.first)) { - log::warn( - "{} is {}; this may cause issues because it's a special " - "system folder", - what, sd.second); + log::warn("{} is {}; this may cause issues because it's a special " + "system folder", + what, sd.second); log::debug("path '{}' starts with '{}'", path, sd.first); @@ -383,12 +362,11 @@ int checkProtected(const QDir& d, const QString& what) int checkMicrosoftStore(const QDir& gameDir) { const QStringList pathsToCheck = { - "/ModifiableWindowsApps/", - "/WindowsApps/", + "/ModifiableWindowsApps/", + "/WindowsApps/", }; for (auto badPath : pathsToCheck) { - if (gameDir.path().contains(badPath)) - { + if (gameDir.path().contains(badPath)) { log::error("This game is not supported by Mod Organizer."); log::error("Games installed through the Microsoft Store will not work properly."); return 1; @@ -431,9 +409,8 @@ void checkEnvironment(const env::Environment& e) n += checkMissingFiles(); n += checkIncompatibilities(e); - log::debug( - "sanity checks done, {}", - (n > 0 ? "problems were found" : "everything looks okay")); + log::debug("sanity checks done, {}", + (n > 0 ? "problems were found" : "everything looks okay")); } -} // namespace +} // namespace sanity diff --git a/src/sanitychecks.h b/src/sanitychecks.h index 8786ee78e..bfafa015a 100644 --- a/src/sanitychecks.h +++ b/src/sanitychecks.h @@ -3,18 +3,17 @@ namespace env { - class Environment; - class Module; -} +class Environment; +class Module; +} // namespace env namespace MOBase { - class IPluginGame; +class IPluginGame; } class Settings; - namespace sanity { @@ -22,6 +21,6 @@ void checkEnvironment(const env::Environment& env); int checkIncompatibleModule(const env::Module& m); int checkPaths(MOBase::IPluginGame& game, const Settings& s); -} // namespace +} // namespace sanity #endif // MODORGANIZER_SANITYCHECKS_INCLUDED diff --git a/src/savestab.cpp b/src/savestab.cpp index 5b0b5275d..c6ab645f3 100644 --- a/src/savestab.cpp +++ b/src/savestab.cpp @@ -1,15 +1,16 @@ #include "savestab.h" -#include "ui_mainwindow.h" -#include "organizercore.h" #include "activatemodsdialog.h" +#include "organizercore.h" +#include "ui_mainwindow.h" #include #include using namespace MOBase; SavesTab::SavesTab(QWidget* window, OrganizerCore& core, Ui::MainWindow* mwui) - : m_window(window), m_core(core), m_CurrentSaveView(nullptr), ui{ - mwui->tabWidget, mwui->savesTab, mwui->savegameList} + : m_window(window), m_core(core), + m_CurrentSaveView(nullptr), ui{mwui->tabWidget, mwui->savesTab, + mwui->savegameList} { m_SavesWatcherTimer.setSingleShot(true); m_SavesWatcherTimer.setInterval(500); @@ -17,21 +18,21 @@ SavesTab::SavesTab(QWidget* window, OrganizerCore& core, Ui::MainWindow* mwui) ui.list->installEventFilter(this); ui.list->setMouseTracking(true); - connect( - &m_SavesWatcher, &QFileSystemWatcher::directoryChanged, - [&]{ m_SavesWatcherTimer.start(); }); + connect(&m_SavesWatcher, &QFileSystemWatcher::directoryChanged, [&] { + m_SavesWatcherTimer.start(); + }); - connect( - &m_SavesWatcherTimer, &QTimer::timeout, - [&]{ refreshSavesIfOpen(); }); + connect(&m_SavesWatcherTimer, &QTimer::timeout, [&] { + refreshSavesIfOpen(); + }); - connect( - ui.list, &QWidget::customContextMenuRequested, - [&](auto pos){ onContextMenu(pos); }); + connect(ui.list, &QWidget::customContextMenuRequested, [&](auto pos) { + onContextMenu(pos); + }); - connect( - ui.list, &QTreeWidget::itemEntered, - [&](auto* item){ saveSelectionChanged(item); }); + connect(ui.list, &QTreeWidget::itemEntered, [&](auto* item) { + saveSelectionChanged(item); + }); } bool SavesTab::eventFilter(QObject* object, QEvent* e) @@ -40,7 +41,7 @@ bool SavesTab::eventFilter(QObject* object, QEvent* e) if (e->type() == QEvent::Leave || e->type() == QEvent::WindowDeactivate) { hideSaveGameInfo(); } else if (e->type() == QEvent::KeyPress) { - QKeyEvent *keyEvent = static_cast(e); + QKeyEvent* keyEvent = static_cast(e); if (keyEvent->key() == Qt::Key_Delete) { deleteSavegame(); } @@ -50,19 +51,19 @@ bool SavesTab::eventFilter(QObject* object, QEvent* e) return false; } -void SavesTab::displaySaveGameInfo(QTreeWidgetItem *newItem) +void SavesTab::displaySaveGameInfo(QTreeWidgetItem* newItem) { // don't display the widget if the main window doesn't have focus // // this goes against the standard behaviour for tooltips, which are displayed // on hover regardless of focus, but this widget is so large and busy that // it's probably better this way - if (!m_window->isActiveWindow()){ + if (!m_window->isActiveWindow()) { return; } if (m_CurrentSaveView == nullptr) { - const IPluginGame* game = m_core.managedGame(); + const IPluginGame* game = m_core.managedGame(); const SaveGameInfo* info = game->feature(); if (info != nullptr) { @@ -76,7 +77,7 @@ void SavesTab::displaySaveGameInfo(QTreeWidgetItem *newItem) m_CurrentSaveView->setSave(*m_SaveGames[ui.list->indexOfTopLevelItem(newItem)]); - QWindow *window = m_CurrentSaveView->window()->windowHandle(); + QWindow* window = m_CurrentSaveView->window()->windowHandle(); QRect screenRect; if (window == nullptr) screenRect = QGuiApplication::primaryScreen()->geometry(); @@ -98,20 +99,20 @@ void SavesTab::displaySaveGameInfo(QTreeWidgetItem *newItem) m_CurrentSaveView->move(pos); m_CurrentSaveView->show(); - m_CurrentSaveView->setProperty("displayItem", QVariant::fromValue(static_cast(newItem))); + m_CurrentSaveView->setProperty("displayItem", + QVariant::fromValue(static_cast(newItem))); } - -void SavesTab::saveSelectionChanged(QTreeWidgetItem *newItem) +void SavesTab::saveSelectionChanged(QTreeWidgetItem* newItem) { if (newItem == nullptr) { hideSaveGameInfo(); - } else if (m_CurrentSaveView == nullptr || newItem != m_CurrentSaveView->property("displayItem").value()) { + } else if (m_CurrentSaveView == nullptr || + newItem != m_CurrentSaveView->property("displayItem").value()) { displaySaveGameInfo(newItem); } } - void SavesTab::hideSaveGameInfo() { if (m_CurrentSaveView != nullptr) { @@ -142,14 +143,11 @@ QDir SavesTab::currentSavesDir() const QString iniPath = m_core.currentProfile()->absoluteIniFilePath(iniFiles[0]); wchar_t path[MAX_PATH]; - if (::GetPrivateProfileStringW( - L"General", L"SLocalSavePath", L"", - path, MAX_PATH, - iniPath.toStdWString().c_str() - )) { - savesDir.setPath(m_core.managedGame()->documentsDirectory().absoluteFilePath(QString::fromWCharArray(path))); - } - else { + if (::GetPrivateProfileStringW(L"General", L"SLocalSavePath", L"", path, MAX_PATH, + iniPath.toStdWString().c_str())) { + savesDir.setPath(m_core.managedGame()->documentsDirectory().absoluteFilePath( + QString::fromWCharArray(path))); + } else { savesDir = m_core.managedGame()->savesDirectory(); } } @@ -177,26 +175,24 @@ void SavesTab::refreshSaveList() { TimeThis tt("MainWindow::refreshSaveList()"); - startMonitorSaves(); // re-starts monitoring + startMonitorSaves(); // re-starts monitoring - try - { + try { QDir savesDir = currentSavesDir(); MOBase::log::debug("reading save games from {}", savesDir.absolutePath()); m_SaveGames = m_core.managedGame()->listSaves(savesDir); - std::sort(m_SaveGames.begin(), m_SaveGames.end(), [](auto const& lhs, auto const& rhs) { - return lhs->getCreationTime() > rhs->getCreationTime(); - }); + std::sort(m_SaveGames.begin(), m_SaveGames.end(), + [](auto const& lhs, auto const& rhs) { + return lhs->getCreationTime() > rhs->getCreationTime(); + }); ui.list->clear(); - for (auto& save: m_SaveGames) { + for (auto& save : m_SaveGames) { auto relpath = savesDir.relativeFilePath(save->getFilepath()); auto display = save->getName(); - ui.list->addTopLevelItem(new QTreeWidgetItem(ui.list, { display, relpath })); + ui.list->addTopLevelItem(new QTreeWidgetItem(ui.list, {display, relpath})); } - } - catch(std::exception& e) - { + } catch (std::exception& e) { // listSaves() can throw log::error("{}", e.what()); } @@ -204,19 +200,20 @@ void SavesTab::refreshSaveList() void SavesTab::deleteSavegame() { - SaveGameInfo const *info = m_core.managedGame()->feature(); + SaveGameInfo const* info = m_core.managedGame()->feature(); QString savesMsgLabel; QStringList deleteFiles; int count = 0; - for (const QModelIndex &idx : ui.list->selectionModel()->selectedRows()) { + for (const QModelIndex& idx : ui.list->selectionModel()->selectedRows()) { auto& saveGame = m_SaveGames[idx.row()]; if (count < 10) { - savesMsgLabel += "
            • " + QFileInfo(saveGame->getFilepath()).completeBaseName() + "
            • "; + savesMsgLabel += + "
            • " + QFileInfo(saveGame->getFilepath()).completeBaseName() + "
            • "; } ++count; @@ -227,18 +224,19 @@ void SavesTab::deleteSavegame() savesMsgLabel += "
            • ... " + tr("%1 more").arg(count - 10) + "
            • "; } - if (QMessageBox::question(m_window, tr("Confirm"), - tr("Are you sure you want to remove the following %n save(s)?
              " - "
                %1

              " - "Removed saves will be sent to the Recycle Bin.", "", count) - .arg(savesMsgLabel), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - shellDelete(deleteFiles, true); // recycle bin delete. + if (QMessageBox::question( + m_window, tr("Confirm"), + tr("Are you sure you want to remove the following %n save(s)?
              " + "
                %1

              " + "Removed saves will be sent to the Recycle Bin.", + "", count) + .arg(savesMsgLabel), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + shellDelete(deleteFiles, true); // recycle bin delete. refreshSaveList(); } } - void SavesTab::onContextMenu(const QPoint& pos) { QItemSelectionModel* selection = ui.list->selectionModel(); @@ -257,27 +255,35 @@ void SavesTab::onContextMenu(const QPoint& pos) auto& save = m_SaveGames[selection->selectedRows()[0].row()]; SaveGameInfo::MissingAssets missing = info->getMissingAssets(*save); if (missing.size() != 0) { - connect(action, &QAction::triggered, this, [this, missing] { fixMods(missing); }); + connect(action, &QAction::triggered, this, [this, missing] { + fixMods(missing); + }); action->setEnabled(true); } } } - QString deleteMenuLabel = tr("Delete %n save(s)", "", selection->selectedRows().count()); - menu.addAction(deleteMenuLabel, [&]{ deleteSavegame(); }); + QString deleteMenuLabel = + tr("Delete %n save(s)", "", selection->selectedRows().count()); + menu.addAction(deleteMenuLabel, [&] { + deleteSavegame(); + }); - menu.addAction(tr("Open in Explorer..."), [&]{ openInExplorer(); }); + menu.addAction(tr("Open in Explorer..."), [&] { + openInExplorer(); + }); menu.exec(ui.list->viewport()->mapToGlobal(pos)); } -void SavesTab::fixMods(SaveGameInfo::MissingAssets const &missingAssets) +void SavesTab::fixMods(SaveGameInfo::MissingAssets const& missingAssets) { ActivateModsDialog dialog(missingAssets, m_window); if (dialog.exec() == QDialog::Accepted) { // activate the required mods, then enable all esps std::set modsToActivate = dialog.getModsToActivate(); - for (std::set::iterator iter = modsToActivate.begin(); iter != modsToActivate.end(); ++iter) { + for (std::set::iterator iter = modsToActivate.begin(); + iter != modsToActivate.end(); ++iter) { if ((*iter != "") && (*iter != "")) { unsigned int modIndex = ModInfo::getIndex(*iter); m_core.currentProfile()->setModEnabled(modIndex, true); @@ -288,7 +294,8 @@ void SavesTab::fixMods(SaveGameInfo::MissingAssets const &missingAssets) m_core.refreshLists(); std::set espsToActivate = dialog.getESPsToActivate(); - for (std::set::iterator iter = espsToActivate.begin(); iter != espsToActivate.end(); ++iter) { + for (std::set::iterator iter = espsToActivate.begin(); + iter != espsToActivate.end(); ++iter) { m_core.pluginList()->enableESP(*iter); } diff --git a/src/savestab.h b/src/savestab.h index 5ab8c55b4..a903e4fb5 100644 --- a/src/savestab.h +++ b/src/savestab.h @@ -4,18 +4,20 @@ #include "savegameinfo.h" #include -namespace Ui { class MainWindow; } +namespace Ui +{ +class MainWindow; +} namespace MOBase { - class ISaveGame; - class ISaveGameInfoWidget; -} +class ISaveGame; +class ISaveGameInfoWidget; +} // namespace MOBase class MainWindow; class OrganizerCore; - class SavesTab : public QObject { Q_OBJECT; @@ -24,7 +26,7 @@ class SavesTab : public QObject SavesTab(QWidget* window, OrganizerCore& core, Ui::MainWindow* ui); void refreshSaveList(); - void displaySaveGameInfo(QTreeWidgetItem *newItem); + void displaySaveGameInfo(QTreeWidgetItem* newItem); QDir currentSavesDir() const; @@ -48,17 +50,17 @@ class SavesTab : public QObject SavesTabUi ui; MOBase::FilterWidget m_filter; std::vector> m_SaveGames; - MOBase::ISaveGameInfoWidget *m_CurrentSaveView; + MOBase::ISaveGameInfoWidget* m_CurrentSaveView; QTimer m_SavesWatcherTimer; QFileSystemWatcher m_SavesWatcher; - void onContextMenu(const QPoint &pos); + void onContextMenu(const QPoint& pos); void deleteSavegame(); - void saveSelectionChanged(QTreeWidgetItem *newItem); - void fixMods(SaveGameInfo::MissingAssets const &missingAssets); + void saveSelectionChanged(QTreeWidgetItem* newItem); + void fixMods(SaveGameInfo::MissingAssets const& missingAssets); void refreshSavesIfOpen(); void openInExplorer(); }; -#endif // MODORGANIZER_SAVESTAB_INCLUDED +#endif // MODORGANIZER_SAVESTAB_INCLUDED diff --git a/src/savetextasdialog.cpp b/src/savetextasdialog.cpp index 8f0095d83..9be7fdf23 100644 --- a/src/savetextasdialog.cpp +++ b/src/savetextasdialog.cpp @@ -1,50 +1,49 @@ -#include "savetextasdialog.h" -#include "ui_savetextasdialog.h" -#include -#include -#include - - -using MOBase::reportError; - - -SaveTextAsDialog::SaveTextAsDialog(QWidget *parent) - : QDialog(parent), ui(new Ui::SaveTextAsDialog) -{ - ui->setupUi(this); -} - -SaveTextAsDialog::~SaveTextAsDialog() -{ - delete ui; -} - -void SaveTextAsDialog::setText(const QString &text) -{ - ui->textEdit->setPlainText(text); -} - -void SaveTextAsDialog::on_closeBtn_clicked() -{ - this->close(); -} - -void SaveTextAsDialog::on_clipboardBtn_clicked() -{ - QClipboard *clipboard = QApplication::clipboard(); - clipboard->setText(ui->textEdit->toPlainText()); -} - -void SaveTextAsDialog::on_saveAsBtn_clicked() -{ - QString fileName = QFileDialog::getSaveFileName(this, tr("Save CSV"), QString(), tr("Text Files") + " (*.txt *.csv)"); - if (!fileName.isEmpty()) { - QFile file(fileName); - if (!file.open(QIODevice::WriteOnly)) { - reportError(tr("failed to open \"%1\" for writing").arg(fileName)); - return; - } - - file.write(ui->textEdit->toPlainText().toUtf8()); - } -} +#include "savetextasdialog.h" +#include "ui_savetextasdialog.h" +#include +#include +#include + +using MOBase::reportError; + +SaveTextAsDialog::SaveTextAsDialog(QWidget* parent) + : QDialog(parent), ui(new Ui::SaveTextAsDialog) +{ + ui->setupUi(this); +} + +SaveTextAsDialog::~SaveTextAsDialog() +{ + delete ui; +} + +void SaveTextAsDialog::setText(const QString& text) +{ + ui->textEdit->setPlainText(text); +} + +void SaveTextAsDialog::on_closeBtn_clicked() +{ + this->close(); +} + +void SaveTextAsDialog::on_clipboardBtn_clicked() +{ + QClipboard* clipboard = QApplication::clipboard(); + clipboard->setText(ui->textEdit->toPlainText()); +} + +void SaveTextAsDialog::on_saveAsBtn_clicked() +{ + QString fileName = QFileDialog::getSaveFileName(this, tr("Save CSV"), QString(), + tr("Text Files") + " (*.txt *.csv)"); + if (!fileName.isEmpty()) { + QFile file(fileName); + if (!file.open(QIODevice::WriteOnly)) { + reportError(tr("failed to open \"%1\" for writing").arg(fileName)); + return; + } + + file.write(ui->textEdit->toPlainText().toUtf8()); + } +} diff --git a/src/savetextasdialog.h b/src/savetextasdialog.h index 1a17a2dbc..0d4423fec 100644 --- a/src/savetextasdialog.h +++ b/src/savetextasdialog.h @@ -1,31 +1,32 @@ -#ifndef SAVETEXTASDIALOG_H -#define SAVETEXTASDIALOG_H - -#include - -namespace Ui { -class SaveTextAsDialog; -} - -class SaveTextAsDialog : public QDialog -{ - Q_OBJECT - -public: - explicit SaveTextAsDialog(QWidget *parent = 0); - ~SaveTextAsDialog(); - - void setText(const QString &text); - -private slots: - void on_closeBtn_clicked(); - - void on_clipboardBtn_clicked(); - - void on_saveAsBtn_clicked(); - -private: - Ui::SaveTextAsDialog *ui; -}; - -#endif // SAVETEXTASDIALOG_H +#ifndef SAVETEXTASDIALOG_H +#define SAVETEXTASDIALOG_H + +#include + +namespace Ui +{ +class SaveTextAsDialog; +} + +class SaveTextAsDialog : public QDialog +{ + Q_OBJECT + +public: + explicit SaveTextAsDialog(QWidget* parent = 0); + ~SaveTextAsDialog(); + + void setText(const QString& text); + +private slots: + void on_closeBtn_clicked(); + + void on_clipboardBtn_clicked(); + + void on_saveAsBtn_clicked(); + +private: + Ui::SaveTextAsDialog* ui; +}; + +#endif // SAVETEXTASDIALOG_H diff --git a/src/selectiondialog.cpp b/src/selectiondialog.cpp index 089760f94..ec190ca9c 100644 --- a/src/selectiondialog.cpp +++ b/src/selectiondialog.cpp @@ -1,109 +1,116 @@ -/* -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 "selectiondialog.h" -#include "ui_selectiondialog.h" - -#include - -SelectionDialog::SelectionDialog(const QString &description, QWidget *parent, const QSize &iconSize) - : QDialog(parent), ui(new Ui::SelectionDialog), m_Choice(nullptr), m_ValidateByData(false), m_IconSize(iconSize) -{ - ui->setupUi(this); - - ui->descriptionLabel->setText(description); -} - -SelectionDialog::~SelectionDialog() -{ - delete ui; -} - -void SelectionDialog::addChoice(const QString &buttonText, const QString &description, const QVariant &data) -{ - QAbstractButton *button = new QCommandLinkButton(buttonText, description, ui->buttonBox); - if (m_IconSize.isValid()) { - button->setIconSize(m_IconSize); - } - button->setProperty("data", data); - ui->buttonBox->addButton(button, QDialogButtonBox::AcceptRole); - if (data.isValid()) m_ValidateByData = true; -} - -void SelectionDialog::addChoice(const QIcon &icon, const QString &buttonText, const QString &description, const QVariant &data) -{ - QAbstractButton *button = new QCommandLinkButton(buttonText, description, ui->buttonBox); - if (m_IconSize.isValid()) { - button->setIconSize(m_IconSize); - } - button->setIcon(icon); - button->setProperty("data", data); - ui->buttonBox->addButton(button, QDialogButtonBox::AcceptRole); - if (data.isValid()) m_ValidateByData = true; -} - -int SelectionDialog::numChoices() const -{ - return ui->buttonBox->findChildren(QString()).count(); -} - -QVariant SelectionDialog::getChoiceData() -{ - return m_Choice->property("data"); -} - - -QString SelectionDialog::getChoiceString() -{ - if ((m_Choice == nullptr) || - (m_ValidateByData && !m_Choice->property("data").isValid())) { - return QString(); - } else { - return m_Choice->text(); - } -} - -QString SelectionDialog::getChoiceDescription() -{ - if (m_Choice == nullptr) - return QString(); - else - return m_Choice->accessibleDescription(); -} - -void SelectionDialog::disableCancel() -{ - ui->cancelButton->setEnabled(false); - ui->cancelButton->setHidden(true); -} - -void SelectionDialog::on_buttonBox_clicked(QAbstractButton *button) -{ - m_Choice = button; - if (!m_ValidateByData || m_Choice->property("data").isValid()) { - this->accept(); - } else { - this->reject(); - } -} - -void SelectionDialog::on_cancelButton_clicked() -{ - this->reject(); -} +/* +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 "selectiondialog.h" +#include "ui_selectiondialog.h" + +#include + +SelectionDialog::SelectionDialog(const QString& description, QWidget* parent, + const QSize& iconSize) + : QDialog(parent), ui(new Ui::SelectionDialog), m_Choice(nullptr), + m_ValidateByData(false), m_IconSize(iconSize) +{ + ui->setupUi(this); + + ui->descriptionLabel->setText(description); +} + +SelectionDialog::~SelectionDialog() +{ + delete ui; +} + +void SelectionDialog::addChoice(const QString& buttonText, const QString& description, + const QVariant& data) +{ + QAbstractButton* button = + new QCommandLinkButton(buttonText, description, ui->buttonBox); + if (m_IconSize.isValid()) { + button->setIconSize(m_IconSize); + } + button->setProperty("data", data); + ui->buttonBox->addButton(button, QDialogButtonBox::AcceptRole); + if (data.isValid()) + m_ValidateByData = true; +} + +void SelectionDialog::addChoice(const QIcon& icon, const QString& buttonText, + const QString& description, const QVariant& data) +{ + QAbstractButton* button = + new QCommandLinkButton(buttonText, description, ui->buttonBox); + if (m_IconSize.isValid()) { + button->setIconSize(m_IconSize); + } + button->setIcon(icon); + button->setProperty("data", data); + ui->buttonBox->addButton(button, QDialogButtonBox::AcceptRole); + if (data.isValid()) + m_ValidateByData = true; +} + +int SelectionDialog::numChoices() const +{ + return ui->buttonBox->findChildren(QString()).count(); +} + +QVariant SelectionDialog::getChoiceData() +{ + return m_Choice->property("data"); +} + +QString SelectionDialog::getChoiceString() +{ + if ((m_Choice == nullptr) || + (m_ValidateByData && !m_Choice->property("data").isValid())) { + return QString(); + } else { + return m_Choice->text(); + } +} + +QString SelectionDialog::getChoiceDescription() +{ + if (m_Choice == nullptr) + return QString(); + else + return m_Choice->accessibleDescription(); +} + +void SelectionDialog::disableCancel() +{ + ui->cancelButton->setEnabled(false); + ui->cancelButton->setHidden(true); +} + +void SelectionDialog::on_buttonBox_clicked(QAbstractButton* button) +{ + m_Choice = button; + if (!m_ValidateByData || m_Choice->property("data").isValid()) { + this->accept(); + } else { + this->reject(); + } +} + +void SelectionDialog::on_cancelButton_clicked() +{ + this->reject(); +} diff --git a/src/selectiondialog.h b/src/selectiondialog.h index 5a70aa5e5..8c333d3d5 100644 --- a/src/selectiondialog.h +++ b/src/selectiondialog.h @@ -1,74 +1,77 @@ -/* -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 SELECTIONDIALOG_H -#define SELECTIONDIALOG_H - -#include -#include - -namespace Ui { -class SelectionDialog; -} - -class SelectionDialog : public QDialog -{ - Q_OBJECT - -public: - - explicit SelectionDialog(const QString &description, QWidget *parent = 0, const QSize &iconSize = QSize()); - - ~SelectionDialog(); - - /** - * @brief add a choice to the dialog - * @param buttonText the text to be displayed on the button - * @param description the description that shows up under in small letters inside the button - * @param data data to be stored with the button. Please note that as soon as one choice has data associated with it (non-invalid QVariant) - * all buttons that contain no data will be treated as "cancel" buttons - */ - void addChoice(const QString &buttonText, const QString &description, const QVariant &data); - - void addChoice(const QIcon &icon, const QString &buttonText, const QString &description, const QVariant &data); - - int numChoices() const; - - QVariant getChoiceData(); - QString getChoiceString(); - QString getChoiceDescription(); - - void disableCancel(); - -private slots: - - void on_buttonBox_clicked(QAbstractButton *button); - - void on_cancelButton_clicked(); - -private: - - Ui::SelectionDialog *ui; - QAbstractButton *m_Choice; - bool m_ValidateByData; - QSize m_IconSize; - -}; - -#endif // SELECTIONDIALOG_H +/* +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 SELECTIONDIALOG_H +#define SELECTIONDIALOG_H + +#include +#include + +namespace Ui +{ +class SelectionDialog; +} + +class SelectionDialog : public QDialog +{ + Q_OBJECT + +public: + explicit SelectionDialog(const QString& description, QWidget* parent = 0, + const QSize& iconSize = QSize()); + + ~SelectionDialog(); + + /** + * @brief add a choice to the dialog + * @param buttonText the text to be displayed on the button + * @param description the description that shows up under in small letters inside the + * button + * @param data data to be stored with the button. Please note that as soon as one + * choice has data associated with it (non-invalid QVariant) all buttons that contain + * no data will be treated as "cancel" buttons + */ + void addChoice(const QString& buttonText, const QString& description, + const QVariant& data); + + void addChoice(const QIcon& icon, const QString& buttonText, + const QString& description, const QVariant& data); + + int numChoices() const; + + QVariant getChoiceData(); + QString getChoiceString(); + QString getChoiceDescription(); + + void disableCancel(); + +private slots: + + void on_buttonBox_clicked(QAbstractButton* button); + + void on_cancelButton_clicked(); + +private: + Ui::SelectionDialog* ui; + QAbstractButton* m_Choice; + bool m_ValidateByData; + QSize m_IconSize; +}; + +#endif // SELECTIONDIALOG_H diff --git a/src/selfupdater.cpp b/src/selfupdater.cpp index 1b24f3f1b..7762c9004 100644 --- a/src/selfupdater.cpp +++ b/src/selfupdater.cpp @@ -1,361 +1,350 @@ -/* -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 "selfupdater.h" - -#include "utility.h" -#include "iplugingame.h" -#include "messagedialog.h" -#include "downloadmanager.h" -#include "nexusinterface.h" -#include "nxmaccessmanager.h" -#include "settings.h" -#include "bbcode.h" -#include "plugincontainer.h" -#include "organizercore.h" -#include -#include -#include "shared/util.h" -#include "updatedialog.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include - -#include //for VS_FIXEDFILEINFO, GetLastError - -#include -#include -#include //for size_t -#include - -using namespace MOBase; -using namespace MOShared; - -SelfUpdater::SelfUpdater(NexusInterface *nexusInterface) - : m_Parent(nullptr) - , m_Interface(nexusInterface) - , m_Reply(nullptr) - , m_Attempts(3) -{ - m_MOVersion = createVersionInfo(); -} - - -SelfUpdater::~SelfUpdater() -{ -} - -void SelfUpdater::setUserInterface(QWidget *widget) -{ - m_Parent = widget; -} - -void SelfUpdater::setPluginContainer(PluginContainer *pluginContainer) -{ - m_Interface->setPluginContainer(pluginContainer); -} - -void SelfUpdater::testForUpdate(const Settings& settings) -{ - if (settings.network().offlineMode()) { - log::debug("not checking for updates, in offline mode"); - return; - } - - if (!settings.checkForUpdates()) { - log::debug("not checking for updates, disabled"); - return; - } - - // TODO: if prereleases are disabled we could just request the latest release - // directly - try { - m_GitHub.releases(GitHub::Repository("Modorganizer2", "modorganizer"), - [this](const QJsonArray &releases) { - if (releases.isEmpty()) { - // error message already logged - return; - } - - // We store all releases: - CandidatesMap mreleases; - for (const QJsonValue &releaseVal : releases) { - QJsonObject release = releaseVal.toObject(); - if (!release["draft"].toBool() && (Settings::instance().usePrereleases() - || !release["prerelease"].toBool())) { - auto version = VersionInfo(release["tag_name"].toString()); - mreleases[version] = release; - } - } - - if (!mreleases.empty()) { - auto lastKey = mreleases.begin()->first; - if (lastKey > this->m_MOVersion) { - - // Fill m_UpdateCandidates with version strictly greater than the - // current version: - m_UpdateCandidates.clear(); - for (auto p : mreleases) { - if (p.first > this->m_MOVersion) { - m_UpdateCandidates.insert(p); - } - } - log::info("update available: {} -> {}", - this->m_MOVersion.displayString(3), - lastKey.displayString(3)); - emit updateAvailable(); - } else if (lastKey < this->m_MOVersion) { - // this could happen if the user switches from using prereleases to - // stable builds. Should we downgrade? - log::debug("This version is newer than the latest released one: {} -> {}", - this->m_MOVersion.displayString(3), - lastKey.displayString(3)); - } - } - }); - } - //Catch all is bad by design, should be improved - catch (...) { - log::debug("Unable to connect to github.com to check version"); - } -} - -void SelfUpdater::startUpdate() -{ - // the button can't be pressed if there isn't an update candidate - Q_ASSERT(!m_UpdateCandidates.empty()); - - auto latestRelease = m_UpdateCandidates.begin()->second; - - UpdateDialog dialog(m_Parent); - dialog.setVersions( - MOShared::createVersionInfo().displayString(3), - latestRelease["tag_name"].toString() - ); - - // We concatenate release details. We only include pre-release if those are - // the latest release: - QString details; - bool includePreRelease = true; - for (auto& p : m_UpdateCandidates) { - auto& release = p.second; - - // Ignore details for pre-release after a release has been found: - if (release["prerelease"].toBool() && !includePreRelease) { - continue; - } - - // Stop including pre-release as soon as we find a non-prerelease: - if (!release["prerelease"].toBool()) { - includePreRelease = false; - } - - details += "\n## " + release["name"].toString() + "\n---\n"; - details += release["body"].toString(); - } - - // Need to call setDetailedText to create the QTextEdit and then be able to retrieve it: - dialog.setChangeLogs(details); - - int res = dialog.exec(); - - if (dialog.result() == QDialog::Accepted) { - bool found = false; - for (const QJsonValue &assetVal : latestRelease["assets"].toArray()) { - QJsonObject asset = assetVal.toObject(); - if (asset["content_type"].toString() == "application/x-msdownload") { - openOutputFile(asset["name"].toString()); - download(asset["browser_download_url"].toString()); - found = true; - break; - } - } - if (!found) { - QMessageBox::warning( - m_Parent, tr("Download failed"), - tr("Failed to find correct download, please try again later.")); - } - } -} - - -void SelfUpdater::showProgress() -{ - if (m_Progress == nullptr) { - m_Progress = new QProgressDialog(m_Parent, Qt::Dialog); - connect(m_Progress, SIGNAL(canceled()), this, SLOT(downloadCancel())); - } - m_Progress->setModal(true); - m_Progress->show(); - m_Progress->setValue(0); - m_Progress->setWindowTitle(tr("Update")); - m_Progress->setLabelText(tr("Download in progress")); -} - -void SelfUpdater::closeProgress() -{ - if (m_Progress != nullptr) { - m_Progress->hide(); - m_Progress->deleteLater(); - m_Progress = nullptr; - } -} - -void SelfUpdater::openOutputFile(const QString &fileName) -{ - QString outputPath = QDir::fromNativeSeparators(qApp->property("dataPath").toString()) + "/" + fileName; - log::debug("downloading to {}", outputPath); - m_UpdateFile.setFileName(outputPath); - m_UpdateFile.open(QIODevice::WriteOnly); -} - -void SelfUpdater::download(const QString &downloadLink) -{ - QNetworkAccessManager *accessManager = m_Interface->getAccessManager(); - QUrl dlUrl(downloadLink); - QNetworkRequest request(dlUrl); - m_Canceled = false; - m_Reply = accessManager->get(request); - showProgress(); - - connect(m_Reply, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(downloadProgress(qint64, qint64))); - connect(m_Reply, SIGNAL(finished()), this, SLOT(downloadFinished())); - connect(m_Reply, SIGNAL(readyRead()), this, SLOT(downloadReadyRead())); -} - - -void SelfUpdater::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) -{ - if (m_Reply != nullptr) { - if (m_Canceled) { - m_Reply->abort(); - } else { - if (bytesTotal != 0) { - if (m_Progress != nullptr) { - m_Progress->setValue((bytesReceived * 100) / bytesTotal); - } - } - } - } -} - - -void SelfUpdater::downloadReadyRead() -{ - if (m_Reply != nullptr) { - m_UpdateFile.write(m_Reply->readAll()); - } -} - - -void SelfUpdater::downloadFinished() -{ - int error = QNetworkReply::NoError; - - if (m_Reply != nullptr) { - if (m_Reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 302) { - QUrl url = m_Reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl(); - m_UpdateFile.reset(); - download(url.toString()); - return; - } - m_UpdateFile.write(m_Reply->readAll()); - - error = m_Reply->error(); - - if (m_Reply->header(QNetworkRequest::ContentTypeHeader).toString().startsWith("text", Qt::CaseInsensitive)) { - m_Canceled = true; - } - - closeProgress(); - - m_Reply->close(); - m_Reply->deleteLater(); - m_Reply = nullptr; - } - - m_UpdateFile.close(); - - if ((m_UpdateFile.size() == 0) || - (error != QNetworkReply::NoError) || - m_Canceled) { - if (!m_Canceled) { - reportError(tr("Download failed: %1").arg(error)); - } - m_UpdateFile.remove(); - return; - } - - log::debug("download: {}", m_UpdateFile.fileName()); - - try { - installUpdate(); - } catch (const std::exception &e) { - reportError(tr("Failed to install update: %1").arg(e.what())); - } -} - - -void SelfUpdater::downloadCancel() -{ - m_Canceled = true; -} - - -void SelfUpdater::installUpdate() -{ - const QString parameters = "/DIR=\"" + qApp->applicationDirPath() + "\" "; - const auto r = shell::Execute(m_UpdateFile.fileName(), parameters); - - if (r.success()) { - QCoreApplication::quit(); - } else { - reportError(tr("Failed to start %1: %2") - .arg(m_UpdateFile.fileName()) - .arg(r.toString())); - } - - m_UpdateFile.remove(); -} - -void SelfUpdater::report7ZipError(QString const &errorMessage) -{ - QMessageBox::critical(m_Parent, tr("Error"), errorMessage); -} +/* +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 "selfupdater.h" + +#include "bbcode.h" +#include "downloadmanager.h" +#include "iplugingame.h" +#include "messagedialog.h" +#include "nexusinterface.h" +#include "nxmaccessmanager.h" +#include "organizercore.h" +#include "plugincontainer.h" +#include "settings.h" +#include "shared/util.h" +#include "updatedialog.h" +#include "utility.h" +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include //for VS_FIXEDFILEINFO, GetLastError + +#include +#include +#include //for size_t +#include + +using namespace MOBase; +using namespace MOShared; + +SelfUpdater::SelfUpdater(NexusInterface* nexusInterface) + : m_Parent(nullptr), m_Interface(nexusInterface), m_Reply(nullptr), m_Attempts(3) +{ + m_MOVersion = createVersionInfo(); +} + +SelfUpdater::~SelfUpdater() {} + +void SelfUpdater::setUserInterface(QWidget* widget) +{ + m_Parent = widget; +} + +void SelfUpdater::setPluginContainer(PluginContainer* pluginContainer) +{ + m_Interface->setPluginContainer(pluginContainer); +} + +void SelfUpdater::testForUpdate(const Settings& settings) +{ + if (settings.network().offlineMode()) { + log::debug("not checking for updates, in offline mode"); + return; + } + + if (!settings.checkForUpdates()) { + log::debug("not checking for updates, disabled"); + return; + } + + // TODO: if prereleases are disabled we could just request the latest release + // directly + try { + m_GitHub.releases( + GitHub::Repository("Modorganizer2", "modorganizer"), + [this](const QJsonArray& releases) { + if (releases.isEmpty()) { + // error message already logged + return; + } + + // We store all releases: + CandidatesMap mreleases; + for (const QJsonValue& releaseVal : releases) { + QJsonObject release = releaseVal.toObject(); + if (!release["draft"].toBool() && (Settings::instance().usePrereleases() || + !release["prerelease"].toBool())) { + auto version = VersionInfo(release["tag_name"].toString()); + mreleases[version] = release; + } + } + + if (!mreleases.empty()) { + auto lastKey = mreleases.begin()->first; + if (lastKey > this->m_MOVersion) { + + // Fill m_UpdateCandidates with version strictly greater than the + // current version: + m_UpdateCandidates.clear(); + for (auto p : mreleases) { + if (p.first > this->m_MOVersion) { + m_UpdateCandidates.insert(p); + } + } + log::info("update available: {} -> {}", + this->m_MOVersion.displayString(3), lastKey.displayString(3)); + emit updateAvailable(); + } else if (lastKey < this->m_MOVersion) { + // this could happen if the user switches from using prereleases to + // stable builds. Should we downgrade? + log::debug("This version is newer than the latest released one: {} -> {}", + this->m_MOVersion.displayString(3), lastKey.displayString(3)); + } + } + }); + } + // Catch all is bad by design, should be improved + catch (...) { + log::debug("Unable to connect to github.com to check version"); + } +} + +void SelfUpdater::startUpdate() +{ + // the button can't be pressed if there isn't an update candidate + Q_ASSERT(!m_UpdateCandidates.empty()); + + auto latestRelease = m_UpdateCandidates.begin()->second; + + UpdateDialog dialog(m_Parent); + dialog.setVersions(MOShared::createVersionInfo().displayString(3), + latestRelease["tag_name"].toString()); + + // We concatenate release details. We only include pre-release if those are + // the latest release: + QString details; + bool includePreRelease = true; + for (auto& p : m_UpdateCandidates) { + auto& release = p.second; + + // Ignore details for pre-release after a release has been found: + if (release["prerelease"].toBool() && !includePreRelease) { + continue; + } + + // Stop including pre-release as soon as we find a non-prerelease: + if (!release["prerelease"].toBool()) { + includePreRelease = false; + } + + details += "\n## " + release["name"].toString() + "\n---\n"; + details += release["body"].toString(); + } + + // Need to call setDetailedText to create the QTextEdit and then be able to retrieve + // it: + dialog.setChangeLogs(details); + + int res = dialog.exec(); + + if (dialog.result() == QDialog::Accepted) { + bool found = false; + for (const QJsonValue& assetVal : latestRelease["assets"].toArray()) { + QJsonObject asset = assetVal.toObject(); + if (asset["content_type"].toString() == "application/x-msdownload") { + openOutputFile(asset["name"].toString()); + download(asset["browser_download_url"].toString()); + found = true; + break; + } + } + if (!found) { + QMessageBox::warning( + m_Parent, tr("Download failed"), + tr("Failed to find correct download, please try again later.")); + } + } +} + +void SelfUpdater::showProgress() +{ + if (m_Progress == nullptr) { + m_Progress = new QProgressDialog(m_Parent, Qt::Dialog); + connect(m_Progress, SIGNAL(canceled()), this, SLOT(downloadCancel())); + } + m_Progress->setModal(true); + m_Progress->show(); + m_Progress->setValue(0); + m_Progress->setWindowTitle(tr("Update")); + m_Progress->setLabelText(tr("Download in progress")); +} + +void SelfUpdater::closeProgress() +{ + if (m_Progress != nullptr) { + m_Progress->hide(); + m_Progress->deleteLater(); + m_Progress = nullptr; + } +} + +void SelfUpdater::openOutputFile(const QString& fileName) +{ + QString outputPath = + QDir::fromNativeSeparators(qApp->property("dataPath").toString()) + "/" + + fileName; + log::debug("downloading to {}", outputPath); + m_UpdateFile.setFileName(outputPath); + m_UpdateFile.open(QIODevice::WriteOnly); +} + +void SelfUpdater::download(const QString& downloadLink) +{ + QNetworkAccessManager* accessManager = m_Interface->getAccessManager(); + QUrl dlUrl(downloadLink); + QNetworkRequest request(dlUrl); + m_Canceled = false; + m_Reply = accessManager->get(request); + showProgress(); + + connect(m_Reply, SIGNAL(downloadProgress(qint64, qint64)), this, + SLOT(downloadProgress(qint64, qint64))); + connect(m_Reply, SIGNAL(finished()), this, SLOT(downloadFinished())); + connect(m_Reply, SIGNAL(readyRead()), this, SLOT(downloadReadyRead())); +} + +void SelfUpdater::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) +{ + if (m_Reply != nullptr) { + if (m_Canceled) { + m_Reply->abort(); + } else { + if (bytesTotal != 0) { + if (m_Progress != nullptr) { + m_Progress->setValue((bytesReceived * 100) / bytesTotal); + } + } + } + } +} + +void SelfUpdater::downloadReadyRead() +{ + if (m_Reply != nullptr) { + m_UpdateFile.write(m_Reply->readAll()); + } +} + +void SelfUpdater::downloadFinished() +{ + int error = QNetworkReply::NoError; + + if (m_Reply != nullptr) { + if (m_Reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 302) { + QUrl url = + m_Reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl(); + m_UpdateFile.reset(); + download(url.toString()); + return; + } + m_UpdateFile.write(m_Reply->readAll()); + + error = m_Reply->error(); + + if (m_Reply->header(QNetworkRequest::ContentTypeHeader) + .toString() + .startsWith("text", Qt::CaseInsensitive)) { + m_Canceled = true; + } + + closeProgress(); + + m_Reply->close(); + m_Reply->deleteLater(); + m_Reply = nullptr; + } + + m_UpdateFile.close(); + + if ((m_UpdateFile.size() == 0) || (error != QNetworkReply::NoError) || m_Canceled) { + if (!m_Canceled) { + reportError(tr("Download failed: %1").arg(error)); + } + m_UpdateFile.remove(); + return; + } + + log::debug("download: {}", m_UpdateFile.fileName()); + + try { + installUpdate(); + } catch (const std::exception& e) { + reportError(tr("Failed to install update: %1").arg(e.what())); + } +} + +void SelfUpdater::downloadCancel() +{ + m_Canceled = true; +} + +void SelfUpdater::installUpdate() +{ + const QString parameters = "/DIR=\"" + qApp->applicationDirPath() + "\" "; + const auto r = shell::Execute(m_UpdateFile.fileName(), parameters); + + if (r.success()) { + QCoreApplication::quit(); + } else { + reportError( + tr("Failed to start %1: %2").arg(m_UpdateFile.fileName()).arg(r.toString())); + } + + m_UpdateFile.remove(); +} + +void SelfUpdater::report7ZipError(QString const& errorMessage) +{ + QMessageBox::critical(m_Parent, tr("Error"), errorMessage); +} diff --git a/src/selfupdater.h b/src/selfupdater.h index f7637292f..cc68ad275 100644 --- a/src/selfupdater.h +++ b/src/selfupdater.h @@ -1,153 +1,153 @@ -/* -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 SELFUPDATER_H -#define SELFUPDATER_H - -#include - -#include -#include - -class Archive; -class NexusInterface; -class PluginContainer; -namespace MOBase { class IPluginGame; } - -#include -#include -#include -#include -#include //for qint64 - -class QNetworkReply; -class QProgressDialog; -class Settings; - -/** - * @brief manages updates for Mod Organizer itself - * This class is used to update the Mod Organizer - * The process looks like this: - * 1. call testForUpdate() to determine is available - * 2. if the updateAvailable() signal is received, allow the user to start the update - * 3. if the user start the update, call startUpdate() - * 4. startUpdate() will first query a list of files, try to determine if there is an - * incremental update. If not, the user will have to confirm the download of a full download. - * Once the correct file is selected, it is downloaded. - * 5. before the downloaded file is extracted, existing files that are going to be replaced are - * moved to "update_backup" on because files that are currently open can't be replaced. - * 6. the update is extracted and then deleted - * 7. finally, a restart is requested via signal. - * 8. at restart, Mod Organizer will remove the update_backup directory since none of the files - * should now be open - * - * @todo use NexusBridge - **/ -class SelfUpdater : public QObject -{ - - Q_OBJECT - -public: - - /** - * @brief constructor - * - * @param nexusInterface interface to query information from nexus - * @param parent parent widget - * @todo passing the nexus interface is unneccessary - **/ - explicit SelfUpdater(NexusInterface *nexusInterface); - - virtual ~SelfUpdater(); - - void setUserInterface(QWidget *widget); - - void setPluginContainer(PluginContainer *pluginContainer); - - /** - * @brief request information about the current version - **/ - void testForUpdate(const Settings& settings); - - /** - * @brief start the update process - * @note this should not be called if there is no update available - **/ - void startUpdate(); - - /** - * @return current version of Mod Organizer - **/ - MOBase::VersionInfo getVersion() const { return m_MOVersion; } - -signals: - - /** - * @brief emitted if a restart of the client is necessary to complete the update - **/ - void restart(); - - /** - * @brief emitted if an update is available - **/ - void updateAvailable(); - - /** - * @brief emitted if a message of the day was received - **/ - void motdAvailable(const QString &motd); - -private: - - void openOutputFile(const QString &fileName); - void download(const QString &downloadLink); - void installUpdate(); - void report7ZipError(const QString &errorMessage); - void showProgress(); - void closeProgress(); - -private slots: - - void downloadProgress(qint64 bytesReceived, qint64 bytesTotal); - void downloadReadyRead(); - void downloadFinished(); - void downloadCancel(); - -private: - - QWidget *m_Parent; - MOBase::VersionInfo m_MOVersion; - NexusInterface *m_Interface; - QFile m_UpdateFile; - QNetworkReply *m_Reply; - QProgressDialog *m_Progress { nullptr }; - bool m_Canceled; - int m_Attempts; - - GitHub m_GitHub; - - // Map from version to release, in decreasing order (first element is the latest release): - using CandidatesMap = std::map>; - CandidatesMap m_UpdateCandidates; - -}; - - -#endif // SELFUPDATER_H +/* +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 SELFUPDATER_H +#define SELFUPDATER_H + +#include + +#include +#include + +class Archive; +class NexusInterface; +class PluginContainer; +namespace MOBase +{ +class IPluginGame; +} + +#include +#include +#include +#include +#include //for qint64 + +class QNetworkReply; +class QProgressDialog; +class Settings; + +/** + * @brief manages updates for Mod Organizer itself + * This class is used to update the Mod Organizer + * The process looks like this: + * 1. call testForUpdate() to determine is available + * 2. if the updateAvailable() signal is received, allow the user to start the update + * 3. if the user start the update, call startUpdate() + * 4. startUpdate() will first query a list of files, try to determine if there is an + * incremental update. If not, the user will have to confirm the download of a full + *download. Once the correct file is selected, it is downloaded. + * 5. before the downloaded file is extracted, existing files that are going to be + *replaced are moved to "update_backup" on because files that are currently open can't + *be replaced. + * 6. the update is extracted and then deleted + * 7. finally, a restart is requested via signal. + * 8. at restart, Mod Organizer will remove the update_backup directory since none of + *the files should now be open + * + * @todo use NexusBridge + **/ +class SelfUpdater : public QObject +{ + + Q_OBJECT + +public: + /** + * @brief constructor + * + * @param nexusInterface interface to query information from nexus + * @param parent parent widget + * @todo passing the nexus interface is unneccessary + **/ + explicit SelfUpdater(NexusInterface* nexusInterface); + + virtual ~SelfUpdater(); + + void setUserInterface(QWidget* widget); + + void setPluginContainer(PluginContainer* pluginContainer); + + /** + * @brief request information about the current version + **/ + void testForUpdate(const Settings& settings); + + /** + * @brief start the update process + * @note this should not be called if there is no update available + **/ + void startUpdate(); + + /** + * @return current version of Mod Organizer + **/ + MOBase::VersionInfo getVersion() const { return m_MOVersion; } + +signals: + + /** + * @brief emitted if a restart of the client is necessary to complete the update + **/ + void restart(); + + /** + * @brief emitted if an update is available + **/ + void updateAvailable(); + + /** + * @brief emitted if a message of the day was received + **/ + void motdAvailable(const QString& motd); + +private: + void openOutputFile(const QString& fileName); + void download(const QString& downloadLink); + void installUpdate(); + void report7ZipError(const QString& errorMessage); + void showProgress(); + void closeProgress(); + +private slots: + + void downloadProgress(qint64 bytesReceived, qint64 bytesTotal); + void downloadReadyRead(); + void downloadFinished(); + void downloadCancel(); + +private: + QWidget* m_Parent; + MOBase::VersionInfo m_MOVersion; + NexusInterface* m_Interface; + QFile m_UpdateFile; + QNetworkReply* m_Reply; + QProgressDialog* m_Progress{nullptr}; + bool m_Canceled; + int m_Attempts; + + GitHub m_GitHub; + + // Map from version to release, in decreasing order (first element is the latest + // release): + using CandidatesMap = std::map>; + CandidatesMap m_UpdateCandidates; +}; + +#endif // SELFUPDATER_H diff --git a/src/serverinfo.cpp b/src/serverinfo.cpp index aece61daa..4d99f6db1 100644 --- a/src/serverinfo.cpp +++ b/src/serverinfo.cpp @@ -1,178 +1,169 @@ -#include "serverinfo.h" -#include "log.h" - -using namespace MOBase; - -const std::size_t MaxDownloadCount = 5; - - -ServerInfo::ServerInfo() - : ServerInfo({}, false, {}, 0, {}) -{ -} - -ServerInfo::ServerInfo( - QString name, bool premium, QDate last, int preferred, - SpeedList lastDownloads) : - m_name(std::move(name)), m_premium(premium), m_lastSeen(std::move(last)), - m_preferred(preferred), m_lastDownloads(std::move(lastDownloads)) -{ - if (m_lastDownloads.size() > MaxDownloadCount) { - m_lastDownloads.resize(MaxDownloadCount); - } -} - -const QString& ServerInfo::name() const -{ - return m_name; -} - -bool ServerInfo::isPremium() const -{ - return m_premium; -} - -void ServerInfo::setPremium(bool b) -{ - m_premium = b; -} - -const QDate& ServerInfo::lastSeen() const -{ - return m_lastSeen; -} - -void ServerInfo::updateLastSeen() -{ - m_lastSeen = QDate::currentDate(); -} - -int ServerInfo::preferred() const -{ - return m_preferred; -} - -void ServerInfo::setPreferred(int i) -{ - m_preferred = i; -} - -const ServerInfo::SpeedList& ServerInfo::lastDownloads() const -{ - return m_lastDownloads; -} - -int ServerInfo::averageSpeed() const -{ - int count = 0; - int total = 0; - - for (const auto& s : m_lastDownloads) { - if (s > 0) { - ++count; - total += s; - } - } - - if (count > 0) { - return static_cast(total) / count; - } - - return 0; -} - -void ServerInfo::addDownload(int bytesPerSecond) -{ - if (bytesPerSecond <= 0) { - log::error( - "trying to add download with {} B/s to server '{}'; ignoring", - bytesPerSecond, m_name); - - return; - } - - if (m_lastDownloads.size() == MaxDownloadCount) { - std::rotate( - m_lastDownloads.begin(), - m_lastDownloads.begin() + 1, - m_lastDownloads.end()); - - m_lastDownloads.back() = bytesPerSecond; - } else { - m_lastDownloads.push_back(bytesPerSecond); - } - - log::debug("added download at {} B/s to server '{}'", bytesPerSecond, m_name); -} - - -void ServerList::add(ServerInfo s) -{ - m_servers.push_back(std::move(s)); - - std::sort(m_servers.begin(), m_servers.end(), [](auto&& a, auto&& b){ - return (a.preferred() < b.preferred()); - }); -} - -ServerList::iterator ServerList::begin() -{ - return m_servers.begin(); -} - -ServerList::const_iterator ServerList::begin() const -{ - return m_servers.begin(); -} - -ServerList::iterator ServerList::end() -{ - return m_servers.end(); -} - -ServerList::const_iterator ServerList::end() const -{ - return m_servers.end(); -} - -std::size_t ServerList::size() const -{ - return m_servers.size(); -} - -bool ServerList::empty() const -{ - return m_servers.empty(); -} - -ServerList::container ServerList::getPreferred() const -{ - container v; - - for (const auto& server : m_servers) { - if (server.preferred() > 0) { - v.push_back(server); - } - } - - return v; -} - -void ServerList::cleanup() -{ - QDate now = QDate::currentDate(); - - for (auto itor=m_servers.begin(); itor!=m_servers.end(); ) { - const QDate lastSeen = itor->lastSeen(); - - if (lastSeen.daysTo(now) > 30) { - log::debug( - "removing server {} since it hasn't been available for downloads " - "in over a month", itor->name()); - - itor = m_servers.erase(itor); - } else { - ++itor; - } - } -} +#include "serverinfo.h" +#include "log.h" + +using namespace MOBase; + +const std::size_t MaxDownloadCount = 5; + +ServerInfo::ServerInfo() : ServerInfo({}, false, {}, 0, {}) {} + +ServerInfo::ServerInfo(QString name, bool premium, QDate last, int preferred, + SpeedList lastDownloads) + : m_name(std::move(name)), m_premium(premium), m_lastSeen(std::move(last)), + m_preferred(preferred), m_lastDownloads(std::move(lastDownloads)) +{ + if (m_lastDownloads.size() > MaxDownloadCount) { + m_lastDownloads.resize(MaxDownloadCount); + } +} + +const QString& ServerInfo::name() const +{ + return m_name; +} + +bool ServerInfo::isPremium() const +{ + return m_premium; +} + +void ServerInfo::setPremium(bool b) +{ + m_premium = b; +} + +const QDate& ServerInfo::lastSeen() const +{ + return m_lastSeen; +} + +void ServerInfo::updateLastSeen() +{ + m_lastSeen = QDate::currentDate(); +} + +int ServerInfo::preferred() const +{ + return m_preferred; +} + +void ServerInfo::setPreferred(int i) +{ + m_preferred = i; +} + +const ServerInfo::SpeedList& ServerInfo::lastDownloads() const +{ + return m_lastDownloads; +} + +int ServerInfo::averageSpeed() const +{ + int count = 0; + int total = 0; + + for (const auto& s : m_lastDownloads) { + if (s > 0) { + ++count; + total += s; + } + } + + if (count > 0) { + return static_cast(total) / count; + } + + return 0; +} + +void ServerInfo::addDownload(int bytesPerSecond) +{ + if (bytesPerSecond <= 0) { + log::error("trying to add download with {} B/s to server '{}'; ignoring", + bytesPerSecond, m_name); + + return; + } + + if (m_lastDownloads.size() == MaxDownloadCount) { + std::rotate(m_lastDownloads.begin(), m_lastDownloads.begin() + 1, + m_lastDownloads.end()); + + m_lastDownloads.back() = bytesPerSecond; + } else { + m_lastDownloads.push_back(bytesPerSecond); + } + + log::debug("added download at {} B/s to server '{}'", bytesPerSecond, m_name); +} + +void ServerList::add(ServerInfo s) +{ + m_servers.push_back(std::move(s)); + + std::sort(m_servers.begin(), m_servers.end(), [](auto&& a, auto&& b) { + return (a.preferred() < b.preferred()); + }); +} + +ServerList::iterator ServerList::begin() +{ + return m_servers.begin(); +} + +ServerList::const_iterator ServerList::begin() const +{ + return m_servers.begin(); +} + +ServerList::iterator ServerList::end() +{ + return m_servers.end(); +} + +ServerList::const_iterator ServerList::end() const +{ + return m_servers.end(); +} + +std::size_t ServerList::size() const +{ + return m_servers.size(); +} + +bool ServerList::empty() const +{ + return m_servers.empty(); +} + +ServerList::container ServerList::getPreferred() const +{ + container v; + + for (const auto& server : m_servers) { + if (server.preferred() > 0) { + v.push_back(server); + } + } + + return v; +} + +void ServerList::cleanup() +{ + QDate now = QDate::currentDate(); + + for (auto itor = m_servers.begin(); itor != m_servers.end();) { + const QDate lastSeen = itor->lastSeen(); + + if (lastSeen.daysTo(now) > 30) { + log::debug("removing server {} since it hasn't been available for downloads " + "in over a month", + itor->name()); + + itor = m_servers.erase(itor); + } else { + ++itor; + } + } +} diff --git a/src/serverinfo.h b/src/serverinfo.h index af8f77c83..00b476434 100644 --- a/src/serverinfo.h +++ b/src/serverinfo.h @@ -1,70 +1,68 @@ -#ifndef SERVERINFO_H -#define SERVERINFO_H - -#include -#include -#include - -class ServerInfo -{ -public: - using SpeedList = std::vector; - - ServerInfo(); - ServerInfo( - QString name, bool premium, QDate lastSeen, int preferred, - SpeedList lastDownloads); - - const QString& name() const; - - bool isPremium() const; - void setPremium(bool b); - - const QDate& lastSeen() const; - void updateLastSeen(); - - int preferred() const; - void setPreferred(int i); - - const SpeedList& lastDownloads() const; - int averageSpeed() const; - void addDownload(int bytesPerSecond); - -private: - QString m_name; - bool m_premium; - QDate m_lastSeen; - int m_preferred; - SpeedList m_lastDownloads; -}; - -Q_DECLARE_METATYPE(ServerInfo) - - -class ServerList -{ -public: - using container = std::vector; - using iterator = container::iterator; - using const_iterator = container::const_iterator; - - void add(ServerInfo s); - - iterator begin(); - const_iterator begin() const; - iterator end(); - const_iterator end() const; - std::size_t size() const; - bool empty() const; - - container getPreferred() const; - - // removes servers that haven't been seen in a while - // - void cleanup(); - -private: - container m_servers; -}; - -#endif // SERVERINFO_H +#ifndef SERVERINFO_H +#define SERVERINFO_H + +#include +#include +#include + +class ServerInfo +{ +public: + using SpeedList = std::vector; + + ServerInfo(); + ServerInfo(QString name, bool premium, QDate lastSeen, int preferred, + SpeedList lastDownloads); + + const QString& name() const; + + bool isPremium() const; + void setPremium(bool b); + + const QDate& lastSeen() const; + void updateLastSeen(); + + int preferred() const; + void setPreferred(int i); + + const SpeedList& lastDownloads() const; + int averageSpeed() const; + void addDownload(int bytesPerSecond); + +private: + QString m_name; + bool m_premium; + QDate m_lastSeen; + int m_preferred; + SpeedList m_lastDownloads; +}; + +Q_DECLARE_METATYPE(ServerInfo) + +class ServerList +{ +public: + using container = std::vector; + using iterator = container::iterator; + using const_iterator = container::const_iterator; + + void add(ServerInfo s); + + iterator begin(); + const_iterator begin() const; + iterator end(); + const_iterator end() const; + std::size_t size() const; + bool empty() const; + + container getPreferred() const; + + // removes servers that haven't been seen in a while + // + void cleanup(); + +private: + container m_servers; +}; + +#endif // SERVERINFO_H diff --git a/src/settings.cpp b/src/settings.cpp index 1ecc4072f..0cf300a0d 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -18,22 +18,21 @@ along with Mod Organizer. If not, see . */ #include "settings.h" -#include "settingsutilities.h" -#include "serverinfo.h" +#include "env.h" +#include "envmetrics.h" #include "executableslist.h" #include "instancemanager.h" -#include "shared/appconfig.h" -#include "env.h" #include "modelutils.h" -#include "envmetrics.h" +#include "serverinfo.h" +#include "settingsutilities.h" +#include "shared/appconfig.h" #include -#include #include +#include using namespace MOBase; using namespace MOShared; - EndorsementState endorsementStateFromString(const QString& s) { if (s == "Endorsed") { @@ -47,31 +46,27 @@ EndorsementState endorsementStateFromString(const QString& s) QString toString(EndorsementState s) { - switch (s) - { + switch (s) { case EndorsementState::Accepted: return "Endorsed"; case EndorsementState::Refused: return "Abstained"; - case EndorsementState::NoDecision: // fall-through + case EndorsementState::NoDecision: // fall-through default: return {}; } } +Settings* Settings::s_Instance = nullptr; -Settings *Settings::s_Instance = nullptr; - -Settings::Settings(const QString& path, bool globalInstance) : - m_Settings(path, QSettings::IniFormat), - m_Game(m_Settings), m_Geometry(m_Settings), - m_Widgets(m_Settings, globalInstance), m_Colors(m_Settings), - m_Plugins(m_Settings), m_Paths(m_Settings), - m_Network(m_Settings, globalInstance), - m_Nexus(*this, m_Settings), m_Steam(*this, m_Settings), - m_Interface(m_Settings), m_Diagnostics(m_Settings) +Settings::Settings(const QString& path, bool globalInstance) + : m_Settings(path, QSettings::IniFormat), m_Game(m_Settings), + m_Geometry(m_Settings), m_Widgets(m_Settings, globalInstance), + m_Colors(m_Settings), m_Plugins(m_Settings), m_Paths(m_Settings), + m_Network(m_Settings, globalInstance), m_Nexus(*this, m_Settings), + m_Steam(*this, m_Settings), m_Interface(m_Settings), m_Diagnostics(m_Settings) { if (globalInstance) { if (s_Instance != nullptr) { @@ -104,8 +99,8 @@ Settings* Settings::maybeInstance() return s_Instance; } -void Settings::processUpdates( - const QVersionNumber& currentVersion, const QVersionNumber& lastVersion) +void Settings::processUpdates(const QVersionNumber& currentVersion, + const QVersionNumber& lastVersion) { if (firstStart()) { set(m_Settings, "General", "version", currentVersion.toString()); @@ -116,9 +111,8 @@ void Settings::processUpdates( return; } - log::info( - "updating from {} to {}", - lastVersion.toString(), currentVersion.toString()); + log::info("updating from {} to {}", lastVersion.toString(), + currentVersion.toString()); auto version = [&](const QVersionNumber& v, auto&& f) { if (lastVersion < v) { @@ -127,7 +121,7 @@ void Settings::processUpdates( } }; - version({2, 2, 0}, [&]{ + version({2, 2, 0}, [&] { remove(m_Settings, "Settings", "steam_password"); remove(m_Settings, "Settings", "nexus_username"); remove(m_Settings, "Settings", "nexus_password"); @@ -139,7 +133,7 @@ void Settings::processUpdates( removeSection(m_Settings, "Servers"); }); - version({2, 2, 1}, [&]{ + version({2, 2, 1}, [&] { remove(m_Settings, "General", "mod_info_tabs"); remove(m_Settings, "General", "mod_info_conflict_expanders"); remove(m_Settings, "General", "mod_info_conflicts"); @@ -149,7 +143,7 @@ void Settings::processUpdates( remove(m_Settings, "General", "mod_info_conflicts_overwritten"); }); - version({2, 2, 2}, [&]{ + version({2, 2, 2}, [&] { // log splitter is gone, it's a dock now remove(m_Settings, "General", "log_split"); @@ -183,13 +177,13 @@ void Settings::processUpdates( m_Network.updateFromOldMap(); }); - version({ 2, 4, 0 }, [&] { + version({2, 4, 0}, [&] { // removed remove(m_Settings, "Settings", "hide_unchecked_plugins"); remove(m_Settings, "Settings", "load_mechanism"); }); - //save version in all case + // save version in all case set(m_Settings, "General", "version", currentVersion.toString()); log::debug("updating done"); @@ -242,7 +236,7 @@ void Settings::setRefreshThreadCount(std::size_t n) const std::optional Settings::version() const { - if (auto v=getOptional(m_Settings, "General", "version")) { + if (auto v = getOptional(m_Settings, "General", "version")) { return QVersionNumber::fromString(*v).normalized(); } @@ -261,18 +255,17 @@ void Settings::setFirstStart(bool b) QString Settings::executablesBlacklist() const { - static const QString def = (QStringList() - << "Chrome.exe" - << "Firefox.exe" - << "TSVNCache.exe" - << "TGitCache.exe" - << "Steam.exe" - << "GameOverlayUI.exe" - << "Discord.exe" - << "GalaxyClient.exe" - << "Spotify.exe" - << "Brave.exe" - ).join(";"); + static const QString def = (QStringList() << "Chrome.exe" + << "Firefox.exe" + << "TSVNCache.exe" + << "TGitCache.exe" + << "Steam.exe" + << "GameOverlayUI.exe" + << "Discord.exe" + << "GalaxyClient.exe" + << "Spotify.exe" + << "Brave.exe") + .join(";"); return get(m_Settings, "Settings", "executable_blacklist", def); } @@ -318,7 +311,7 @@ std::vector> Settings::executables() const ScopedReadArray sra(m_Settings, "customExecutables"); std::vector> v; - sra.for_each([&]{ + sra.for_each([&] { std::map map; for (auto&& key : sra.keys()) { @@ -516,11 +509,9 @@ QSettings::Status Settings::iniStatus() const void Settings::dump() const { - static const QStringList ignore({ - "username", "password", - "nexus_api_key", "nexus_username", "nexus_password", - "steam_username" - }); + static const QStringList ignore({"username", "password", "nexus_api_key", + "nexus_username", "nexus_password", + "steam_username"}); log::debug("settings:"); @@ -540,16 +531,14 @@ void Settings::dump() const m_Nexus.dump(); } -void Settings::managedGameChanged(IPluginGame const *gamePlugin) +void Settings::managedGameChanged(IPluginGame const* gamePlugin) { m_Game.setPlugin(gamePlugin); } - GameSettings::GameSettings(QSettings& settings) - : m_Settings(settings), m_GamePlugin(nullptr) -{ -} + : m_Settings(settings), m_GamePlugin(nullptr) +{} const MOBase::IPluginGame* GameSettings::plugin() { @@ -573,7 +562,7 @@ void GameSettings::setForceEnableCoreFiles(bool b) std::optional GameSettings::directory() const { - if (auto v=getOptional(m_Settings, "General", "gamePath")) { + if (auto v = getOptional(m_Settings, "General", "gamePath")) { return QString::fromUtf8(*v); } @@ -607,7 +596,7 @@ void GameSettings::setEdition(const QString& name) std::optional GameSettings::selectedProfileName() const { - if (auto v=getOptional(m_Settings, "General", "selected_profile")) { + if (auto v = getOptional(m_Settings, "General", "selected_profile")) { return QString::fromUtf8(*v); } @@ -619,10 +608,7 @@ void GameSettings::setSelectedProfileName(const QString& name) set(m_Settings, "General", "selected_profile", name.toUtf8()); } -GeometrySettings::GeometrySettings(QSettings& s) - : m_Settings(s), m_Reset(false) -{ -} +GeometrySettings::GeometrySettings(QSettings& s) : m_Settings(s), m_Reset(false) {} void GeometrySettings::requestReset() { @@ -671,7 +657,7 @@ void GeometrySettings::saveWindowGeometry(const QWidget* w) bool GeometrySettings::restoreWindowGeometry(QWidget* w) const { - if (auto v=getOptional(m_Settings, "Geometry", geoSettingName(w))) { + if (auto v = getOptional(m_Settings, "Geometry", geoSettingName(w))) { w->restoreGeometry(*v); ensureWindowOnScreen(w); return true; @@ -701,14 +687,13 @@ void GeometrySettings::ensureWindowOnScreen(QWidget* w) const // desktop geometry, made smaller to make sure there isn't just a few pixels const auto originalDg = env::Environment().metrics().desktopGeometry(); - const auto dg = originalDg.adjusted(borders, borders, -borders, -borders); + const auto dg = originalDg.adjusted(borders, borders, -borders, -borders); const auto g = w->geometry(); if (!dg.intersects(g)) { - log::warn( - "window '{}' is offscreen, moving to main monitor; geo={}, desktop={}", - w->objectName(), g, originalDg); + log::warn("window '{}' is offscreen, moving to main monitor; geo={}, desktop={}", + w->objectName(), g, originalDg); // widget is off-screen, center it on main monitor centerOnMonitor(w, -1); @@ -725,7 +710,7 @@ void GeometrySettings::saveState(const QMainWindow* w) bool GeometrySettings::restoreState(QMainWindow* w) const { - if (auto v=getOptional(m_Settings, "Geometry", stateSettingName(w))) { + if (auto v = getOptional(m_Settings, "Geometry", stateSettingName(w))) { w->restoreState(*v); return true; } @@ -740,7 +725,7 @@ void GeometrySettings::saveState(const QHeaderView* w) bool GeometrySettings::restoreState(QHeaderView* w) const { - if (auto v=getOptional(m_Settings, "Geometry", stateSettingName(w))) { + if (auto v = getOptional(m_Settings, "Geometry", stateSettingName(w))) { w->restoreState(*v); return true; } @@ -755,7 +740,7 @@ void GeometrySettings::saveState(const QSplitter* w) bool GeometrySettings::restoreState(QSplitter* w) const { - if (auto v=getOptional(m_Settings, "Geometry", stateSettingName(w))) { + if (auto v = getOptional(m_Settings, "Geometry", stateSettingName(w))) { w->restoreState(*v); return true; } @@ -770,7 +755,8 @@ void GeometrySettings::saveState(const ExpanderWidget* expander) bool GeometrySettings::restoreState(ExpanderWidget* expander) const { - if (auto v=getOptional(m_Settings, "Geometry", stateSettingName(expander))) { + if (auto v = + getOptional(m_Settings, "Geometry", stateSettingName(expander))) { expander->restoreState(*v); return true; } @@ -785,7 +771,8 @@ void GeometrySettings::saveVisibility(const QWidget* w) bool GeometrySettings::restoreVisibility(QWidget* w, std::optional def) const { - if (auto v=getOptional(m_Settings, "Geometry", visibilitySettingName(w), def)) { + if (auto v = + getOptional(m_Settings, "Geometry", visibilitySettingName(w), def)) { w->setVisible(*v); return true; } @@ -796,7 +783,7 @@ bool GeometrySettings::restoreVisibility(QWidget* w, std::optional def) co void GeometrySettings::restoreToolbars(QMainWindow* w) const { // all toolbars have the same size and button style settings - const auto size = getOptional(m_Settings, "Geometry", "toolbar_size"); + const auto size = getOptional(m_Settings, "Geometry", "toolbar_size"); const auto style = getOptional(m_Settings, "Geometry", "toolbar_button_style"); for (auto* tb : w->findChildren()) { @@ -827,7 +814,8 @@ void GeometrySettings::saveToolbars(const QMainWindow* w) const auto* tb = tbs[0]; set(m_Settings, "Geometry", "toolbar_size", tb->iconSize()); - set(m_Settings, "Geometry", "toolbar_button_style", static_cast(tb->toolButtonStyle())); + set(m_Settings, "Geometry", "toolbar_button_style", + static_cast(tb->toolButtonStyle())); } } @@ -842,7 +830,7 @@ QStringList GeometrySettings::modInfoTabOrder() const int count = 0; stream >> count; - for (int i=0; i> s; v.push_back(s); @@ -879,8 +867,8 @@ void GeometrySettings::setCenterDialogs(bool b) void GeometrySettings::centerOnMainWindowMonitor(QWidget* w) const { - const auto monitor = getOptional( - m_Settings, "Geometry", "MainWindow_monitor").value_or(-1); + const auto monitor = + getOptional(m_Settings, "Geometry", "MainWindow_monitor").value_or(-1); centerOnMonitor(w, monitor); } @@ -916,7 +904,7 @@ void GeometrySettings::centerOnParent(QWidget* w, QWidget* parent) void GeometrySettings::saveMainWindowMonitor(const QMainWindow* w) { - if (auto* handle=w->windowHandle()) { + if (auto* handle = w->windowHandle()) { if (auto* screen = handle->screen()) { const int screenId = QGuiApplication::screens().indexOf(screen); set(m_Settings, "Geometry", "MainWindow_monitor", screenId); @@ -927,8 +915,7 @@ void GeometrySettings::saveMainWindowMonitor(const QMainWindow* w) Qt::Orientation dockOrientation(const QMainWindow* mw, const QDockWidget* d) { // docks in these areas are horizontal - const auto horizontalAreas = - Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea; + const auto horizontalAreas = Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea; if (mw->dockWidgetArea(const_cast(d)) & horizontalAreas) { return Qt::Horizontal; @@ -986,7 +973,7 @@ void GeometrySettings::restoreDocks(QMainWindow* mw) const // for each dock for (auto* dock : mw->findChildren()) { - if (auto size=getOptional(m_Settings, "Geometry", dockSettingName(dock))) { + if (auto size = getOptional(m_Settings, "Geometry", dockSettingName(dock))) { // remember this dock, its size and orientation dockInfos.push_back({dock, *size, dockOrientation(mw, dock)}); } @@ -1001,18 +988,22 @@ void GeometrySettings::restoreDocks(QMainWindow* mw) const for (const auto& info : dockInfos) { mw->resizeDocks({info.d}, {info.size}, info.ori); } - }); + }); } - -WidgetSettings::WidgetSettings(QSettings& s, bool globalInstance) - : m_Settings(s) +WidgetSettings::WidgetSettings(QSettings& s, bool globalInstance) : m_Settings(s) { if (globalInstance) { MOBase::QuestionBoxMemory::setCallbacks( - [this](auto&& w, auto&& f){ return questionButton(w, f); }, - [this](auto&& w, auto&& b){ setQuestionWindowButton(w, b); }, - [this](auto&& w, auto&& f, auto&& b){ setQuestionFileButton(w, f, b); }); + [this](auto&& w, auto&& f) { + return questionButton(w, f); + }, + [this](auto&& w, auto&& b) { + setQuestionWindowButton(w, b); + }, + [this](auto&& w, auto&& f, auto&& b) { + setQuestionFileButton(w, f, b); + }); } } @@ -1027,9 +1018,11 @@ void WidgetSettings::saveTreeCheckState(const QTreeView* tv, int role) void WidgetSettings::restoreTreeCheckState(QTreeView* tv, int role) const { - if (auto states = getOptional(m_Settings, "Widgets", indexSettingName(tv))) { + if (auto states = + getOptional(m_Settings, "Widgets", indexSettingName(tv))) { auto allIndex = flatIndex(tv->model()); - MOBase::log::debug("restoreTreeCheckState: {}, {}", states->size(), allIndex.size()); + MOBase::log::debug("restoreTreeCheckState: {}, {}", states->size(), + allIndex.size()); if (states->size() != allIndex.size()) { return; } @@ -1052,7 +1045,8 @@ void WidgetSettings::saveTreeExpandState(const QTreeView* tv, int role) void WidgetSettings::restoreTreeExpandState(QTreeView* tv, int role) const { - if (auto expanded = getOptional(m_Settings, "Widgets", indexSettingName(tv))) { + if (auto expanded = + getOptional(m_Settings, "Widgets", indexSettingName(tv))) { tv->collapseAll(); for (auto index : flatIndex(tv->model())) { if (expanded->contains(index.data(role))) { @@ -1074,7 +1068,7 @@ void WidgetSettings::saveIndex(const QComboBox* cb) void WidgetSettings::restoreIndex(QComboBox* cb, std::optional def) const { - if (auto v=getOptional(m_Settings, "Widgets", indexSettingName(cb), def)) { + if (auto v = getOptional(m_Settings, "Widgets", indexSettingName(cb), def)) { cb->setCurrentIndex(*v); } } @@ -1091,7 +1085,7 @@ void WidgetSettings::saveIndex(const QTabWidget* w) void WidgetSettings::restoreIndex(QTabWidget* w, std::optional def) const { - if (auto v=getOptional(m_Settings, "Widgets", indexSettingName(w), def)) { + if (auto v = getOptional(m_Settings, "Widgets", indexSettingName(w), def)) { w->setCurrentIndex(*v); } } @@ -1112,32 +1106,32 @@ void WidgetSettings::restoreChecked(QAbstractButton* w, std::optional def) { warnIfNotCheckable(w); - if (auto v=getOptional(m_Settings, "Widgets", checkedSettingName(w), def)) { + if (auto v = getOptional(m_Settings, "Widgets", checkedSettingName(w), def)) { w->setChecked(*v); } } -QuestionBoxMemory::Button WidgetSettings::questionButton( - const QString& windowName, const QString& filename) const +QuestionBoxMemory::Button WidgetSettings::questionButton(const QString& windowName, + const QString& filename) const { const QString sectionName("DialogChoices"); if (!filename.isEmpty()) { const auto fileSetting = windowName + "/" + filename; - if (auto v=getOptional(m_Settings, sectionName, fileSetting)) { + if (auto v = getOptional(m_Settings, sectionName, fileSetting)) { return static_cast(*v); } } - if (auto v=getOptional(m_Settings, sectionName, windowName)) { + if (auto v = getOptional(m_Settings, sectionName, windowName)) { return static_cast(*v); } return QuestionBoxMemory::NoButton; } -void WidgetSettings::setQuestionWindowButton( - const QString& windowName, QuestionBoxMemory::Button button) +void WidgetSettings::setQuestionWindowButton(const QString& windowName, + QuestionBoxMemory::Button button) { const QString sectionName("DialogChoices"); @@ -1148,9 +1142,9 @@ void WidgetSettings::setQuestionWindowButton( } } -void WidgetSettings::setQuestionFileButton( - const QString& windowName, const QString& filename, - QuestionBoxMemory::Button button) +void WidgetSettings::setQuestionFileButton(const QString& windowName, + const QString& filename, + QuestionBoxMemory::Button button) { const QString sectionName("DialogChoices"); const QString settingName(windowName + "/" + filename); @@ -1167,17 +1161,12 @@ void WidgetSettings::resetQuestionButtons() removeSection(m_Settings, "DialogChoices"); } - -ColorSettings::ColorSettings(QSettings& s) - : m_Settings(s) -{ -} +ColorSettings::ColorSettings(QSettings& s) : m_Settings(s) {} QColor ColorSettings::modlistOverwrittenLoose() const { - return get( - m_Settings, "Settings", "overwrittenLooseFilesColor", - QColor(0, 255, 0, 64)); + return get(m_Settings, "Settings", "overwrittenLooseFilesColor", + QColor(0, 255, 0, 64)); } void ColorSettings::setModlistOverwrittenLoose(const QColor& c) @@ -1187,9 +1176,8 @@ void ColorSettings::setModlistOverwrittenLoose(const QColor& c) QColor ColorSettings::modlistOverwritingLoose() const { - return get( - m_Settings, "Settings", "overwritingLooseFilesColor", - QColor(255, 0, 0, 64)); + return get(m_Settings, "Settings", "overwritingLooseFilesColor", + QColor(255, 0, 0, 64)); } void ColorSettings::setModlistOverwritingLoose(const QColor& c) @@ -1199,9 +1187,8 @@ void ColorSettings::setModlistOverwritingLoose(const QColor& c) QColor ColorSettings::modlistOverwrittenArchive() const { - return get( - m_Settings, "Settings", "overwrittenArchiveFilesColor", - QColor(0, 255, 255, 64)); + return get(m_Settings, "Settings", "overwrittenArchiveFilesColor", + QColor(0, 255, 255, 64)); } void ColorSettings::setModlistOverwrittenArchive(const QColor& c) @@ -1211,9 +1198,8 @@ void ColorSettings::setModlistOverwrittenArchive(const QColor& c) QColor ColorSettings::modlistOverwritingArchive() const { - return get( - m_Settings, "Settings", "overwritingArchiveFilesColor", - QColor(255, 0, 255, 64)); + return get(m_Settings, "Settings", "overwritingArchiveFilesColor", + QColor(255, 0, 255, 64)); } void ColorSettings::setModlistOverwritingArchive(const QColor& c) @@ -1223,9 +1209,8 @@ void ColorSettings::setModlistOverwritingArchive(const QColor& c) QColor ColorSettings::modlistContainsPlugin() const { - return get( - m_Settings, "Settings", "containsPluginColor", - QColor(0, 0, 255, 64)); + return get(m_Settings, "Settings", "containsPluginColor", + QColor(0, 0, 255, 64)); } void ColorSettings::setModlistContainsPlugin(const QColor& c) @@ -1235,9 +1220,7 @@ void ColorSettings::setModlistContainsPlugin(const QColor& c) QColor ColorSettings::pluginListContained() const { - return get( - m_Settings, "Settings", "containedColor", - QColor(0, 0, 255, 64)); + return get(m_Settings, "Settings", "containedColor", QColor(0, 0, 255, 64)); } void ColorSettings::setPluginListContained(const QColor& c) @@ -1281,16 +1264,13 @@ QColor ColorSettings::idealTextColor(const QColor& rBackgroundColor) return QColor(Qt::black); // "inverse' of luminance of the background - int iLuminance = (rBackgroundColor.red() * 0.299) + (rBackgroundColor.green() * 0.587) + (rBackgroundColor.blue() * 0.114); + int iLuminance = (rBackgroundColor.red() * 0.299) + + (rBackgroundColor.green() * 0.587) + + (rBackgroundColor.blue() * 0.114); return QColor(iLuminance >= 128 ? Qt::black : Qt::white); } - - -PluginSettings::PluginSettings(QSettings& settings) - : m_Settings(settings) -{ -} +PluginSettings::PluginSettings(QSettings& settings) : m_Settings(settings) {} void PluginSettings::clearPlugins() { @@ -1301,17 +1281,16 @@ void PluginSettings::clearPlugins() m_PluginBlacklist = readBlacklist(); } -void PluginSettings::registerPlugin(IPlugin *plugin) +void PluginSettings::registerPlugin(IPlugin* plugin) { m_Plugins.push_back(plugin); m_PluginSettings.insert(plugin->name(), QVariantMap()); m_PluginDescriptions.insert(plugin->name(), QVariantMap()); - for (const PluginSetting &setting : plugin->settings()) { + for (const PluginSetting& setting : plugin->settings()) { const QString settingName = plugin->name() + "/" + setting.key; - QVariant temp = get( - m_Settings, "Plugins", settingName, QVariant()); + QVariant temp = get(m_Settings, "Plugins", settingName, QVariant()); // No previous enabled? Skip. if (setting.key == "enabled" && (!temp.isValid() || !temp.canConvert())) { @@ -1320,25 +1299,26 @@ void PluginSettings::registerPlugin(IPlugin *plugin) if (!temp.isValid()) { temp = setting.defaultValue; - } - else if (!temp.convert(setting.defaultValue.type())) { - log::warn( - "failed to interpret \"{}\" as correct type for \"{}\" in plugin \"{}\", using default", - temp.toString(), setting.key, plugin->name()); + } else if (!temp.convert(setting.defaultValue.type())) { + log::warn("failed to interpret \"{}\" as correct type for \"{}\" in plugin " + "\"{}\", using default", + temp.toString(), setting.key, plugin->name()); temp = setting.defaultValue; } m_PluginSettings[plugin->name()][setting.key] = temp; - m_PluginDescriptions[plugin->name()][setting.key] = QString("%1 (default: %2)") - .arg(setting.description) - .arg(setting.defaultValue.toString()); + m_PluginDescriptions[plugin->name()][setting.key] = + QString("%1 (default: %2)") + .arg(setting.description) + .arg(setting.defaultValue.toString()); } // Handle previous "enabled" settings: if (m_PluginSettings[plugin->name()].contains("enabled")) { - setPersistent(plugin->name(), "enabled", m_PluginSettings[plugin->name()]["enabled"].toBool(), true); + setPersistent(plugin->name(), "enabled", + m_PluginSettings[plugin->name()]["enabled"].toBool(), true); m_PluginSettings[plugin->name()].remove("enabled"); m_PluginDescriptions[plugin->name()].remove("enabled"); @@ -1363,7 +1343,7 @@ std::vector PluginSettings::plugins() const return m_Plugins; } -QVariant PluginSettings::setting(const QString &pluginName, const QString &key) const +QVariant PluginSettings::setting(const QString& pluginName, const QString& key) const { auto iterPlugin = m_PluginSettings.find(pluginName); if (iterPlugin == m_PluginSettings.end()) { @@ -1378,14 +1358,14 @@ QVariant PluginSettings::setting(const QString &pluginName, const QString &key) return *iterSetting; } -void PluginSettings::setSetting(const QString &pluginName, const QString &key, const QVariant &value) +void PluginSettings::setSetting(const QString& pluginName, const QString& key, + const QVariant& value) { auto iterPlugin = m_PluginSettings.find(pluginName); if (iterPlugin == m_PluginSettings.end()) { - throw MyException( - QObject::tr("attempt to store setting for unknown plugin \"%1\"") - .arg(pluginName)); + throw MyException(QObject::tr("attempt to store setting for unknown plugin \"%1\"") + .arg(pluginName)); } QVariant oldValue = m_PluginSettings[pluginName][key]; @@ -1398,22 +1378,21 @@ void PluginSettings::setSetting(const QString &pluginName, const QString &key, c emit pluginSettingChanged(pluginName, key, oldValue, value); } -QVariantMap PluginSettings::settings(const QString &pluginName) const +QVariantMap PluginSettings::settings(const QString& pluginName) const { return m_PluginSettings[pluginName]; } -void PluginSettings::setSettings(const QString &pluginName, const QVariantMap& map) +void PluginSettings::setSettings(const QString& pluginName, const QVariantMap& map) { auto iterPlugin = m_PluginSettings.find(pluginName); if (iterPlugin == m_PluginSettings.end()) { - throw MyException( - QObject::tr("attempt to store setting for unknown plugin \"%1\"") - .arg(pluginName)); + throw MyException(QObject::tr("attempt to store setting for unknown plugin \"%1\"") + .arg(pluginName)); } - QVariantMap oldSettings = m_PluginSettings[pluginName]; + QVariantMap oldSettings = m_PluginSettings[pluginName]; m_PluginSettings[pluginName] = map; // Emit signals for settings that have been changed or added: @@ -1433,17 +1412,18 @@ void PluginSettings::setSettings(const QString &pluginName, const QVariantMap& m } } -QVariantMap PluginSettings::descriptions(const QString &pluginName) const +QVariantMap PluginSettings::descriptions(const QString& pluginName) const { return m_PluginDescriptions[pluginName]; } -void PluginSettings::setDescriptions(const QString &pluginName, const QVariantMap& map) +void PluginSettings::setDescriptions(const QString& pluginName, const QVariantMap& map) { m_PluginDescriptions[pluginName] = map; } -QVariant PluginSettings::persistent(const QString &pluginName, const QString &key, const QVariant &def) const +QVariant PluginSettings::persistent(const QString& pluginName, const QString& key, + const QVariant& def) const { if (!m_PluginSettings.contains(pluginName)) { return def; @@ -1452,13 +1432,12 @@ QVariant PluginSettings::persistent(const QString &pluginName, const QString &ke return get(m_Settings, "PluginPersistance", pluginName + "/" + key, def); } -void PluginSettings::setPersistent( - const QString &pluginName, const QString &key, const QVariant &value, bool sync) +void PluginSettings::setPersistent(const QString& pluginName, const QString& key, + const QVariant& value, bool sync) { if (!m_PluginSettings.contains(pluginName)) { - throw MyException( - QObject::tr("attempt to store setting for unknown plugin \"%1\"") - .arg(pluginName)); + throw MyException(QObject::tr("attempt to store setting for unknown plugin \"%1\"") + .arg(pluginName)); } set(m_Settings, "PluginPersistance", pluginName + "/" + key, value); @@ -1468,13 +1447,13 @@ void PluginSettings::setPersistent( } } -void PluginSettings::addBlacklist(const QString &fileName) +void PluginSettings::addBlacklist(const QString& fileName) { m_PluginBlacklist.insert(fileName); writeBlacklist(); } -bool PluginSettings::blacklisted(const QString &fileName) const +bool PluginSettings::blacklisted(const QString& fileName) const { return m_PluginBlacklist.contains(fileName); } @@ -1495,8 +1474,10 @@ const QSet& PluginSettings::blacklist() const void PluginSettings::save() { - for (auto iterPlugins=m_PluginSettings.begin(); iterPlugins!=m_PluginSettings.end(); ++iterPlugins) { - for (auto iterSettings=iterPlugins->begin(); iterSettings!=iterPlugins->end(); ++iterSettings) { + for (auto iterPlugins = m_PluginSettings.begin(); + iterPlugins != m_PluginSettings.end(); ++iterPlugins) { + for (auto iterSettings = iterPlugins->begin(); iterSettings != iterPlugins->end(); + ++iterSettings) { const auto key = iterPlugins.key() + "/" + iterSettings.key(); set(m_Settings, "Plugins", key, iterSettings.value()); } @@ -1516,7 +1497,7 @@ void PluginSettings::writeBlacklist() ScopedWriteArray swa(m_Settings, "pluginBlacklist", m_PluginBlacklist.size()); - for (const QString &plugin : m_PluginBlacklist) { + for (const QString& plugin : m_PluginBlacklist) { swa.next(); swa.set("name", plugin); } @@ -1527,20 +1508,16 @@ QSet PluginSettings::readBlacklist() const QSet set; ScopedReadArray sra(m_Settings, "pluginBlacklist"); - sra.for_each([&]{ + sra.for_each([&] { set.insert(sra.get("name")); - }); + }); return set; } - const QString PathSettings::BaseDirVariable = "%BASE_DIR%"; -PathSettings::PathSettings(QSettings& settings) - : m_Settings(settings) -{ -} +PathSettings::PathSettings(QSettings& settings) : m_Settings(settings) {} std::map PathSettings::recent() const { @@ -1550,7 +1527,7 @@ std::map PathSettings::recent() const sra.for_each([&] { const QVariant name = sra.get("name"); - const QVariant dir = sra.get("directory"); + const QVariant dir = sra.get("directory"); if (name.isValid() && dir.isValid()) { map.emplace(name.toString(), dir.toString()); @@ -1579,12 +1556,11 @@ void PathSettings::setRecent(const std::map& map) } } -QString PathSettings::getConfigurablePath(const QString &key, - const QString &def, - bool resolve) const +QString PathSettings::getConfigurablePath(const QString& key, const QString& def, + bool resolve) const { QString result = QDir::fromNativeSeparators( - get(m_Settings, "Settings", key, makeDefaultPath(def))); + get(m_Settings, "Settings", key, makeDefaultPath(def))); if (resolve) { result = PathSettings::resolve(result, base()); @@ -1593,7 +1569,7 @@ QString PathSettings::getConfigurablePath(const QString &key, return result; } -void PathSettings::setConfigurablePath(const QString &key, const QString& path) +void PathSettings::setConfigurablePath(const QString& key, const QString& path) { if (path.isEmpty()) { remove(m_Settings, "Settings", key); @@ -1618,48 +1594,38 @@ QString PathSettings::base() const { const QString dataPath = QFileInfo(m_Settings.fileName()).dir().path(); - return QDir::fromNativeSeparators(get(m_Settings, - "Settings", "base_directory", dataPath)); + return QDir::fromNativeSeparators( + get(m_Settings, "Settings", "base_directory", dataPath)); } QString PathSettings::downloads(bool resolve) const { - return getConfigurablePath( - "download_directory", - ToQString(AppConfig::downloadPath()), - resolve); + return getConfigurablePath("download_directory", ToQString(AppConfig::downloadPath()), + resolve); } QString PathSettings::cache(bool resolve) const { - return getConfigurablePath( - "cache_directory", - ToQString(AppConfig::cachePath()), - resolve); + return getConfigurablePath("cache_directory", ToQString(AppConfig::cachePath()), + resolve); } QString PathSettings::mods(bool resolve) const { - return getConfigurablePath( - "mod_directory", - ToQString(AppConfig::modsPath()), - resolve); + return getConfigurablePath("mod_directory", ToQString(AppConfig::modsPath()), + resolve); } QString PathSettings::profiles(bool resolve) const { - return getConfigurablePath( - "profiles_directory", - ToQString(AppConfig::profilesPath()), - resolve); + return getConfigurablePath("profiles_directory", ToQString(AppConfig::profilesPath()), + resolve); } QString PathSettings::overwrite(bool resolve) const { - return getConfigurablePath( - "overwrite_directory", - ToQString(AppConfig::overwritePath()), - resolve); + return getConfigurablePath("overwrite_directory", + ToQString(AppConfig::overwritePath()), resolve); } void PathSettings::setBase(const QString& path) @@ -1696,9 +1662,8 @@ void PathSettings::setOverwrite(const QString& path) setConfigurablePath("overwrite_directory", path); } - NetworkSettings::NetworkSettings(QSettings& settings, bool globalInstance) - : m_Settings(settings) + : m_Settings(settings) { if (globalInstance) { updateCustomBrowser(); @@ -1746,9 +1711,8 @@ void NetworkSettings::setDownloadSpeed(const QString& name, int bytesPerSecond) } } - log::error( - "server '{}' not found while trying to add a download with bps {}", - name, bytesPerSecond); + log::error("server '{}' not found while trying to add a download with bps {}", name, + bytesPerSecond); } ServerList NetworkSettings::servers() const @@ -1771,14 +1735,12 @@ ServerList NetworkSettings::servers() const } ServerInfo server( - sra.get("name", ""), - sra.get("premium", false), - QDate::fromString(sra.get("lastSeen", ""), Qt::ISODate), - sra.get("preferred", 0), - lastDownloads); + sra.get("name", ""), sra.get("premium", false), + QDate::fromString(sra.get("lastSeen", ""), Qt::ISODate), + sra.get("preferred", 0), lastDownloads); list.add(std::move(server)); - }); + }); } return list; @@ -1796,7 +1758,6 @@ void NetworkSettings::updateServers(ServerList newServers) removeSection(m_Settings, "Servers"); } - ScopedWriteArray swa(m_Settings, "Servers", newServers.size()); for (const auto& server : newServers) { @@ -1875,12 +1836,8 @@ ServerList NetworkSettings::serversFromOldMap() const sg.for_each([&](auto&& serverKey) { QVariantMap data = sg.get(serverKey); - ServerInfo server( - serverKey, - data["premium"].toBool(), - data["lastSeen"].toDate(), - data["preferred"].toInt(), - {}); + ServerInfo server(serverKey, data["premium"].toBool(), data["lastSeen"].toDate(), + data["preferred"].toInt(), {}); // ignoring download count and speed, it's now a list of values instead of // a total @@ -1901,21 +1858,16 @@ void NetworkSettings::dump() const lastDownloads += QString("%1 ").arg(speed); } - log::debug( - " . {} premium={} lastSeen={} preferred={} lastDownloads={}", - server.name(), - server.isPremium() ? "yes" : "no", - server.lastSeen().toString(Qt::ISODate), - server.preferred(), - lastDownloads.trimmed()); + log::debug(" . {} premium={} lastSeen={} preferred={} lastDownloads={}", + server.name(), server.isPremium() ? "yes" : "no", + server.lastSeen().toString(Qt::ISODate), server.preferred(), + lastDownloads.trimmed()); } } - NexusSettings::NexusSettings(Settings& parent, QSettings& settings) - : m_Parent(parent), m_Settings(settings) -{ -} + : m_Parent(parent), m_Settings(settings) +{} bool NexusSettings::endorsementIntegration() const { @@ -1930,7 +1882,7 @@ void NexusSettings::setEndorsementIntegration(bool b) const EndorsementState NexusSettings::endorsementState() const { return endorsementStateFromString( - get(m_Settings, "General", "endorse_state", "")); + get(m_Settings, "General", "endorse_state", "")); } void NexusSettings::setEndorsementState(EndorsementState s) @@ -1956,13 +1908,12 @@ void NexusSettings::setTrackedIntegration(bool b) const void NexusSettings::registerAsNXMHandler(bool force) { - const auto nxmPath = - QCoreApplication::applicationDirPath() + "/" + - QString::fromStdWString(AppConfig::nxmHandlerExe()); + const auto nxmPath = QCoreApplication::applicationDirPath() + "/" + + QString::fromStdWString(AppConfig::nxmHandlerExe()); const auto executable = QCoreApplication::applicationFilePath(); - QString mode = force ? "forcereg" : "reg"; + QString mode = force ? "forcereg" : "reg"; QString parameters = mode + " " + m_Parent.game().plugin()->gameShortName(); for (const QString& altGame : m_Parent.game().plugin()->validShortNames()) { parameters += "," + altGame; @@ -1973,50 +1924,46 @@ void NexusSettings::registerAsNXMHandler(bool force) if (!r.success()) { QMessageBox::critical( - nullptr, QObject::tr("Failed"), - QObject::tr("Failed to start the helper application: %1").arg(r.toString())); + nullptr, QObject::tr("Failed"), + QObject::tr("Failed to start the helper application: %1").arg(r.toString())); } } std::vector NexusSettings::validationTimeouts() const { - using namespace std::chrono_literals; - - const auto s = get( - m_Settings, "Settings", "validation_timeouts", ""); + using namespace std::chrono_literals; - const auto numbers = s.split(" "); - std::vector v; + const auto s = get(m_Settings, "Settings", "validation_timeouts", ""); - for (auto ns : numbers) - { - ns = ns.trimmed(); - if (ns.isEmpty()) - continue; + const auto numbers = s.split(" "); + std::vector v; - bool ok = false; - const auto n = ns.toInt(&ok); + for (auto ns : numbers) { + ns = ns.trimmed(); + if (ns.isEmpty()) + continue; - if (!ok || n < 0 || n > 100) - { - log::error("bad validation_timeouts number '{}'", ns); - continue; - } + bool ok = false; + const auto n = ns.toInt(&ok); - v.push_back(std::chrono::seconds(n)); + if (!ok || n < 0 || n > 100) { + log::error("bad validation_timeouts number '{}'", ns); + continue; } - if (v.empty()) - v = {10s, 15s, 20s}; + v.push_back(std::chrono::seconds(n)); + } + + if (v.empty()) + v = {10s, 15s, 20s}; - return v; + return v; } void NexusSettings::dump() const { - const auto iniPath = - InstanceManager::singleton().globalInstancesRootPath() + "/" + - QString::fromStdWString(AppConfig::nxmHandlerIni()); + const auto iniPath = InstanceManager::singleton().globalInstancesRootPath() + "/" + + QString::fromStdWString(AppConfig::nxmHandlerIni()); if (!QFileInfo(iniPath).exists()) { log::debug("nxm ini not found at {}", iniPath); @@ -2024,14 +1971,15 @@ void NexusSettings::dump() const } QSettings s(iniPath, QSettings::IniFormat); - if (const auto st=s.status(); st != QSettings::NoError) { + if (const auto st = s.status(); st != QSettings::NoError) { log::debug("can't read nxm ini from {}", iniPath); return; } log::debug("nxmhandler settings:"); - QSettings handler("HKEY_CURRENT_USER\\Software\\Classes\\nxm\\", QSettings::NativeFormat); + QSettings handler("HKEY_CURRENT_USER\\Software\\Classes\\nxm\\", + QSettings::NativeFormat); log::debug(" . primary: {}", handler.value("shell/open/command/Default").toString()); const auto noregister = getOptional(s, "General", "noregister"); @@ -2045,9 +1993,9 @@ void NexusSettings::dump() const ScopedReadArray sra(s, "handlers"); sra.for_each([&] { - const auto games = sra.get("games"); + const auto games = sra.get("games"); const auto executable = sra.get("executable"); - const auto arguments = sra.get("arguments"); + const auto arguments = sra.get("arguments"); log::debug(" . handler:"); log::debug(" . games: {}", games.toString()); @@ -2056,16 +2004,14 @@ void NexusSettings::dump() const }); } - SteamSettings::SteamSettings(Settings& parent, QSettings& settings) - : m_Parent(parent), m_Settings(settings) -{ -} + : m_Parent(parent), m_Settings(settings) +{} QString SteamSettings::appID() const { - return get( - m_Settings, "Settings", "app_id", m_Parent.game().plugin()->steamAPPId()); + return get(m_Settings, "Settings", "app_id", + m_Parent.game().plugin()->steamAPPId()); } void SteamSettings::setAppID(const QString& id) @@ -2077,7 +2023,7 @@ void SteamSettings::setAppID(const QString& id) } } -bool SteamSettings::login(QString &username, QString &password) const +bool SteamSettings::login(QString& username, QString& password) const { username = get(m_Settings, "Settings", "steam_username", ""); password = getWindowsCredential("steam_password"); @@ -2100,11 +2046,7 @@ void SteamSettings::setLogin(QString username, QString password) } } - -InterfaceSettings::InterfaceSettings(QSettings& settings) - : m_Settings(settings) -{ -} +InterfaceSettings::InterfaceSettings(QSettings& settings) : m_Settings(settings) {} bool InterfaceSettings::lockGUI() const { @@ -2129,7 +2071,9 @@ void InterfaceSettings::setStyleName(const QString& name) bool InterfaceSettings::collapsibleSeparators(Qt::SortOrder order) const { return get(m_Settings, "Settings", - order == Qt::AscendingOrder ? "collapsible_separators_asc" : "collapsible_separators_dsc", true); + order == Qt::AscendingOrder ? "collapsible_separators_asc" + : "collapsible_separators_dsc", + true); } void InterfaceSettings::setCollapsibleSeparators(bool ascending, bool descending) @@ -2150,7 +2094,8 @@ void InterfaceSettings::setCollapsibleSeparatorsHighlightTo(bool b) bool InterfaceSettings::collapsibleSeparatorsHighlightFrom() const { - return get(m_Settings, "Settings", "collapsible_separators_conflicts_from", true); + return get(m_Settings, "Settings", "collapsible_separators_conflicts_from", + true); } void InterfaceSettings::setCollapsibleSeparatorsHighlightFrom(bool b) @@ -2160,12 +2105,14 @@ void InterfaceSettings::setCollapsibleSeparatorsHighlightFrom(bool b) bool InterfaceSettings::collapsibleSeparatorsIcons(int column) const { - return get(m_Settings, "Settings", QString("collapsible_separators_icons_%1").arg(column), true); + return get(m_Settings, "Settings", + QString("collapsible_separators_icons_%1").arg(column), true); } void InterfaceSettings::setCollapsibleSeparatorsIcons(int column, bool show) { - set(m_Settings, "Settings", QString("collapsible_separators_icons_%1").arg(column), show); + set(m_Settings, "Settings", QString("collapsible_separators_icons_%1").arg(column), + show); } bool InterfaceSettings::collapsibleSeparatorsPerProfile() const @@ -2327,9 +2274,11 @@ FilterWidget::Options InterfaceSettings::filterOptions() const FilterWidget::Options o; o.useRegex = get(m_Settings, "Settings", "filter_regex", false); - o.regexCaseSensitive = get(m_Settings, "Settings", "regex_case_sensitive", false); + o.regexCaseSensitive = + get(m_Settings, "Settings", "regex_case_sensitive", false); o.regexExtended = get(m_Settings, "Settings", "regex_extended", false); - o.scrollToSelection = get(m_Settings, "Settings", "filter_scroll_to_selection", false); + o.scrollToSelection = + get(m_Settings, "Settings", "filter_scroll_to_selection", false); return o; } @@ -2342,11 +2291,7 @@ void InterfaceSettings::setFilterOptions(const FilterWidget::Options& o) set(m_Settings, "Settings", "filter_scroll_to_selection", o.scrollToSelection); } - -DiagnosticsSettings::DiagnosticsSettings(QSettings& settings) - : m_Settings(settings) -{ -} +DiagnosticsSettings::DiagnosticsSettings(QSettings& settings) : m_Settings(settings) {} log::Levels DiagnosticsSettings::logLevel() const { @@ -2360,8 +2305,8 @@ void DiagnosticsSettings::setLogLevel(log::Levels level) lootcli::LogLevels DiagnosticsSettings::lootLogLevel() const { - return get( - m_Settings, "Settings", "loot_log_level", lootcli::LogLevels::Info); + return get(m_Settings, "Settings", "loot_log_level", + lootcli::LogLevels::Info); } void DiagnosticsSettings::setLootLogLevel(lootcli::LogLevels level) @@ -2371,8 +2316,8 @@ void DiagnosticsSettings::setLootLogLevel(lootcli::LogLevels level) env::CoreDumpTypes DiagnosticsSettings::coreDumpType() const { - return get(m_Settings, - "Settings", "crash_dumps_type", env::CoreDumpTypes::Mini); + return get(m_Settings, "Settings", "crash_dumps_type", + env::CoreDumpTypes::Mini); } void DiagnosticsSettings::setCoreDumpType(env::CoreDumpTypes type) @@ -2392,8 +2337,7 @@ void DiagnosticsSettings::setMaxCoreDumps(int n) std::chrono::seconds DiagnosticsSettings::spawnDelay() const { - return std::chrono::seconds( - get(m_Settings, "Settings", "spawn_delay", 0)); + return std::chrono::seconds(get(m_Settings, "Settings", "spawn_delay", 0)); } void DiagnosticsSettings::setSpawnDelay(std::chrono::seconds t) @@ -2401,11 +2345,10 @@ void DiagnosticsSettings::setSpawnDelay(std::chrono::seconds t) set(m_Settings, "Settings", "spawn_delay", t.count()); } - void GlobalSettings::updateRegistryKey() { - const QString OldOrganization = "Tannin"; - const QString OldApplication = "Mod Organizer"; + const QString OldOrganization = "Tannin"; + const QString OldApplication = "Mod Organizer"; const QString OldInstanceValue = "CurrentInstance"; const QString OldRootKey = "Software\\" + OldOrganization; @@ -2432,7 +2375,7 @@ void GlobalSettings::setCurrentInstance(const QString& s) QSettings GlobalSettings::settings() { const QString Organization = "Mod Organizer Team"; - const QString Application = "Mod Organizer"; + const QString Application = "Mod Organizer"; return QSettings(Organization, Application); } diff --git a/src/settings.h b/src/settings.h index 47eea96d9..8a4d5e171 100644 --- a/src/settings.h +++ b/src/settings.h @@ -22,28 +22,27 @@ along with Mod Organizer. If not, see . #include "envdump.h" #include +#include #include #include -#include #include #ifdef interface - #undef interface +#undef interface #endif -namespace MOBase { - class IPlugin; - class IPluginGame; - class ExpanderWidget; -} +namespace MOBase +{ +class IPlugin; +class IPluginGame; +class ExpanderWidget; +} // namespace MOBase class QSplitter; class ServerList; class Settings; - - // setting for the currently managed game // class GameSettings @@ -87,7 +86,6 @@ class GameSettings const MOBase::IPluginGame* m_GamePlugin; }; - // geometry settings for various widgets; this should contain any setting that // can get invalid through UI changes or when users change display settings // (resolution, monitors, etc.); see WidgetSettings for the counterpart @@ -111,7 +109,6 @@ class GeometrySettings void requestReset(); void resetIfNeeded(); - void saveGeometry(const QMainWindow* w); bool restoreGeometry(QMainWindow* w) const; @@ -131,7 +128,7 @@ class GeometrySettings bool restoreState(MOBase::ExpanderWidget* expander) const; void saveVisibility(const QWidget* w); - bool restoreVisibility(QWidget* w, std::optional def={}) const; + bool restoreVisibility(QWidget* w, std::optional def = {}) const; void saveToolbars(const QMainWindow* w); void restoreToolbars(QMainWindow* w) const; @@ -167,10 +164,9 @@ class GeometrySettings void ensureWindowOnScreen(QWidget* w) const; static void centerOnMonitor(QWidget* w, int monitor); - static void centerOnParent(QWidget* w, QWidget* parent=nullptr); + static void centerOnParent(QWidget* w, QWidget* parent = nullptr); }; - // widget settings that should stay valid regardless of UI changes or when users // change display settings (resolution, monitors, etc.); see GeometrySettings // for the counterpart @@ -198,36 +194,35 @@ class WidgetSettings // std::optional index(const QComboBox* cb) const; void saveIndex(const QComboBox* cb); - void restoreIndex(QComboBox* cb, std::optional def={}) const; + void restoreIndex(QComboBox* cb, std::optional def = {}) const; // selected tab index for a tab widget // std::optional index(const QTabWidget* w) const; void saveIndex(const QTabWidget* w); - void restoreIndex(QTabWidget* w, std::optional def={}) const; + void restoreIndex(QTabWidget* w, std::optional def = {}) const; // check state for a checkable button // std::optional checked(const QAbstractButton* w) const; void saveChecked(const QAbstractButton* w); - void restoreChecked(QAbstractButton* w, std::optional def={}) const; + void restoreChecked(QAbstractButton* w, std::optional def = {}) const; // returns the remembered button for a question dialog, or NoButton if the // user hasn't saved the choice // - MOBase::QuestionBoxMemory::Button questionButton( - const QString& windowName, const QString& filename) const; + MOBase::QuestionBoxMemory::Button questionButton(const QString& windowName, + const QString& filename) const; // sets the button to be remembered for the given window // - void setQuestionWindowButton( - const QString& windowName, MOBase::QuestionBoxMemory::Button button); + void setQuestionWindowButton(const QString& windowName, + MOBase::QuestionBoxMemory::Button button); // sets the button to be remembered for the given file // - void setQuestionFileButton( - const QString& windowName, const QString& filename, - MOBase::QuestionBoxMemory::Button choice); + void setQuestionFileButton(const QString& windowName, const QString& filename, + MOBase::QuestionBoxMemory::Button choice); // wipes all the remembered buttons // @@ -237,7 +232,6 @@ class WidgetSettings QSettings& m_Settings; }; - // various color settings // class ColorSettings @@ -261,7 +255,7 @@ class ColorSettings void setModlistContainsPlugin(const QColor& c); QColor pluginListContained() const; - void setPluginListContained(const QColor& c) ; + void setPluginListContained(const QColor& c); std::optional previousSeparatorColor() const; void setPreviousSeparatorColor(const QColor& c) const; @@ -281,68 +275,65 @@ class ColorSettings QSettings& m_Settings; }; - // settings about plugins // -class PluginSettings: public QObject +class PluginSettings : public QObject { Q_OBJECT public: PluginSettings(QSettings& settings); - // forgets all the plugins // void clearPlugins(); // adds/removes the given plugin to the list and loads all of its settings // - void registerPlugin(MOBase::IPlugin *plugin); + void registerPlugin(MOBase::IPlugin* plugin); void unregisterPlugin(MOBase::IPlugin* plugin); // returns all the registered plugins // std::vector plugins() const; - // returns the plugin setting for the given key // - QVariant setting(const QString &pluginName, const QString &key) const; + QVariant setting(const QString& pluginName, const QString& key) const; // sets the plugin setting for the given key // - void setSetting(const QString &pluginName, const QString &key, const QVariant &value); + void setSetting(const QString& pluginName, const QString& key, const QVariant& value); // returns all settings // - QVariantMap settings(const QString &pluginName) const; + QVariantMap settings(const QString& pluginName) const; // overwrites all settings // - void setSettings(const QString &pluginName, const QVariantMap& map); + void setSettings(const QString& pluginName, const QVariantMap& map); // returns all descriptions // - QVariantMap descriptions(const QString &pluginName) const; + QVariantMap descriptions(const QString& pluginName) const; // overwrites all descriptions // - void setDescriptions(const QString &pluginName, const QVariantMap& map); - + void setDescriptions(const QString& pluginName, const QVariantMap& map); // ? - QVariant persistent(const QString &pluginName, const QString &key, const QVariant &def) const; - void setPersistent(const QString &pluginName, const QString &key, const QVariant &value, bool sync); - + QVariant persistent(const QString& pluginName, const QString& key, + const QVariant& def) const; + void setPersistent(const QString& pluginName, const QString& key, + const QVariant& value, bool sync); // adds the given plugin to the blacklist // - void addBlacklist(const QString &fileName); + void addBlacklist(const QString& fileName); // returns whether the given plugin is blacklisted // - bool blacklisted(const QString &fileName) const; + bool blacklisted(const QString& fileName) const; // overwrites the whole blacklist // @@ -352,7 +343,6 @@ class PluginSettings: public QObject // const QSet& blacklist() const; - // commits all the settings to the ini // void save(); @@ -362,7 +352,8 @@ class PluginSettings: public QObject /** * Emitted when a plugin setting changes. */ - void pluginSettingChanged(QString const& pluginName, const QString& key, const QVariant& oldValue, const QVariant& newValue); + void pluginSettingChanged(QString const& pluginName, const QString& key, + const QVariant& oldValue, const QVariant& newValue); private: QSettings& m_Settings; @@ -380,7 +371,6 @@ class PluginSettings: public QObject QSet readBlacklist() const; }; - // paths for the game and various components // // if the 'resolve' parameter is true, %BASE_DIR% is expanded; it's set to @@ -412,14 +402,12 @@ class PathSettings QString overwrite(bool resolve = true) const; void setOverwrite(const QString& path); - // map of names to directories, used to remember the last directory used in // various file pickers // std::map recent() const; void setRecent(const std::map& map); - // resolves %BASE_DIR% // static QString resolve(const QString& path, const QString& baseDir); @@ -431,11 +419,11 @@ class PathSettings private: QSettings& m_Settings; - QString getConfigurablePath(const QString &key, const QString &def, bool resolve) const; - void setConfigurablePath(const QString &key, const QString& path); + QString getConfigurablePath(const QString& key, const QString& def, + bool resolve) const; + void setConfigurablePath(const QString& key, const QString& path); }; - class NetworkSettings { public: @@ -458,7 +446,7 @@ class NetworkSettings // remembers the last couple of download speeds and displays the average in // the network settings // - void setDownloadSpeed(const QString &serverName, int bytesPerSecond); + void setDownloadSpeed(const QString& serverName, int bytesPerSecond); // known servers // @@ -496,7 +484,6 @@ class NetworkSettings void updateCustomBrowser(); }; - enum class EndorsementState { Accepted = 1, @@ -507,7 +494,6 @@ enum class EndorsementState EndorsementState endorsementStateFromString(const QString& s); QString toString(EndorsementState s); - class NexusSettings { public: @@ -546,7 +532,6 @@ class NexusSettings QSettings& m_Settings; }; - class SteamSettings { public: @@ -573,7 +558,7 @@ class SteamSettings // // returns whether _both_ the username and password have a value // - bool login(QString &username, QString &password) const; + bool login(QString& username, QString& password) const; // sets the steam login; the username is saved in the ini file and the // password in the credentials store @@ -587,7 +572,6 @@ class SteamSettings QSettings& m_Settings; }; - class InterfaceSettings { public: @@ -681,7 +665,7 @@ class InterfaceSettings // whether the given tutorial has been completed // bool isTutorialCompleted(const QString& windowName) const; - void setTutorialCompleted(const QString& windowName, bool b=true); + void setTutorialCompleted(const QString& windowName, bool b = true); // whether to show the confirmation when switching instances // @@ -707,7 +691,6 @@ class InterfaceSettings QSettings& m_Settings; }; - class DiagnosticsSettings { public: @@ -739,7 +722,6 @@ class DiagnosticsSettings QSettings& m_Settings; }; - // manages the settings for MO; the settings are accessed directly through a // QSettings and so are not cached here // @@ -762,10 +744,9 @@ class Settings : public QObject // @param globalInsance whether this is the global instance; creates the // singleton and asserts if it already exists // - Settings(const QString& path, bool globalInstance=false); + Settings(const QString& path, bool globalInstance = false); ~Settings(); - // throws if there is no global Settings instance // static Settings& instance(); @@ -774,7 +755,6 @@ class Settings : public QObject // static Settings* maybeInstance(); - // name of the ini file // QString filename() const; @@ -886,16 +866,16 @@ class Settings : public QObject public slots: // this slot is connected to by various parts of MO // - void managedGameChanged(MOBase::IPluginGame const *gamePlugin); + void managedGameChanged(MOBase::IPluginGame const* gamePlugin); signals: // these are fired from outside the settings, mostly by the settings dialog // - void languageChanged(const QString &newLanguage); - void styleChanged(const QString &newStyle); + void languageChanged(const QString& newLanguage); + void styleChanged(const QString& newStyle); private: - static Settings *s_Instance; + static Settings* s_Instance; mutable QSettings m_Settings; GameSettings m_Game; @@ -911,7 +891,6 @@ public slots: DiagnosticsSettings m_Diagnostics; }; - // manages global settings in the registry // class GlobalSettings @@ -957,7 +936,6 @@ class GlobalSettings static QSettings settings(); }; - // helper class that calls restoreGeometry() in the constructor and // saveGeometry() in the destructor // @@ -965,20 +943,16 @@ template class GeometrySaver { public: - GeometrySaver(Settings& s, W* w) - : m_settings(s), m_widget(w) + GeometrySaver(Settings& s, W* w) : m_settings(s), m_widget(w) { m_settings.geometry().restoreGeometry(m_widget); } - ~GeometrySaver() - { - m_settings.geometry().saveGeometry(m_widget); - } + ~GeometrySaver() { m_settings.geometry().saveGeometry(m_widget); } private: Settings& m_settings; W* m_widget; }; -#endif // SETTINGS_H +#endif // SETTINGS_H diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index 0a93d5be3..3a44f2844 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -18,35 +18,38 @@ along with Mod Organizer. If not, see . */ #include "settingsdialog.h" -#include "ui_settingsdialog.h" #include "settingsdialogdiagnostics.h" #include "settingsdialoggeneral.h" +#include "settingsdialogmodlist.h" #include "settingsdialognexus.h" #include "settingsdialogpaths.h" #include "settingsdialogplugins.h" -#include "settingsdialogmodlist.h" #include "settingsdialogtheme.h" #include "settingsdialogworkarounds.h" +#include "ui_settingsdialog.h" using namespace MOBase; -SettingsDialog::SettingsDialog(PluginContainer *pluginContainer, Settings& settings, QWidget *parent) - : TutorableDialog("SettingsDialog", parent) - , ui(new Ui::SettingsDialog) - , m_settings(settings) - , m_exit(Exit::None) - , m_pluginContainer(pluginContainer) +SettingsDialog::SettingsDialog(PluginContainer* pluginContainer, Settings& settings, + QWidget* parent) + : TutorableDialog("SettingsDialog", parent), ui(new Ui::SettingsDialog), + m_settings(settings), m_exit(Exit::None), m_pluginContainer(pluginContainer) { ui->setupUi(this); - m_tabs.push_back(std::unique_ptr(new GeneralSettingsTab(settings, m_pluginContainer, *this))); + m_tabs.push_back( + std::unique_ptr(new GeneralSettingsTab(settings, m_pluginContainer, *this))); m_tabs.push_back(std::unique_ptr(new ThemeSettingsTab(settings, *this))); - m_tabs.push_back(std::unique_ptr(new ModListSettingsTab(settings, *this))); + m_tabs.push_back( + std::unique_ptr(new ModListSettingsTab(settings, *this))); m_tabs.push_back(std::unique_ptr(new PathsSettingsTab(settings, *this))); - m_tabs.push_back(std::unique_ptr(new DiagnosticsSettingsTab(settings, *this))); + m_tabs.push_back( + std::unique_ptr(new DiagnosticsSettingsTab(settings, *this))); m_tabs.push_back(std::unique_ptr(new NexusSettingsTab(settings, *this))); - m_tabs.push_back(std::unique_ptr(new PluginsSettingsTab(settings, m_pluginContainer, *this))); - m_tabs.push_back(std::unique_ptr(new WorkaroundsSettingsTab(settings, *this))); + m_tabs.push_back(std::unique_ptr( + new PluginsSettingsTab(settings, m_pluginContainer, *this))); + m_tabs.push_back( + std::unique_ptr(new WorkaroundsSettingsTab(settings, *this))); } PluginContainer* SettingsDialog::pluginContainer() @@ -89,7 +92,7 @@ int SettingsDialog::exec() } // update settings for each tab - for (std::unique_ptr const &tab: m_tabs) { + for (std::unique_ptr const& tab : m_tabs) { tab->update(); } } @@ -106,23 +109,22 @@ SettingsDialog::~SettingsDialog() QString SettingsDialog::getColoredButtonStyleSheet() const { return QString("QPushButton {" - "background-color: %1;" - "color: %2;" - "border: 1px solid;" - "padding: 3px;" - "}"); + "background-color: %1;" + "color: %2;" + "border: 1px solid;" + "padding: 3px;" + "}"); } void SettingsDialog::accept() { QString newModPath = ui->modDirEdit->text(); - newModPath = PathSettings::resolve(newModPath, ui->baseDirEdit->text()); + newModPath = PathSettings::resolve(newModPath, ui->baseDirEdit->text()); if ((QDir::fromNativeSeparators(newModPath) != - QDir::fromNativeSeparators( - Settings::instance().paths().mods(true))) && + QDir::fromNativeSeparators(Settings::instance().paths().mods(true))) && (QMessageBox::question( - parentWidgetForDialogs(), tr("Confirm"), + parentWidgetForDialogs(), tr("Confirm"), tr("Changing the mod directory affects all your profiles! " "Mods not present (or named differently) in the new location " "will be disabled in all profiles. " @@ -135,11 +137,9 @@ void SettingsDialog::accept() TutorableDialog::accept(); } - SettingsTab::SettingsTab(Settings& s, SettingsDialog& d) - : ui(d.ui), m_settings(s), m_dialog(d) -{ -} + : ui(d.ui), m_settings(s), m_dialog(d) +{} SettingsTab::~SettingsTab() = default; diff --git a/src/settingsdialog.h b/src/settingsdialog.h index 160f0748c..eca0b2662 100644 --- a/src/settingsdialog.h +++ b/src/settingsdialog.h @@ -20,14 +20,16 @@ along with Mod Organizer. If not, see . #ifndef SETTINGSDIALOG_H #define SETTINGSDIALOG_H -#include "tutorabledialog.h" #include "shared/util.h" +#include "tutorabledialog.h" class PluginContainer; class Settings; class SettingsDialog; -namespace Ui { class SettingsDialog; } - +namespace Ui +{ +class SettingsDialog; +} class SettingsTab { @@ -50,7 +52,6 @@ class SettingsTab SettingsDialog& m_dialog; }; - /** * dialog used to change settings for Mod Organizer. On top of the * settings managed by the "Settings" class, this offers a button to open the @@ -58,19 +59,19 @@ class SettingsTab **/ class SettingsDialog : public MOBase::TutorableDialog { - Q_OBJECT; - friend class SettingsTab; + Q_OBJECT; + friend class SettingsTab; public: - explicit SettingsDialog( - PluginContainer* pluginContainer, Settings& settings, QWidget* parent = 0); + explicit SettingsDialog(PluginContainer* pluginContainer, Settings& settings, + QWidget* parent = 0); ~SettingsDialog(); /** - * @brief get stylesheet of settings buttons with colored background - * @return string of stylesheet - */ + * @brief get stylesheet of settings buttons with colored background + * @return string of stylesheet + */ QString getColoredButtonStyleSheet() const; PluginContainer* pluginContainer(); @@ -92,4 +93,4 @@ public slots: PluginContainer* m_pluginContainer; }; -#endif // SETTINGSDIALOG_H +#endif // SETTINGSDIALOG_H diff --git a/src/settingsdialogdiagnostics.cpp b/src/settingsdialogdiagnostics.cpp index 33175a669..012a33488 100644 --- a/src/settingsdialogdiagnostics.cpp +++ b/src/settingsdialogdiagnostics.cpp @@ -1,13 +1,13 @@ #include "settingsdialogdiagnostics.h" -#include "ui_settingsdialog.h" -#include "shared/appconfig.h" #include "organizercore.h" +#include "shared/appconfig.h" +#include "ui_settingsdialog.h" #include using namespace MOBase; DiagnosticsSettingsTab::DiagnosticsSettingsTab(Settings& s, SettingsDialog& d) - : SettingsTab(s, d) + : SettingsTab(s, d) { setLogLevel(); setLootLogLevel(); @@ -15,16 +15,16 @@ DiagnosticsSettingsTab::DiagnosticsSettingsTab(Settings& s, SettingsDialog& d) ui->dumpsMaxEdit->setValue(settings().diagnostics().maxCoreDumps()); - QString logsPath = qApp->property("dataPath").toString() - + "/" + QString::fromStdWString(AppConfig::logPath()); + QString logsPath = qApp->property("dataPath").toString() + "/" + + QString::fromStdWString(AppConfig::logPath()); ui->diagnosticsExplainedLabel->setText( - ui->diagnosticsExplainedLabel->text() - .replace("LOGS_FULL_PATH", logsPath) - .replace("LOGS_DIR", QString::fromStdWString(AppConfig::logPath())) - .replace("DUMPS_FULL_PATH", QString::fromStdWString(OrganizerCore::getGlobalCoreDumpPath())) - .replace("DUMPS_DIR", QString::fromStdWString(AppConfig::dumpsDir())) - ); + ui->diagnosticsExplainedLabel->text() + .replace("LOGS_FULL_PATH", logsPath) + .replace("LOGS_DIR", QString::fromStdWString(AppConfig::logPath())) + .replace("DUMPS_FULL_PATH", + QString::fromStdWString(OrganizerCore::getGlobalCoreDumpPath())) + .replace("DUMPS_DIR", QString::fromStdWString(AppConfig::dumpsDir()))); } void DiagnosticsSettingsTab::setLogLevel() @@ -38,7 +38,7 @@ void DiagnosticsSettingsTab::setLogLevel() const auto sel = settings().diagnostics().logLevel(); - for (int i=0; ilogLevelBox->count(); ++i) { + for (int i = 0; i < ui->logLevelBox->count(); ++i) { if (ui->logLevelBox->itemData(i) == sel) { ui->logLevelBox->setCurrentIndex(i); break; @@ -50,7 +50,9 @@ void DiagnosticsSettingsTab::setLootLogLevel() { using L = lootcli::LogLevels; - auto v = [](L level) { return QVariant(static_cast(level)); }; + auto v = [](L level) { + return QVariant(static_cast(level)); + }; ui->lootLogLevel->clear(); @@ -62,7 +64,7 @@ void DiagnosticsSettingsTab::setLootLogLevel() const auto sel = settings().diagnostics().lootLogLevel(); - for (int i=0; ilootLogLevel->count(); ++i) { + for (int i = 0; i < ui->lootLogLevel->count(); ++i) { if (ui->lootLogLevel->itemData(i) == v(sel)) { ui->lootLogLevel->setCurrentIndex(i); break; @@ -83,10 +85,9 @@ void DiagnosticsSettingsTab::setCrashDumpTypesBox() add(QObject::tr("Data"), env::CoreDumpTypes::Data); add(QObject::tr("Full"), env::CoreDumpTypes::Full); - const auto current = static_cast( - settings().diagnostics().coreDumpType()); + const auto current = static_cast(settings().diagnostics().coreDumpType()); - for (int i=0; idumpsTypeBox->count(); ++i) { + for (int i = 0; i < ui->dumpsTypeBox->count(); ++i) { if (ui->dumpsTypeBox->itemData(i) == current) { ui->dumpsTypeBox->setCurrentIndex(i); break; @@ -97,13 +98,13 @@ void DiagnosticsSettingsTab::setCrashDumpTypesBox() void DiagnosticsSettingsTab::update() { settings().diagnostics().setLogLevel( - static_cast(ui->logLevelBox->currentData().toInt())); + static_cast(ui->logLevelBox->currentData().toInt())); settings().diagnostics().setCoreDumpType( - static_cast(ui->dumpsTypeBox->currentData().toInt())); + static_cast(ui->dumpsTypeBox->currentData().toInt())); settings().diagnostics().setMaxCoreDumps(ui->dumpsMaxEdit->value()); settings().diagnostics().setLootLogLevel( - static_cast(ui->lootLogLevel->currentData().toInt())); + static_cast(ui->lootLogLevel->currentData().toInt())); } diff --git a/src/settingsdialogdiagnostics.h b/src/settingsdialogdiagnostics.h index e01ee22f9..7818d4bf0 100644 --- a/src/settingsdialogdiagnostics.h +++ b/src/settingsdialogdiagnostics.h @@ -17,4 +17,4 @@ class DiagnosticsSettingsTab : public SettingsTab void setCrashDumpTypesBox(); }; -#endif // SETTINGSDIALOGDIAGNOSTICS_H +#endif // SETTINGSDIALOGDIAGNOSTICS_H diff --git a/src/settingsdialoggeneral.cpp b/src/settingsdialoggeneral.cpp index a4f4af58f..b4893fab3 100644 --- a/src/settingsdialoggeneral.cpp +++ b/src/settingsdialoggeneral.cpp @@ -1,14 +1,14 @@ #include "settingsdialoggeneral.h" -#include "ui_settingsdialog.h" -#include "shared/appconfig.h" #include "categoriesdialog.h" #include "colortable.h" -#include +#include "shared/appconfig.h" +#include "ui_settingsdialog.h" #include +#include using namespace MOBase; -GeneralSettingsTab::GeneralSettingsTab(Settings& s, PluginContainer *pluginContainer, SettingsDialog& d) +GeneralSettingsTab::GeneralSettingsTab(Settings& s, PluginContainer* pluginContainer, SettingsDialog& d) : SettingsTab(s, d), m_PluginContainer(pluginContainer) { // language @@ -18,7 +18,8 @@ GeneralSettingsTab::GeneralSettingsTab(Settings& s, PluginContainer *pluginConta // download list ui->compactBox->setChecked(settings().interface().compactDownloads()); ui->showMetaBox->setChecked(settings().interface().metaDownloads()); - ui->hideDownloadInstallBox->setChecked(settings().interface().hideDownloadsAfterInstallation()); + ui->hideDownloadInstallBox->setChecked( + settings().interface().hideDownloadsAfterInstallation()); // updates ui->checkForUpdates->setChecked(settings().checkForUpdates()); @@ -26,23 +27,27 @@ GeneralSettingsTab::GeneralSettingsTab(Settings& s, PluginContainer *pluginConta // miscellaneous ui->centerDialogs->setChecked(settings().geometry().centerDialogs()); - ui->changeGameConfirmation->setChecked(settings().interface().showChangeGameConfirmation()); + ui->changeGameConfirmation->setChecked( + settings().interface().showChangeGameConfirmation()); ui->showMenubarOnAlt->setChecked(settings().interface().showMenubarOnAlt()); - ui->doubleClickPreviews->setChecked(settings().interface().doubleClicksOpenPreviews()); + ui->doubleClickPreviews->setChecked( + settings().interface().doubleClicksOpenPreviews()); - QObject::connect( - ui->categoriesBtn, &QPushButton::clicked, [&]{ onEditCategories(); }); + QObject::connect(ui->categoriesBtn, &QPushButton::clicked, [&] { + onEditCategories(); + }); - QObject::connect( - ui->resetDialogsButton, &QPushButton::clicked, [&]{ onResetDialogs(); }); + QObject::connect(ui->resetDialogsButton, &QPushButton::clicked, [&] { + onResetDialogs(); + }); } void GeneralSettingsTab::update() { // language const QString oldLanguage = settings().interface().language(); - const QString newLanguage = ui->languageBox->itemData( - ui->languageBox->currentIndex()).toString(); + const QString newLanguage = + ui->languageBox->itemData(ui->languageBox->currentIndex()).toString(); if (newLanguage != oldLanguage) { settings().interface().setLanguage(newLanguage); @@ -52,7 +57,8 @@ void GeneralSettingsTab::update() // download list settings().interface().setCompactDownloads(ui->compactBox->isChecked()); settings().interface().setMetaDownloads(ui->showMetaBox->isChecked()); - settings().interface().setHideDownloadsAfterInstallation(ui->hideDownloadInstallBox->isChecked()); + settings().interface().setHideDownloadsAfterInstallation( + ui->hideDownloadInstallBox->isChecked()); // updates settings().setCheckForUpdates(ui->checkForUpdates->isChecked()); @@ -60,23 +66,23 @@ void GeneralSettingsTab::update() // miscellaneous settings().geometry().setCenterDialogs(ui->centerDialogs->isChecked()); - settings().interface().setShowChangeGameConfirmation(ui->changeGameConfirmation->isChecked()); + settings().interface().setShowChangeGameConfirmation( + ui->changeGameConfirmation->isChecked()); settings().interface().setShowMenubarOnAlt(ui->showMenubarOnAlt->isChecked()); - settings().interface().setDoubleClicksOpenPreviews(ui->doubleClickPreviews->isChecked()); + settings().interface().setDoubleClicksOpenPreviews( + ui->doubleClickPreviews->isChecked()); } void GeneralSettingsTab::addLanguages() { // matches the end of filenames for something like "_en.qm" or "_zh_CN.qm" - const QString pattern = - QString::fromStdWString(AppConfig::translationPrefix()) + - "_([a-z]{2,3}(_[A-Z]{2,2})?).qm"; + const QString pattern = QString::fromStdWString(AppConfig::translationPrefix()) + + "_([a-z]{2,3}(_[A-Z]{2,2})?).qm"; const QRegularExpression exp(QRegularExpression::anchoredPattern(pattern)); - QDirIterator iter( - QCoreApplication::applicationDirPath() + "/translations", - QDir::Files); + QDirIterator iter(QCoreApplication::applicationDirPath() + "/translations", + QDir::Files); std::vector> languages; @@ -84,7 +90,7 @@ void GeneralSettingsTab::addLanguages() iter.next(); const QString file = iter.fileName(); - auto match = exp.match(file); + auto match = exp.match(file); if (!match.hasMatch()) { continue; } @@ -93,8 +99,8 @@ void GeneralSettingsTab::addLanguages() const QLocale locale(languageCode); QString languageString = QString("%1 (%2)") - .arg(locale.nativeLanguageName()) - .arg(locale.nativeCountryName()); + .arg(locale.nativeLanguageName()) + .arg(locale.nativeCountryName()); if (locale.language() == QLocale::Chinese) { if (languageCode == "zh_TW") { @@ -152,12 +158,11 @@ void GeneralSettingsTab::onEditCategories() void GeneralSettingsTab::onResetDialogs() { const auto r = QMessageBox::question( - parentWidget(), - QObject::tr("Confirm?"), - QObject::tr( - "This will reset all the choices you made to dialogs and make them all " - "visible again. Continue?"), - QMessageBox::Yes | QMessageBox::No); + parentWidget(), QObject::tr("Confirm?"), + QObject::tr( + "This will reset all the choices you made to dialogs and make them all " + "visible again. Continue?"), + QMessageBox::Yes | QMessageBox::No); if (r == QMessageBox::Yes) { resetDialogs(); diff --git a/src/settingsdialoggeneral.h b/src/settingsdialoggeneral.h index a4c484391..ffbeb50cf 100644 --- a/src/settingsdialoggeneral.h +++ b/src/settingsdialoggeneral.h @@ -1,8 +1,8 @@ #ifndef SETTINGSDIALOGGENERAL_H #define SETTINGSDIALOGGENERAL_H -#include "settingsdialog.h" #include "settings.h" +#include "settingsdialog.h" #include "plugincontainer.h" class GeneralSettingsTab : public SettingsTab @@ -26,4 +26,4 @@ class GeneralSettingsTab : public SettingsTab }; -#endif // SETTINGSDIALOGGENERAL_H +#endif // SETTINGSDIALOGGENERAL_H diff --git a/src/settingsdialogmodlist.cpp b/src/settingsdialogmodlist.cpp index 4c04e3f5b..394111bd0 100644 --- a/src/settingsdialogmodlist.cpp +++ b/src/settingsdialogmodlist.cpp @@ -1,42 +1,50 @@ #include "settingsdialogmodlist.h" -#include "ui_settingsdialog.h" -#include "shared/appconfig.h" #include "categoriesdialog.h" #include "colortable.h" #include "modlist.h" -#include +#include "shared/appconfig.h" +#include "ui_settingsdialog.h" #include +#include using namespace MOBase; ModListSettingsTab::ModListSettingsTab(Settings& s, SettingsDialog& d) - : SettingsTab(s, d), - m_columnToBox{ - { ModList::COL_CONFLICTFLAGS, ui->collapsibleSeparatorsIconsConflictsBox }, - { ModList::COL_FLAGS, ui->collapsibleSeparatorsIconsFlagsBox }, - { ModList::COL_CONTENT, ui->collapsibleSeparatorsIconsContentsBox}, - { ModList::COL_VERSION, ui->collapsibleSeparatorsIconsVersionBox } - } + : SettingsTab(s, d), + m_columnToBox{ + {ModList::COL_CONFLICTFLAGS, ui->collapsibleSeparatorsIconsConflictsBox}, + {ModList::COL_FLAGS, ui->collapsibleSeparatorsIconsFlagsBox}, + {ModList::COL_CONTENT, ui->collapsibleSeparatorsIconsContentsBox}, + {ModList::COL_VERSION, ui->collapsibleSeparatorsIconsVersionBox}} { // connect before setting to trigger - QObject::connect(ui->collapsibleSeparatorsAscBox, &QCheckBox::toggled, [=] { updateCollapsibleSeparatorsGroup(); }); - QObject::connect(ui->collapsibleSeparatorsDscBox, &QCheckBox::toggled, [=] { updateCollapsibleSeparatorsGroup(); }); + QObject::connect(ui->collapsibleSeparatorsAscBox, &QCheckBox::toggled, [=] { + updateCollapsibleSeparatorsGroup(); + }); + QObject::connect(ui->collapsibleSeparatorsDscBox, &QCheckBox::toggled, [=] { + updateCollapsibleSeparatorsGroup(); + }); ui->colorSeparatorsBox->setChecked(settings().colors().colorSeparatorScrollbar()); ui->displayForeignBox->setChecked(settings().interface().displayForeign()); - ui->collapsibleSeparatorsAscBox->setChecked(settings().interface().collapsibleSeparators(Qt::AscendingOrder)); - ui->collapsibleSeparatorsDscBox->setChecked(settings().interface().collapsibleSeparators(Qt::DescendingOrder)); - ui->collapsibleSeparatorsHighlightFromBox->setChecked(settings().interface().collapsibleSeparatorsHighlightFrom()); - ui->collapsibleSeparatorsHighlightToBox->setChecked(settings().interface().collapsibleSeparatorsHighlightTo()); - ui->collapsibleSeparatorsPerProfileBox->setChecked(settings().interface().collapsibleSeparatorsPerProfile()); + ui->collapsibleSeparatorsAscBox->setChecked( + settings().interface().collapsibleSeparators(Qt::AscendingOrder)); + ui->collapsibleSeparatorsDscBox->setChecked( + settings().interface().collapsibleSeparators(Qt::DescendingOrder)); + ui->collapsibleSeparatorsHighlightFromBox->setChecked( + settings().interface().collapsibleSeparatorsHighlightFrom()); + ui->collapsibleSeparatorsHighlightToBox->setChecked( + settings().interface().collapsibleSeparatorsHighlightTo()); + ui->collapsibleSeparatorsPerProfileBox->setChecked( + settings().interface().collapsibleSeparatorsPerProfile()); ui->saveFiltersBox->setChecked(settings().interface().saveFilters()); ui->autoCollapseDelayBox->setChecked(settings().interface().autoCollapseOnHover()); - ui->checkUpdateInstallBox->setChecked(settings().interface().checkUpdateAfterInstallation()); + ui->checkUpdateInstallBox->setChecked( + settings().interface().checkUpdateAfterInstallation()); for (auto& p : m_columnToBox) { p.second->setChecked(settings().interface().collapsibleSeparatorsIcons(p.first)); } - } void ModListSettingsTab::update() @@ -45,23 +53,29 @@ void ModListSettingsTab::update() settings().colors().setColorSeparatorScrollbar(ui->colorSeparatorsBox->isChecked()); settings().interface().setDisplayForeign(ui->displayForeignBox->isChecked()); settings().interface().setCollapsibleSeparators( - ui->collapsibleSeparatorsAscBox->isChecked(), ui->collapsibleSeparatorsDscBox->isChecked()); - settings().interface().setCollapsibleSeparatorsHighlightFrom(ui->collapsibleSeparatorsHighlightFromBox->isChecked()); - settings().interface().setCollapsibleSeparatorsHighlightTo(ui->collapsibleSeparatorsHighlightToBox->isChecked()); - settings().interface().setCollapsibleSeparatorsPerProfile(ui->collapsibleSeparatorsPerProfileBox->isChecked()); + ui->collapsibleSeparatorsAscBox->isChecked(), + ui->collapsibleSeparatorsDscBox->isChecked()); + settings().interface().setCollapsibleSeparatorsHighlightFrom( + ui->collapsibleSeparatorsHighlightFromBox->isChecked()); + settings().interface().setCollapsibleSeparatorsHighlightTo( + ui->collapsibleSeparatorsHighlightToBox->isChecked()); + settings().interface().setCollapsibleSeparatorsPerProfile( + ui->collapsibleSeparatorsPerProfileBox->isChecked()); settings().interface().setSaveFilters(ui->saveFiltersBox->isChecked()); settings().interface().setAutoCollapseOnHover(ui->autoCollapseDelayBox->isChecked()); - settings().interface().setCheckUpdateAfterInstallation(ui->checkUpdateInstallBox->isChecked()); + settings().interface().setCheckUpdateAfterInstallation( + ui->checkUpdateInstallBox->isChecked()); for (auto& p : m_columnToBox) { - settings().interface().setCollapsibleSeparatorsIcons(p.first, p.second->isChecked()); + settings().interface().setCollapsibleSeparatorsIcons(p.first, + p.second->isChecked()); } } void ModListSettingsTab::updateCollapsibleSeparatorsGroup() { const auto checked = ui->collapsibleSeparatorsAscBox->isChecked() || - ui->collapsibleSeparatorsDscBox->isChecked(); + ui->collapsibleSeparatorsDscBox->isChecked(); for (auto* widget : ui->collapsibleSeparatorsWidget->findChildren()) { widget->setEnabled(checked); } diff --git a/src/settingsdialogmodlist.h b/src/settingsdialogmodlist.h index 0c0510b50..4489eacdf 100644 --- a/src/settingsdialogmodlist.h +++ b/src/settingsdialogmodlist.h @@ -3,8 +3,8 @@ #include -#include "settingsdialog.h" #include "settings.h" +#include "settingsdialog.h" class ModListSettingsTab : public SettingsTab { @@ -20,8 +20,7 @@ protected slots: void updateCollapsibleSeparatorsGroup(); private: - const std::map m_columnToBox; }; -#endif // SETTINGSDIALOGGENERAL_H +#endif // SETTINGSDIALOGGENERAL_H diff --git a/src/settingsdialognexus.cpp b/src/settingsdialognexus.cpp index 364c6aa77..2e706e42c 100644 --- a/src/settingsdialognexus.cpp +++ b/src/settingsdialognexus.cpp @@ -1,39 +1,49 @@ #include "settingsdialognexus.h" -#include "ui_settingsdialog.h" -#include "ui_nexusmanualkey.h" +#include "log.h" #include "nexusinterface.h" #include "serverinfo.h" -#include "log.h" +#include "ui_nexusmanualkey.h" +#include "ui_settingsdialog.h" #include using namespace MOBase; template -class ServerItem : public QListWidgetItem { +class ServerItem : public QListWidgetItem +{ public: - ServerItem(const QString &text, int sortRole = Qt::DisplayRole, QListWidget *parent = 0, int type = Type) - : QListWidgetItem(text, parent, type), m_SortRole(sortRole) {} + ServerItem(const QString& text, int sortRole = Qt::DisplayRole, + QListWidget* parent = 0, int type = Type) + : QListWidgetItem(text, parent, type), m_SortRole(sortRole) + {} - virtual bool operator< ( const QListWidgetItem & other ) const { + virtual bool operator<(const QListWidgetItem& other) const + { return this->data(m_SortRole).value() < other.data(m_SortRole).value(); } + private: int m_SortRole; }; - class NexusManualKeyDialog : public QDialog { public: NexusManualKeyDialog(QWidget* parent) - : QDialog(parent), ui(new Ui::NexusManualKeyDialog) + : QDialog(parent), ui(new Ui::NexusManualKeyDialog) { ui->setupUi(this); ui->key->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); - connect(ui->openBrowser, &QPushButton::clicked, [&]{ openBrowser(); }); - connect(ui->paste, &QPushButton::clicked, [&]{ paste(); }); - connect(ui->clear, &QPushButton::clicked, [&]{ clear(); }); + connect(ui->openBrowser, &QPushButton::clicked, [&] { + openBrowser(); + }); + connect(ui->paste, &QPushButton::clicked, [&] { + paste(); + }); + connect(ui->clear, &QPushButton::clicked, [&] { + clear(); + }); } void accept() override @@ -42,10 +52,7 @@ class NexusManualKeyDialog : public QDialog QDialog::accept(); } - const QString& key() const - { - return m_key; - } + const QString& key() const { return m_key; } void openBrowser() { @@ -60,41 +67,37 @@ class NexusManualKeyDialog : public QDialog } } - void clear() - { - ui->key->clear(); - } + void clear() { ui->key->clear(); } private: std::unique_ptr ui; QString m_key; }; - -NexusConnectionUI::NexusConnectionUI( - QWidget* parent, - Settings* s, - QAbstractButton* connectButton, - QAbstractButton* disconnectButton, - QAbstractButton* manualButton, - QListWidget* logList) : - m_parent(parent), - m_settings(s), - m_connect(connectButton), - m_disconnect(disconnectButton), - m_manual(manualButton), - m_log(logList) +NexusConnectionUI::NexusConnectionUI(QWidget* parent, Settings* s, + QAbstractButton* connectButton, + QAbstractButton* disconnectButton, + QAbstractButton* manualButton, + QListWidget* logList) + : m_parent(parent), m_settings(s), m_connect(connectButton), + m_disconnect(disconnectButton), m_manual(manualButton), m_log(logList) { if (m_connect) { - QObject::connect(m_connect, &QPushButton::clicked, [&]{ connect(); }); + QObject::connect(m_connect, &QPushButton::clicked, [&] { + connect(); + }); } if (m_disconnect) { - QObject::connect(m_disconnect, &QPushButton::clicked, [&]{ disconnect(); }); + QObject::connect(m_disconnect, &QPushButton::clicked, [&] { + disconnect(); + }); } if (m_manual) { - QObject::connect(manualButton, &QPushButton::clicked, [&]{ manual(); }); + QObject::connect(manualButton, &QPushButton::clicked, [&] { + manual(); + }); } if (GlobalSettings::hasNexusApiKey()) { @@ -116,11 +119,11 @@ void NexusConnectionUI::connect() if (!m_nexusLogin) { m_nexusLogin.reset(new NexusSSOLogin); - m_nexusLogin->keyChanged = [&](auto&& s){ + m_nexusLogin->keyChanged = [&](auto&& s) { onSSOKeyChanged(s); }; - m_nexusLogin->stateChanged = [&](auto&& s, auto&& e){ + m_nexusLogin->stateChanged = [&](auto&& s, auto&& e) { onSSOStateChanged(s, e); }; } @@ -163,7 +166,7 @@ void NexusConnectionUI::validateKey(const QString& key) { if (!m_nexusValidator) { m_nexusValidator.reset(new NexusKeyValidator( - m_settings, *NexusInterface::instance().getAccessManager())); + m_settings, *NexusInterface::instance().getAccessManager())); m_nexusValidator->finished = [&](auto&& r, auto&& m, auto&& u) { onValidatorFinished(r, m, u); @@ -184,8 +187,7 @@ void NexusConnectionUI::onSSOKeyChanged(const QString& key) } } -void NexusConnectionUI::onSSOStateChanged( - NexusSSOLogin::States s, const QString& e) +void NexusConnectionUI::onSSOStateChanged(NexusSSOLogin::States s, const QString& e) { if (s != NexusSSOLogin::Finished) { // finished state is handled in onSSOKeyChanged() @@ -199,9 +201,9 @@ void NexusConnectionUI::onSSOStateChanged( updateState(); } -void NexusConnectionUI::onValidatorFinished( - ValidationAttempt::Result r, const QString& message, - std::optional user) +void NexusConnectionUI::onValidatorFinished(ValidationAttempt::Result r, + const QString& message, + std::optional user) { if (user) { NexusInterface::instance().setUserAccount(*user); @@ -254,7 +256,7 @@ bool NexusConnectionUI::clearKey() void NexusConnectionUI::updateState() { - auto setButton = [&](QAbstractButton* b, bool enabled, QString caption={}) { + auto setButton = [&](QAbstractButton* b, bool enabled, QString caption = {}) { if (b) { b->setEnabled(enabled); if (!caption.isEmpty()) { @@ -267,15 +269,13 @@ void NexusConnectionUI::updateState() // api key is in the process of being retrieved setButton(m_connect, true, QObject::tr("Cancel")); setButton(m_disconnect, false); - setButton(m_manual, false,QObject::tr("Enter API Key Manually")); - } - else if (m_nexusValidator && m_nexusValidator->isActive()) { + setButton(m_manual, false, QObject::tr("Enter API Key Manually")); + } else if (m_nexusValidator && m_nexusValidator->isActive()) { // api key is in the process of being tested setButton(m_connect, false, QObject::tr("Connect to Nexus")); setButton(m_disconnect, false); setButton(m_manual, true, QObject::tr("Cancel")); - } - else if (GlobalSettings::hasNexusApiKey()) { + } else if (GlobalSettings::hasNexusApiKey()) { // api key is present setButton(m_connect, false, QObject::tr("Connect to Nexus")); setButton(m_disconnect, true); @@ -290,10 +290,7 @@ void NexusConnectionUI::updateState() emit stateChanged(); } - - -NexusSettingsTab::NexusSettingsTab(Settings& s, SettingsDialog& d) - : SettingsTab(s, d) +NexusSettingsTab::NexusSettingsTab(Settings& s, SettingsDialog& d) : SettingsTab(s, d) { ui->endorsementBox->setChecked(settings().nexus().endorsementIntegration()); ui->trackedBox->setChecked(settings().nexus().trackedIntegration()); @@ -312,7 +309,7 @@ NexusSettingsTab::NexusSettingsTab(Settings& s, SettingsDialog& d) descriptor += QString(" (%1)").arg(MOBase::localizedByteSpeed(averageSpeed)); } - QListWidgetItem *newItem = new ServerItem(descriptor, Qt::UserRole + 1); + QListWidgetItem* newItem = new ServerItem(descriptor, Qt::UserRole + 1); newItem->setData(Qt::UserRole, server.name()); newItem->setData(Qt::UserRole + 1, server.preferred()); @@ -326,27 +323,33 @@ NexusSettingsTab::NexusSettingsTab(Settings& s, SettingsDialog& d) ui->preferredServersList->sortItems(Qt::DescendingOrder); } - m_connectionUI.reset(new NexusConnectionUI( - &dialog(), - &settings(), - ui->nexusConnect, - ui->nexusDisconnect, - ui->nexusManualKey, - ui->nexusLog)); - - QObject::connect( - m_connectionUI.get(), &NexusConnectionUI::stateChanged, &d, - [&]{ updateNexusData(); }, Qt::QueuedConnection); + m_connectionUI.reset(new NexusConnectionUI(&dialog(), &settings(), ui->nexusConnect, + ui->nexusDisconnect, ui->nexusManualKey, + ui->nexusLog)); QObject::connect( - m_connectionUI.get(), &NexusConnectionUI::keyChanged, &d, - [&]{ dialog().setExitNeeded(Exit::Restart); }); - - - QObject::connect(ui->clearCacheButton, &QPushButton::clicked, [&]{ clearCache(); }); - QObject::connect(ui->associateButton, &QPushButton::clicked, [&]{ associate(); }); - QObject::connect(ui->useCustomBrowser, &QCheckBox::clicked, [&]{ updateCustomBrowser(); }); - QObject::connect(ui->browseCustomBrowser, &QPushButton::clicked, [&]{ browseCustomBrowser(); }); + m_connectionUI.get(), &NexusConnectionUI::stateChanged, &d, + [&] { + updateNexusData(); + }, + Qt::QueuedConnection); + + QObject::connect(m_connectionUI.get(), &NexusConnectionUI::keyChanged, &d, [&] { + dialog().setExitNeeded(Exit::Restart); + }); + + QObject::connect(ui->clearCacheButton, &QPushButton::clicked, [&] { + clearCache(); + }); + QObject::connect(ui->associateButton, &QPushButton::clicked, [&] { + associate(); + }); + QObject::connect(ui->useCustomBrowser, &QCheckBox::clicked, [&] { + updateCustomBrowser(); + }); + QObject::connect(ui->browseCustomBrowser, &QPushButton::clicked, [&] { + browseCustomBrowser(); + }); updateNexusData(); updateCustomBrowser(); @@ -382,7 +385,8 @@ void NexusSettingsTab::update() const int count = ui->preferredServersList->count(); for (int i = 0; i < count; ++i) { - const QString key = ui->preferredServersList->item(i)->data(Qt::UserRole).toString(); + const QString key = + ui->preferredServersList->item(i)->data(Qt::UserRole).toString(); const int newPreferred = count - i; bool found = false; @@ -397,9 +401,8 @@ void NexusSettingsTab::update() } if (!found) { - log::error( - "while setting preference to {}, server '{}' not found", - newPreferred, key); + log::error("while setting preference to {}, server '{}' not found", newPreferred, + key); } } @@ -427,12 +430,12 @@ void NexusSettingsTab::updateNexusData() ui->nexusAccount->setText(localizedUserAccountType(user.type())); ui->nexusDailyRequests->setText(QString("%1/%2") - .arg(user.limits().remainingDailyRequests) - .arg(user.limits().maxDailyRequests)); + .arg(user.limits().remainingDailyRequests) + .arg(user.limits().maxDailyRequests)); ui->nexusHourlyRequests->setText(QString("%1/%2") - .arg(user.limits().remainingHourlyRequests) - .arg(user.limits().maxHourlyRequests)); + .arg(user.limits().remainingHourlyRequests) + .arg(user.limits().maxHourlyRequests)); } else { ui->nexusUserID->setText(QObject::tr("N/A")); ui->nexusName->setText(QObject::tr("N/A")); @@ -447,15 +450,14 @@ void NexusSettingsTab::updateCustomBrowser() ui->browserCommand->setEnabled(ui->useCustomBrowser->isChecked()); } -void NexusSettingsTab::browseCustomBrowser () +void NexusSettingsTab::browseCustomBrowser() { const QString Filters = - QObject::tr("Executables (*.exe)") + ";;" + - QObject::tr("All Files (*.*)"); + QObject::tr("Executables (*.exe)") + ";;" + QObject::tr("All Files (*.*)"); QString file = QFileDialog::getOpenFileName( - parentWidget(), QObject::tr("Select the browser executable"), - ui->browserCommand->text(), Filters); + parentWidget(), QObject::tr("Select the browser executable"), + ui->browserCommand->text(), Filters); if (file.isNull() || file == "") { return; diff --git a/src/settingsdialognexus.h b/src/settingsdialognexus.h index a9bd46c79..41ab207a4 100644 --- a/src/settingsdialognexus.h +++ b/src/settingsdialognexus.h @@ -1,9 +1,9 @@ #ifndef SETTINGSDIALOGNEXUS_H #define SETTINGSDIALOGNEXUS_H +#include "nxmaccessmanager.h" #include "settings.h" #include "settingsdialog.h" -#include "nxmaccessmanager.h" // used by the settings dialog and the create instance dialog // @@ -12,13 +12,9 @@ class NexusConnectionUI : public QObject Q_OBJECT; public: - NexusConnectionUI( - QWidget* parent, - Settings* s, - QAbstractButton* connectButton, - QAbstractButton* disconnectButton, - QAbstractButton* manualButton, - QListWidget* logList); + NexusConnectionUI(QWidget* parent, Settings* s, QAbstractButton* connectButton, + QAbstractButton* disconnectButton, QAbstractButton* manualButton, + QListWidget* logList); void connect(); void manual(); @@ -50,12 +46,10 @@ class NexusConnectionUI : public QObject void onSSOKeyChanged(const QString& key); void onSSOStateChanged(NexusSSOLogin::States s, const QString& e); - void onValidatorFinished( - ValidationAttempt::Result r, const QString& message, - std::optional useR); + void onValidatorFinished(ValidationAttempt::Result r, const QString& message, + std::optional useR); }; - class NexusSettingsTab : public SettingsTab { public: @@ -73,4 +67,4 @@ class NexusSettingsTab : public SettingsTab void browseCustomBrowser(); }; -#endif // SETTINGSDIALOGNEXUS_H +#endif // SETTINGSDIALOGNEXUS_H diff --git a/src/settingsdialogpaths.cpp b/src/settingsdialogpaths.cpp index aafa750ca..1907b7fae 100644 --- a/src/settingsdialogpaths.cpp +++ b/src/settingsdialogpaths.cpp @@ -1,89 +1,121 @@ #include "settingsdialogpaths.h" -#include "ui_settingsdialog.h" #include "shared/appconfig.h" +#include "ui_settingsdialog.h" #include PathsSettingsTab::PathsSettingsTab(Settings& s, SettingsDialog& d) - : SettingsTab(s, d), m_gameDir(settings().game().plugin()->gameDirectory()) + : SettingsTab(s, d), m_gameDir(settings().game().plugin()->gameDirectory()) { ui->baseDirEdit->setText(settings().paths().base()); - ui->managedGameDirEdit->setText( - QDir::toNativeSeparators(m_gameDir.absoluteFilePath(settings().game().plugin()->binaryName()))); + ui->managedGameDirEdit->setText(QDir::toNativeSeparators( + m_gameDir.absoluteFilePath(settings().game().plugin()->binaryName()))); QString basePath = settings().paths().base(); QDir baseDir(basePath); - for (const auto &dir : { - std::make_pair(ui->downloadDirEdit, settings().paths().downloads(false)), - std::make_pair(ui->modDirEdit, settings().paths().mods(false)), - std::make_pair(ui->cacheDirEdit, settings().paths().cache(false)), - std::make_pair(ui->profilesDirEdit, settings().paths().profiles(false)), - std::make_pair(ui->overwriteDirEdit, settings().paths().overwrite(false)) - }) { + for (const auto& dir : + {std::make_pair(ui->downloadDirEdit, settings().paths().downloads(false)), + std::make_pair(ui->modDirEdit, settings().paths().mods(false)), + std::make_pair(ui->cacheDirEdit, settings().paths().cache(false)), + std::make_pair(ui->profilesDirEdit, settings().paths().profiles(false)), + std::make_pair(ui->overwriteDirEdit, settings().paths().overwrite(false))}) { QString storePath = baseDir.relativeFilePath(dir.second); - storePath = dir.second; + storePath = dir.second; dir.first->setText(storePath); } - QObject::connect(ui->browseBaseDirBtn, &QPushButton::clicked, [&]{ on_browseBaseDirBtn_clicked(); }); - QObject::connect(ui->browseCacheDirBtn, &QPushButton::clicked, [&]{ on_browseCacheDirBtn_clicked(); }); - QObject::connect(ui->browseDownloadDirBtn, &QPushButton::clicked, [&]{ on_browseDownloadDirBtn_clicked(); }); - QObject::connect(ui->browseGameDirBtn, &QPushButton::clicked, [&]{ on_browseGameDirBtn_clicked(); }); - QObject::connect(ui->browseModDirBtn, &QPushButton::clicked, [&]{ on_browseModDirBtn_clicked(); }); - QObject::connect(ui->browseOverwriteDirBtn, &QPushButton::clicked, [&]{ on_browseOverwriteDirBtn_clicked(); }); - QObject::connect(ui->browseProfilesDirBtn, &QPushButton::clicked, [&]{ on_browseProfilesDirBtn_clicked(); }); - - QObject::connect(ui->baseDirEdit, &QLineEdit::editingFinished, [&]{ on_baseDirEdit_editingFinished(); }); - QObject::connect(ui->cacheDirEdit, &QLineEdit::editingFinished, [&]{ on_cacheDirEdit_editingFinished(); }); - QObject::connect(ui->downloadDirEdit, &QLineEdit::editingFinished, [&]{ on_downloadDirEdit_editingFinished(); }); - QObject::connect(ui->modDirEdit, &QLineEdit::editingFinished, [&]{ on_modDirEdit_editingFinished(); }); - QObject::connect(ui->overwriteDirEdit, &QLineEdit::editingFinished, [&]{ on_overwriteDirEdit_editingFinished(); }); - QObject::connect(ui->profilesDirEdit, &QLineEdit::editingFinished, [&]{ on_profilesDirEdit_editingFinished(); }); + QObject::connect(ui->browseBaseDirBtn, &QPushButton::clicked, [&] { + on_browseBaseDirBtn_clicked(); + }); + QObject::connect(ui->browseCacheDirBtn, &QPushButton::clicked, [&] { + on_browseCacheDirBtn_clicked(); + }); + QObject::connect(ui->browseDownloadDirBtn, &QPushButton::clicked, [&] { + on_browseDownloadDirBtn_clicked(); + }); + QObject::connect(ui->browseGameDirBtn, &QPushButton::clicked, [&] { + on_browseGameDirBtn_clicked(); + }); + QObject::connect(ui->browseModDirBtn, &QPushButton::clicked, [&] { + on_browseModDirBtn_clicked(); + }); + QObject::connect(ui->browseOverwriteDirBtn, &QPushButton::clicked, [&] { + on_browseOverwriteDirBtn_clicked(); + }); + QObject::connect(ui->browseProfilesDirBtn, &QPushButton::clicked, [&] { + on_browseProfilesDirBtn_clicked(); + }); + + QObject::connect(ui->baseDirEdit, &QLineEdit::editingFinished, [&] { + on_baseDirEdit_editingFinished(); + }); + QObject::connect(ui->cacheDirEdit, &QLineEdit::editingFinished, [&] { + on_cacheDirEdit_editingFinished(); + }); + QObject::connect(ui->downloadDirEdit, &QLineEdit::editingFinished, [&] { + on_downloadDirEdit_editingFinished(); + }); + QObject::connect(ui->modDirEdit, &QLineEdit::editingFinished, [&] { + on_modDirEdit_editingFinished(); + }); + QObject::connect(ui->overwriteDirEdit, &QLineEdit::editingFinished, [&] { + on_overwriteDirEdit_editingFinished(); + }); + QObject::connect(ui->profilesDirEdit, &QLineEdit::editingFinished, [&] { + on_profilesDirEdit_editingFinished(); + }); } void PathsSettingsTab::update() { - using Setter = void (PathSettings::*)(const QString&); + using Setter = void (PathSettings::*)(const QString&); using Directory = std::tuple; QString basePath = settings().paths().base(); - for (const Directory &dir :{ - Directory{ui->downloadDirEdit->text(), &PathSettings::setDownloads, AppConfig::downloadPath()}, - Directory{ui->cacheDirEdit->text(), &PathSettings::setCache, AppConfig::cachePath()}, - Directory{ui->modDirEdit->text(), &PathSettings::setMods, AppConfig::modsPath()}, - Directory{ui->overwriteDirEdit->text(), &PathSettings::setOverwrite, AppConfig::overwritePath()}, - Directory{ui->profilesDirEdit->text(), &PathSettings::setProfiles, AppConfig::profilesPath()} - }) { + for (const Directory& dir : + {Directory{ui->downloadDirEdit->text(), &PathSettings::setDownloads, + AppConfig::downloadPath()}, + Directory{ui->cacheDirEdit->text(), &PathSettings::setCache, + AppConfig::cachePath()}, + Directory{ui->modDirEdit->text(), &PathSettings::setMods, + AppConfig::modsPath()}, + Directory{ui->overwriteDirEdit->text(), &PathSettings::setOverwrite, + AppConfig::overwritePath()}, + Directory{ui->profilesDirEdit->text(), &PathSettings::setProfiles, + AppConfig::profilesPath()}}) { QString path; Setter setter; std::wstring defaultName; std::tie(path, setter, defaultName) = dir; QString realPath = path; - realPath = PathSettings::resolve(realPath, ui->baseDirEdit->text()); + realPath = PathSettings::resolve(realPath, ui->baseDirEdit->text()); if (!QDir(realPath).exists()) { if (!QDir().mkpath(realPath)) { - QMessageBox::warning(parentWidget(), QObject::tr("Error"), - QObject::tr("Failed to create \"%1\", you may not have the " - "necessary permissions. Path remains unchanged.") - .arg(realPath)); + QMessageBox::warning( + parentWidget(), QObject::tr("Error"), + QObject::tr("Failed to create \"%1\", you may not have the " + "necessary permissions. Path remains unchanged.") + .arg(realPath)); continue; } } - if (QFileInfo(realPath) != QFileInfo(basePath + "/" + QString::fromStdWString(defaultName))) { + if (QFileInfo(realPath) != + QFileInfo(basePath + "/" + QString::fromStdWString(defaultName))) { (settings().paths().*setter)(path); } else { (settings().paths().*setter)(""); } } - if (QFileInfo(ui->baseDirEdit->text()) != QFileInfo(qApp->property("dataPath").toString())) { + if (QFileInfo(ui->baseDirEdit->text()) != + QFileInfo(qApp->property("dataPath").toString())) { settings().paths().setBase(ui->baseDirEdit->text()); } else { settings().paths().setBase(""); @@ -97,7 +129,7 @@ void PathsSettingsTab::update() void PathsSettingsTab::on_browseBaseDirBtn_clicked() { QString temp = QFileDialog::getExistingDirectory( - &dialog(), QObject::tr("Select base directory"), ui->baseDirEdit->text()); + &dialog(), QObject::tr("Select base directory"), ui->baseDirEdit->text()); if (!temp.isEmpty()) { ui->baseDirEdit->setText(temp); } @@ -106,9 +138,10 @@ void PathsSettingsTab::on_browseBaseDirBtn_clicked() void PathsSettingsTab::on_browseDownloadDirBtn_clicked() { QString searchPath = ui->downloadDirEdit->text(); - searchPath = PathSettings::resolve(searchPath, ui->baseDirEdit->text()); + searchPath = PathSettings::resolve(searchPath, ui->baseDirEdit->text()); - QString temp = QFileDialog::getExistingDirectory(&dialog(), QObject::tr("Select download directory"), searchPath); + QString temp = QFileDialog::getExistingDirectory( + &dialog(), QObject::tr("Select download directory"), searchPath); if (!temp.isEmpty()) { ui->downloadDirEdit->setText(temp); } @@ -117,9 +150,10 @@ void PathsSettingsTab::on_browseDownloadDirBtn_clicked() void PathsSettingsTab::on_browseModDirBtn_clicked() { QString searchPath = ui->modDirEdit->text(); - searchPath = PathSettings::resolve(searchPath, ui->baseDirEdit->text()); + searchPath = PathSettings::resolve(searchPath, ui->baseDirEdit->text()); - QString temp = QFileDialog::getExistingDirectory(&dialog(), QObject::tr("Select mod directory"), searchPath); + QString temp = QFileDialog::getExistingDirectory( + &dialog(), QObject::tr("Select mod directory"), searchPath); if (!temp.isEmpty()) { ui->modDirEdit->setText(temp); } @@ -128,9 +162,10 @@ void PathsSettingsTab::on_browseModDirBtn_clicked() void PathsSettingsTab::on_browseCacheDirBtn_clicked() { QString searchPath = ui->cacheDirEdit->text(); - searchPath = PathSettings::resolve(searchPath, ui->baseDirEdit->text()); + searchPath = PathSettings::resolve(searchPath, ui->baseDirEdit->text()); - QString temp = QFileDialog::getExistingDirectory(&dialog(), QObject::tr("Select cache directory"), searchPath); + QString temp = QFileDialog::getExistingDirectory( + &dialog(), QObject::tr("Select cache directory"), searchPath); if (!temp.isEmpty()) { ui->cacheDirEdit->setText(temp); } @@ -139,9 +174,10 @@ void PathsSettingsTab::on_browseCacheDirBtn_clicked() void PathsSettingsTab::on_browseProfilesDirBtn_clicked() { QString searchPath = ui->profilesDirEdit->text(); - searchPath = PathSettings::resolve(searchPath, ui->baseDirEdit->text()); + searchPath = PathSettings::resolve(searchPath, ui->baseDirEdit->text()); - QString temp = QFileDialog::getExistingDirectory(&dialog(), QObject::tr("Select profiles directory"), searchPath); + QString temp = QFileDialog::getExistingDirectory( + &dialog(), QObject::tr("Select profiles directory"), searchPath); if (!temp.isEmpty()) { ui->profilesDirEdit->setText(temp); } @@ -150,9 +186,10 @@ void PathsSettingsTab::on_browseProfilesDirBtn_clicked() void PathsSettingsTab::on_browseOverwriteDirBtn_clicked() { QString searchPath = ui->overwriteDirEdit->text(); - searchPath = PathSettings::resolve(searchPath, ui->baseDirEdit->text()); + searchPath = PathSettings::resolve(searchPath, ui->baseDirEdit->text()); - QString temp = QFileDialog::getExistingDirectory(&dialog(), QObject::tr("Select overwrite directory"), searchPath); + QString temp = QFileDialog::getExistingDirectory( + &dialog(), QObject::tr("Select overwrite directory"), searchPath); if (!temp.isEmpty()) { ui->overwriteDirEdit->setText(temp); } @@ -162,7 +199,9 @@ void PathsSettingsTab::on_browseGameDirBtn_clicked() { QFileInfo oldGameExe(ui->managedGameDirEdit->text()); - QString temp = QFileDialog::getOpenFileName(&dialog(), QObject::tr("Select game executable"), oldGameExe.absolutePath(), oldGameExe.fileName()); + QString temp = + QFileDialog::getOpenFileName(&dialog(), QObject::tr("Select game executable"), + oldGameExe.absolutePath(), oldGameExe.fileName()); if (temp.isEmpty()) { return; @@ -177,23 +216,23 @@ void PathsSettingsTab::on_browseGameDirBtn_clicked() // QFileInfo newExe(temp); const auto binaryPath = settings().game().plugin()->binaryName(); - QDir folder = newExe.absoluteDir(); - while (folder.exists() && !folder.isRoot() - && QFileInfo(folder.filePath(binaryPath)) != newExe) { + QDir folder = newExe.absoluteDir(); + while (folder.exists() && !folder.isRoot() && + QFileInfo(folder.filePath(binaryPath)) != newExe) { folder.cdUp(); } if (folder.exists(binaryPath)) { m_gameDir = folder; - ui->managedGameDirEdit->setText( - QDir::toNativeSeparators(m_gameDir.absoluteFilePath(settings().game().plugin()->binaryName()))); - } - else { - QMessageBox::warning(parentWidget(), QObject::tr("Error"), - QObject::tr("The given path was not recognized as a valid game installation. " - "The current game plugin requires the executable to be in a \"%1\" subfolder of the game directory.") - .arg(QFileInfo(binaryPath).path())); - + ui->managedGameDirEdit->setText(QDir::toNativeSeparators( + m_gameDir.absoluteFilePath(settings().game().plugin()->binaryName()))); + } else { + QMessageBox::warning( + parentWidget(), QObject::tr("Error"), + QObject::tr("The given path was not recognized as a valid game installation. " + "The current game plugin requires the executable to be in a \"%1\" " + "subfolder of the game directory.") + .arg(QFileInfo(binaryPath).path())); } } @@ -227,7 +266,7 @@ void PathsSettingsTab::on_overwriteDirEdit_editingFinished() normalizePath(ui->overwriteDirEdit); } -void PathsSettingsTab::normalizePath(QLineEdit *lineEdit) +void PathsSettingsTab::normalizePath(QLineEdit* lineEdit) { QString text = lineEdit->text(); while (text.endsWith('/') || text.endsWith('\\')) { diff --git a/src/settingsdialogpaths.h b/src/settingsdialogpaths.h index 9562289bb..239c79c78 100644 --- a/src/settingsdialogpaths.h +++ b/src/settingsdialogpaths.h @@ -26,9 +26,9 @@ class PathsSettingsTab : public SettingsTab void on_overwriteDirEdit_editingFinished(); void on_profilesDirEdit_editingFinished(); - void normalizePath(QLineEdit *lineEdit); + void normalizePath(QLineEdit* lineEdit); QDir m_gameDir; }; -#endif // SETTINGSDIALOGPATHS_H +#endif // SETTINGSDIALOGPATHS_H diff --git a/src/settingsdialogplugins.cpp b/src/settingsdialogplugins.cpp index 2f7f39854..db00ca081 100644 --- a/src/settingsdialogplugins.cpp +++ b/src/settingsdialogplugins.cpp @@ -1,6 +1,6 @@ #include "settingsdialogplugins.h" -#include "ui_settingsdialog.h" #include "noeditdelegate.h" +#include "ui_settingsdialog.h" #include #include "disableproxyplugindialog.h" @@ -9,8 +9,9 @@ using namespace MOBase; -PluginsSettingsTab::PluginsSettingsTab(Settings& s, PluginContainer* pluginContainer, SettingsDialog& d) - : SettingsTab(s, d), m_pluginContainer(pluginContainer) +PluginsSettingsTab::PluginsSettingsTab(Settings& s, PluginContainer* pluginContainer, + SettingsDialog& d) + : SettingsTab(s, d), m_pluginContainer(pluginContainer) { ui->pluginSettingsList->setStyleSheet("QTreeWidget::item {padding-right: 10px;}"); @@ -19,7 +20,7 @@ PluginsSettingsTab::PluginsSettingsTab(Settings& s, PluginContainer* pluginConta pluginInterfaces.sort(Qt::CaseInsensitive); std::map topItems; for (QString interfaceName : pluginInterfaces) { - auto* item = new QTreeWidgetItem(ui->pluginsList, { interfaceName }); + auto* item = new QTreeWidgetItem(ui->pluginsList, {interfaceName}); item->setFlags(item->flags() & ~Qt::ItemIsSelectable); auto font = item->font(0); font.setBold(true); @@ -33,16 +34,18 @@ PluginsSettingsTab::PluginsSettingsTab(Settings& s, PluginContainer* pluginConta // display plugin settings QSet handledNames; for (IPlugin* plugin : settings().plugins().plugins()) { - if (handledNames.contains(plugin->name()) || m_pluginContainer->requirements(plugin).master()) { + if (handledNames.contains(plugin->name()) || + m_pluginContainer->requirements(plugin).master()) { continue; } QTreeWidgetItem* listItem = new QTreeWidgetItem( - topItems.at(m_pluginContainer->topImplementedInterface(plugin))); + topItems.at(m_pluginContainer->topImplementedInterface(plugin))); listItem->setData(0, Qt::DisplayRole, plugin->localizedName()); listItem->setData(0, PluginRole, QVariant::fromValue((void*)plugin)); listItem->setData(0, SettingsRole, settings().plugins().settings(plugin->name())); - listItem->setData(0, DescriptionsRole, settings().plugins().descriptions(plugin->name())); + listItem->setData(0, DescriptionsRole, + settings().plugins().descriptions(plugin->name())); // Handle child item: auto children = m_pluginContainer->requirements(plugin).children(); @@ -51,7 +54,8 @@ PluginsSettingsTab::PluginsSettingsTab(Settings& s, PluginContainer* pluginConta childItem->setData(0, Qt::DisplayRole, child->localizedName()); childItem->setData(0, PluginRole, QVariant::fromValue((void*)child)); childItem->setData(0, SettingsRole, settings().plugins().settings(child->name())); - childItem->setData(0, DescriptionsRole, settings().plugins().descriptions(child->name())); + childItem->setData(0, DescriptionsRole, + settings().plugins().descriptions(child->name())); handledNames.insert(child->name()); } @@ -68,23 +72,28 @@ PluginsSettingsTab::PluginsSettingsTab(Settings& s, PluginContainer* pluginConta ui->pluginsList->sortByColumn(0, Qt::AscendingOrder); // display plugin blacklist - for (const QString &pluginName : settings().plugins().blacklist()) { + for (const QString& pluginName : settings().plugins().blacklist()) { ui->pluginBlacklist->addItem(pluginName); } m_filter.setEdit(ui->pluginFilterEdit); - QObject::connect( - ui->pluginsList, &QTreeWidget::currentItemChanged, - [&](auto* current, auto* previous) { on_pluginsList_currentItemChanged(current, previous); }); - QObject::connect( - ui->enabledCheckbox, &QCheckBox::clicked, - [&](bool checked) { on_checkboxEnabled_clicked(checked); }); - - QShortcut *delShortcut = new QShortcut( - QKeySequence(Qt::Key_Delete), ui->pluginBlacklist); - QObject::connect(delShortcut, &QShortcut::activated, &dialog(), [&] { deleteBlacklistItem(); }); - QObject::connect(&m_filter, &FilterWidget::changed, [&] { filterPluginList(); }); + QObject::connect(ui->pluginsList, &QTreeWidget::currentItemChanged, + [&](auto* current, auto* previous) { + on_pluginsList_currentItemChanged(current, previous); + }); + QObject::connect(ui->enabledCheckbox, &QCheckBox::clicked, [&](bool checked) { + on_checkboxEnabled_clicked(checked); + }); + + QShortcut* delShortcut = + new QShortcut(QKeySequence(Qt::Key_Delete), ui->pluginBlacklist); + QObject::connect(delShortcut, &QShortcut::activated, &dialog(), [&] { + deleteBlacklistItem(); + }); + QObject::connect(&m_filter, &FilterWidget::changed, [&] { + filterPluginList(); + }); updateListItems(); filterPluginList(); @@ -95,11 +104,11 @@ void PluginsSettingsTab::updateListItems() for (auto i = 0; i < ui->pluginsList->topLevelItemCount(); ++i) { auto* topLevelItem = ui->pluginsList->topLevelItem(i); for (auto j = 0; j < topLevelItem->childCount(); ++j) { - auto* item = topLevelItem->child(j); + auto* item = topLevelItem->child(j); auto* plugin = this->plugin(item); - bool inactive = !m_pluginContainer->implementInterface(plugin) - && !m_pluginContainer->isEnabled(plugin); + bool inactive = !m_pluginContainer->implementInterface(plugin) && + !m_pluginContainer->isEnabled(plugin); auto font = item->font(0); font.setItalic(inactive); @@ -109,12 +118,11 @@ void PluginsSettingsTab::updateListItems() } } } - } void PluginsSettingsTab::filterPluginList() { - auto selectedItems = ui->pluginsList->selectedItems(); + auto selectedItems = ui->pluginsList->selectedItems(); QTreeWidgetItem* firstNotHidden = nullptr; for (auto i = 0; i < ui->pluginsList->topLevelItemCount(); ++i) { @@ -122,7 +130,7 @@ void PluginsSettingsTab::filterPluginList() bool found = false; for (auto j = 0; j < topLevelItem->childCount(); ++j) { - auto* item = topLevelItem->child(j); + auto* item = topLevelItem->child(j); auto* plugin = this->plugin(item); // Check the item or the child - If any match (item or child), the whole @@ -143,8 +151,7 @@ void PluginsSettingsTab::filterPluginList() if (firstNotHidden == nullptr) { firstNotHidden = item; } - } - else { + } else { item->setHidden(true); } } @@ -159,17 +166,14 @@ void PluginsSettingsTab::filterPluginList() ui->noPluginLabel->setVisible(false); if (selectedItems.isEmpty()) { ui->pluginsList->setCurrentItem(firstNotHidden); - } - else if (selectedItems[0]->isHidden()) { + } else if (selectedItems[0]->isHidden()) { ui->pluginsList->setCurrentItem(firstNotHidden); } - } - else { + } else { ui->pluginDescription->setVisible(false); ui->pluginSettingsList->setVisible(false); ui->noPluginLabel->setVisible(true); } - } IPlugin* PluginsSettingsTab::plugin(QTreeWidgetItem* pluginItem) const @@ -181,17 +185,17 @@ void PluginsSettingsTab::update() { // transfer plugin settings to in-memory structure for (int i = 0; i < ui->pluginsList->topLevelItemCount(); ++i) { - auto *topLevelItem = ui->pluginsList->topLevelItem(i); + auto* topLevelItem = ui->pluginsList->topLevelItem(i); for (int j = 0; j < topLevelItem->childCount(); ++j) { auto* item = topLevelItem->child(j); - settings().plugins().setSettings( - plugin(item)->name(), item->data(0, SettingsRole).toMap()); + settings().plugins().setSettings(plugin(item)->name(), + item->data(0, SettingsRole).toMap()); } } // set plugin blacklist QStringList names; - for (QListWidgetItem *item : ui->pluginBlacklist->findItems("*", Qt::MatchWildcard)) { + for (QListWidgetItem* item : ui->pluginBlacklist->findItems("*", Qt::MatchWildcard)) { names.push_back(item->text()); } @@ -208,28 +212,28 @@ void PluginsSettingsTab::closing() void PluginsSettingsTab::on_checkboxEnabled_clicked(bool checked) { // Retrieve the plugin: - auto *item = ui->pluginsList->currentItem(); + auto* item = ui->pluginsList->currentItem(); if (!item || !item->data(0, PluginRole).isValid()) { return; } - IPlugin* plugin = this->plugin(item); + IPlugin* plugin = this->plugin(item); const auto& requirements = m_pluginContainer->requirements(plugin); // User wants to enable: if (checked) { m_pluginContainer->setEnabled(plugin, true, false); - } - else { + } else { // Custom check for proxy + current game: if (m_pluginContainer->implementInterface(plugin)) { // Current game: auto* game = m_pluginContainer->managedGame(); if (m_pluginContainer->requirements(game).proxy() == plugin) { - QMessageBox::warning( - parentWidget(), QObject::tr("Cannot disable plugin"), - QObject::tr("The '%1' plugin is used by the current game plugin and cannot disabled.") - .arg(plugin->localizedName()), QMessageBox::Ok); + QMessageBox::warning(parentWidget(), QObject::tr("Cannot disable plugin"), + QObject::tr("The '%1' plugin is used by the current game " + "plugin and cannot disabled.") + .arg(plugin->localizedName()), + QMessageBox::Ok); ui->enabledCheckbox->setChecked(true); return; } @@ -253,12 +257,14 @@ void PluginsSettingsTab::on_checkboxEnabled_clicked(bool checked) pluginNames.append(p->localizedName()); } pluginNames.sort(); - QString message = QObject::tr( - "

              Disabling the '%1' plugin will also disable the following plugins:

                %1

              Do you want to continue?

              ") - .arg(plugin->localizedName()).arg("
            • " + pluginNames.join("
            • ") + "
            • "); - if (QMessageBox::warning( - parentWidget(), QObject::tr("Really disable plugin?"), message, - QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) { + QString message = + QObject::tr("

              Disabling the '%1' plugin will also disable the following " + "plugins:

                %1

              Do you want to continue?

              ") + .arg(plugin->localizedName()) + .arg("
            • " + pluginNames.join("
            • ") + "
            • "); + if (QMessageBox::warning(parentWidget(), QObject::tr("Really disable plugin?"), + message, + QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) { ui->enabledCheckbox->setChecked(true); return; } @@ -274,7 +280,8 @@ void PluginsSettingsTab::on_checkboxEnabled_clicked(bool checked) updateListItems(); } -void PluginsSettingsTab::on_pluginsList_currentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *previous) +void PluginsSettingsTab::on_pluginsList_currentItemChanged(QTreeWidgetItem* current, + QTreeWidgetItem* previous) { storeSettings(previous); @@ -291,17 +298,18 @@ void PluginsSettingsTab::on_pluginsList_currentItemChanged(QTreeWidgetItem *curr // Checkbox, do not show for children or game plugins, disable // if the plugin cannot be enabled. ui->enabledCheckbox->setVisible( - !m_pluginContainer->implementInterface(plugin) - && plugin->master().isEmpty()); + !m_pluginContainer->implementInterface(plugin) && + plugin->master().isEmpty()); - bool enabled = m_pluginContainer->isEnabled(plugin); + bool enabled = m_pluginContainer->isEnabled(plugin); auto& requirements = m_pluginContainer->requirements(plugin); - auto problems = requirements.problems(); + auto problems = requirements.problems(); if (m_pluginContainer->requirements(plugin).isCorePlugin()) { ui->enabledCheckbox->setDisabled(true); ui->enabledCheckbox->setToolTip( - QObject::tr("This plugin is required for Mod Organizer to work properly and cannot be disabled.")); + QObject::tr("This plugin is required for Mod Organizer to work properly and " + "cannot be disabled.")); } // Plugin is enable or can be enabled. else if (enabled || problems.empty()) { @@ -315,22 +323,22 @@ void PluginsSettingsTab::on_pluginsList_currentItemChanged(QTreeWidgetItem *curr ui->enabledCheckbox->setChecked(false); if (problems.size() == 1) { ui->enabledCheckbox->setToolTip(problems[0].shortDescription()); - } - else { + } else { QStringList descriptions; for (auto& problem : problems) { descriptions.append(problem.shortDescription()); } - ui->enabledCheckbox->setToolTip("
              • " + descriptions.join("
              • ") + "
              "); + ui->enabledCheckbox->setToolTip("
              • " + descriptions.join("
              • ") + + "
              "); } } - QVariantMap settings = current->data(0, SettingsRole).toMap(); + QVariantMap settings = current->data(0, SettingsRole).toMap(); QVariantMap descriptions = current->data(0, DescriptionsRole).toMap(); ui->pluginSettingsList->setEnabled(settings.count() != 0); for (auto iter = settings.begin(); iter != settings.end(); ++iter) { - QTreeWidgetItem *newItem = new QTreeWidgetItem(QStringList(iter.key())); - QVariant value = *iter; + QTreeWidgetItem* newItem = new QTreeWidgetItem(QStringList(iter.key())); + QVariant value = *iter; QString description; { auto descriptionIter = descriptions.find(iter.key()); @@ -357,14 +365,14 @@ void PluginsSettingsTab::deleteBlacklistItem() ui->pluginBlacklist->takeItem(ui->pluginBlacklist->currentIndex().row()); } -void PluginsSettingsTab::storeSettings(QTreeWidgetItem *pluginItem) +void PluginsSettingsTab::storeSettings(QTreeWidgetItem* pluginItem) { if (pluginItem != nullptr && pluginItem->data(0, PluginRole).isValid()) { QVariantMap settings = pluginItem->data(0, SettingsRole).toMap(); for (int i = 0; i < ui->pluginSettingsList->topLevelItemCount(); ++i) { - const QTreeWidgetItem *item = ui->pluginSettingsList->topLevelItem(i); - settings[item->text(0)] = item->data(1, Qt::DisplayRole); + const QTreeWidgetItem* item = ui->pluginSettingsList->topLevelItem(i); + settings[item->text(0)] = item->data(1, Qt::DisplayRole); } pluginItem->setData(0, SettingsRole, settings); diff --git a/src/settingsdialogplugins.h b/src/settingsdialogplugins.h index c784b83e1..416f457ae 100644 --- a/src/settingsdialogplugins.h +++ b/src/settingsdialogplugins.h @@ -9,16 +9,18 @@ class PluginsSettingsTab : public SettingsTab { public: - PluginsSettingsTab(Settings& settings, PluginContainer* pluginContainer, SettingsDialog& dialog); + PluginsSettingsTab(Settings& settings, PluginContainer* pluginContainer, + SettingsDialog& dialog); void update(); void closing() override; private: - void on_pluginsList_currentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *previous); + void on_pluginsList_currentItemChanged(QTreeWidgetItem* current, + QTreeWidgetItem* previous); void on_checkboxEnabled_clicked(bool checked); void deleteBlacklistItem(); - void storeSettings(QTreeWidgetItem *pluginItem); + void storeSettings(QTreeWidgetItem* pluginItem); private slots: @@ -37,19 +39,19 @@ private slots: * @brief Retrieve the plugin associated to the given item in the list. * */ - MOBase::IPlugin* plugin(QTreeWidgetItem *pluginItem) const; + MOBase::IPlugin* plugin(QTreeWidgetItem* pluginItem) const; - enum { - PluginRole = Qt::UserRole, - SettingsRole = Qt::UserRole + 1, + enum + { + PluginRole = Qt::UserRole, + SettingsRole = Qt::UserRole + 1, DescriptionsRole = Qt::UserRole + 2 }; private: - PluginContainer* m_pluginContainer; MOBase::FilterWidget m_filter; }; -#endif // SETTINGSDIALOGPLUGINS_H +#endif // SETTINGSDIALOGPLUGINS_H diff --git a/src/settingsdialogtheme.cpp b/src/settingsdialogtheme.cpp index 9b32773b4..75bcdb598 100644 --- a/src/settingsdialogtheme.cpp +++ b/src/settingsdialogtheme.cpp @@ -1,16 +1,15 @@ #include "settingsdialogtheme.h" -#include "ui_settingsdialog.h" -#include "shared/appconfig.h" #include "categoriesdialog.h" #include "colortable.h" #include "modlist.h" -#include +#include "shared/appconfig.h" +#include "ui_settingsdialog.h" #include +#include using namespace MOBase; -ThemeSettingsTab::ThemeSettingsTab(Settings& s, SettingsDialog& d) - : SettingsTab(s, d) +ThemeSettingsTab::ThemeSettingsTab(Settings& s, SettingsDialog& d) : SettingsTab(s, d) { // style addStyles(); @@ -19,17 +18,21 @@ ThemeSettingsTab::ThemeSettingsTab(Settings& s, SettingsDialog& d) // colors ui->colorTable->load(s); - QObject::connect(ui->resetColorsBtn, &QPushButton::clicked, [&] { ui->colorTable->resetColors(); }); + QObject::connect(ui->resetColorsBtn, &QPushButton::clicked, [&] { + ui->colorTable->resetColors(); + }); - QObject::connect(ui->exploreStyles, &QPushButton::clicked, [&] { onExploreStyles(); }); + QObject::connect(ui->exploreStyles, &QPushButton::clicked, [&] { + onExploreStyles(); + }); } void ThemeSettingsTab::update() { // style const QString oldStyle = settings().interface().styleName().value_or(""); - const QString newStyle = ui->styleBox->itemData( - ui->styleBox->currentIndex()).toString(); + const QString newStyle = + ui->styleBox->itemData(ui->styleBox->currentIndex()).toString(); if (oldStyle != newStyle) { settings().interface().setStyleName(newStyle); @@ -49,25 +52,21 @@ void ThemeSettingsTab::addStyles() ui->styleBox->insertSeparator(ui->styleBox->count()); - QDirIterator iter( - QCoreApplication::applicationDirPath() + "/" + - QString::fromStdWString(AppConfig::stylesheetsPath()), - QStringList("*.qss"), - QDir::Files); + QDirIterator iter(QCoreApplication::applicationDirPath() + "/" + + QString::fromStdWString(AppConfig::stylesheetsPath()), + QStringList("*.qss"), QDir::Files); while (iter.hasNext()) { iter.next(); - ui->styleBox->addItem( - iter.fileInfo().completeBaseName(), - iter.fileName()); + ui->styleBox->addItem(iter.fileInfo().completeBaseName(), iter.fileName()); } } void ThemeSettingsTab::selectStyle() { - const int currentID = ui->styleBox->findData( - settings().interface().styleName().value_or("")); + const int currentID = + ui->styleBox->findData(settings().interface().styleName().value_or("")); if (currentID != -1) { ui->styleBox->setCurrentIndex(currentID); @@ -76,7 +75,7 @@ void ThemeSettingsTab::selectStyle() void ThemeSettingsTab::onExploreStyles() { - QString ssPath = QCoreApplication::applicationDirPath() + "/" + ToQString(AppConfig::stylesheetsPath()); + QString ssPath = QCoreApplication::applicationDirPath() + "/" + + ToQString(AppConfig::stylesheetsPath()); shell::Explore(ssPath); } - diff --git a/src/settingsdialogtheme.h b/src/settingsdialogtheme.h index 61334c0b5..2a53dc38c 100644 --- a/src/settingsdialogtheme.h +++ b/src/settingsdialogtheme.h @@ -3,8 +3,8 @@ #include -#include "settingsdialog.h" #include "settings.h" +#include "settingsdialog.h" class ThemeSettingsTab : public SettingsTab { @@ -14,11 +14,9 @@ class ThemeSettingsTab : public SettingsTab void update() override; private: - void addStyles(); void selectStyle(); void onExploreStyles(); - }; -#endif // SETTINGSDIALOGGENERAL_H +#endif // SETTINGSDIALOGGENERAL_H diff --git a/src/settingsdialogworkarounds.cpp b/src/settingsdialogworkarounds.cpp index cf54908a9..36e5dd44a 100644 --- a/src/settingsdialogworkarounds.cpp +++ b/src/settingsdialogworkarounds.cpp @@ -1,13 +1,13 @@ #include "settingsdialogworkarounds.h" -#include "ui_settingsdialog.h" -#include "spawn.h" #include "settings.h" -#include +#include "spawn.h" +#include "ui_settingsdialog.h" #include #include +#include WorkaroundsSettingsTab::WorkaroundsSettingsTab(Settings& s, SettingsDialog& d) - : SettingsTab(s, d) + : SettingsTab(s, d) { // options ui->forceEnableBox->setChecked(settings().game().forceEnableCoreFiles()); @@ -35,9 +35,15 @@ WorkaroundsSettingsTab::WorkaroundsSettingsTab(Settings& s, SettingsDialog& d) // buttons m_ExecutableBlacklist = settings().executablesBlacklist(); - QObject::connect(ui->bsaDateBtn, &QPushButton::clicked, [&]{ on_bsaDateBtn_clicked(); }); - QObject::connect(ui->execBlacklistBtn, &QPushButton::clicked, [&]{ on_execBlacklistBtn_clicked(); }); - QObject::connect(ui->resetGeometryBtn, &QPushButton::clicked, [&]{ on_resetGeometryBtn_clicked(); }); + QObject::connect(ui->bsaDateBtn, &QPushButton::clicked, [&] { + on_bsaDateBtn_clicked(); + }); + QObject::connect(ui->execBlacklistBtn, &QPushButton::clicked, [&] { + on_execBlacklistBtn_clicked(); + }); + QObject::connect(ui->resetGeometryBtn, &QPushButton::clicked, [&] { + on_resetGeometryBtn_clicked(); + }); } void WorkaroundsSettingsTab::update() @@ -65,12 +71,11 @@ void WorkaroundsSettingsTab::update() settings().setExecutablesBlacklist(m_ExecutableBlacklist); } -bool WorkaroundsSettingsTab::changeBlacklistNow( - QWidget* parent, Settings& settings) +bool WorkaroundsSettingsTab::changeBlacklistNow(QWidget* parent, Settings& settings) { const auto current = settings.executablesBlacklist(); - if (auto s=changeBlacklistLater(parent, current)) { + if (auto s = changeBlacklistLater(parent, current)) { settings.setExecutablesBlacklist(*s); return true; } @@ -78,23 +83,22 @@ bool WorkaroundsSettingsTab::changeBlacklistNow( return false; } -std::optional WorkaroundsSettingsTab::changeBlacklistLater( - QWidget* parent, const QString& current) +std::optional +WorkaroundsSettingsTab::changeBlacklistLater(QWidget* parent, const QString& current) { bool ok = false; QString result = QInputDialog::getMultiLineText( - parent, - QObject::tr("Executables Blacklist"), - QObject::tr("Enter one executable per line to be blacklisted from the virtual file system.\n" - "Mods and other virtualized files will not be visible to these executables and\n" - "any executables launched by them.\n\n" - "Example:\n" - " Chrome.exe\n" - " Firefox.exe"), - current.split(";").join("\n"), - &ok - ); + parent, QObject::tr("Executables Blacklist"), + QObject::tr("Enter one executable per line to be blacklisted from the virtual " + "file system.\n" + "Mods and other virtualized files will not be visible to these " + "executables and\n" + "any executables launched by them.\n\n" + "Example:\n" + " Chrome.exe\n" + " Firefox.exe"), + current.split(";").join("\n"), &ok); if (!ok) { return {}; @@ -112,7 +116,7 @@ std::optional WorkaroundsSettingsTab::changeBlacklistLater( void WorkaroundsSettingsTab::on_execBlacklistBtn_clicked() { - if (auto s=changeBlacklistLater(parentWidget(), m_ExecutableBlacklist)) { + if (auto s = changeBlacklistLater(parentWidget(), m_ExecutableBlacklist)) { m_ExecutableBlacklist = *s; } } @@ -120,24 +124,23 @@ void WorkaroundsSettingsTab::on_execBlacklistBtn_clicked() void WorkaroundsSettingsTab::on_bsaDateBtn_clicked() { const auto* game = qApp->property("managed_game").value(); - QDir dir = game->dataDirectory(); + QDir dir = game->dataDirectory(); - helper::backdateBSAs( - parentWidget(), - qApp->applicationDirPath().toStdWString(), - dir.absolutePath().toStdWString()); + helper::backdateBSAs(parentWidget(), qApp->applicationDirPath().toStdWString(), + dir.absolutePath().toStdWString()); } void WorkaroundsSettingsTab::on_resetGeometryBtn_clicked() { - const auto r = MOBase::TaskDialog(parentWidget()) - .title(QObject::tr("Restart Mod Organizer")) - .main(QObject::tr("Restart Mod Organizer")) - .content(QObject::tr("Geometries will be reset to their default values.")) - .icon(QMessageBox::Question) - .button({QObject::tr("Restart Mod Organizer"), QMessageBox::Ok}) - .button({QObject::tr("Cancel"), QMessageBox::Cancel}) - .exec(); + const auto r = + MOBase::TaskDialog(parentWidget()) + .title(QObject::tr("Restart Mod Organizer")) + .main(QObject::tr("Restart Mod Organizer")) + .content(QObject::tr("Geometries will be reset to their default values.")) + .icon(QMessageBox::Question) + .button({QObject::tr("Restart Mod Organizer"), QMessageBox::Ok}) + .button({QObject::tr("Cancel"), QMessageBox::Cancel}) + .exec(); if (r == QMessageBox::Ok) { settings().geometry().requestReset(); diff --git a/src/settingsdialogworkarounds.h b/src/settingsdialogworkarounds.h index cffc54a02..8c86fd69e 100644 --- a/src/settingsdialogworkarounds.h +++ b/src/settingsdialogworkarounds.h @@ -17,8 +17,8 @@ class WorkaroundsSettingsTab : public SettingsTab // shows the blacklist dialog from the given string and returns the new // blacklist if the user accepted it // - static std::optional changeBlacklistLater( - QWidget* parent, const QString& current); + static std::optional changeBlacklistLater(QWidget* parent, + const QString& current); void update(); @@ -30,4 +30,4 @@ class WorkaroundsSettingsTab : public SettingsTab void on_resetGeometryBtn_clicked(); }; -#endif // SETTINGSDIALOGWORKAROUNDS_H +#endif // SETTINGSDIALOGWORKAROUNDS_H diff --git a/src/settingsutilities.cpp b/src/settingsutilities.cpp index db7c1818d..698649e61 100644 --- a/src/settingsutilities.cpp +++ b/src/settingsutilities.cpp @@ -27,7 +27,6 @@ void logRemoval(const QString& name) log::debug("setting '{}' removed", name); } - QString settingName(const QString& section, const QString& key) { if (section.isEmpty()) { @@ -43,9 +42,8 @@ QString settingName(const QString& section, const QString& key) } } -void removeImpl( - QSettings& settings, const QString& displayName, - const QString& section, const QString& key) +void removeImpl(QSettings& settings, const QString& displayName, const QString& section, + const QString& key) { if (key.isEmpty()) { if (!settings.childGroups().contains(section, Qt::CaseInsensitive)) { @@ -73,9 +71,8 @@ void removeSection(QSettings& settings, const QString& section) removeImpl(settings, section, section, ""); } - ScopedGroup::ScopedGroup(QSettings& s, const QString& name) - : m_settings(s), m_name(name) + : m_settings(s), m_name(name) { m_settings.beginGroup(m_name); } @@ -95,9 +92,8 @@ QStringList ScopedGroup::keys() const return m_settings.childKeys(); } - ScopedReadArray::ScopedReadArray(QSettings& s, const QString& section) - : m_settings(s), m_count(0) + : m_settings(s), m_count(0) { m_count = m_settings.beginReadArray(section); } @@ -117,13 +113,11 @@ QStringList ScopedReadArray::keys() const return m_settings.childKeys(); } - -ScopedWriteArray::ScopedWriteArray( - QSettings& s, const QString& section, std::size_t size) +ScopedWriteArray::ScopedWriteArray(QSettings& s, const QString& section, + std::size_t size) : m_settings(s), m_section(section), m_i(0) { - m_settings.beginWriteArray( - section, size == NoSize ? -1 : static_cast(size)); + m_settings.beginWriteArray(section, size == NoSize ? -1 : static_cast(size)); } ScopedWriteArray::~ScopedWriteArray() @@ -137,7 +131,6 @@ void ScopedWriteArray::next() ++m_i; } - QString widgetNameWithTopLevel(const QWidget* widget) { QStringList components; @@ -206,13 +199,12 @@ QString checkedSettingName(const QAbstractButton* b) void warnIfNotCheckable(const QAbstractButton* b) { if (!b->isCheckable()) { - log::warn( - "button '{}' used in the settings as a checkbox or radio button " - "but is not checkable", b->objectName()); + log::warn("button '{}' used in the settings as a checkbox or radio button " + "but is not checkable", + b->objectName()); } } - QString credentialName(const QString& key) { return "ModOrganizer2_" + key; @@ -232,9 +224,8 @@ bool deleteWindowsCredential(const QString& key) return true; } - log::error( - "failed to delete windows credential {}, {}", - credName, formatSystemMessage(e)); + log::error("failed to delete windows credential {}, {}", credName, + formatSystemMessage(e)); return false; } @@ -250,23 +241,22 @@ bool addWindowsCredential(const QString& key, const QString& data) const auto wname = credName.toStdWString(); const auto wdata = data.toStdWString(); - const auto* blob = reinterpret_cast(wdata.data()); + const auto* blob = reinterpret_cast(wdata.data()); const auto blobSize = wdata.size() * sizeof(decltype(wdata)::value_type); - CREDENTIALW cred = {}; - cred.Flags = 0; - cred.Type = CRED_TYPE_GENERIC; - cred.TargetName = const_cast(wname.c_str()); - cred.CredentialBlob = const_cast(blob); + CREDENTIALW cred = {}; + cred.Flags = 0; + cred.Type = CRED_TYPE_GENERIC; + cred.TargetName = const_cast(wname.c_str()); + cred.CredentialBlob = const_cast(blob); cred.CredentialBlobSize = static_cast(blobSize); - cred.Persist = CRED_PERSIST_LOCAL_MACHINE; + cred.Persist = CRED_PERSIST_LOCAL_MACHINE; if (!CredWriteW(&cred, 0)) { const auto e = GetLastError(); - log::error( - "failed to delete windows credential {}, {}", - credName, formatSystemMessage(e)); + log::error("failed to delete windows credential {}, {}", credName, + formatSystemMessage(e)); return false; } @@ -294,8 +284,8 @@ QString getWindowsCredential(const QString& key) CREDENTIALW* rawCreds = nullptr; - const auto ret = CredReadW( - credName.toStdWString().c_str(), CRED_TYPE_GENERIC, 0, &rawCreds); + const auto ret = + CredReadW(credName.toStdWString().c_str(), CRED_TYPE_GENERIC, 0, &rawCreds); CredentialPtr creds(rawCreds); @@ -303,9 +293,8 @@ QString getWindowsCredential(const QString& key) const auto e = GetLastError(); if (e != ERROR_NOT_FOUND) { - log::error( - "failed to retrieve windows credential {}: {}", - credName, formatSystemMessage(e)); + log::error("failed to retrieve windows credential {}: {}", credName, + formatSystemMessage(e)); } return {}; @@ -313,9 +302,9 @@ QString getWindowsCredential(const QString& key) QString value; if (creds->CredentialBlob) { - value = QString::fromWCharArray( - reinterpret_cast(creds->CredentialBlob), - creds->CredentialBlobSize / sizeof(wchar_t)); + value = + QString::fromWCharArray(reinterpret_cast(creds->CredentialBlob), + creds->CredentialBlobSize / sizeof(wchar_t)); } return value; diff --git a/src/settingsutilities.h b/src/settingsutilities.h index 53eeff874..11b2af1c9 100644 --- a/src/settingsutilities.h +++ b/src/settingsutilities.h @@ -3,17 +3,15 @@ #include -namespace MOBase { - class ExpanderWidget; +namespace MOBase +{ +class ExpanderWidget; } -template +template struct ValueConverter { - static const T& convert(const T& t) - { - return t; - } + static const T& convert(const T& t) { return t; } }; template @@ -37,8 +35,7 @@ struct ValueConverter bool shouldLogSetting(const QString& displayName); template -void logChange( - const QString& displayName, std::optional oldValue, const T& newValue) +void logChange(const QString& displayName, std::optional oldValue, const T& newValue) { if (!shouldLogSetting(displayName)) { return; @@ -47,25 +44,20 @@ void logChange( using VC = ValueConverter; if (oldValue) { - MOBase::log::debug( - "setting '{}' changed from '{}' to '{}'", - displayName, VC::convert(*oldValue), VC::convert(newValue)); + MOBase::log::debug("setting '{}' changed from '{}' to '{}'", displayName, + VC::convert(*oldValue), VC::convert(newValue)); } else { - MOBase::log::debug( - "setting '{}' set to '{}'", - displayName, VC::convert(newValue)); + MOBase::log::debug("setting '{}' set to '{}'", displayName, VC::convert(newValue)); } } void logRemoval(const QString& name); - QString settingName(const QString& section, const QString& key); template -void setImpl( - QSettings& settings, const QString& displayName, - const QString& section, const QString& key, const T& value) +void setImpl(QSettings& settings, const QString& displayName, const QString& section, + const QString& key, const T& value) { const auto current = getOptional(settings, section, key); @@ -79,22 +71,18 @@ void setImpl( logChange(displayName, current, value); if constexpr (std::is_enum_v) { - settings.setValue( - name, static_cast>(value)); + settings.setValue(name, static_cast>(value)); } else { settings.setValue(name, value); } } -void removeImpl( - QSettings& settings, const QString& displayName, - const QString& section, const QString& key); - +void removeImpl(QSettings& settings, const QString& displayName, const QString& section, + const QString& key); template -std::optional getOptional( - const QSettings& settings, - const QString& section, const QString& key, std::optional def={}) +std::optional getOptional(const QSettings& settings, const QString& section, + const QString& key, std::optional def = {}) { if (settings.contains(settingName(section, key))) { const auto v = settings.value(settingName(section, key)); @@ -110,11 +98,9 @@ std::optional getOptional( } template -T get( - const QSettings& settings, - const QString& section, const QString& key, T def) +T get(const QSettings& settings, const QString& section, const QString& key, T def) { - if (auto v=getOptional(settings, section, key)) { + if (auto v = getOptional(settings, section, key)) { return *v; } else { return def; @@ -122,9 +108,8 @@ T get( } template -void set( - QSettings& settings, - const QString& section, const QString& key, const T& value) +void set(QSettings& settings, const QString& section, const QString& key, + const T& value) { setImpl(settings, settingName(section, key), section, key, value); } @@ -132,14 +117,13 @@ void set( void remove(QSettings& settings, const QString& section, const QString& key); void removeSection(QSettings& settings, const QString& section); - class ScopedGroup { public: ScopedGroup(QSettings& s, const QString& name); ~ScopedGroup(); - ScopedGroup(const ScopedGroup&) = delete; + ScopedGroup(const ScopedGroup&) = delete; ScopedGroup& operator=(const ScopedGroup&) = delete; template @@ -161,13 +145,13 @@ class ScopedGroup } template - std::optional getOptional(const QString& key, std::optional def={}) const + std::optional getOptional(const QString& key, std::optional def = {}) const { return ::getOptional(m_settings, "", key, def); } template - T get(const QString& key, T def={}) const + T get(const QString& key, T def = {}) const { return ::get(m_settings, "", key, def); } @@ -177,33 +161,32 @@ class ScopedGroup QString m_name; }; - class ScopedReadArray { public: ScopedReadArray(QSettings& s, const QString& section); ~ScopedReadArray(); - ScopedReadArray(const ScopedReadArray&) = delete; + ScopedReadArray(const ScopedReadArray&) = delete; ScopedReadArray& operator=(const ScopedReadArray&) = delete; template void for_each(F&& f) const { - for (int i=0; i - std::optional getOptional(const QString& key, std::optional def={}) const + std::optional getOptional(const QString& key, std::optional def = {}) const { return ::getOptional(m_settings, "", key, def); } template - T get(const QString& key, T def={}) const + T get(const QString& key, T def = {}) const { return ::get(m_settings, "", key, def); } @@ -216,16 +199,15 @@ class ScopedReadArray int m_count; }; - class ScopedWriteArray { public: static const auto NoSize = std::numeric_limits::max(); - ScopedWriteArray(QSettings& s, const QString& section, std::size_t size=NoSize); + ScopedWriteArray(QSettings& s, const QString& section, std::size_t size = NoSize); ~ScopedWriteArray(); - ScopedWriteArray(const ScopedWriteArray&) = delete; + ScopedWriteArray(const ScopedWriteArray&) = delete; ScopedWriteArray& operator=(const ScopedWriteArray&) = delete; void next(); @@ -233,10 +215,7 @@ class ScopedWriteArray template void set(const QString& key, const T& value) { - const auto displayName = QString("%1/%2\\%3") - .arg(m_section) - .arg(m_i) - .arg(key); + const auto displayName = QString("%1/%2\\%3").arg(m_section).arg(m_i).arg(key); setImpl(m_settings, displayName, "", key, value); } @@ -247,7 +226,6 @@ class ScopedWriteArray int m_i; }; - QString widgetNameWithTopLevel(const QWidget* widget); QString widgetName(const QMainWindow* w); QString widgetName(const QHeaderView* w); @@ -281,4 +259,4 @@ void warnIfNotCheckable(const QAbstractButton* b); bool setWindowsCredential(const QString& key, const QString& data); QString getWindowsCredential(const QString& key); -#endif // SETTINGSUTILITIES_H +#endif // SETTINGSUTILITIES_H diff --git a/src/shared/appconfig.cpp b/src/shared/appconfig.cpp index 49edcbcce..221732ded 100644 --- a/src/shared/appconfig.cpp +++ b/src/shared/appconfig.cpp @@ -1,33 +1,39 @@ -/* -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 "appconfig.h" - -namespace AppConfig { - -#define PARWSTRING wstring -#define APPPARAM(partype, parid, value) partype parid () { return value; } -#include "appconfig.inc" - -namespace MOShared { -#undef PARWSTRING -#undef APPPARAM - -} -} // namespace MOShared +/* +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 "appconfig.h" + +namespace AppConfig +{ + +#define PARWSTRING wstring +#define APPPARAM(partype, parid, value) \ + partype parid() \ + { \ + return value; \ + } +#include "appconfig.inc" + +namespace MOShared +{ +#undef PARWSTRING +#undef APPPARAM + +} // namespace MOShared +} // namespace AppConfig diff --git a/src/shared/appconfig.h b/src/shared/appconfig.h index b32043e53..598ee19c0 100644 --- a/src/shared/appconfig.h +++ b/src/shared/appconfig.h @@ -1,40 +1,41 @@ -/* -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 APPCONFIG_H -#define APPCONFIG_H - -#include - -namespace AppConfig { - - -#define PARWSTRING wstring -#define APPPARAM(partype, parid, value) partype parid (); -#include "appconfig.inc" - -namespace MOShared { -#undef PARWSTRING -#undef APPPARAM - -} - -} // namespace MOShared - -#endif // APPCONFIG_H +/* +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 APPCONFIG_H +#define APPCONFIG_H + +#include + +namespace AppConfig +{ + +#define PARWSTRING wstring +#define APPPARAM(partype, parid, value) partype parid(); +#include "appconfig.inc" + +namespace MOShared +{ +#undef PARWSTRING +#undef APPPARAM + +} // namespace MOShared + +} // namespace AppConfig + +#endif // APPCONFIG_H diff --git a/src/shared/directoryentry.cpp b/src/shared/directoryentry.cpp index badb56362..84da66e23 100644 --- a/src/shared/directoryentry.cpp +++ b/src/shared/directoryentry.cpp @@ -1,978 +1,960 @@ -/* -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 "directoryentry.h" -#include "originconnection.h" -#include "filesorigin.h" -#include "fileentry.h" -#include "../envfs.h" -#include "util.h" -#include "windows_error.h" -#include -#include - -namespace MOShared -{ - -using namespace MOBase; -const int MAXPATH_UNICODE = 32767; - -template -void elapsedImpl(std::chrono::nanoseconds& out, F&& f) -{ - if constexpr (DirectoryStats::EnableInstrumentation) { - const auto start = std::chrono::high_resolution_clock::now(); - f(); - const auto end = std::chrono::high_resolution_clock::now(); - out += (end - start); - } else { - f(); - } -} - -// elapsed() is not optimized out when EnableInstrumentation is false even -// though it's equivalent that this macro -#define elapsed(OUT, F) (F)(); -//#define elapsed(OUT, F) elapsedImpl(OUT, F); - -static bool SupportOptimizedFind() -{ - // large fetch and basic info for FindFirstFileEx is supported on win server 2008 r2, win 7 and newer - - OSVERSIONINFOEX versionInfo; - versionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); - versionInfo.dwMajorVersion = 6; - versionInfo.dwMinorVersion = 1; - - ULONGLONG mask = ::VerSetConditionMask( - ::VerSetConditionMask(0, VER_MAJORVERSION, VER_GREATER_EQUAL), - VER_MINORVERSION, VER_GREATER_EQUAL); - - return (::VerifyVersionInfo(&versionInfo, VER_MAJORVERSION | VER_MINORVERSION, mask) == TRUE); -} - -bool DirCompareByName::operator()( - const DirectoryEntry* lhs, const DirectoryEntry* rhs) const -{ - return _wcsicmp(lhs->getName().c_str(), rhs->getName().c_str()) < 0; -} - - -DirectoryEntry::DirectoryEntry( - std::wstring name, DirectoryEntry* parent, int originID) : - m_OriginConnection(new OriginConnection), - m_Name(std::move(name)), m_Parent(parent), m_Populated(false), m_TopLevel(true) -{ - m_FileRegister.reset(new FileRegister(m_OriginConnection)); - m_Origins.insert(originID); -} - -DirectoryEntry::DirectoryEntry( - std::wstring name, DirectoryEntry* parent, int originID, - boost::shared_ptr fileRegister, - boost::shared_ptr originConnection) : - m_FileRegister(fileRegister), m_OriginConnection(originConnection), - m_Name(std::move(name)), m_Parent(parent), m_Populated(false), m_TopLevel(false) -{ - m_Origins.insert(originID); -} - -DirectoryEntry::~DirectoryEntry() -{ - clear(); -} - -void DirectoryEntry::clear() -{ - for (auto itor=m_SubDirectories.rbegin(); itor!=m_SubDirectories.rend(); ++itor) { - delete *itor; - } - - m_Files.clear(); - m_FilesLookup.clear(); - m_SubDirectories.clear(); - m_SubDirectoriesLookup.clear(); -} - -void DirectoryEntry::addFromOrigin( - const std::wstring &originName, const std::wstring &directory, int priority, - DirectoryStats& stats) -{ - env::DirectoryWalker walker; - addFromOrigin(walker, originName, directory, priority, stats); -} - -void DirectoryEntry::addFromOrigin( - env::DirectoryWalker& walker, const std::wstring &originName, - const std::wstring &directory, int priority, DirectoryStats& stats) -{ - FilesOrigin &origin = createOrigin(originName, directory, priority, stats); - - if (!directory.empty()) { - addFiles(walker, origin, directory, stats); - } - - m_Populated = true; -} - -void DirectoryEntry::addFromList( - const std::wstring &originName, const std::wstring &directory, - env::Directory& root, int priority, DirectoryStats& stats) -{ - stats = {}; - - FilesOrigin &origin = createOrigin(originName, directory, priority, stats); - addDir(origin, root, stats); -} - -void DirectoryEntry::addDir( - FilesOrigin& origin, env::Directory& d, DirectoryStats& stats) -{ - elapsed(stats.dirTimes, [&]{ - for (auto& sd : d.dirs) { - auto* sdirEntry = getSubDirectory(sd, true, stats, origin.getID()); - sdirEntry->addDir(origin, sd, stats); - } - }); - - elapsed(stats.fileTimes, [&]{ - for (auto& f : d.files) { - insert(f, origin, L"", -1, stats); - } - }); - - m_Populated = true; -} - -void DirectoryEntry::addFromAllBSAs( - const std::wstring& originName, const std::wstring& directory, - int priority, const std::vector& archives, - const std::set& enabledArchives, - const std::vector& loadOrder, - DirectoryStats& stats) -{ - for (const auto& archive : archives) { - const std::filesystem::path archivePath(archive); - const auto filename = archivePath.filename().native(); - - if (!enabledArchives.contains(filename)) { - continue; - } - - const auto filenameLc = ToLowerCopy(filename); - - int order = -1; - - for (auto plugin : loadOrder) - { - const auto pluginNameLc = - ToLowerCopy(std::filesystem::path(plugin).stem().native()); - - if (filenameLc.starts_with(pluginNameLc + L" - ") || - filenameLc.starts_with(pluginNameLc + L".")) { - auto itor = std::find(loadOrder.begin(), loadOrder.end(), plugin); - if (itor != loadOrder.end()) { - order = std::distance(loadOrder.begin(), itor); - } - } - } - - addFromBSA( - originName, directory, archivePath.native(), - priority, order, stats); - } -} - -void DirectoryEntry::addFromBSA( - const std::wstring& originName, const std::wstring& directory, - const std::wstring& archivePath, int priority, int order, DirectoryStats& stats) -{ - FilesOrigin& origin = createOrigin(originName, directory, priority, stats); - const auto archiveName = std::filesystem::path(archivePath).filename().native(); - - if (containsArchive(archiveName)) { - return; - } - - BSA::Archive archive; - BSA::EErrorCode res = BSA::ERROR_NONE; - - try - { - // read() can return an error, but it can also throw if the file is not a - // valid bsa - res = archive.read(ToString(archivePath, false).c_str(), false); - } - catch(std::exception& e) - { - log::error("invalid bsa '{}', error {}", archivePath, e.what()); - return; - } - - if ((res != BSA::ERROR_NONE) && (res != BSA::ERROR_INVALIDHASHES)) { - log::error("invalid bsa '{}', error {}", archivePath, res); - return; - } - - std::error_code ec; - const auto lwt = std::filesystem::last_write_time(archivePath, ec); - FILETIME ft = {}; - - if (ec) { - log::warn( - "failed to get last modified date for '{}', {}", - archivePath, ec.message()); - } else { - ft = ToFILETIME(lwt); - } - - addFiles(origin, archive.getRoot(), ft, archiveName, order, stats); - - m_Populated = true; -} - -void DirectoryEntry::propagateOrigin(int origin) -{ - { - std::scoped_lock lock(m_OriginsMutex); - m_Origins.insert(origin); - } - - if (m_Parent != nullptr) { - m_Parent->propagateOrigin(origin); - } -} - -bool DirectoryEntry::originExists(const std::wstring &name) const -{ - return m_OriginConnection->exists(name); -} - -FilesOrigin &DirectoryEntry::getOriginByID(int ID) const -{ - return m_OriginConnection->getByID(ID); -} - -FilesOrigin &DirectoryEntry::getOriginByName(const std::wstring &name) const -{ - return m_OriginConnection->getByName(name); -} - -const FilesOrigin* DirectoryEntry::findOriginByID(int ID) const -{ - return m_OriginConnection->findByID(ID); -} - -int DirectoryEntry::anyOrigin() const -{ - bool ignore; - - for (auto iter = m_Files.begin(); iter != m_Files.end(); ++iter) { - FileEntryPtr entry = m_FileRegister->getFile(iter->second); - if ((entry.get() != nullptr) && !entry->isFromArchive()) { - return entry->getOrigin(ignore); - } - } - - // if we got here, no file directly within this directory is a valid indicator for a mod, thus - // we continue looking in subdirectories - for (DirectoryEntry* entry : m_SubDirectories) { - int res = entry->anyOrigin(); - if (res != InvalidOriginID){ - return res; - } - } - - return *(m_Origins.begin()); -} - -std::vector DirectoryEntry::getFiles() const -{ - std::vector result; - - for (auto iter = m_Files.begin(); iter != m_Files.end(); ++iter) { - result.push_back(m_FileRegister->getFile(iter->second)); - } - - return result; -} - -DirectoryEntry* DirectoryEntry::findSubDirectory( - const std::wstring &name, bool alreadyLowerCase) const -{ - SubDirectoriesLookup::const_iterator itor; - - if (alreadyLowerCase) { - itor = m_SubDirectoriesLookup.find(name); - } else { - itor = m_SubDirectoriesLookup.find(ToLowerCopy(name)); - } - - if (itor == m_SubDirectoriesLookup.end()) { - return nullptr; - } - - return itor->second; -} - -DirectoryEntry* DirectoryEntry::findSubDirectoryRecursive(const std::wstring &path) -{ - DirectoryStats dummy; - return getSubDirectoryRecursive(path, false, dummy, InvalidOriginID); -} - -const FileEntryPtr DirectoryEntry::findFile( - const std::wstring &name, bool alreadyLowerCase) const -{ - FilesLookup::const_iterator iter; - - if (alreadyLowerCase) { - iter = m_FilesLookup.find(DirectoryEntryFileKey(name)); - } else { - iter = m_FilesLookup.find(DirectoryEntryFileKey(ToLowerCopy(name))); - } - - if (iter != m_FilesLookup.end()) { - return m_FileRegister->getFile(iter->second); - } else { - return FileEntryPtr(); - } -} - -const FileEntryPtr DirectoryEntry::findFile(const DirectoryEntryFileKey& key) const -{ - auto iter = m_FilesLookup.find(key); - - if (iter != m_FilesLookup.end()) { - return m_FileRegister->getFile(iter->second); - } else { - return FileEntryPtr(); - } -} - -bool DirectoryEntry::hasFile(const std::wstring& name) const -{ - return m_Files.contains(ToLowerCopy(name)); -} - -bool DirectoryEntry::containsArchive(std::wstring archiveName) -{ - for (auto iter = m_Files.begin(); iter != m_Files.end(); ++iter) { - FileEntryPtr entry = m_FileRegister->getFile(iter->second); - if (entry->isFromArchive(archiveName)) { - return true; - } - } - - return false; -} - -const FileEntryPtr DirectoryEntry::searchFile( - const std::wstring &path, const DirectoryEntry** directory) const -{ - if (directory != nullptr) { - *directory = nullptr; - } - - if ((path.length() == 0) || (path == L"*")) { - // no file name -> the path ended on a (back-)slash - if (directory != nullptr) { - *directory = this; - } - - return FileEntryPtr(); - } - - const size_t len = path.find_first_of(L"\\/"); - - if (len == std::string::npos) { - // no more path components - auto iter = m_Files.find(ToLowerCopy(path)); - - if (iter != m_Files.end()) { - return m_FileRegister->getFile(iter->second); - } else if (directory != nullptr) { - DirectoryEntry* temp = findSubDirectory(path); - if (temp != nullptr) { - *directory = temp; - } - } - } else { - // file is in a subdirectory, recurse into the matching subdirectory - std::wstring pathComponent = path.substr(0, len); - DirectoryEntry* temp = findSubDirectory(pathComponent); - - if (temp != nullptr) { - if (len >= path.size()) { - log::error(QObject::tr("unexpected end of path").toStdString()); - return FileEntryPtr(); - } - - return temp->searchFile(path.substr(len + 1), directory); - } - } - - return FileEntryPtr(); -} - -void DirectoryEntry::removeFile(FileIndex index) -{ - removeFileFromList(index); -} - -bool DirectoryEntry::removeFile(const std::wstring &filePath, int* origin) -{ - size_t pos = filePath.find_first_of(L"\\/"); - - if (pos == std::string::npos) { - return this->remove(filePath, origin); - } - - std::wstring dirName = filePath.substr(0, pos); - std::wstring rest = filePath.substr(pos + 1); - - DirectoryStats dummy; - DirectoryEntry* entry = getSubDirectoryRecursive(dirName, false, dummy); - - if (entry != nullptr) { - return entry->removeFile(rest, origin); - } else { - return false; - } -} - -void DirectoryEntry::removeDir(const std::wstring &path) -{ - size_t pos = path.find_first_of(L"\\/"); - - if (pos == std::string::npos) { - for (auto iter = m_SubDirectories.begin(); iter != m_SubDirectories.end(); ++iter) { - DirectoryEntry* entry = *iter; - - if (CaseInsensitiveEqual(entry->getName(), path)) { - entry->removeDirRecursive(); - removeDirectoryFromList(iter); - delete entry; - break; - } - } - } else { - std::wstring dirName = path.substr(0, pos); - std::wstring rest = path.substr(pos + 1); - - DirectoryStats dummy; - DirectoryEntry* entry = getSubDirectoryRecursive(dirName, false, dummy); - - if (entry != nullptr) { - entry->removeDir(rest); - } - } -} - -bool DirectoryEntry::remove(const std::wstring &fileName, int* origin) -{ - const auto lcFileName = ToLowerCopy(fileName); - - auto iter = m_Files.find(lcFileName); - bool b = false; - - if (iter != m_Files.end()) { - if (origin != nullptr) { - FileEntryPtr entry = m_FileRegister->getFile(iter->second); - if (entry.get() != nullptr) { - bool ignore; - *origin = entry->getOrigin(ignore); - } - } - - b = m_FileRegister->removeFile(iter->second); - } - - return b; -} - -bool DirectoryEntry::hasContentsFromOrigin(int originID) const -{ - return m_Origins.find(originID) != m_Origins.end(); -} - -FilesOrigin &DirectoryEntry::createOrigin( - const std::wstring &originName, const std::wstring &directory, int priority, - DirectoryStats& stats) -{ - auto r = m_OriginConnection->getOrCreate( - originName, directory, priority, - m_FileRegister, m_OriginConnection, stats); - - if (r.second) { - ++stats.originCreate; - } else { - ++stats.originExists; - } - - return r.first; -} - -void DirectoryEntry::removeFiles(const std::set &indices) -{ - removeFilesFromList(indices); -} - -FileEntryPtr DirectoryEntry::insert( - std::wstring_view fileName, FilesOrigin &origin, FILETIME fileTime, - std::wstring_view archive, int order, DirectoryStats& stats) -{ - std::wstring fileNameLower = ToLowerCopy(fileName); - FileEntryPtr fe; - - DirectoryEntryFileKey key(std::move(fileNameLower)); - - { - std::unique_lock lock(m_FilesMutex); - - FilesLookup::iterator itor; - - elapsed(stats.filesLookupTimes, [&]{ - itor = m_FilesLookup.find(key); - }); - - if (itor != m_FilesLookup.end()) { - lock.unlock(); - ++stats.fileExists; - fe = m_FileRegister->getFile(itor->second); - } else { - ++stats.fileCreate; - fe = m_FileRegister->createFile( - std::wstring(fileName.begin(), fileName.end()), this, stats); - - elapsed(stats.addFileTimes, [&] { - addFileToList(std::move(key.value), fe->getIndex()); - }); - - // fileNameLower has moved from this point - } - } - - elapsed(stats.addOriginToFileTimes, [&]{ - fe->addOrigin(origin.getID(), fileTime, archive, order); - }); - - elapsed(stats.addFileToOriginTimes, [&]{ - origin.addFile(fe->getIndex()); - }); - - return fe; -} - -FileEntryPtr DirectoryEntry::insert( - env::File& file, FilesOrigin &origin, std::wstring_view archive, int order, - DirectoryStats& stats) -{ - FileEntryPtr fe; - - { - std::unique_lock lock(m_FilesMutex); - - FilesMap::iterator itor; - - elapsed(stats.filesLookupTimes, [&]{ - itor = m_Files.find(file.lcname); - }); - - if (itor != m_Files.end()) { - lock.unlock(); - ++stats.fileExists; - fe = m_FileRegister->getFile(itor->second); - } else { - ++stats.fileCreate; - fe = m_FileRegister->createFile(std::move(file.name), this, stats); - // file.name has been moved from this point - - elapsed(stats.addFileTimes, [&]{ - addFileToList(std::move(file.lcname), fe->getIndex()); - }); - - // file.lcname has been moved from this point - } - } - - elapsed(stats.addOriginToFileTimes, [&]{ - fe->addOrigin(origin.getID(), file.lastModified, archive, order); - }); - - elapsed(stats.addFileToOriginTimes, [&]{ - origin.addFile(fe->getIndex()); - }); - - return fe; -} - -struct DirectoryEntry::Context -{ - FilesOrigin& origin; - DirectoryStats& stats; - std::stack current; -}; - -void DirectoryEntry::addFiles( - env::DirectoryWalker& walker, FilesOrigin &origin, - const std::wstring& path, DirectoryStats& stats) -{ - Context cx = {origin, stats}; - cx.current.push(this); - - walker.forEachEntry(path, &cx, - [](void* pcx, std::wstring_view path) - { - onDirectoryStart((Context*)pcx, path); - }, - - [](void* pcx, std::wstring_view path) - { - onDirectoryEnd((Context*)pcx, path); - }, - - [](void* pcx, std::wstring_view path, FILETIME ft, uint64_t) - { - onFile((Context*)pcx, path, ft); - } - ); -} - -void DirectoryEntry::onDirectoryStart(Context* cx, std::wstring_view path) -{ - elapsed(cx->stats.dirTimes, [&] { - auto* sd = cx->current.top()->getSubDirectory( - path, true, cx->stats, cx->origin.getID()); - - cx->current.push(sd); - }); -} - -void DirectoryEntry::onDirectoryEnd(Context* cx, std::wstring_view path) -{ - elapsed(cx->stats.dirTimes, [&] { - cx->current.pop(); - }); -} - -void DirectoryEntry::onFile(Context* cx, std::wstring_view path, FILETIME ft) -{ - elapsed(cx->stats.fileTimes, [&]{ - cx->current.top()->insert(path, cx->origin, ft, L"", -1, cx->stats); - }); -} - -void DirectoryEntry::addFiles( - FilesOrigin& origin, const BSA::Folder::Ptr archiveFolder, FILETIME fileTime, - const std::wstring& archiveName, int order, DirectoryStats& stats) -{ - // add files - const auto fileCount = archiveFolder->getNumFiles(); - for (unsigned int i=0; igetFile(i); - - auto f = insert( - ToWString(file->getName(), true), origin, fileTime, - archiveName, order, stats); - - if (f) { - if (file->getUncompressedFileSize() > 0) { - f->setFileSize(file->getFileSize(), file->getUncompressedFileSize()); - } else { - f->setFileSize(file->getFileSize(), FileEntry::NoFileSize); - } - } - } - - // recurse into subdirectories - const auto dirCount = archiveFolder->getNumSubFolders(); - for (unsigned int i=0; igetSubFolder(i); - - DirectoryEntry* folderEntry = getSubDirectoryRecursive( - ToWString(folder->getName(), true), true, stats, origin.getID()); - - folderEntry->addFiles(origin, folder, fileTime, archiveName, order, stats); - } -} - -DirectoryEntry* DirectoryEntry::getSubDirectory( - std::wstring_view name, bool create, DirectoryStats& stats, int originID) -{ - std::wstring nameLc = ToLowerCopy(name); - - std::scoped_lock lock(m_SubDirMutex); - - SubDirectoriesLookup::iterator itor; - elapsed(stats.subdirLookupTimes, [&] { - itor = m_SubDirectoriesLookup.find(nameLc); - }); - - if (itor != m_SubDirectoriesLookup.end()) { - ++stats.subdirExists; - return itor->second; - } - - if (create) { - ++stats.subdirCreate; - - auto* entry = new DirectoryEntry( - std::wstring(name.begin(), name.end()), this, originID, - m_FileRegister, m_OriginConnection); - - elapsed(stats.addDirectoryTimes, [&] { - addDirectoryToList(entry, std::move(nameLc)); - // nameLc is moved from this point - }); - - return entry; - } else { - return nullptr; - } -} - -DirectoryEntry* DirectoryEntry::getSubDirectory( - env::Directory& dir, bool create, DirectoryStats& stats, int originID) -{ - SubDirectoriesLookup::iterator itor; - - std::scoped_lock lock(m_SubDirMutex); - - elapsed(stats.subdirLookupTimes, [&] { - itor = m_SubDirectoriesLookup.find(dir.lcname); - }); - - if (itor != m_SubDirectoriesLookup.end()) { - ++stats.subdirExists; - return itor->second; - } - - if (create) { - ++stats.subdirCreate; - - auto* entry = new DirectoryEntry( - std::move(dir.name), this, originID, - m_FileRegister, m_OriginConnection); - // dir.name is moved from this point - - elapsed(stats.addDirectoryTimes, [&]{ - addDirectoryToList(entry, std::move(dir.lcname)); - }); - - // dir.lcname is moved from this point - - return entry; - } else { - return nullptr; - } -} - -DirectoryEntry* DirectoryEntry::getSubDirectoryRecursive( - const std::wstring& path, bool create, DirectoryStats& stats, int originID) -{ - if (path.length() == 0) { - // path ended with a backslash? - return this; - } - - const size_t pos = path.find_first_of(L"\\/"); - - if (pos == std::wstring::npos) { - return getSubDirectory(path, create, stats); - } else { - DirectoryEntry* nextChild = getSubDirectory( - path.substr(0, pos), create, stats, originID); - - if (nextChild == nullptr) { - return nullptr; - } else { - return nextChild->getSubDirectoryRecursive( - path.substr(pos + 1), create, stats, originID); - } - } -} - -void DirectoryEntry::removeDirRecursive() -{ - while (!m_Files.empty()) { - m_FileRegister->removeFile(m_Files.begin()->second); - } - - m_FilesLookup.clear(); - - for (DirectoryEntry* entry : m_SubDirectories) { - entry->removeDirRecursive(); - delete entry; - } - - m_SubDirectories.clear(); - m_SubDirectoriesLookup.clear(); -} - -void DirectoryEntry::addDirectoryToList(DirectoryEntry* e, std::wstring nameLc) -{ - m_SubDirectories.insert(e); - m_SubDirectoriesLookup.emplace(std::move(nameLc), e); -} - -void DirectoryEntry::removeDirectoryFromList(SubDirectories::iterator itor) -{ - const auto* entry = *itor; - - { - auto itor2 = std::find_if( - m_SubDirectoriesLookup.begin(), m_SubDirectoriesLookup.end(), - [&](auto&& d) { return (d.second == entry); }); - - if (itor2 == m_SubDirectoriesLookup.end()) { - log::error("entry {} not in sub directories map", entry->getName()); - } else { - m_SubDirectoriesLookup.erase(itor2); - } - } - - m_SubDirectories.erase(itor); -} - -void DirectoryEntry::removeFileFromList(FileIndex index) -{ - auto removeFrom = [&](auto& list) { - auto iter = std::find_if( - list.begin(), list.end(), - [&index](auto&& pair) { return (pair.second == index); } - ); - - if (iter == list.end()) { - auto f = m_FileRegister->getFile(index); - - if (f) { - log::error( - "can't remove file '{}', not in directory entry '{}'", - f->getName(), getName()); - } else { - log::error( - "can't remove file with index {}, not in directory entry '{}' and " - "not in register", - index, getName()); - } - } else { - list.erase(iter); - } - }; - - removeFrom(m_FilesLookup); - removeFrom(m_Files); -} - -void DirectoryEntry::removeFilesFromList(const std::set& indices) -{ - for (auto iter = m_Files.begin(); iter != m_Files.end();) { - if (indices.find(iter->second) != indices.end()) { - iter = m_Files.erase(iter); - } else { - ++iter; - } - } - - for (auto iter = m_FilesLookup.begin(); iter != m_FilesLookup.end();) { - if (indices.find(iter->second) != indices.end()) { - iter = m_FilesLookup.erase(iter); - } else { - ++iter; - } - } -} - -void DirectoryEntry::addFileToList(std::wstring fileNameLower, FileIndex index) -{ - m_FilesLookup.emplace(fileNameLower, index); - m_Files.emplace(std::move(fileNameLower), index); - // fileNameLower has been moved from this point -} - -struct DumpFailed : public std::runtime_error -{ - using runtime_error::runtime_error; -}; - -void DirectoryEntry::dump(const std::wstring& file) const -{ - try - { - std::FILE* f = nullptr; - auto e = _wfopen_s(&f, file.c_str(), L"wb"); - - if (e != 0 || !f) { - throw DumpFailed(fmt::format( - "failed to open, {} ({})", std::strerror(e), e)); - } - - Guard g([&]{ std::fclose(f); }); - - dump(f, L"Data"); - } - catch(DumpFailed& e) - { - log::error( - "failed to write list to '{}': {}", - QString::fromStdWString(file).toStdString(), e.what()); - } -} - -void DirectoryEntry::dump(std::FILE* f, const std::wstring& parentPath) const -{ - { - std::scoped_lock lock(m_FilesMutex); - - for (auto&& index : m_Files) { - const auto file = m_FileRegister->getFile(index.second); - if (!file) { - continue; - } - - if (file->isFromArchive()) { - // TODO: don't list files from archives. maybe make this an option? - continue; - } - - const auto& o = m_OriginConnection->getByID(file->getOrigin()); - const auto path = parentPath + L"\\" + file->getName(); - const auto line = path + L"\t(" + o.getName() + L")\r\n"; - - const auto lineu8 = MOShared::ToString(line, true); - - if (std::fwrite(lineu8.data(), lineu8.size(), 1, f) != 1) { - const auto e = errno; - throw DumpFailed(fmt::format( - "failed to write, {} ({})", std::strerror(e), e)); - } - } - } - - { - std::scoped_lock lock(m_SubDirMutex); - for (auto&& d : m_SubDirectories) { - const auto path = parentPath + L"\\" + d->m_Name; - d->dump(f, path); - } - } -} - -} // namespace MOShared +/* +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 "directoryentry.h" +#include "../envfs.h" +#include "fileentry.h" +#include "filesorigin.h" +#include "originconnection.h" +#include "util.h" +#include "windows_error.h" +#include +#include + +namespace MOShared +{ + +using namespace MOBase; +const int MAXPATH_UNICODE = 32767; + +template +void elapsedImpl(std::chrono::nanoseconds& out, F&& f) +{ + if constexpr (DirectoryStats::EnableInstrumentation) { + const auto start = std::chrono::high_resolution_clock::now(); + f(); + const auto end = std::chrono::high_resolution_clock::now(); + out += (end - start); + } else { + f(); + } +} + +// elapsed() is not optimized out when EnableInstrumentation is false even +// though it's equivalent that this macro +#define elapsed(OUT, F) (F)(); +// #define elapsed(OUT, F) elapsedImpl(OUT, F); + +static bool SupportOptimizedFind() +{ + // large fetch and basic info for FindFirstFileEx is supported on win server 2008 r2, + // win 7 and newer + + OSVERSIONINFOEX versionInfo; + versionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); + versionInfo.dwMajorVersion = 6; + versionInfo.dwMinorVersion = 1; + + ULONGLONG mask = ::VerSetConditionMask( + ::VerSetConditionMask(0, VER_MAJORVERSION, VER_GREATER_EQUAL), VER_MINORVERSION, + VER_GREATER_EQUAL); + + return (::VerifyVersionInfo(&versionInfo, VER_MAJORVERSION | VER_MINORVERSION, + mask) == TRUE); +} + +bool DirCompareByName::operator()(const DirectoryEntry* lhs, + const DirectoryEntry* rhs) const +{ + return _wcsicmp(lhs->getName().c_str(), rhs->getName().c_str()) < 0; +} + +DirectoryEntry::DirectoryEntry(std::wstring name, DirectoryEntry* parent, int originID) + : m_OriginConnection(new OriginConnection), m_Name(std::move(name)), + m_Parent(parent), m_Populated(false), m_TopLevel(true) +{ + m_FileRegister.reset(new FileRegister(m_OriginConnection)); + m_Origins.insert(originID); +} + +DirectoryEntry::DirectoryEntry(std::wstring name, DirectoryEntry* parent, int originID, + boost::shared_ptr fileRegister, + boost::shared_ptr originConnection) + : m_FileRegister(fileRegister), m_OriginConnection(originConnection), + m_Name(std::move(name)), m_Parent(parent), m_Populated(false), m_TopLevel(false) +{ + m_Origins.insert(originID); +} + +DirectoryEntry::~DirectoryEntry() +{ + clear(); +} + +void DirectoryEntry::clear() +{ + for (auto itor = m_SubDirectories.rbegin(); itor != m_SubDirectories.rend(); ++itor) { + delete *itor; + } + + m_Files.clear(); + m_FilesLookup.clear(); + m_SubDirectories.clear(); + m_SubDirectoriesLookup.clear(); +} + +void DirectoryEntry::addFromOrigin(const std::wstring& originName, + const std::wstring& directory, int priority, + DirectoryStats& stats) +{ + env::DirectoryWalker walker; + addFromOrigin(walker, originName, directory, priority, stats); +} + +void DirectoryEntry::addFromOrigin(env::DirectoryWalker& walker, + const std::wstring& originName, + const std::wstring& directory, int priority, + DirectoryStats& stats) +{ + FilesOrigin& origin = createOrigin(originName, directory, priority, stats); + + if (!directory.empty()) { + addFiles(walker, origin, directory, stats); + } + + m_Populated = true; +} + +void DirectoryEntry::addFromList(const std::wstring& originName, + const std::wstring& directory, env::Directory& root, + int priority, DirectoryStats& stats) +{ + stats = {}; + + FilesOrigin& origin = createOrigin(originName, directory, priority, stats); + addDir(origin, root, stats); +} + +void DirectoryEntry::addDir(FilesOrigin& origin, env::Directory& d, + DirectoryStats& stats) +{ + elapsed(stats.dirTimes, [&] { + for (auto& sd : d.dirs) { + auto* sdirEntry = getSubDirectory(sd, true, stats, origin.getID()); + sdirEntry->addDir(origin, sd, stats); + } + }); + + elapsed(stats.fileTimes, [&] { + for (auto& f : d.files) { + insert(f, origin, L"", -1, stats); + } + }); + + m_Populated = true; +} + +void DirectoryEntry::addFromAllBSAs(const std::wstring& originName, + const std::wstring& directory, int priority, + const std::vector& archives, + const std::set& enabledArchives, + const std::vector& loadOrder, + DirectoryStats& stats) +{ + for (const auto& archive : archives) { + const std::filesystem::path archivePath(archive); + const auto filename = archivePath.filename().native(); + + if (!enabledArchives.contains(filename)) { + continue; + } + + const auto filenameLc = ToLowerCopy(filename); + + int order = -1; + + for (auto plugin : loadOrder) { + const auto pluginNameLc = + ToLowerCopy(std::filesystem::path(plugin).stem().native()); + + if (filenameLc.starts_with(pluginNameLc + L" - ") || + filenameLc.starts_with(pluginNameLc + L".")) { + auto itor = std::find(loadOrder.begin(), loadOrder.end(), plugin); + if (itor != loadOrder.end()) { + order = std::distance(loadOrder.begin(), itor); + } + } + } + + addFromBSA(originName, directory, archivePath.native(), priority, order, stats); + } +} + +void DirectoryEntry::addFromBSA(const std::wstring& originName, + const std::wstring& directory, + const std::wstring& archivePath, int priority, + int order, DirectoryStats& stats) +{ + FilesOrigin& origin = createOrigin(originName, directory, priority, stats); + const auto archiveName = std::filesystem::path(archivePath).filename().native(); + + if (containsArchive(archiveName)) { + return; + } + + BSA::Archive archive; + BSA::EErrorCode res = BSA::ERROR_NONE; + + try { + // read() can return an error, but it can also throw if the file is not a + // valid bsa + res = archive.read(ToString(archivePath, false).c_str(), false); + } catch (std::exception& e) { + log::error("invalid bsa '{}', error {}", archivePath, e.what()); + return; + } + + if ((res != BSA::ERROR_NONE) && (res != BSA::ERROR_INVALIDHASHES)) { + log::error("invalid bsa '{}', error {}", archivePath, res); + return; + } + + std::error_code ec; + const auto lwt = std::filesystem::last_write_time(archivePath, ec); + FILETIME ft = {}; + + if (ec) { + log::warn("failed to get last modified date for '{}', {}", archivePath, + ec.message()); + } else { + ft = ToFILETIME(lwt); + } + + addFiles(origin, archive.getRoot(), ft, archiveName, order, stats); + + m_Populated = true; +} + +void DirectoryEntry::propagateOrigin(int origin) +{ + { + std::scoped_lock lock(m_OriginsMutex); + m_Origins.insert(origin); + } + + if (m_Parent != nullptr) { + m_Parent->propagateOrigin(origin); + } +} + +bool DirectoryEntry::originExists(const std::wstring& name) const +{ + return m_OriginConnection->exists(name); +} + +FilesOrigin& DirectoryEntry::getOriginByID(int ID) const +{ + return m_OriginConnection->getByID(ID); +} + +FilesOrigin& DirectoryEntry::getOriginByName(const std::wstring& name) const +{ + return m_OriginConnection->getByName(name); +} + +const FilesOrigin* DirectoryEntry::findOriginByID(int ID) const +{ + return m_OriginConnection->findByID(ID); +} + +int DirectoryEntry::anyOrigin() const +{ + bool ignore; + + for (auto iter = m_Files.begin(); iter != m_Files.end(); ++iter) { + FileEntryPtr entry = m_FileRegister->getFile(iter->second); + if ((entry.get() != nullptr) && !entry->isFromArchive()) { + return entry->getOrigin(ignore); + } + } + + // if we got here, no file directly within this directory is a valid indicator for a + // mod, thus we continue looking in subdirectories + for (DirectoryEntry* entry : m_SubDirectories) { + int res = entry->anyOrigin(); + if (res != InvalidOriginID) { + return res; + } + } + + return *(m_Origins.begin()); +} + +std::vector DirectoryEntry::getFiles() const +{ + std::vector result; + + for (auto iter = m_Files.begin(); iter != m_Files.end(); ++iter) { + result.push_back(m_FileRegister->getFile(iter->second)); + } + + return result; +} + +DirectoryEntry* DirectoryEntry::findSubDirectory(const std::wstring& name, + bool alreadyLowerCase) const +{ + SubDirectoriesLookup::const_iterator itor; + + if (alreadyLowerCase) { + itor = m_SubDirectoriesLookup.find(name); + } else { + itor = m_SubDirectoriesLookup.find(ToLowerCopy(name)); + } + + if (itor == m_SubDirectoriesLookup.end()) { + return nullptr; + } + + return itor->second; +} + +DirectoryEntry* DirectoryEntry::findSubDirectoryRecursive(const std::wstring& path) +{ + DirectoryStats dummy; + return getSubDirectoryRecursive(path, false, dummy, InvalidOriginID); +} + +const FileEntryPtr DirectoryEntry::findFile(const std::wstring& name, + bool alreadyLowerCase) const +{ + FilesLookup::const_iterator iter; + + if (alreadyLowerCase) { + iter = m_FilesLookup.find(DirectoryEntryFileKey(name)); + } else { + iter = m_FilesLookup.find(DirectoryEntryFileKey(ToLowerCopy(name))); + } + + if (iter != m_FilesLookup.end()) { + return m_FileRegister->getFile(iter->second); + } else { + return FileEntryPtr(); + } +} + +const FileEntryPtr DirectoryEntry::findFile(const DirectoryEntryFileKey& key) const +{ + auto iter = m_FilesLookup.find(key); + + if (iter != m_FilesLookup.end()) { + return m_FileRegister->getFile(iter->second); + } else { + return FileEntryPtr(); + } +} + +bool DirectoryEntry::hasFile(const std::wstring& name) const +{ + return m_Files.contains(ToLowerCopy(name)); +} + +bool DirectoryEntry::containsArchive(std::wstring archiveName) +{ + for (auto iter = m_Files.begin(); iter != m_Files.end(); ++iter) { + FileEntryPtr entry = m_FileRegister->getFile(iter->second); + if (entry->isFromArchive(archiveName)) { + return true; + } + } + + return false; +} + +const FileEntryPtr DirectoryEntry::searchFile(const std::wstring& path, + const DirectoryEntry** directory) const +{ + if (directory != nullptr) { + *directory = nullptr; + } + + if ((path.length() == 0) || (path == L"*")) { + // no file name -> the path ended on a (back-)slash + if (directory != nullptr) { + *directory = this; + } + + return FileEntryPtr(); + } + + const size_t len = path.find_first_of(L"\\/"); + + if (len == std::string::npos) { + // no more path components + auto iter = m_Files.find(ToLowerCopy(path)); + + if (iter != m_Files.end()) { + return m_FileRegister->getFile(iter->second); + } else if (directory != nullptr) { + DirectoryEntry* temp = findSubDirectory(path); + if (temp != nullptr) { + *directory = temp; + } + } + } else { + // file is in a subdirectory, recurse into the matching subdirectory + std::wstring pathComponent = path.substr(0, len); + DirectoryEntry* temp = findSubDirectory(pathComponent); + + if (temp != nullptr) { + if (len >= path.size()) { + log::error(QObject::tr("unexpected end of path").toStdString()); + return FileEntryPtr(); + } + + return temp->searchFile(path.substr(len + 1), directory); + } + } + + return FileEntryPtr(); +} + +void DirectoryEntry::removeFile(FileIndex index) +{ + removeFileFromList(index); +} + +bool DirectoryEntry::removeFile(const std::wstring& filePath, int* origin) +{ + size_t pos = filePath.find_first_of(L"\\/"); + + if (pos == std::string::npos) { + return this->remove(filePath, origin); + } + + std::wstring dirName = filePath.substr(0, pos); + std::wstring rest = filePath.substr(pos + 1); + + DirectoryStats dummy; + DirectoryEntry* entry = getSubDirectoryRecursive(dirName, false, dummy); + + if (entry != nullptr) { + return entry->removeFile(rest, origin); + } else { + return false; + } +} + +void DirectoryEntry::removeDir(const std::wstring& path) +{ + size_t pos = path.find_first_of(L"\\/"); + + if (pos == std::string::npos) { + for (auto iter = m_SubDirectories.begin(); iter != m_SubDirectories.end(); ++iter) { + DirectoryEntry* entry = *iter; + + if (CaseInsensitiveEqual(entry->getName(), path)) { + entry->removeDirRecursive(); + removeDirectoryFromList(iter); + delete entry; + break; + } + } + } else { + std::wstring dirName = path.substr(0, pos); + std::wstring rest = path.substr(pos + 1); + + DirectoryStats dummy; + DirectoryEntry* entry = getSubDirectoryRecursive(dirName, false, dummy); + + if (entry != nullptr) { + entry->removeDir(rest); + } + } +} + +bool DirectoryEntry::remove(const std::wstring& fileName, int* origin) +{ + const auto lcFileName = ToLowerCopy(fileName); + + auto iter = m_Files.find(lcFileName); + bool b = false; + + if (iter != m_Files.end()) { + if (origin != nullptr) { + FileEntryPtr entry = m_FileRegister->getFile(iter->second); + if (entry.get() != nullptr) { + bool ignore; + *origin = entry->getOrigin(ignore); + } + } + + b = m_FileRegister->removeFile(iter->second); + } + + return b; +} + +bool DirectoryEntry::hasContentsFromOrigin(int originID) const +{ + return m_Origins.find(originID) != m_Origins.end(); +} + +FilesOrigin& DirectoryEntry::createOrigin(const std::wstring& originName, + const std::wstring& directory, int priority, + DirectoryStats& stats) +{ + auto r = m_OriginConnection->getOrCreate(originName, directory, priority, + m_FileRegister, m_OriginConnection, stats); + + if (r.second) { + ++stats.originCreate; + } else { + ++stats.originExists; + } + + return r.first; +} + +void DirectoryEntry::removeFiles(const std::set& indices) +{ + removeFilesFromList(indices); +} + +FileEntryPtr DirectoryEntry::insert(std::wstring_view fileName, FilesOrigin& origin, + FILETIME fileTime, std::wstring_view archive, + int order, DirectoryStats& stats) +{ + std::wstring fileNameLower = ToLowerCopy(fileName); + FileEntryPtr fe; + + DirectoryEntryFileKey key(std::move(fileNameLower)); + + { + std::unique_lock lock(m_FilesMutex); + + FilesLookup::iterator itor; + + elapsed(stats.filesLookupTimes, [&] { + itor = m_FilesLookup.find(key); + }); + + if (itor != m_FilesLookup.end()) { + lock.unlock(); + ++stats.fileExists; + fe = m_FileRegister->getFile(itor->second); + } else { + ++stats.fileCreate; + fe = m_FileRegister->createFile(std::wstring(fileName.begin(), fileName.end()), + this, stats); + + elapsed(stats.addFileTimes, [&] { + addFileToList(std::move(key.value), fe->getIndex()); + }); + + // fileNameLower has moved from this point + } + } + + elapsed(stats.addOriginToFileTimes, [&] { + fe->addOrigin(origin.getID(), fileTime, archive, order); + }); + + elapsed(stats.addFileToOriginTimes, [&] { + origin.addFile(fe->getIndex()); + }); + + return fe; +} + +FileEntryPtr DirectoryEntry::insert(env::File& file, FilesOrigin& origin, + std::wstring_view archive, int order, + DirectoryStats& stats) +{ + FileEntryPtr fe; + + { + std::unique_lock lock(m_FilesMutex); + + FilesMap::iterator itor; + + elapsed(stats.filesLookupTimes, [&] { + itor = m_Files.find(file.lcname); + }); + + if (itor != m_Files.end()) { + lock.unlock(); + ++stats.fileExists; + fe = m_FileRegister->getFile(itor->second); + } else { + ++stats.fileCreate; + fe = m_FileRegister->createFile(std::move(file.name), this, stats); + // file.name has been moved from this point + + elapsed(stats.addFileTimes, [&] { + addFileToList(std::move(file.lcname), fe->getIndex()); + }); + + // file.lcname has been moved from this point + } + } + + elapsed(stats.addOriginToFileTimes, [&] { + fe->addOrigin(origin.getID(), file.lastModified, archive, order); + }); + + elapsed(stats.addFileToOriginTimes, [&] { + origin.addFile(fe->getIndex()); + }); + + return fe; +} + +struct DirectoryEntry::Context +{ + FilesOrigin& origin; + DirectoryStats& stats; + std::stack current; +}; + +void DirectoryEntry::addFiles(env::DirectoryWalker& walker, FilesOrigin& origin, + const std::wstring& path, DirectoryStats& stats) +{ + Context cx = {origin, stats}; + cx.current.push(this); + + walker.forEachEntry( + path, &cx, + [](void* pcx, std::wstring_view path) { + onDirectoryStart((Context*)pcx, path); + }, + + [](void* pcx, std::wstring_view path) { + onDirectoryEnd((Context*)pcx, path); + }, + + [](void* pcx, std::wstring_view path, FILETIME ft, uint64_t) { + onFile((Context*)pcx, path, ft); + }); +} + +void DirectoryEntry::onDirectoryStart(Context* cx, std::wstring_view path) +{ + elapsed(cx->stats.dirTimes, [&] { + auto* sd = + cx->current.top()->getSubDirectory(path, true, cx->stats, cx->origin.getID()); + + cx->current.push(sd); + }); +} + +void DirectoryEntry::onDirectoryEnd(Context* cx, std::wstring_view path) +{ + elapsed(cx->stats.dirTimes, [&] { + cx->current.pop(); + }); +} + +void DirectoryEntry::onFile(Context* cx, std::wstring_view path, FILETIME ft) +{ + elapsed(cx->stats.fileTimes, [&] { + cx->current.top()->insert(path, cx->origin, ft, L"", -1, cx->stats); + }); +} + +void DirectoryEntry::addFiles(FilesOrigin& origin, const BSA::Folder::Ptr archiveFolder, + FILETIME fileTime, const std::wstring& archiveName, + int order, DirectoryStats& stats) +{ + // add files + const auto fileCount = archiveFolder->getNumFiles(); + for (unsigned int i = 0; i < fileCount; ++i) { + const BSA::File::Ptr file = archiveFolder->getFile(i); + + auto f = insert(ToWString(file->getName(), true), origin, fileTime, archiveName, + order, stats); + + if (f) { + if (file->getUncompressedFileSize() > 0) { + f->setFileSize(file->getFileSize(), file->getUncompressedFileSize()); + } else { + f->setFileSize(file->getFileSize(), FileEntry::NoFileSize); + } + } + } + + // recurse into subdirectories + const auto dirCount = archiveFolder->getNumSubFolders(); + for (unsigned int i = 0; i < dirCount; ++i) { + const BSA::Folder::Ptr folder = archiveFolder->getSubFolder(i); + + DirectoryEntry* folderEntry = getSubDirectoryRecursive( + ToWString(folder->getName(), true), true, stats, origin.getID()); + + folderEntry->addFiles(origin, folder, fileTime, archiveName, order, stats); + } +} + +DirectoryEntry* DirectoryEntry::getSubDirectory(std::wstring_view name, bool create, + DirectoryStats& stats, int originID) +{ + std::wstring nameLc = ToLowerCopy(name); + + std::scoped_lock lock(m_SubDirMutex); + + SubDirectoriesLookup::iterator itor; + elapsed(stats.subdirLookupTimes, [&] { + itor = m_SubDirectoriesLookup.find(nameLc); + }); + + if (itor != m_SubDirectoriesLookup.end()) { + ++stats.subdirExists; + return itor->second; + } + + if (create) { + ++stats.subdirCreate; + + auto* entry = new DirectoryEntry(std::wstring(name.begin(), name.end()), this, + originID, m_FileRegister, m_OriginConnection); + + elapsed(stats.addDirectoryTimes, [&] { + addDirectoryToList(entry, std::move(nameLc)); + // nameLc is moved from this point + }); + + return entry; + } else { + return nullptr; + } +} + +DirectoryEntry* DirectoryEntry::getSubDirectory(env::Directory& dir, bool create, + DirectoryStats& stats, int originID) +{ + SubDirectoriesLookup::iterator itor; + + std::scoped_lock lock(m_SubDirMutex); + + elapsed(stats.subdirLookupTimes, [&] { + itor = m_SubDirectoriesLookup.find(dir.lcname); + }); + + if (itor != m_SubDirectoriesLookup.end()) { + ++stats.subdirExists; + return itor->second; + } + + if (create) { + ++stats.subdirCreate; + + auto* entry = new DirectoryEntry(std::move(dir.name), this, originID, + m_FileRegister, m_OriginConnection); + // dir.name is moved from this point + + elapsed(stats.addDirectoryTimes, [&] { + addDirectoryToList(entry, std::move(dir.lcname)); + }); + + // dir.lcname is moved from this point + + return entry; + } else { + return nullptr; + } +} + +DirectoryEntry* DirectoryEntry::getSubDirectoryRecursive(const std::wstring& path, + bool create, + DirectoryStats& stats, + int originID) +{ + if (path.length() == 0) { + // path ended with a backslash? + return this; + } + + const size_t pos = path.find_first_of(L"\\/"); + + if (pos == std::wstring::npos) { + return getSubDirectory(path, create, stats); + } else { + DirectoryEntry* nextChild = + getSubDirectory(path.substr(0, pos), create, stats, originID); + + if (nextChild == nullptr) { + return nullptr; + } else { + return nextChild->getSubDirectoryRecursive(path.substr(pos + 1), create, stats, + originID); + } + } +} + +void DirectoryEntry::removeDirRecursive() +{ + while (!m_Files.empty()) { + m_FileRegister->removeFile(m_Files.begin()->second); + } + + m_FilesLookup.clear(); + + for (DirectoryEntry* entry : m_SubDirectories) { + entry->removeDirRecursive(); + delete entry; + } + + m_SubDirectories.clear(); + m_SubDirectoriesLookup.clear(); +} + +void DirectoryEntry::addDirectoryToList(DirectoryEntry* e, std::wstring nameLc) +{ + m_SubDirectories.insert(e); + m_SubDirectoriesLookup.emplace(std::move(nameLc), e); +} + +void DirectoryEntry::removeDirectoryFromList(SubDirectories::iterator itor) +{ + const auto* entry = *itor; + + { + auto itor2 = std::find_if(m_SubDirectoriesLookup.begin(), + m_SubDirectoriesLookup.end(), [&](auto&& d) { + return (d.second == entry); + }); + + if (itor2 == m_SubDirectoriesLookup.end()) { + log::error("entry {} not in sub directories map", entry->getName()); + } else { + m_SubDirectoriesLookup.erase(itor2); + } + } + + m_SubDirectories.erase(itor); +} + +void DirectoryEntry::removeFileFromList(FileIndex index) +{ + auto removeFrom = [&](auto& list) { + auto iter = std::find_if(list.begin(), list.end(), [&index](auto&& pair) { + return (pair.second == index); + }); + + if (iter == list.end()) { + auto f = m_FileRegister->getFile(index); + + if (f) { + log::error("can't remove file '{}', not in directory entry '{}'", f->getName(), + getName()); + } else { + log::error("can't remove file with index {}, not in directory entry '{}' and " + "not in register", + index, getName()); + } + } else { + list.erase(iter); + } + }; + + removeFrom(m_FilesLookup); + removeFrom(m_Files); +} + +void DirectoryEntry::removeFilesFromList(const std::set& indices) +{ + for (auto iter = m_Files.begin(); iter != m_Files.end();) { + if (indices.find(iter->second) != indices.end()) { + iter = m_Files.erase(iter); + } else { + ++iter; + } + } + + for (auto iter = m_FilesLookup.begin(); iter != m_FilesLookup.end();) { + if (indices.find(iter->second) != indices.end()) { + iter = m_FilesLookup.erase(iter); + } else { + ++iter; + } + } +} + +void DirectoryEntry::addFileToList(std::wstring fileNameLower, FileIndex index) +{ + m_FilesLookup.emplace(fileNameLower, index); + m_Files.emplace(std::move(fileNameLower), index); + // fileNameLower has been moved from this point +} + +struct DumpFailed : public std::runtime_error +{ + using runtime_error::runtime_error; +}; + +void DirectoryEntry::dump(const std::wstring& file) const +{ + try { + std::FILE* f = nullptr; + auto e = _wfopen_s(&f, file.c_str(), L"wb"); + + if (e != 0 || !f) { + throw DumpFailed(fmt::format("failed to open, {} ({})", std::strerror(e), e)); + } + + Guard g([&] { + std::fclose(f); + }); + + dump(f, L"Data"); + } catch (DumpFailed& e) { + log::error("failed to write list to '{}': {}", + QString::fromStdWString(file).toStdString(), e.what()); + } +} + +void DirectoryEntry::dump(std::FILE* f, const std::wstring& parentPath) const +{ + { + std::scoped_lock lock(m_FilesMutex); + + for (auto&& index : m_Files) { + const auto file = m_FileRegister->getFile(index.second); + if (!file) { + continue; + } + + if (file->isFromArchive()) { + // TODO: don't list files from archives. maybe make this an option? + continue; + } + + const auto& o = m_OriginConnection->getByID(file->getOrigin()); + const auto path = parentPath + L"\\" + file->getName(); + const auto line = path + L"\t(" + o.getName() + L")\r\n"; + + const auto lineu8 = MOShared::ToString(line, true); + + if (std::fwrite(lineu8.data(), lineu8.size(), 1, f) != 1) { + const auto e = errno; + throw DumpFailed(fmt::format("failed to write, {} ({})", std::strerror(e), e)); + } + } + } + + { + std::scoped_lock lock(m_SubDirMutex); + for (auto&& d : m_SubDirectories) { + const auto path = parentPath + L"\\" + d->m_Name; + d->dump(f, path); + } + } +} + +} // namespace MOShared diff --git a/src/shared/directoryentry.h b/src/shared/directoryentry.h index 0ee3c9190..42aef8c7e 100644 --- a/src/shared/directoryentry.h +++ b/src/shared/directoryentry.h @@ -1,321 +1,284 @@ -/* -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 MO_REGISTER_DIRECTORYENTRY_INCLUDED -#define MO_REGISTER_DIRECTORYENTRY_INCLUDED - -#include "fileregister.h" -#include - -namespace env -{ - class DirectoryWalker; - struct Directory; - struct File; -} - -namespace std -{ - template <> - struct hash - { - using argument_type = MOShared::DirectoryEntryFileKey; - using result_type = std::size_t; - - inline result_type operator()(const argument_type& key) const; - }; -} - - -namespace MOShared -{ - -struct DirCompareByName -{ - bool operator()(const DirectoryEntry* a, const DirectoryEntry* b) const; -}; - - -class DirectoryEntry -{ -public: - using SubDirectories = std::set; - - DirectoryEntry( - std::wstring name, DirectoryEntry* parent, OriginID originID); - - DirectoryEntry( - std::wstring name, DirectoryEntry* parent, OriginID originID, - boost::shared_ptr fileRegister, - boost::shared_ptr originConnection); - - ~DirectoryEntry(); - - // noncopyable - DirectoryEntry(const DirectoryEntry&) = delete; - DirectoryEntry& operator=(const DirectoryEntry&) = delete; - - void clear(); - - bool isPopulated() const - { - return m_Populated; - } - - bool isTopLevel() const - { - return m_TopLevel; - } - - bool isEmpty() const - { - return m_Files.empty() && m_SubDirectories.empty(); - } - - bool hasFiles() const - { - return !m_Files.empty(); - } - - const DirectoryEntry* getParent() const - { - return m_Parent; - } - - // add files to this directory (and subdirectories) from the specified origin. - // That origin may exist or not - void addFromOrigin( - const std::wstring& originName, - const std::wstring& directory, int priority, DirectoryStats& stats); - - void addFromOrigin( - env::DirectoryWalker& walker, const std::wstring& originName, - const std::wstring& directory, int priority, DirectoryStats& stats); - - void addFromAllBSAs( - const std::wstring& originName, const std::wstring& directory, - int priority, const std::vector& archives, - const std::set& enabledArchives, - const std::vector& loadOrder, - DirectoryStats& stats); - - void addFromBSA( - const std::wstring& originName, const std::wstring& directory, - const std::wstring& archivePath, int priority, int order, - DirectoryStats& stats); - - void addFromList( - const std::wstring& originName, const std::wstring& directory, - env::Directory& root, int priority, DirectoryStats& stats); - - void propagateOrigin(OriginID origin); - - const std::wstring& getName() const - { - return m_Name; - } - - boost::shared_ptr getFileRegister() - { - return m_FileRegister; - } - - bool originExists(const std::wstring& name) const; - FilesOrigin& getOriginByID(OriginID ID) const; - FilesOrigin& getOriginByName(const std::wstring& name) const; - const FilesOrigin* findOriginByID(OriginID ID) const; - - OriginID anyOrigin() const; - - std::vector getFiles() const; - - const SubDirectories& getSubDirectories() const - { - return m_SubDirectories; - } - - template - void forEachDirectory(F&& f) const - { - for (auto&& d : m_SubDirectories) { - if (!f(*d)) { - break; - } - } - } - - template - void forEachFile(F&& f) const - { - for (auto&& p : m_Files) { - if (auto file=m_FileRegister->getFile(p.second)) { - if (!f(*file)) { - break; - } - } - } - } - - template - void forEachFileIndex(F&& f) const - { - for (auto&& p : m_Files) { - if (!f(p.second)) { - break; - } - } - } - - FileEntryPtr getFileByIndex(FileIndex index) const - { - return m_FileRegister->getFile(index); - } - - DirectoryEntry* findSubDirectory( - const std::wstring& name, bool alreadyLowerCase=false) const; - - DirectoryEntry* findSubDirectoryRecursive(const std::wstring& path); - - /** retrieve a file in this directory by name. - * @param name name of the file - * @return fileentry object for the file or nullptr if no file matches - */ - const FileEntryPtr findFile(const std::wstring& name, bool alreadyLowerCase=false) const; - const FileEntryPtr findFile(const DirectoryEntryFileKey& key) const; - - bool hasFile(const std::wstring& name) const; - bool containsArchive(std::wstring archiveName); - - // search through this directory and all subdirectories for a file by the - // specified name (relative path). - // - // if directory is not nullptr, the referenced variable will be set to the - // path containing the file - // - const FileEntryPtr searchFile( - const std::wstring& path, const DirectoryEntry** directory=nullptr) const; - - void removeFile(FileIndex index); - - // remove the specified file from the tree. This can be a path leading to a - // file in a subdirectory - bool removeFile(const std::wstring& filePath, OriginID* origin = nullptr); - - /** - * @brief remove the specified directory - * @param path directory to remove - */ - void removeDir(const std::wstring& path); - - bool remove(const std::wstring& fileName, OriginID* origin); - - bool hasContentsFromOrigin(OriginID originID) const; - - FilesOrigin& createOrigin( - const std::wstring& originName, - const std::wstring& directory, int priority, DirectoryStats& stats); - - void removeFiles(const std::set& indices); - - void dump(const std::wstring& file) const; - -private: - using FilesMap = std::map; - using FilesLookup = std::unordered_map; - using SubDirectoriesLookup = std::unordered_map; - - boost::shared_ptr m_FileRegister; - boost::shared_ptr m_OriginConnection; - - std::wstring m_Name; - FilesMap m_Files; - FilesLookup m_FilesLookup; - SubDirectories m_SubDirectories; - SubDirectoriesLookup m_SubDirectoriesLookup; - - DirectoryEntry* m_Parent; - std::set m_Origins; - bool m_Populated; - bool m_TopLevel; - mutable std::mutex m_SubDirMutex; - mutable std::mutex m_FilesMutex; - mutable std::mutex m_OriginsMutex; - - - FileEntryPtr insert( - std::wstring_view fileName, FilesOrigin& origin, FILETIME fileTime, - std::wstring_view archive, int order, DirectoryStats& stats); - - FileEntryPtr insert( - env::File& file, FilesOrigin& origin, - std::wstring_view archive, int order, DirectoryStats& stats); - - void addFiles( - env::DirectoryWalker& walker, FilesOrigin& origin, - const std::wstring& path, DirectoryStats& stats); - - void addFiles( - FilesOrigin& origin, BSA::Folder::Ptr archiveFolder, FILETIME fileTime, - const std::wstring& archiveName, int order, DirectoryStats& stats); - - void addDir(FilesOrigin& origin, env::Directory& d, DirectoryStats& stats); - - DirectoryEntry* getSubDirectory( - std::wstring_view name, bool create, DirectoryStats& stats, - OriginID originID = InvalidOriginID); - - DirectoryEntry* getSubDirectory( - env::Directory& dir, bool create, DirectoryStats& stats, - OriginID originID = InvalidOriginID); - - DirectoryEntry* getSubDirectoryRecursive( - const std::wstring& path, bool create, DirectoryStats& stats, - OriginID originID = InvalidOriginID); - - void removeDirRecursive(); - - void addDirectoryToList(DirectoryEntry* e, std::wstring nameLc); - void removeDirectoryFromList(SubDirectories::iterator itor); - - void addFileToList(std::wstring fileNameLower, FileIndex index); - void removeFileFromList(FileIndex index); - void removeFilesFromList(const std::set& indices); - - struct Context; - static void onDirectoryStart(Context* cx, std::wstring_view path); - static void onDirectoryEnd(Context* cx, std::wstring_view path); - static void onFile(Context* cx, std::wstring_view path, FILETIME ft); - - void dump(std::FILE* f, const std::wstring& parentPath) const; -}; - -} // namespace MOShared - - -namespace std -{ - hash::result_type - hash::operator()( - const argument_type& key) const - { - return key.hash; - } -} - -#endif // MO_REGISTER_DIRECTORYENTRY_INCLUDED +/* +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 MO_REGISTER_DIRECTORYENTRY_INCLUDED +#define MO_REGISTER_DIRECTORYENTRY_INCLUDED + +#include "fileregister.h" +#include + +namespace env +{ +class DirectoryWalker; +struct Directory; +struct File; +} // namespace env + +namespace std +{ +template <> +struct hash +{ + using argument_type = MOShared::DirectoryEntryFileKey; + using result_type = std::size_t; + + inline result_type operator()(const argument_type& key) const; +}; +} // namespace std + +namespace MOShared +{ + +struct DirCompareByName +{ + bool operator()(const DirectoryEntry* a, const DirectoryEntry* b) const; +}; + +class DirectoryEntry +{ +public: + using SubDirectories = std::set; + + DirectoryEntry(std::wstring name, DirectoryEntry* parent, OriginID originID); + + DirectoryEntry(std::wstring name, DirectoryEntry* parent, OriginID originID, + boost::shared_ptr fileRegister, + boost::shared_ptr originConnection); + + ~DirectoryEntry(); + + // noncopyable + DirectoryEntry(const DirectoryEntry&) = delete; + DirectoryEntry& operator=(const DirectoryEntry&) = delete; + + void clear(); + + bool isPopulated() const { return m_Populated; } + + bool isTopLevel() const { return m_TopLevel; } + + bool isEmpty() const { return m_Files.empty() && m_SubDirectories.empty(); } + + bool hasFiles() const { return !m_Files.empty(); } + + const DirectoryEntry* getParent() const { return m_Parent; } + + // add files to this directory (and subdirectories) from the specified origin. + // That origin may exist or not + void addFromOrigin(const std::wstring& originName, const std::wstring& directory, + int priority, DirectoryStats& stats); + + void addFromOrigin(env::DirectoryWalker& walker, const std::wstring& originName, + const std::wstring& directory, int priority, + DirectoryStats& stats); + + void addFromAllBSAs(const std::wstring& originName, const std::wstring& directory, + int priority, const std::vector& archives, + const std::set& enabledArchives, + const std::vector& loadOrder, + DirectoryStats& stats); + + void addFromBSA(const std::wstring& originName, const std::wstring& directory, + const std::wstring& archivePath, int priority, int order, + DirectoryStats& stats); + + void addFromList(const std::wstring& originName, const std::wstring& directory, + env::Directory& root, int priority, DirectoryStats& stats); + + void propagateOrigin(OriginID origin); + + const std::wstring& getName() const { return m_Name; } + + boost::shared_ptr getFileRegister() { return m_FileRegister; } + + bool originExists(const std::wstring& name) const; + FilesOrigin& getOriginByID(OriginID ID) const; + FilesOrigin& getOriginByName(const std::wstring& name) const; + const FilesOrigin* findOriginByID(OriginID ID) const; + + OriginID anyOrigin() const; + + std::vector getFiles() const; + + const SubDirectories& getSubDirectories() const { return m_SubDirectories; } + + template + void forEachDirectory(F&& f) const + { + for (auto&& d : m_SubDirectories) { + if (!f(*d)) { + break; + } + } + } + + template + void forEachFile(F&& f) const + { + for (auto&& p : m_Files) { + if (auto file = m_FileRegister->getFile(p.second)) { + if (!f(*file)) { + break; + } + } + } + } + + template + void forEachFileIndex(F&& f) const + { + for (auto&& p : m_Files) { + if (!f(p.second)) { + break; + } + } + } + + FileEntryPtr getFileByIndex(FileIndex index) const + { + return m_FileRegister->getFile(index); + } + + DirectoryEntry* findSubDirectory(const std::wstring& name, + bool alreadyLowerCase = false) const; + + DirectoryEntry* findSubDirectoryRecursive(const std::wstring& path); + + /** retrieve a file in this directory by name. + * @param name name of the file + * @return fileentry object for the file or nullptr if no file matches + */ + const FileEntryPtr findFile(const std::wstring& name, + bool alreadyLowerCase = false) const; + const FileEntryPtr findFile(const DirectoryEntryFileKey& key) const; + + bool hasFile(const std::wstring& name) const; + bool containsArchive(std::wstring archiveName); + + // search through this directory and all subdirectories for a file by the + // specified name (relative path). + // + // if directory is not nullptr, the referenced variable will be set to the + // path containing the file + // + const FileEntryPtr searchFile(const std::wstring& path, + const DirectoryEntry** directory = nullptr) const; + + void removeFile(FileIndex index); + + // remove the specified file from the tree. This can be a path leading to a + // file in a subdirectory + bool removeFile(const std::wstring& filePath, OriginID* origin = nullptr); + + /** + * @brief remove the specified directory + * @param path directory to remove + */ + void removeDir(const std::wstring& path); + + bool remove(const std::wstring& fileName, OriginID* origin); + + bool hasContentsFromOrigin(OriginID originID) const; + + FilesOrigin& createOrigin(const std::wstring& originName, + const std::wstring& directory, int priority, + DirectoryStats& stats); + + void removeFiles(const std::set& indices); + + void dump(const std::wstring& file) const; + +private: + using FilesMap = std::map; + using FilesLookup = std::unordered_map; + using SubDirectoriesLookup = std::unordered_map; + + boost::shared_ptr m_FileRegister; + boost::shared_ptr m_OriginConnection; + + std::wstring m_Name; + FilesMap m_Files; + FilesLookup m_FilesLookup; + SubDirectories m_SubDirectories; + SubDirectoriesLookup m_SubDirectoriesLookup; + + DirectoryEntry* m_Parent; + std::set m_Origins; + bool m_Populated; + bool m_TopLevel; + mutable std::mutex m_SubDirMutex; + mutable std::mutex m_FilesMutex; + mutable std::mutex m_OriginsMutex; + + FileEntryPtr insert(std::wstring_view fileName, FilesOrigin& origin, + FILETIME fileTime, std::wstring_view archive, int order, + DirectoryStats& stats); + + FileEntryPtr insert(env::File& file, FilesOrigin& origin, std::wstring_view archive, + int order, DirectoryStats& stats); + + void addFiles(env::DirectoryWalker& walker, FilesOrigin& origin, + const std::wstring& path, DirectoryStats& stats); + + void addFiles(FilesOrigin& origin, BSA::Folder::Ptr archiveFolder, FILETIME fileTime, + const std::wstring& archiveName, int order, DirectoryStats& stats); + + void addDir(FilesOrigin& origin, env::Directory& d, DirectoryStats& stats); + + DirectoryEntry* getSubDirectory(std::wstring_view name, bool create, + DirectoryStats& stats, + OriginID originID = InvalidOriginID); + + DirectoryEntry* getSubDirectory(env::Directory& dir, bool create, + DirectoryStats& stats, + OriginID originID = InvalidOriginID); + + DirectoryEntry* getSubDirectoryRecursive(const std::wstring& path, bool create, + DirectoryStats& stats, + OriginID originID = InvalidOriginID); + + void removeDirRecursive(); + + void addDirectoryToList(DirectoryEntry* e, std::wstring nameLc); + void removeDirectoryFromList(SubDirectories::iterator itor); + + void addFileToList(std::wstring fileNameLower, FileIndex index); + void removeFileFromList(FileIndex index); + void removeFilesFromList(const std::set& indices); + + struct Context; + static void onDirectoryStart(Context* cx, std::wstring_view path); + static void onDirectoryEnd(Context* cx, std::wstring_view path); + static void onFile(Context* cx, std::wstring_view path, FILETIME ft); + + void dump(std::FILE* f, const std::wstring& parentPath) const; +}; + +} // namespace MOShared + +namespace std +{ +hash::result_type +hash::operator()(const argument_type& key) const +{ + return key.hash; +} +} // namespace std + +#endif // MO_REGISTER_DIRECTORYENTRY_INCLUDED diff --git a/src/shared/fileentry.cpp b/src/shared/fileentry.cpp index 5be9a9dc2..486706170 100644 --- a/src/shared/fileentry.cpp +++ b/src/shared/fileentry.cpp @@ -5,20 +5,18 @@ namespace MOShared { -FileEntry::FileEntry() : - m_Index(InvalidFileIndex), m_Name(), m_Origin(-1), m_Parent(nullptr), - m_FileSize(NoFileSize), m_CompressedFileSize(NoFileSize) -{ -} - -FileEntry::FileEntry(FileIndex index, std::wstring name, DirectoryEntry *parent) : - m_Index(index), m_Name(std::move(name)), m_Origin(-1), m_Archive(L"", -1), m_Parent(parent), - m_FileSize(NoFileSize), m_CompressedFileSize(NoFileSize) -{ -} - -void FileEntry::addOrigin( - OriginID origin, FILETIME fileTime, std::wstring_view archive, int order) +FileEntry::FileEntry() + : m_Index(InvalidFileIndex), m_Name(), m_Origin(-1), m_Parent(nullptr), + m_FileSize(NoFileSize), m_CompressedFileSize(NoFileSize) +{} + +FileEntry::FileEntry(FileIndex index, std::wstring name, DirectoryEntry* parent) + : m_Index(index), m_Name(std::move(name)), m_Origin(-1), m_Archive(L"", -1), + m_Parent(parent), m_FileSize(NoFileSize), m_CompressedFileSize(NoFileSize) +{} + +void FileEntry::addOrigin(OriginID origin, FILETIME fileTime, std::wstring_view archive, + int order) { std::scoped_lock lock(m_OriginsMutex); @@ -29,32 +27,30 @@ void FileEntry::addOrigin( if (m_Origin == -1) { // If this file has no previous origin, this mod is now the origin with no // alternatives - m_Origin = origin; + m_Origin = origin; m_FileTime = fileTime; - m_Archive = DataArchiveOrigin(std::wstring(archive.begin(), archive.end()), order); - } - else if ( - (m_Parent != nullptr) && ( - (m_Parent->getOriginByID(origin).getPriority() > m_Parent->getOriginByID(m_Origin).getPriority()) || - (archive.size() == 0 && m_Archive.isValid())) - ) { + m_Archive = DataArchiveOrigin(std::wstring(archive.begin(), archive.end()), order); + } else if ((m_Parent != nullptr) && + ((m_Parent->getOriginByID(origin).getPriority() > + m_Parent->getOriginByID(m_Origin).getPriority()) || + (archive.size() == 0 && m_Archive.isValid()))) { // If this mod has a higher priority than the origin mod OR // this mod has a loose file and the origin mod has an archived file, // this mod is now the origin and the previous origin is the first alternative - auto itor = std::find_if( - m_Alternatives.begin(), m_Alternatives.end(), - [&](auto&& i) { return i.originID() == m_Origin; }); + auto itor = + std::find_if(m_Alternatives.begin(), m_Alternatives.end(), [&](auto&& i) { + return i.originID() == m_Origin; + }); if (itor == m_Alternatives.end()) { m_Alternatives.push_back({m_Origin, m_Archive}); } - m_Origin = origin; + m_Origin = origin; m_FileTime = fileTime; - m_Archive = DataArchiveOrigin(std::wstring(archive.begin(), archive.end()), order); - } - else { + m_Archive = DataArchiveOrigin(std::wstring(archive.begin(), archive.end()), order); + } else { // This mod is just an alternative bool found = false; @@ -70,15 +66,18 @@ void FileEntry::addOrigin( } if ((m_Parent != nullptr) && - (m_Parent->getOriginByID(iter->originID()).getPriority() < m_Parent->getOriginByID(origin).getPriority())) { - m_Alternatives.insert(iter, {origin, {std::wstring(archive.begin(), archive.end()), order}}); + (m_Parent->getOriginByID(iter->originID()).getPriority() < + m_Parent->getOriginByID(origin).getPriority())) { + m_Alternatives.insert( + iter, {origin, {std::wstring(archive.begin(), archive.end()), order}}); found = true; break; } } if (!found) { - m_Alternatives.push_back({origin, {std::wstring(archive.begin(), archive.end()), order}}); + m_Alternatives.push_back( + {origin, {std::wstring(archive.begin(), archive.end()), order}}); } } } @@ -93,21 +92,21 @@ bool FileEntry::removeOrigin(OriginID origin) auto currentIter = m_Alternatives.begin(); for (auto iter = m_Alternatives.begin(); iter != m_Alternatives.end(); ++iter) { if (iter->originID() != origin) { - //Both files are not from archives. + // Both files are not from archives. if (!iter->isFromArchive() && !currentIter->isFromArchive()) { - if ((m_Parent->getOriginByID(iter->originID()).getPriority() > m_Parent->getOriginByID(currentIter->originID()).getPriority())) { + if ((m_Parent->getOriginByID(iter->originID()).getPriority() > + m_Parent->getOriginByID(currentIter->originID()).getPriority())) { currentIter = iter; } - } - else { - //Both files are from archives + } else { + // Both files are from archives if (iter->isFromArchive() && currentIter->isFromArchive()) { if (iter->archive().order() > currentIter->archive().order()) { currentIter = iter; } - } - else { - //Only one of the two is an archive, so we change currentIter only if he is the archive one. + } else { + // Only one of the two is an archive, so we change currentIter only if he + // is the archive one. if (currentIter->isFromArchive()) { currentIter = iter; } @@ -117,19 +116,20 @@ bool FileEntry::removeOrigin(OriginID origin) } OriginID currentID = currentIter->originID(); - m_Archive = currentIter->archive(); + m_Archive = currentIter->archive(); m_Alternatives.erase(currentIter); m_Origin = currentID; } else { - m_Origin = -1; + m_Origin = -1; m_Archive = DataArchiveOrigin(L"", -1); return true; } } else { - auto newEnd = std::remove_if( - m_Alternatives.begin(), m_Alternatives.end(), - [&](auto &i) { return i.originID() == origin; }); + auto newEnd = + std::remove_if(m_Alternatives.begin(), m_Alternatives.end(), [&](auto& i) { + return i.originID() == origin; + }); if (newEnd != m_Alternatives.end()) { m_Alternatives.erase(newEnd, m_Alternatives.end()); @@ -160,8 +160,12 @@ void FileEntry::sortOrigins() } if (LHS.isFromArchive() && RHS.isFromArchive()) { - int l = LHS.archive().order(); if (l < 0) l = INT_MAX; - int r = RHS.archive().order(); if (r < 0) r = INT_MAX; + int l = LHS.archive().order(); + if (l < 0) + l = INT_MAX; + int r = RHS.archive().order(); + if (r < 0) + r = INT_MAX; return l < r; } @@ -171,10 +175,10 @@ void FileEntry::sortOrigins() } return true; - }); + }); if (!m_Alternatives.empty()) { - m_Origin = m_Alternatives.back().originID(); + m_Origin = m_Alternatives.back().originID(); m_Archive = m_Alternatives.back().archive(); m_Alternatives.pop_back(); } @@ -207,7 +211,7 @@ std::wstring FileEntry::getFullPath(OriginID originID) const if (originID == InvalidOriginID) { bool ignore = false; - originID = getOrigin(ignore); + originID = getOrigin(ignore); } // base directory for origin @@ -234,7 +238,7 @@ std::wstring FileEntry::getRelativePath() const return result + L"\\" + m_Name; } -bool FileEntry::recurseParents(std::wstring &path, const DirectoryEntry *parent) const +bool FileEntry::recurseParents(std::wstring& path, const DirectoryEntry* parent) const { if (parent == nullptr) { return false; @@ -248,4 +252,4 @@ bool FileEntry::recurseParents(std::wstring &path, const DirectoryEntry *parent) } } -} // namespace +} // namespace MOShared diff --git a/src/shared/fileentry.h b/src/shared/fileentry.h index 31655bb12..80d8b2924 100644 --- a/src/shared/fileentry.h +++ b/src/shared/fileentry.h @@ -9,23 +9,19 @@ namespace MOShared class FileEntry { public: - static constexpr uint64_t NoFileSize = - std::numeric_limits::max(); + static constexpr uint64_t NoFileSize = std::numeric_limits::max(); FileEntry(); - FileEntry(FileIndex index, std::wstring name, DirectoryEntry *parent); + FileEntry(FileIndex index, std::wstring name, DirectoryEntry* parent); // noncopyable - FileEntry(const FileEntry&) = delete; + FileEntry(const FileEntry&) = delete; FileEntry& operator=(const FileEntry&) = delete; - FileIndex getIndex() const - { - return m_Index; - } + FileIndex getIndex() const { return m_Index; } - void addOrigin( - OriginID origin, FILETIME fileTime, std::wstring_view archive, int order); + void addOrigin(OriginID origin, FILETIME fileTime, std::wstring_view archive, + int order); // remove the specified origin from the list of origins that contain this // file. if no origin is left, the file is effectively deleted and true is @@ -37,71 +33,44 @@ class FileEntry // gets the list of alternative origins (origins with lower priority than // the primary one). if sortOrigins has been called, it is sorted by priority // (ascending) - const AlternativesVector &getAlternatives() const - { - return m_Alternatives; - } + const AlternativesVector& getAlternatives() const { return m_Alternatives; } - const std::wstring &getName() const - { - return m_Name; - } + const std::wstring& getName() const { return m_Name; } - OriginID getOrigin() const - { - return m_Origin; - } + OriginID getOrigin() const { return m_Origin; } - OriginID getOrigin(bool &archive) const + OriginID getOrigin(bool& archive) const { archive = m_Archive.isValid(); return m_Origin; } - const DataArchiveOrigin &getArchive() const - { - return m_Archive; - } + const DataArchiveOrigin& getArchive() const { return m_Archive; } bool isFromArchive(std::wstring archiveName = L"") const; // if originID is -1, uses the main origin; if this file doesn't exist in the // given origin, returns an empty string // - std::wstring getFullPath(OriginID originID=InvalidOriginID) const; + std::wstring getFullPath(OriginID originID = InvalidOriginID) const; std::wstring getRelativePath() const; - DirectoryEntry *getParent() - { - return m_Parent; - } + DirectoryEntry* getParent() { return m_Parent; } - void setFileTime(FILETIME fileTime) const - { - m_FileTime = fileTime; - } + void setFileTime(FILETIME fileTime) const { m_FileTime = fileTime; } - FILETIME getFileTime() const - { - return m_FileTime; - } + FILETIME getFileTime() const { return m_FileTime; } void setFileSize(uint64_t size, uint64_t compressedSize) { - m_FileSize = size; + m_FileSize = size; m_CompressedFileSize = compressedSize; } - uint64_t getFileSize() const - { - return m_FileSize; - } + uint64_t getFileSize() const { return m_FileSize; } - uint64_t getCompressedFileSize() const - { - return m_CompressedFileSize; - } + uint64_t getCompressedFileSize() const { return m_CompressedFileSize; } private: FileIndex m_Index; @@ -109,14 +78,14 @@ class FileEntry OriginID m_Origin; DataArchiveOrigin m_Archive; AlternativesVector m_Alternatives; - DirectoryEntry *m_Parent; + DirectoryEntry* m_Parent; mutable FILETIME m_FileTime; uint64_t m_FileSize, m_CompressedFileSize; mutable std::mutex m_OriginsMutex; - bool recurseParents(std::wstring &path, const DirectoryEntry *parent) const; + bool recurseParents(std::wstring& path, const DirectoryEntry* parent) const; }; -} // namespace +} // namespace MOShared -#endif // MO_REGISTER_FILEENTRY_INCLUDED +#endif // MO_REGISTER_FILEENTRY_INCLUDED diff --git a/src/shared/fileregister.cpp b/src/shared/fileregister.cpp index 98e494528..ab94367f7 100644 --- a/src/shared/fileregister.cpp +++ b/src/shared/fileregister.cpp @@ -1,8 +1,8 @@ #include "fileregister.h" -#include "fileentry.h" #include "directoryentry.h" -#include "originconnection.h" +#include "fileentry.h" #include "filesorigin.h" +#include "originconnection.h" #include namespace MOShared @@ -11,9 +11,8 @@ namespace MOShared using namespace MOBase; FileRegister::FileRegister(boost::shared_ptr originConnection) - : m_OriginConnection(originConnection), m_NextIndex(0) -{ -} + : m_OriginConnection(originConnection), m_NextIndex(0) +{} bool FileRegister::indexValid(FileIndex index) const { @@ -26,11 +25,11 @@ bool FileRegister::indexValid(FileIndex index) const return false; } -FileEntryPtr FileRegister::createFile( - std::wstring name, DirectoryEntry *parent, DirectoryStats& stats) +FileEntryPtr FileRegister::createFile(std::wstring name, DirectoryEntry* parent, + DirectoryStats& stats) { const auto index = generateIndex(); - auto p = FileEntryPtr(new FileEntry(index, std::move(name), parent)); + auto p = FileEntryPtr(new FileEntry(index, std::move(name), parent)); { std::scoped_lock lock(m_Mutex); @@ -96,18 +95,19 @@ void FileRegister::removeOrigin(FileIndex index, OriginID originID) } } - log::error(QObject::tr("invalid file index for remove (for origin): {}").toStdString(), index); + log::error( + QObject::tr("invalid file index for remove (for origin): {}").toStdString(), + index); } -void FileRegister::removeOriginMulti( - std::set indices, OriginID originID) +void FileRegister::removeOriginMulti(std::set indices, OriginID originID) { std::vector removedFiles; { std::scoped_lock lock(m_Mutex); - for (auto iter = indices.begin(); iter != indices.end(); ) { + for (auto iter = indices.begin(); iter != indices.end();) { const auto index = *iter; if (index < m_Files.size()) { @@ -140,13 +140,13 @@ void FileRegister::removeOriginMulti( // frequently the case std::set parents; - for (const FileEntryPtr &file : removedFiles) { + for (const FileEntryPtr& file : removedFiles) { if (file->getParent() != nullptr) { parents.insert(file->getParent()); } } - for (DirectoryEntry *parent : parents) { + for (DirectoryEntry* parent : parents) { parent->removeFiles(indices); } } @@ -181,4 +181,4 @@ void FileRegister::unregisterFile(FileEntryPtr file) } } -} // namespace +} // namespace MOShared diff --git a/src/shared/fileregister.h b/src/shared/fileregister.h index c27dc93ba..aeb2c5b77 100644 --- a/src/shared/fileregister.h +++ b/src/shared/fileregister.h @@ -2,8 +2,8 @@ #define MO_REGISTER_FILESREGISTER_INCLUDED #include "fileregisterfwd.h" -#include #include +#include namespace MOShared { @@ -14,13 +14,13 @@ class FileRegister FileRegister(boost::shared_ptr originConnection); // noncopyable - FileRegister(const FileRegister&) = delete; + FileRegister(const FileRegister&) = delete; FileRegister& operator=(const FileRegister&) = delete; bool indexValid(FileIndex index) const; - FileEntryPtr createFile( - std::wstring name, DirectoryEntry *parent, DirectoryStats& stats); + FileEntryPtr createFile(std::wstring name, DirectoryEntry* parent, + DirectoryStats& stats); FileEntryPtr getFile(FileIndex index) const; @@ -48,6 +48,6 @@ class FileRegister FileIndex generateIndex(); }; -} // namespace +} // namespace MOShared -#endif // MO_REGISTER_FILESREGISTER_INCLUDED +#endif // MO_REGISTER_FILESREGISTER_INCLUDED diff --git a/src/shared/fileregisterfwd.h b/src/shared/fileregisterfwd.h index 57b3715aa..dce40ab5e 100644 --- a/src/shared/fileregisterfwd.h +++ b/src/shared/fileregisterfwd.h @@ -8,15 +8,9 @@ namespace MOShared struct DirectoryEntryFileKey { - DirectoryEntryFileKey(std::wstring v) - : value(std::move(v)), hash(getHash(value)) - { - } + DirectoryEntryFileKey(std::wstring v) : value(std::move(v)), hash(getHash(value)) {} - bool operator==(const DirectoryEntryFileKey& o) const - { - return (value == o.value); - } + bool operator==(const DirectoryEntryFileKey& o) const { return (value == o.value); } static std::size_t getHash(const std::wstring& value) { @@ -27,7 +21,6 @@ struct DirectoryEntryFileKey const std::size_t hash; }; - class DirectoryEntry; class OriginConnection; class FileRegister; @@ -36,11 +29,11 @@ class FileEntry; struct DirectoryStats; using FileEntryPtr = boost::shared_ptr; -using FileIndex = unsigned int; -using OriginID = int; +using FileIndex = unsigned int; +using OriginID = int; constexpr FileIndex InvalidFileIndex = UINT_MAX; -constexpr OriginID InvalidOriginID = -1; +constexpr OriginID InvalidOriginID = -1; // if a file is in an archive, name is the name of the bsa and order // is the order of the associated plugin in the plugins list @@ -49,19 +42,17 @@ constexpr OriginID InvalidOriginID = -1; class DataArchiveOrigin { std::wstring name_ = L""; - int order_ = -1; - -public: + int order_ = -1; +public: int order() const { return order_; } const std::wstring& name() const { return name_; } - - bool isValid() const { - return name_.size() > 0; - } + + bool isValid() const { return name_.size() > 0; } DataArchiveOrigin(std::wstring name, int order) - : name_(std::move(name)), order_(order) {} + : name_(std::move(name)), order_(order) + {} DataArchiveOrigin() = default; }; @@ -72,18 +63,16 @@ class FileAlternative DataArchiveOrigin archive_; public: - OriginID originID() const { return originID_; } const DataArchiveOrigin& archive() const { return archive_; } - bool isFromArchive() const { - return archive_.isValid(); - } + bool isFromArchive() const { return archive_.isValid(); } FileAlternative() = default; - FileAlternative(OriginID originID, DataArchiveOrigin archive) - : originID_(originID), archive_(std::move(archive)) {} + FileAlternative(OriginID originID, DataArchiveOrigin archive) + : originID_(originID), archive_(std::move(archive)) + {} }; using AlternativesVector = std::vector; @@ -127,6 +116,6 @@ struct DirectoryStats std::string toCsv() const; }; -} // namespace +} // namespace MOShared -#endif // MO_REGISTER_FILEREGISTERFWD_INCLUDED +#endif // MO_REGISTER_FILEREGISTERFWD_INCLUDED diff --git a/src/shared/filesorigin.cpp b/src/shared/filesorigin.cpp index 049e42581..dd1aec61a 100644 --- a/src/shared/filesorigin.cpp +++ b/src/shared/filesorigin.cpp @@ -1,12 +1,12 @@ #include "filesorigin.h" -#include "originconnection.h" -#include "fileregister.h" #include "fileentry.h" +#include "fileregister.h" +#include "originconnection.h" namespace MOShared { -std::wstring tail(const std::wstring &source, const size_t count) +std::wstring tail(const std::wstring& source, const size_t count) { if (count >= source.length()) { return source; @@ -15,28 +15,24 @@ std::wstring tail(const std::wstring &source, const size_t count) return source.substr(source.length() - count); } - FilesOrigin::FilesOrigin() - : m_ID(0), m_Disabled(false), m_Name(), m_Path(), m_Priority(0) -{ -} + : m_ID(0), m_Disabled(false), m_Name(), m_Path(), m_Priority(0) +{} -FilesOrigin::FilesOrigin( - OriginID ID, const std::wstring &name, const std::wstring &path, int priority, - boost::shared_ptr fileRegister, - boost::shared_ptr originConnection) : - m_ID(ID), m_Disabled(false), m_Name(name), m_Path(path), - m_Priority(priority), m_FileRegister(fileRegister), - m_OriginConnection(originConnection) -{ -} +FilesOrigin::FilesOrigin(OriginID ID, const std::wstring& name, + const std::wstring& path, int priority, + boost::shared_ptr fileRegister, + boost::shared_ptr originConnection) + : m_ID(ID), m_Disabled(false), m_Name(name), m_Path(path), m_Priority(priority), + m_FileRegister(fileRegister), m_OriginConnection(originConnection) +{} void FilesOrigin::setPriority(int priority) { m_Priority = priority; } -void FilesOrigin::setName(const std::wstring &name) +void FilesOrigin::setName(const std::wstring& name) { m_OriginConnection.lock()->changeNameLookup(m_Name, name); @@ -121,4 +117,4 @@ bool FilesOrigin::containsArchive(std::wstring archiveName) return false; } -} // namespace +} // namespace MOShared diff --git a/src/shared/filesorigin.h b/src/shared/filesorigin.h index 1aa4c645e..92fe12efa 100644 --- a/src/shared/filesorigin.h +++ b/src/shared/filesorigin.h @@ -12,40 +12,26 @@ class FilesOrigin public: FilesOrigin(); - FilesOrigin( - OriginID ID, const std::wstring &name, const std::wstring &path, - int priority, - boost::shared_ptr fileRegister, - boost::shared_ptr originConnection); + FilesOrigin(OriginID ID, const std::wstring& name, const std::wstring& path, + int priority, boost::shared_ptr fileRegister, + boost::shared_ptr originConnection); // noncopyable - FilesOrigin(const FilesOrigin&) = delete; + FilesOrigin(const FilesOrigin&) = delete; FilesOrigin& operator=(const FilesOrigin&) = delete; // sets priority for this origin (does not automatically refresh // the structure) void setPriority(int priority); - int getPriority() const - { - return m_Priority; - } + int getPriority() const { return m_Priority; } - void setName(const std::wstring &name); - const std::wstring &getName() const - { - return m_Name; - } + void setName(const std::wstring& name); + const std::wstring& getName() const { return m_Name; } - OriginID getID() const - { - return m_ID; - } + OriginID getID() const { return m_ID; } - const std::wstring &getPath() const - { - return m_Path; - } + const std::wstring& getPath() const { return m_Path; } std::vector getFiles() const; FileEntryPtr findFile(FileIndex index) const; @@ -53,10 +39,7 @@ class FilesOrigin void enable(bool enabled, DirectoryStats& stats); void enable(bool enabled); - bool isDisabled() const - { - return m_Disabled; - } + bool isDisabled() const { return m_Disabled; } void addFile(FileIndex index) { @@ -80,6 +63,6 @@ class FilesOrigin mutable std::mutex m_Mutex; }; -} // namespace +} // namespace MOShared -#endif // MO_REGISTER_FILESORIGIN_INCLUDED +#endif // MO_REGISTER_FILESORIGIN_INCLUDED diff --git a/src/shared/originconnection.cpp b/src/shared/originconnection.cpp index 2830e8e37..e433891cd 100644 --- a/src/shared/originconnection.cpp +++ b/src/shared/originconnection.cpp @@ -8,24 +8,20 @@ namespace MOShared using namespace MOBase; -OriginConnection::OriginConnection() - : m_NextID(0) -{ -} +OriginConnection::OriginConnection() : m_NextID(0) {} std::pair OriginConnection::getOrCreate( - const std::wstring &originName, const std::wstring &directory, int priority, - const boost::shared_ptr& fileRegister, - const boost::shared_ptr& originConnection, - DirectoryStats& stats) + const std::wstring& originName, const std::wstring& directory, int priority, + const boost::shared_ptr& fileRegister, + const boost::shared_ptr& originConnection, DirectoryStats& stats) { std::unique_lock lock(m_Mutex); auto itor = m_OriginsNameMap.find(originName); if (itor == m_OriginsNameMap.end()) { - FilesOrigin& origin = createOriginNoLock( - originName, directory, priority, fileRegister, originConnection); + FilesOrigin& origin = createOriginNoLock(originName, directory, priority, + fileRegister, originConnection); return {origin, true}; } else { @@ -37,18 +33,19 @@ std::pair OriginConnection::getOrCreate( } } -FilesOrigin& OriginConnection::createOrigin( - const std::wstring &originName, const std::wstring &directory, int priority, - boost::shared_ptr fileRegister, - boost::shared_ptr originConnection) +FilesOrigin& +OriginConnection::createOrigin(const std::wstring& originName, + const std::wstring& directory, int priority, + boost::shared_ptr fileRegister, + boost::shared_ptr originConnection) { std::scoped_lock lock(m_Mutex); - return createOriginNoLock( - originName, directory, priority, fileRegister, originConnection); + return createOriginNoLock(originName, directory, priority, fileRegister, + originConnection); } -bool OriginConnection::exists(const std::wstring &name) +bool OriginConnection::exists(const std::wstring& name) { std::scoped_lock lock(m_Mutex); return m_OriginsNameMap.find(name) != m_OriginsNameMap.end(); @@ -73,7 +70,7 @@ const FilesOrigin* OriginConnection::findByID(OriginID ID) const } } -FilesOrigin& OriginConnection::getByName(const std::wstring &name) +FilesOrigin& OriginConnection::getByName(const std::wstring& name) { std::scoped_lock lock(m_Mutex); @@ -83,12 +80,14 @@ FilesOrigin& OriginConnection::getByName(const std::wstring &name) return m_Origins[iter->second]; } else { std::ostringstream stream; - stream << QObject::tr("invalid origin name: ").toStdString() << ToString(name, true); + stream << QObject::tr("invalid origin name: ").toStdString() + << ToString(name, true); throw std::runtime_error(stream.str()); } } -void OriginConnection::changeNameLookup(const std::wstring &oldName, const std::wstring &newName) +void OriginConnection::changeNameLookup(const std::wstring& oldName, + const std::wstring& newName) { std::scoped_lock lock(m_Mutex); @@ -99,7 +98,8 @@ void OriginConnection::changeNameLookup(const std::wstring &oldName, const std:: m_OriginsNameMap.erase(iter); m_OriginsNameMap[newName] = idx; } else { - log::error(QObject::tr("failed to change name lookup from {} to {}").toStdString(), oldName, newName); + log::error(QObject::tr("failed to change name lookup from {} to {}").toStdString(), + oldName, newName); } } @@ -109,23 +109,21 @@ OriginID OriginConnection::createID() } FilesOrigin& OriginConnection::createOriginNoLock( - const std::wstring &originName, const std::wstring &directory, int priority, - boost::shared_ptr fileRegister, - boost::shared_ptr originConnection) + const std::wstring& originName, const std::wstring& directory, int priority, + boost::shared_ptr fileRegister, + boost::shared_ptr originConnection) { OriginID newID = createID(); - auto itor = m_Origins.emplace( - std::piecewise_construct, - std::forward_as_tuple(newID), - std::forward_as_tuple( - newID, originName, directory, priority, - fileRegister, originConnection)) - .first; + auto itor = m_Origins + .emplace(std::piecewise_construct, std::forward_as_tuple(newID), + std::forward_as_tuple(newID, originName, directory, priority, + fileRegister, originConnection)) + .first; m_OriginsNameMap.insert({originName, newID}); return itor->second; } -} // namespace +} // namespace MOShared diff --git a/src/shared/originconnection.h b/src/shared/originconnection.h index ef8692bbd..d8a676486 100644 --- a/src/shared/originconnection.h +++ b/src/shared/originconnection.h @@ -12,29 +12,29 @@ class OriginConnection OriginConnection(); // noncopyable - OriginConnection(const OriginConnection&) = delete; + OriginConnection(const OriginConnection&) = delete; OriginConnection& operator=(const OriginConnection&) = delete; - std::pair getOrCreate( - const std::wstring &originName, const std::wstring &directory, int priority, - const boost::shared_ptr& fileRegister, - const boost::shared_ptr& originConnection, - DirectoryStats& stats); + std::pair + getOrCreate(const std::wstring& originName, const std::wstring& directory, + int priority, const boost::shared_ptr& fileRegister, + const boost::shared_ptr& originConnection, + DirectoryStats& stats); - FilesOrigin& createOrigin( - const std::wstring &originName, const std::wstring &directory, int priority, - boost::shared_ptr fileRegister, - boost::shared_ptr originConnection); + FilesOrigin& createOrigin(const std::wstring& originName, + const std::wstring& directory, int priority, + boost::shared_ptr fileRegister, + boost::shared_ptr originConnection); - bool exists(const std::wstring &name); + bool exists(const std::wstring& name); - FilesOrigin &getByID(OriginID ID); + FilesOrigin& getByID(OriginID ID); const FilesOrigin* findByID(OriginID ID) const; - FilesOrigin &getByName(const std::wstring &name); + FilesOrigin& getByName(const std::wstring& name); void changePriorityLookup(int oldPriority, int newPriority); - void changeNameLookup(const std::wstring &oldName, const std::wstring &newName); + void changeNameLookup(const std::wstring& oldName, const std::wstring& newName); private: std::atomic m_NextID; @@ -44,12 +44,12 @@ class OriginConnection OriginID createID(); - FilesOrigin& createOriginNoLock( - const std::wstring &originName, const std::wstring &directory, int priority, - boost::shared_ptr fileRegister, - boost::shared_ptr originConnection); + FilesOrigin& createOriginNoLock(const std::wstring& originName, + const std::wstring& directory, int priority, + boost::shared_ptr fileRegister, + boost::shared_ptr originConnection); }; -} // namespace +} // namespace MOShared -#endif // MO_REGISTER_ORIGINCONNECTION_INCLUDED +#endif // MO_REGISTER_ORIGINCONNECTION_INCLUDED diff --git a/src/shared/util.cpp b/src/shared/util.cpp index 54c6b8414..c3b4d7aa0 100644 --- a/src/shared/util.cpp +++ b/src/shared/util.cpp @@ -1,444 +1,429 @@ -/* -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 "util.h" -#include "windows_error.h" -#include "../mainwindow.h" -#include "../env.h" -#include -#include -#include - -using namespace MOBase; - -namespace MOShared -{ - -bool FileExists(const std::string &filename) -{ - DWORD dwAttrib = ::GetFileAttributesA(filename.c_str()); - - return (dwAttrib != INVALID_FILE_ATTRIBUTES); -} - -bool FileExists(const std::wstring &filename) -{ - DWORD dwAttrib = ::GetFileAttributesW(filename.c_str()); - - return (dwAttrib != INVALID_FILE_ATTRIBUTES); -} - -bool FileExists(const std::wstring &searchPath, const std::wstring &filename) -{ - std::wstringstream stream; - stream << searchPath << "\\" << filename; - return FileExists(stream.str()); -} - -std::string ToString(const std::wstring &source, bool utf8) -{ - std::string result; - if (source.length() > 0) { - UINT codepage = CP_UTF8; - if (!utf8) { - codepage = AreFileApisANSI() ? GetACP() : GetOEMCP(); - } - int sizeRequired = ::WideCharToMultiByte(codepage, 0, &source[0], -1, nullptr, 0, nullptr, nullptr); - if (sizeRequired == 0) { - throw windows_error("failed to convert string to multibyte"); - } - // the size returned by WideCharToMultiByte contains zero termination IF -1 is specified for the length. - // we don't want that \0 in the string because then the length field would be wrong. Because madness - result.resize(sizeRequired - 1, '\0'); - ::WideCharToMultiByte(codepage, 0, &source[0], (int)source.size(), &result[0], sizeRequired, nullptr, nullptr); - } - - return result; -} - -std::wstring ToWString(const std::string &source, bool utf8) -{ - std::wstring result; - if (source.length() > 0) { - UINT codepage = CP_UTF8; - if (!utf8) { - codepage = AreFileApisANSI() ? GetACP() : GetOEMCP(); - } - int sizeRequired - = ::MultiByteToWideChar(codepage, 0, source.c_str(), - static_cast(source.length()), nullptr, 0); - if (sizeRequired == 0) { - throw windows_error("failed to convert string to wide character"); - } - result.resize(sizeRequired, L'\0'); - ::MultiByteToWideChar(codepage, 0, source.c_str(), - static_cast(source.length()), &result[0], - sizeRequired); - } - - return result; -} - -static std::locale loc(""); -static auto locToLowerW = [] (wchar_t in) -> wchar_t { - return std::tolower(in, loc); -}; - -static auto locToLower = [] (char in) -> char { - return std::tolower(in, loc); -}; - -std::string& ToLowerInPlace(std::string& text) -{ - CharLowerBuffA(const_cast(text.c_str()), static_cast(text.size())); - return text; -} - -std::string ToLowerCopy(const std::string& text) -{ - std::string result(text); - CharLowerBuffA(const_cast(result.c_str()), static_cast(result.size())); - return result; -} - -std::wstring& ToLowerInPlace(std::wstring& text) -{ - CharLowerBuffW(const_cast(text.c_str()), static_cast(text.size())); - return text; -} - -std::wstring ToLowerCopy(const std::wstring& text) -{ - std::wstring result(text); - CharLowerBuffW(const_cast(result.c_str()), static_cast(result.size())); - return result; -} - -std::wstring ToLowerCopy(std::wstring_view text) -{ - std::wstring result(text.begin(), text.end()); - ToLowerInPlace(result); - return result; -} - -bool CaseInsenstiveComparePred(wchar_t lhs, wchar_t rhs) -{ - return std::tolower(lhs, loc) == std::tolower(rhs, loc); -} - -bool CaseInsensitiveEqual(const std::wstring &lhs, const std::wstring &rhs) -{ - return (lhs.length() == rhs.length()) - && std::equal(lhs.begin(), lhs.end(), - rhs.begin(), - [] (wchar_t lhs, wchar_t rhs) -> bool { - return std::tolower(lhs, loc) == std::tolower(rhs, loc); - }); -} - -VS_FIXEDFILEINFO GetFileVersion(const std::wstring &fileName) -{ - DWORD handle = 0UL; - DWORD size = ::GetFileVersionInfoSizeW(fileName.c_str(), &handle); - if (size == 0) { - throw windows_error("failed to determine file version info size"); - } - - boost::scoped_array buffer(new char[size]); - try { - handle = 0UL; - if (!::GetFileVersionInfoW(fileName.c_str(), handle, size, buffer.get())) { - throw windows_error("failed to determine file version info"); - } - - void *versionInfoPtr = nullptr; - UINT versionInfoLength = 0; - if (!::VerQueryValue(buffer.get(), L"\\", &versionInfoPtr, &versionInfoLength)) { - throw windows_error("failed to determine file version"); - } - - VS_FIXEDFILEINFO result = *(VS_FIXEDFILEINFO*)versionInfoPtr; - return result; - } catch (...) { - throw; - } -} - -std::wstring GetFileVersionString(const std::wstring &fileName) -{ - DWORD handle = 0UL; - DWORD size = ::GetFileVersionInfoSizeW(fileName.c_str(), &handle); - if (size == 0) { - throw windows_error("failed to determine file version info size"); - } - - boost::scoped_array buffer(new char[size]); - try { - handle = 0UL; - if (!::GetFileVersionInfoW(fileName.c_str(), handle, size, buffer.get())) { - throw windows_error("failed to determine file version info"); - } - - LPVOID strBuffer = nullptr; - UINT strLength = 0; - if (!::VerQueryValue(buffer.get(), L"\\StringFileInfo\\040904B0\\ProductVersion", &strBuffer, &strLength)) { - throw windows_error("failed to determine file version"); - } - - return std::wstring((LPCTSTR)strBuffer); - } - catch (...) { - throw; - } -} - -VersionInfo createVersionInfo() -{ - VS_FIXEDFILEINFO version = GetFileVersion(env::thisProcessPath().native()); - - if (version.dwFileFlags & VS_FF_PRERELEASE) - { - // Pre-release builds need annotating - QString versionString = QString::fromStdWString( - GetFileVersionString(env::thisProcessPath().native())); - - // The pre-release flag can be set without the string specifying what type of pre-release - bool noLetters = true; - for (QChar character : versionString) - { - if (character.isLetter()) - { - noLetters = false; - break; - } - } - - if (noLetters) - { - // Default to pre-alpha when release type is unspecified - return VersionInfo(version.dwFileVersionMS >> 16, - version.dwFileVersionMS & 0xFFFF, - version.dwFileVersionLS >> 16, - version.dwFileVersionLS & 0xFFFF, - VersionInfo::RELEASE_PREALPHA); - } - else - { - // Trust the string to make sense - return VersionInfo(versionString); - } - } - else - { - // Non-pre-release builds just need their version numbers reading - return VersionInfo(version.dwFileVersionMS >> 16, - version.dwFileVersionMS & 0xFFFF, - version.dwFileVersionLS >> 16, - version.dwFileVersionLS & 0xFFFF); - } -} - -QString getUsvfsDLLVersion() -{ - // once 2.2.2 is released, this can be changed to call USVFSVersionString() - // directly; until then, using GetProcAddress() allows for mixing up devbuilds - // and usvfs dlls - - using USVFSVersionStringType = const char* WINAPI (); - - QString s; - - const auto m = ::LoadLibraryW(L"usvfs_x64.dll"); - - if (m) { - auto* f = reinterpret_cast( - ::GetProcAddress(m, "USVFSVersionString")); - - if (f) { - s = f(); - } - - ::FreeLibrary(m); - } - - if (s.isEmpty()) { - s = "?"; - } - - return s; -} - -QString getUsvfsVersionString() -{ - const QString dll = getUsvfsDLLVersion(); - const QString header = USVFS_VERSION_STRING; - - QString usvfsVersion; - - if (dll == header) { - return dll; - } else { - return "dll is " + dll + ", compiled against " + header; - } -} - -void SetThisThreadName(const QString& s) -{ - using SetThreadDescriptionType = HRESULT ( - HANDLE hThread, - PCWSTR lpThreadDescription - ); - - static SetThreadDescriptionType* SetThreadDescription = [] { - SetThreadDescriptionType* p = nullptr; - - env::LibraryPtr kernel32(LoadLibraryW(L"kernel32.dll")); - if (!kernel32) { - return p; - } - - p = reinterpret_cast( - GetProcAddress(kernel32.get(), "SetThreadDescription")); - - return p; - }(); - - if (SetThreadDescription) { - SetThreadDescription(GetCurrentThread(), s.toStdWString().c_str()); - } -} - - -char shortcutChar(const QAction* a) -{ - const auto text = a->text(); - char shortcut = 0; - - for (int i=0; i= (text.size() - 1)) { - log::error("ampersand at the end"); - return 0; - } - - return text[i + 1].toLatin1(); - } - } - - log::error("action {} has no shortcut", text); - return 0; -} - -void checkDuplicateShortcuts(const QMenu& m) -{ - const auto actions = m.actions(); - - for (int i=0; iisSeparator()) { - continue; - } - - const char shortcut1 = shortcutChar(action1); - if (shortcut1 == 0) { - continue; - } - - for (int j=i+1; jisSeparator()) { - continue; - } - - const char shortcut2 = shortcutChar(action2); - - if (shortcut1 == shortcut2) { - log::error( - "duplicate shortcut {} for {} and {}", - shortcut1, action1->text(), action2->text()); - - break; - } - } - } -} - -} // namespace MOShared - - -static bool g_exiting = false; -static bool g_canClose = false; - -MainWindow* findMainWindow() -{ - for (auto* tl : qApp->topLevelWidgets()) { - if (auto* mw=dynamic_cast(tl)) { - return mw; - } - } - - return nullptr; -} - -bool ExitModOrganizer(ExitFlags e) -{ - if (g_exiting) { - return true; - } - - g_exiting = true; - Guard g([&]{ g_exiting = false; }); - - if (!e.testFlag(Exit::Force)) { - if (auto* mw=findMainWindow()) { - if (!mw->canExit()) { - return false; - } - } - } - - g_canClose = true; - - const int code = (e.testFlag(Exit::Restart) ? RestartExitCode : 0); - qApp->exit(code); - - return true; -} - -bool ModOrganizerCanCloseNow() -{ - return g_canClose; -} - -bool ModOrganizerExiting() -{ - return g_exiting; -} - -void ResetExitFlag() -{ - g_exiting = false; -} - - -bool isNxmLink(const QString& link) -{ - return link.startsWith("nxm://", Qt::CaseInsensitive); -} +/* +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 "util.h" +#include "../env.h" +#include "../mainwindow.h" +#include "windows_error.h" +#include +#include +#include + +using namespace MOBase; + +namespace MOShared +{ + +bool FileExists(const std::string& filename) +{ + DWORD dwAttrib = ::GetFileAttributesA(filename.c_str()); + + return (dwAttrib != INVALID_FILE_ATTRIBUTES); +} + +bool FileExists(const std::wstring& filename) +{ + DWORD dwAttrib = ::GetFileAttributesW(filename.c_str()); + + return (dwAttrib != INVALID_FILE_ATTRIBUTES); +} + +bool FileExists(const std::wstring& searchPath, const std::wstring& filename) +{ + std::wstringstream stream; + stream << searchPath << "\\" << filename; + return FileExists(stream.str()); +} + +std::string ToString(const std::wstring& source, bool utf8) +{ + std::string result; + if (source.length() > 0) { + UINT codepage = CP_UTF8; + if (!utf8) { + codepage = AreFileApisANSI() ? GetACP() : GetOEMCP(); + } + int sizeRequired = ::WideCharToMultiByte(codepage, 0, &source[0], -1, nullptr, 0, + nullptr, nullptr); + if (sizeRequired == 0) { + throw windows_error("failed to convert string to multibyte"); + } + // the size returned by WideCharToMultiByte contains zero termination IF -1 is + // specified for the length. we don't want that \0 in the string because then the + // length field would be wrong. Because madness + result.resize(sizeRequired - 1, '\0'); + ::WideCharToMultiByte(codepage, 0, &source[0], (int)source.size(), &result[0], + sizeRequired, nullptr, nullptr); + } + + return result; +} + +std::wstring ToWString(const std::string& source, bool utf8) +{ + std::wstring result; + if (source.length() > 0) { + UINT codepage = CP_UTF8; + if (!utf8) { + codepage = AreFileApisANSI() ? GetACP() : GetOEMCP(); + } + int sizeRequired = ::MultiByteToWideChar( + codepage, 0, source.c_str(), static_cast(source.length()), nullptr, 0); + if (sizeRequired == 0) { + throw windows_error("failed to convert string to wide character"); + } + result.resize(sizeRequired, L'\0'); + ::MultiByteToWideChar(codepage, 0, source.c_str(), + static_cast(source.length()), &result[0], sizeRequired); + } + + return result; +} + +static std::locale loc(""); +static auto locToLowerW = [](wchar_t in) -> wchar_t { + return std::tolower(in, loc); +}; + +static auto locToLower = [](char in) -> char { + return std::tolower(in, loc); +}; + +std::string& ToLowerInPlace(std::string& text) +{ + CharLowerBuffA(const_cast(text.c_str()), static_cast(text.size())); + return text; +} + +std::string ToLowerCopy(const std::string& text) +{ + std::string result(text); + CharLowerBuffA(const_cast(result.c_str()), static_cast(result.size())); + return result; +} + +std::wstring& ToLowerInPlace(std::wstring& text) +{ + CharLowerBuffW(const_cast(text.c_str()), static_cast(text.size())); + return text; +} + +std::wstring ToLowerCopy(const std::wstring& text) +{ + std::wstring result(text); + CharLowerBuffW(const_cast(result.c_str()), static_cast(result.size())); + return result; +} + +std::wstring ToLowerCopy(std::wstring_view text) +{ + std::wstring result(text.begin(), text.end()); + ToLowerInPlace(result); + return result; +} + +bool CaseInsenstiveComparePred(wchar_t lhs, wchar_t rhs) +{ + return std::tolower(lhs, loc) == std::tolower(rhs, loc); +} + +bool CaseInsensitiveEqual(const std::wstring& lhs, const std::wstring& rhs) +{ + return (lhs.length() == rhs.length()) && + std::equal(lhs.begin(), lhs.end(), rhs.begin(), + [](wchar_t lhs, wchar_t rhs) -> bool { + return std::tolower(lhs, loc) == std::tolower(rhs, loc); + }); +} + +VS_FIXEDFILEINFO GetFileVersion(const std::wstring& fileName) +{ + DWORD handle = 0UL; + DWORD size = ::GetFileVersionInfoSizeW(fileName.c_str(), &handle); + if (size == 0) { + throw windows_error("failed to determine file version info size"); + } + + boost::scoped_array buffer(new char[size]); + try { + handle = 0UL; + if (!::GetFileVersionInfoW(fileName.c_str(), handle, size, buffer.get())) { + throw windows_error("failed to determine file version info"); + } + + void* versionInfoPtr = nullptr; + UINT versionInfoLength = 0; + if (!::VerQueryValue(buffer.get(), L"\\", &versionInfoPtr, &versionInfoLength)) { + throw windows_error("failed to determine file version"); + } + + VS_FIXEDFILEINFO result = *(VS_FIXEDFILEINFO*)versionInfoPtr; + return result; + } catch (...) { + throw; + } +} + +std::wstring GetFileVersionString(const std::wstring& fileName) +{ + DWORD handle = 0UL; + DWORD size = ::GetFileVersionInfoSizeW(fileName.c_str(), &handle); + if (size == 0) { + throw windows_error("failed to determine file version info size"); + } + + boost::scoped_array buffer(new char[size]); + try { + handle = 0UL; + if (!::GetFileVersionInfoW(fileName.c_str(), handle, size, buffer.get())) { + throw windows_error("failed to determine file version info"); + } + + LPVOID strBuffer = nullptr; + UINT strLength = 0; + if (!::VerQueryValue(buffer.get(), L"\\StringFileInfo\\040904B0\\ProductVersion", + &strBuffer, &strLength)) { + throw windows_error("failed to determine file version"); + } + + return std::wstring((LPCTSTR)strBuffer); + } catch (...) { + throw; + } +} + +VersionInfo createVersionInfo() +{ + VS_FIXEDFILEINFO version = GetFileVersion(env::thisProcessPath().native()); + + if (version.dwFileFlags & VS_FF_PRERELEASE) { + // Pre-release builds need annotating + QString versionString = + QString::fromStdWString(GetFileVersionString(env::thisProcessPath().native())); + + // The pre-release flag can be set without the string specifying what type of + // pre-release + bool noLetters = true; + for (QChar character : versionString) { + if (character.isLetter()) { + noLetters = false; + break; + } + } + + if (noLetters) { + // Default to pre-alpha when release type is unspecified + return VersionInfo( + version.dwFileVersionMS >> 16, version.dwFileVersionMS & 0xFFFF, + version.dwFileVersionLS >> 16, version.dwFileVersionLS & 0xFFFF, + VersionInfo::RELEASE_PREALPHA); + } else { + // Trust the string to make sense + return VersionInfo(versionString); + } + } else { + // Non-pre-release builds just need their version numbers reading + return VersionInfo(version.dwFileVersionMS >> 16, version.dwFileVersionMS & 0xFFFF, + version.dwFileVersionLS >> 16, version.dwFileVersionLS & 0xFFFF); + } +} + +QString getUsvfsDLLVersion() +{ + // once 2.2.2 is released, this can be changed to call USVFSVersionString() + // directly; until then, using GetProcAddress() allows for mixing up devbuilds + // and usvfs dlls + + using USVFSVersionStringType = const char* WINAPI(); + + QString s; + + const auto m = ::LoadLibraryW(L"usvfs_x64.dll"); + + if (m) { + auto* f = reinterpret_cast( + ::GetProcAddress(m, "USVFSVersionString")); + + if (f) { + s = f(); + } + + ::FreeLibrary(m); + } + + if (s.isEmpty()) { + s = "?"; + } + + return s; +} + +QString getUsvfsVersionString() +{ + const QString dll = getUsvfsDLLVersion(); + const QString header = USVFS_VERSION_STRING; + + QString usvfsVersion; + + if (dll == header) { + return dll; + } else { + return "dll is " + dll + ", compiled against " + header; + } +} + +void SetThisThreadName(const QString& s) +{ + using SetThreadDescriptionType = HRESULT(HANDLE hThread, PCWSTR lpThreadDescription); + + static SetThreadDescriptionType* SetThreadDescription = [] { + SetThreadDescriptionType* p = nullptr; + + env::LibraryPtr kernel32(LoadLibraryW(L"kernel32.dll")); + if (!kernel32) { + return p; + } + + p = reinterpret_cast( + GetProcAddress(kernel32.get(), "SetThreadDescription")); + + return p; + }(); + + if (SetThreadDescription) { + SetThreadDescription(GetCurrentThread(), s.toStdWString().c_str()); + } +} + +char shortcutChar(const QAction* a) +{ + const auto text = a->text(); + char shortcut = 0; + + for (int i = 0; i < text.size(); ++i) { + const auto c = text[i]; + if (c == '&') { + if (i >= (text.size() - 1)) { + log::error("ampersand at the end"); + return 0; + } + + return text[i + 1].toLatin1(); + } + } + + log::error("action {} has no shortcut", text); + return 0; +} + +void checkDuplicateShortcuts(const QMenu& m) +{ + const auto actions = m.actions(); + + for (int i = 0; i < actions.size(); ++i) { + const auto* action1 = actions[i]; + if (action1->isSeparator()) { + continue; + } + + const char shortcut1 = shortcutChar(action1); + if (shortcut1 == 0) { + continue; + } + + for (int j = i + 1; j < actions.size(); ++j) { + const auto* action2 = actions[j]; + if (action2->isSeparator()) { + continue; + } + + const char shortcut2 = shortcutChar(action2); + + if (shortcut1 == shortcut2) { + log::error("duplicate shortcut {} for {} and {}", shortcut1, action1->text(), + action2->text()); + + break; + } + } + } +} + +} // namespace MOShared + +static bool g_exiting = false; +static bool g_canClose = false; + +MainWindow* findMainWindow() +{ + for (auto* tl : qApp->topLevelWidgets()) { + if (auto* mw = dynamic_cast(tl)) { + return mw; + } + } + + return nullptr; +} + +bool ExitModOrganizer(ExitFlags e) +{ + if (g_exiting) { + return true; + } + + g_exiting = true; + Guard g([&] { + g_exiting = false; + }); + + if (!e.testFlag(Exit::Force)) { + if (auto* mw = findMainWindow()) { + if (!mw->canExit()) { + return false; + } + } + } + + g_canClose = true; + + const int code = (e.testFlag(Exit::Restart) ? RestartExitCode : 0); + qApp->exit(code); + + return true; +} + +bool ModOrganizerCanCloseNow() +{ + return g_canClose; +} + +bool ModOrganizerExiting() +{ + return g_exiting; +} + +void ResetExitFlag() +{ + g_exiting = false; +} + +bool isNxmLink(const QString& link) +{ + return link.startsWith("nxm://", Qt::CaseInsensitive); +} diff --git a/src/shared/util.h b/src/shared/util.h index d8fdb3a38..1bc6bd47a 100644 --- a/src/shared/util.h +++ b/src/shared/util.h @@ -1,89 +1,89 @@ -/* -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 UTIL_H -#define UTIL_H - -#include -#include -#include -#include - -class Executable; - -namespace MOShared { - -/// Test if a file (or directory) by the specified name exists -bool FileExists(const std::string &filename); -bool FileExists(const std::wstring &filename); - -bool FileExists(const std::wstring &searchPath, const std::wstring &filename); - -std::string ToString(const std::wstring &source, bool utf8); -std::wstring ToWString(const std::string &source, bool utf8); - -std::string& ToLowerInPlace(std::string& text); -std::string ToLowerCopy(const std::string& text); - -std::wstring& ToLowerInPlace(std::wstring& text); -std::wstring ToLowerCopy(const std::wstring& text); -std::wstring ToLowerCopy(std::wstring_view text); - -bool CaseInsensitiveEqual(const std::wstring &lhs, const std::wstring &rhs); - -MOBase::VersionInfo createVersionInfo(); -QString getUsvfsVersionString(); - -void SetThisThreadName(const QString& s); -void checkDuplicateShortcuts(const QMenu& m); - -inline FILETIME ToFILETIME(std::filesystem::file_time_type t) -{ - FILETIME ft; - static_assert(sizeof(t) == sizeof(ft)); - - std::memcpy(&ft, &t, sizeof(FILETIME)); - return ft; -} - -} // namespace MOShared - - -enum class Exit -{ - None = 0x00, - Normal = 0x01, - Restart = 0x02, - Force = 0x04 -}; - -const int RestartExitCode = INT_MAX; -const int ReselectExitCode = INT_MAX - 1; - -using ExitFlags = QFlags; -Q_DECLARE_OPERATORS_FOR_FLAGS(ExitFlags); - -bool ExitModOrganizer(ExitFlags e=Exit::Normal); -bool ModOrganizerExiting(); -bool ModOrganizerCanCloseNow(); -void ResetExitFlag(); - -bool isNxmLink(const QString& link); - -#endif // UTIL_H +/* +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 UTIL_H +#define UTIL_H + +#include +#include +#include +#include + +class Executable; + +namespace MOShared +{ + +/// Test if a file (or directory) by the specified name exists +bool FileExists(const std::string& filename); +bool FileExists(const std::wstring& filename); + +bool FileExists(const std::wstring& searchPath, const std::wstring& filename); + +std::string ToString(const std::wstring& source, bool utf8); +std::wstring ToWString(const std::string& source, bool utf8); + +std::string& ToLowerInPlace(std::string& text); +std::string ToLowerCopy(const std::string& text); + +std::wstring& ToLowerInPlace(std::wstring& text); +std::wstring ToLowerCopy(const std::wstring& text); +std::wstring ToLowerCopy(std::wstring_view text); + +bool CaseInsensitiveEqual(const std::wstring& lhs, const std::wstring& rhs); + +MOBase::VersionInfo createVersionInfo(); +QString getUsvfsVersionString(); + +void SetThisThreadName(const QString& s); +void checkDuplicateShortcuts(const QMenu& m); + +inline FILETIME ToFILETIME(std::filesystem::file_time_type t) +{ + FILETIME ft; + static_assert(sizeof(t) == sizeof(ft)); + + std::memcpy(&ft, &t, sizeof(FILETIME)); + return ft; +} + +} // namespace MOShared + +enum class Exit +{ + None = 0x00, + Normal = 0x01, + Restart = 0x02, + Force = 0x04 +}; + +const int RestartExitCode = INT_MAX; +const int ReselectExitCode = INT_MAX - 1; + +using ExitFlags = QFlags; +Q_DECLARE_OPERATORS_FOR_FLAGS(ExitFlags); + +bool ExitModOrganizer(ExitFlags e = Exit::Normal); +bool ModOrganizerExiting(); +bool ModOrganizerCanCloseNow(); +void ResetExitFlag(); + +bool isNxmLink(const QString& link); + +#endif // UTIL_H diff --git a/src/shared/windows_error.cpp b/src/shared/windows_error.cpp index 97a58a205..f0add1daa 100644 --- a/src/shared/windows_error.cpp +++ b/src/shared/windows_error.cpp @@ -1,49 +1,52 @@ -/* -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 "windows_error.h" -#include - -namespace MOShared { - -std::string windows_error::constructMessage(const std::string& input, int inErrorCode) -{ - std::ostringstream finalMessage; - finalMessage << input; - - LPSTR buffer = nullptr; - - DWORD errorCode = inErrorCode != -1 ? inErrorCode : ::GetLastError(); - - // TODO: the message is not english? - if (FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, - nullptr, errorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&buffer, 0, nullptr) == 0) { - finalMessage << " (errorcode " << errorCode << ")"; - } else { - LPSTR lastChar = buffer + strlen(buffer) - 2; - *lastChar = '\0'; - finalMessage << " (" << buffer << " [" << errorCode << "])"; - LocalFree(buffer); // allocated by FormatMessage - } - - ::SetLastError(errorCode); // restore error code because FormatMessage might have modified it - return finalMessage.str(); -} - -} // namespace MOShared +/* +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 "windows_error.h" +#include + +namespace MOShared +{ + +std::string windows_error::constructMessage(const std::string& input, int inErrorCode) +{ + std::ostringstream finalMessage; + finalMessage << input; + + LPSTR buffer = nullptr; + + DWORD errorCode = inErrorCode != -1 ? inErrorCode : ::GetLastError(); + + // TODO: the message is not english? + if (FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, + nullptr, errorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPSTR)&buffer, 0, nullptr) == 0) { + finalMessage << " (errorcode " << errorCode << ")"; + } else { + LPSTR lastChar = buffer + strlen(buffer) - 2; + *lastChar = '\0'; + finalMessage << " (" << buffer << " [" << errorCode << "])"; + LocalFree(buffer); // allocated by FormatMessage + } + + ::SetLastError( + errorCode); // restore error code because FormatMessage might have modified it + return finalMessage.str(); +} + +} // namespace MOShared diff --git a/src/shared/windows_error.h b/src/shared/windows_error.h index 03ce0abdb..2bdd63921 100644 --- a/src/shared/windows_error.h +++ b/src/shared/windows_error.h @@ -1,44 +1,47 @@ -/* -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 WINDOWS_ERROR_H -#define WINDOWS_ERROR_H - - -#include -#define WIN32_LEAN_AND_MEAN -#include - -namespace MOShared { - -class windows_error : public std::runtime_error { -public: - windows_error(const std::string& message, int errorcode = ::GetLastError()) - : runtime_error(constructMessage(message, errorcode)), m_ErrorCode(errorcode) - {} - int getErrorCode() const { return m_ErrorCode; } -private: - std::string constructMessage(const std::string& input, int errorcode); -private: - int m_ErrorCode; -}; - -} // namespace MOShared - -#endif // WINDOWS_ERROR_H +/* +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 WINDOWS_ERROR_H +#define WINDOWS_ERROR_H + +#include +#define WIN32_LEAN_AND_MEAN +#include + +namespace MOShared +{ + +class windows_error : public std::runtime_error +{ +public: + windows_error(const std::string& message, int errorcode = ::GetLastError()) + : runtime_error(constructMessage(message, errorcode)), m_ErrorCode(errorcode) + {} + int getErrorCode() const { return m_ErrorCode; } + +private: + std::string constructMessage(const std::string& input, int errorcode); + +private: + int m_ErrorCode; +}; + +} // namespace MOShared + +#endif // WINDOWS_ERROR_H diff --git a/src/spawn.cpp b/src/spawn.cpp index 5b14c52c0..05f796a57 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -1,1082 +1,1010 @@ -/* -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 "spawn.h" - -#include "report.h" -#include "utility.h" -#include "env.h" -#include "envwindows.h" -#include "envsecurity.h" -#include "envmodule.h" -#include "settings.h" -#include "settingsdialogworkarounds.h" -#include -#include -#include -#include -#include -#include "shared/appconfig.h" -#include "shared/windows_error.h" -#include -#include -#include -#include -#include -#include - -using namespace MOBase; -using namespace MOShared; - -namespace spawn::dialogs -{ - -std::wstring makeRightsDetails(const env::FileSecurity& fs) -{ - if (fs.rights.normalRights) { - return L"(normal rights)"; - } - - if (fs.rights.list.isEmpty()) { - return L"(none)"; - } - - std::wstring s = fs.rights.list.join("|").toStdWString(); - if (!fs.rights.hasExecute) { - s += L" (execute is missing)"; - } - - return s; -} - -QString makeDetails(const SpawnParameters& sp, DWORD code, const QString& more={}) -{ - std::wstring owner, rights; - - if (sp.binary.isFile()) { - const auto fs = env::getFileSecurity(sp.binary.absoluteFilePath()); - - if (fs.error.isEmpty()) { - owner = fs.owner.toStdWString(); - rights = makeRightsDetails(fs); - } else { - owner = fs.error.toStdWString(); - rights = fs.error.toStdWString(); - } - } else { - owner = L"(file not found)"; - rights = L"(file not found)"; - } - - const bool cwdExists = (sp.currentDirectory.isEmpty() ? - true : sp.currentDirectory.exists()); - - const auto appDir = QCoreApplication::applicationDirPath(); - const auto sep = QDir::separator(); - - const std::wstring usvfs_x86_dll = - QFileInfo(appDir + sep + "usvfs_x86.dll").isFile() ? L"ok" : L"not found"; - - const std::wstring usvfs_x64_dll = - QFileInfo(appDir + sep + "usvfs_x64.dll").isFile() ? L"ok" : L"not found"; - - const std::wstring usvfs_x86_proxy = - QFileInfo(appDir + sep + "usvfs_proxy_x86.exe").isFile() ? L"ok" : L"not found"; - - const std::wstring usvfs_x64_proxy = - QFileInfo(appDir + sep + "usvfs_proxy_x64.exe").isFile() ? L"ok" : L"not found"; - - std::wstring elevated; - if (auto b=env::Environment().windowsInfo().isElevated()) { - elevated = (*b ? L"yes" : L"no"); - } else { - elevated = L"(not available)"; - } - - std::wstring f = - L"Error {code} {codename}{more}: {error}\n" - L" . binary: '{bin}'\n" - L" . owner: {owner}\n" - L" . rights: {rights}\n" - L" . arguments: '{args}'\n" - L" . cwd: '{cwd}'{cwdexists}\n" - L" . stdout: {stdout}, stderr: {stderr}, hooked: {hooked}\n" - L" . MO elevated: {elevated}"; - - if (sp.hooked) { - f += L"\n . usvfs x86:{x86_dll} x64:{x64_dll} proxy_x86:{x86_proxy} proxy_x64:{x64_proxy}"; - } - - const std::wstring wmore = (more.isEmpty() ? L"" : (", " + more).toStdWString()); - - const auto s = fmt::format(f, - fmt::arg(L"code", code), - fmt::arg(L"codename", errorCodeName(code)), - fmt::arg(L"more", wmore), - fmt::arg(L"bin", QDir::toNativeSeparators(sp.binary.absoluteFilePath()).toStdWString()), - fmt::arg(L"owner", owner), - fmt::arg(L"rights", rights), - fmt::arg(L"error", formatSystemMessage(code)), - fmt::arg(L"args", sp.arguments.toStdWString()), - fmt::arg(L"cwd", QDir::toNativeSeparators(sp.currentDirectory.absolutePath()).toStdWString()), - fmt::arg(L"cwdexists", (cwdExists ? L"" : L" (not found)")), - fmt::arg(L"stdout", (sp.stdOut == INVALID_HANDLE_VALUE ? L"no" : L"yes")), - fmt::arg(L"stderr", (sp.stdErr == INVALID_HANDLE_VALUE ? L"no" : L"yes")), - fmt::arg(L"hooked", (sp.hooked ? L"yes" : L"no")), - fmt::arg(L"x86_dll", usvfs_x86_dll), - fmt::arg(L"x64_dll", usvfs_x64_dll), - fmt::arg(L"x86_proxy", usvfs_x86_proxy), - fmt::arg(L"x64_proxy", usvfs_x64_proxy), - fmt::arg(L"elevated", elevated)); - - return QString::fromStdWString(s); -} - -QString makeContent(const SpawnParameters& sp, DWORD code) -{ - if (code == ERROR_INVALID_PARAMETER) { - return QObject::tr( - "This error typically happens because an antivirus has deleted critical " - "files from Mod Organizer's installation folder or has made them " - "generally inaccessible. Add an exclusion for Mod Organizer's " - "installation folder in your antivirus, reinstall Mod Organizer and try " - "again."); - } else if (code == ERROR_ACCESS_DENIED) { - return QObject::tr( - "This error typically happens because an antivirus is preventing Mod " - "Organizer from starting programs. Add an exclusion for Mod Organizer's " - "installation folder in your antivirus and try again."); - } else if (code == ERROR_FILE_NOT_FOUND) { - return QObject::tr("The file '%1' does not exist.") - .arg(QDir::toNativeSeparators(sp.binary.absoluteFilePath())); - } else if (code == ERROR_DIRECTORY) { - if (!sp.currentDirectory.exists()) { - return QObject::tr("The working directory '%1' does not exist.") - .arg(QDir::toNativeSeparators(sp.currentDirectory.absolutePath())); - } - } - - return QString::fromStdWString(formatSystemMessage(code)); -} - -QMessageBox::StandardButton badSteamReg( - QWidget* parent, const QString& keyName, const QString& valueName) -{ - const auto details = QString( - "can't start steam, registry value at '%1' is empty or doesn't exist") - .arg(keyName + "\\" + valueName); - - log::error("{}", details); - - return MOBase::TaskDialog(parent, QObject::tr("Cannot start Steam")) - .main(QObject::tr("Cannot start Steam")) - .content(QObject::tr( - "The path to the Steam executable cannot be found. You might try " - "reinstalling Steam.")) - .details(details) - .icon(QMessageBox::Critical) - .button({ - QObject::tr("Continue without starting Steam"), - QObject::tr("The program may fail to launch."), - QMessageBox::Yes}) - .button({ - QObject::tr("Cancel"), - QMessageBox::Cancel}) - .exec(); -} - -QMessageBox::StandardButton startSteamFailed( - QWidget* parent, - const QString& keyName, const QString& valueName, const QString& exe, - const SpawnParameters& sp, DWORD e) -{ - auto details = QString( - "a steam install was found in the registry at '%1': '%2'\n\n") - .arg(keyName + "\\" + valueName) - .arg(exe); - - details += makeDetails(sp, e); - - log::error("{}", details); - - return MOBase::TaskDialog(parent, QObject::tr("Cannot start Steam")) - .main(QObject::tr("Cannot start Steam")) - .content(makeContent(sp, e)) - .details(details) - .icon(QMessageBox::Critical) - .button({ - QObject::tr("Continue without starting Steam"), - QObject::tr("The program may fail to launch."), - QMessageBox::Yes}) - .button({ - QObject::tr("Cancel"), - QMessageBox::Cancel}) - .exec(); -} - -void spawnFailed(QWidget* parent, const SpawnParameters& sp, DWORD code) -{ - const auto details = makeDetails(sp, code); - log::error("{}", details); - - const auto title = QObject::tr("Cannot launch program"); - - const auto mainText = QObject::tr("Cannot start %1") - .arg(sp.binary.fileName()); - - MOBase::TaskDialog(parent, title) - .main(mainText) - .content(makeContent(sp, code)) - .details(details) - .icon(QMessageBox::Critical) - .exec(); -} - -void helperFailed( - QWidget* parent, DWORD code, const QString& why, const std::wstring& binary, - const std::wstring& cwd, const std::wstring& args) -{ - SpawnParameters sp; - sp.binary = QFileInfo(QString::fromStdWString(binary)); - sp.currentDirectory.setPath(QString::fromStdWString(cwd)); - sp.arguments = QString::fromStdWString(args); - - const auto details = makeDetails(sp, code, "in " + why); - log::error("{}", details); - - const auto title = QObject::tr("Cannot launch helper"); - - const auto mainText = QObject::tr("Cannot start %1") - .arg(sp.binary.fileName()); - - MOBase::TaskDialog(parent, title) - .main(mainText) - .content(makeContent(sp, code)) - .details(details) - .icon(QMessageBox::Critical) - .exec(); -} - -bool confirmRestartAsAdmin(QWidget* parent, const SpawnParameters& sp) -{ - const auto details = makeDetails(sp, ERROR_ELEVATION_REQUIRED); - - log::error("{}", details); - - const auto title = QObject::tr("Elevation required"); - - const auto mainText = QObject::tr("Cannot start %1") - .arg(sp.binary.fileName()); - - const auto content = QObject::tr( - "This program is requesting to run as administrator but Mod Organizer " - "itself is not running as administrator. Running programs as administrator " - "is typically unnecessary as long as the game and Mod Organizer have been " - "installed outside \"Program Files\".\r\n\r\n" - "You can restart Mod Organizer as administrator and try launching the " - "program again."); - - log::debug("asking user to restart MO as administrator"); - - const auto r = MOBase::TaskDialog(parent, title) - .main(mainText) - .content(content) - .details(details) - .icon(QMessageBox::Question) - .button({ - QObject::tr("Restart Mod Organizer as administrator"), - QObject::tr("You must allow \"helper.exe\" to make changes to the system."), - QMessageBox::Yes}) - .button({ - QObject::tr("Cancel"), - QMessageBox::Cancel}) - .exec(); - - return (r == QMessageBox::Yes); -} - -QMessageBox::StandardButton confirmStartSteam( - QWidget* parent, const SpawnParameters& sp, const QString& details) -{ - const auto title = QObject::tr("Launch Steam"); - const auto mainText = QObject::tr("This program requires Steam"); - const auto content = QObject::tr( - "Mod Organizer has detected that this program likely requires Steam to be " - "running to function properly."); - - return MOBase::TaskDialog(parent, title) - .main(mainText) - .content(content) - .details(details) - .icon(QMessageBox::Question) - .button({ - QObject::tr("Start Steam"), - QMessageBox::Yes}) - .button({ - QObject::tr("Continue without starting Steam"), - QObject::tr("The program might fail to run."), - QMessageBox::No}) - .button({ - QObject::tr("Cancel"), - QMessageBox::Cancel}) - .remember("steamQuery", sp.binary.fileName()) - .exec(); -} - -QMessageBox::StandardButton confirmRestartAsAdminForSteam( - QWidget* parent, const SpawnParameters& sp) -{ - const auto title = QObject::tr("Elevation required"); - const auto mainText = QObject::tr("Steam is running as administrator"); - const auto content = QObject::tr( - "Running Steam as administrator is typically unnecessary and can cause " - "problems when Mod Organizer itself is not running as administrator." - "\r\n\r\n" - "You can restart Mod Organizer as administrator and try launching the " - "program again."); - - return MOBase::TaskDialog(parent, title) - .main(mainText) - .content(content) - .icon(QMessageBox::Question) - .button({ - QObject::tr("Restart Mod Organizer as administrator"), - QObject::tr("You must allow \"helper.exe\" to make changes to the system."), - QMessageBox::Yes}) - .button({ - QObject::tr("Continue"), - QObject::tr("The program might fail to run."), - QMessageBox::No}) - .button({ - QObject::tr("Cancel"), - QMessageBox::Cancel}) - .remember("steamAdminQuery", sp.binary.fileName()) - .exec(); -} - -bool eventLogNotRunning( - QWidget* parent, const env::Service& s, const SpawnParameters& sp) -{ - const auto title = QObject::tr("Event Log not running"); - const auto mainText = QObject::tr("The Event Log service is not running"); - const auto content = QObject::tr( - "The Windows Event Log service is not running. This can prevent USVFS from " - "running properly and your mods may not be recognized by the program being " - "launched."); - - const auto r = MOBase::TaskDialog(parent, title) - .main(mainText) - .content(content) - .details(s.toString()) - .icon(QMessageBox::Question) - .remember("eventLogService", sp.binary.fileName()) - .button({ - QObject::tr("Continue"), - QObject::tr("Your mods might not work."), - QMessageBox::Yes}) - .button({ - QObject::tr("Cancel"), - QMessageBox::Cancel}) - .exec(); - - return (r == QMessageBox::Yes); -} - -QMessageBox::StandardButton confirmBlacklisted( - QWidget* parent, const SpawnParameters& sp, Settings& settings) -{ - const auto title = QObject::tr("Blacklisted program"); - const auto mainText = QObject::tr("The program %1 is blacklisted") - .arg(sp.binary.fileName()); - const auto content = QObject::tr( - "The program you are attempting to launch is blacklisted in the virtual " - "filesystem. This will likely prevent it from seeing any mods, INI files " - "or any other virtualized files."); - - const auto details = - "Executable: " + sp.binary.fileName() + "\n" - "Current blacklist: " + settings.executablesBlacklist(); - - auto r = MOBase::TaskDialog(parent, title) - .main(mainText) - .content(content) - .details(details) - .icon(QMessageBox::Question) - .remember("blacklistedExecutable", sp.binary.fileName()) - .button({ - QObject::tr("Continue"), - QObject::tr("Your mods might not work."), - QMessageBox::Yes}) - .button({ - QObject::tr("Change the blacklist"), - QMessageBox::Retry}) - .button({ - QObject::tr("Cancel"), - QMessageBox::Cancel}) - .exec(); - - if (r == QMessageBox::Retry) { - if (!WorkaroundsSettingsTab::changeBlacklistNow(parent, settings)) { - r = QMessageBox::Cancel; - } - } - - return r; -} - -} // namespace - - -namespace spawn -{ - -void logSpawning(const SpawnParameters& sp, const QString& realCmd) -{ - log::debug( - "spawning binary:\n" - " . exe: '{}'\n" - " . args: '{}'\n" - " . cwd: '{}'\n" - " . steam id: '{}'\n" - " . hooked: {}\n" - " . stdout: {}\n" - " . stderr: {}\n" - " . real cmd: '{}'", - sp.binary.absoluteFilePath(), sp.arguments, - sp.currentDirectory.absolutePath(), sp.steamAppID, sp.hooked, - (sp.stdOut == INVALID_HANDLE_VALUE ? "no" : "yes"), - (sp.stdErr == INVALID_HANDLE_VALUE ? "no" : "yes"), - realCmd); -} - -DWORD spawn(const SpawnParameters& sp, HANDLE& processHandle) -{ - BOOL inheritHandles = FALSE; - - STARTUPINFO si = {}; - si.cb = sizeof(si); - - // inherit handles if we plan to use stdout or stderr reroute - if (sp.stdOut != INVALID_HANDLE_VALUE) { - si.hStdOutput = sp.stdOut; - inheritHandles = TRUE; - si.dwFlags |= STARTF_USESTDHANDLES; - } - - if (sp.stdErr != INVALID_HANDLE_VALUE) { - si.hStdError = sp.stdErr; - inheritHandles = TRUE; - si.dwFlags |= STARTF_USESTDHANDLES; - } - - const auto bin = QDir::toNativeSeparators(sp.binary.absoluteFilePath()); - const auto cwd = QDir::toNativeSeparators(sp.currentDirectory.absolutePath()); - - QString commandLine = "\"" + bin + "\""; - if (!sp.arguments.isEmpty()) { - commandLine += " " + sp.arguments; - } - - const QString moPath = QCoreApplication::applicationDirPath(); - const auto oldPath = env::appendToPath(QDir::toNativeSeparators(moPath)); - - PROCESS_INFORMATION pi = {}; - BOOL success = FALSE; - - logSpawning(sp, commandLine); - - const auto wcommandLine = commandLine.toStdWString(); - const auto wcwd = cwd.toStdWString(); - - const DWORD flags = CREATE_BREAKAWAY_FROM_JOB; - - if (sp.hooked) { - success = ::CreateProcessHooked( - nullptr, const_cast(wcommandLine.c_str()), nullptr, nullptr, - inheritHandles, flags, nullptr, wcwd.c_str(), &si, &pi); - } else { - success = ::CreateProcess( - nullptr, const_cast(wcommandLine.c_str()), nullptr, nullptr, - inheritHandles, flags, nullptr, wcwd.c_str(), &si, &pi); - } - - const auto e = GetLastError(); - env::setPath(oldPath); - - if (!success) { - return e; - } - - processHandle = pi.hProcess; - ::CloseHandle(pi.hThread); - - return ERROR_SUCCESS; -} - -bool restartAsAdmin(QWidget* parent) -{ - WCHAR cwd[MAX_PATH] = {}; - if (!GetCurrentDirectory(MAX_PATH, cwd)) { - cwd[0] = L'\0'; - } - - if (!helper::adminLaunch( - parent, - qApp->applicationDirPath().toStdWString(), - qApp->applicationFilePath().toStdWString(), - std::wstring(cwd))) - { - log::error("admin launch failed"); - return false; - } - - log::debug("exiting MO"); - ExitModOrganizer(Exit::Force); - - return true; -} - -void startBinaryAdmin(QWidget* parent, const SpawnParameters& sp) -{ - if (!dialogs::confirmRestartAsAdmin(parent, sp)) { - log::debug("user declined"); - return; - } - - log::info("restarting MO as administrator"); - restartAsAdmin(parent); -} - -struct SteamStatus -{ - bool running=false; - bool accessible=false; -}; - -SteamStatus getSteamStatus() -{ - SteamStatus ss; - - const auto ps = env::Environment().runningProcesses(); - - for (const auto& p : ps) { - if ((p.name().compare("Steam.exe", Qt::CaseInsensitive) == 0) || - (p.name().compare("SteamService.exe", Qt::CaseInsensitive) == 0)) - { - ss.running = true; - ss.accessible = p.canAccess(); - - log::debug( - "'{}' is running, accessible={}", - p.name(), (ss.accessible ? "yes" : "no")); - - break; - } - } - - return ss; -} - -QString makeSteamArguments(const QString& username, const QString& password) -{ - QString args; - - if (username != "") { - args += "-login " + username; - - if (password != "") { - args += " " + password; - } - } - - return args; -} - -bool startSteam(QWidget* parent) -{ - const QString keyName = "HKEY_CURRENT_USER\\Software\\Valve\\Steam"; - const QString valueName = "SteamExe"; - - const QSettings steamSettings(keyName, QSettings::NativeFormat); - const QString exe = steamSettings.value(valueName, "").toString(); - - if (exe.isEmpty()) { - return (dialogs::badSteamReg(parent, keyName, valueName) == QMessageBox::Yes); - } - - SpawnParameters sp; - sp.binary = QFileInfo(exe); - - // See if username and password supplied. If so, pass them into steam. - QString username, password; - if (Settings::instance().steam().login(username, password)) { - if (username.length() > 0) - MOBase::log::getDefault().addToBlacklist(username.toStdString(), "STEAM_USERNAME"); - if (password.length() > 0) - MOBase::log::getDefault().addToBlacklist(password.toStdString(), "STEAM_PASSWORD"); - sp.arguments = makeSteamArguments(username, password); - } - - log::debug( - "starting steam process:\n" - " . program: '{}'\n" - " . username={}, password={}", - sp.binary.filePath().toStdString(), - (username.isEmpty() ? "no" : "yes"), - (password.isEmpty() ? "no" : "yes")); - - HANDLE ph = INVALID_HANDLE_VALUE; - const auto e = spawn(sp, ph); - ::CloseHandle(ph); - - if (e != ERROR_SUCCESS) { - // make sure username and passwords are not shown - sp.arguments = makeSteamArguments( - (username.isEmpty() ? "" : "USERNAME"), - (password.isEmpty() ? "" : "PASSWORD")); - - const auto r = dialogs::startSteamFailed( - parent, keyName, valueName, exe, sp, e); - - return (r == QMessageBox::Yes); - } - - QMessageBox::information( - parent, QObject::tr("Waiting"), - QObject::tr("Please press OK once you're logged into steam.")); - - return true; -} - -bool checkSteam( - QWidget* parent, const SpawnParameters& sp, - const QDir& gameDirectory, const QString &steamAppID, const Settings& settings) -{ - static const std::vector steamFiles = { - "steam_api.dll", "steam_api64.dll" - }; - - log::debug("checking steam"); - - if (!steamAppID.isEmpty()) { - env::set("SteamAPPId", steamAppID); - } else { - env::set("SteamAPPId", settings.steam().appID()); - } - - - bool steamRequired = false; - QString details; - - for (const auto& file : steamFiles) { - const QFileInfo fi(gameDirectory.absoluteFilePath(file)); - if (fi.exists()) { - details = QString( - "managed game is located at '%1' and file '%2' exists") - .arg(gameDirectory.absolutePath()) - .arg(fi.absoluteFilePath()); - - log::debug("{}", details); - steamRequired = true; - - break; - } - } - - if (!steamRequired) { - log::debug("program doesn't seem to require steam"); - return true; - } - - - auto ss = getSteamStatus(); - - if (!ss.running) { - log::debug("steam isn't running, asking to start steam"); - - const auto c = dialogs::confirmStartSteam(parent, sp, details); - - if (c == QDialogButtonBox::Yes) { - log::debug("user wants to start steam"); - - if (!startSteam(parent)) { - // cancel - return false; - } - - // double-check that Steam is started - ss = getSteamStatus(); - if (!ss.running) { - log::error("steam is still not running, hoping for the best"); - return true; - } - } else if (c == QDialogButtonBox::No) { - log::debug("user declined to start steam"); - return true; - } else { - log::debug("user cancelled"); - return false; - } - } - - if (ss.running && !ss.accessible) { - log::debug("steam is running but is not accessible, asking to restart MO"); - const auto c = dialogs::confirmRestartAsAdminForSteam(parent, sp); - - if (c == QDialogButtonBox::Yes) { - restartAsAdmin(parent); - return false; - } else if (c == QDialogButtonBox::No) { - log::debug("user declined to restart MO, continuing"); - return true; - } else { - log::debug("user cancelled"); - return false; - } - } - - return true; -} - -bool checkBlacklist( - QWidget* parent, const SpawnParameters& sp, Settings& settings) -{ - for (;;) { - if (!settings.isExecutableBlacklisted(sp.binary.fileName())) { - return true; - } - - const auto r = dialogs::confirmBlacklisted(parent, sp, settings); - - if (r != QMessageBox::Retry) { - return (r == QMessageBox::Yes); - } - } -} - -HANDLE startBinary(QWidget* parent, const SpawnParameters& sp) -{ - HANDLE handle = INVALID_HANDLE_VALUE; - const auto e = spawn::spawn(sp, handle); - - switch (e) - { - case ERROR_SUCCESS: - { - return handle; - } - - case ERROR_ELEVATION_REQUIRED: - { - startBinaryAdmin(parent, sp); - return INVALID_HANDLE_VALUE; - } - - default: - { - dialogs::spawnFailed(parent, sp, e); - return INVALID_HANDLE_VALUE; - } - } -} - -QString getExecutableForJarFile(const QString& jarFile) -{ - const std::wstring jarFileW = jarFile.toStdWString(); - - WCHAR buffer[MAX_PATH]; - - const auto hinst = ::FindExecutableW(jarFileW.c_str(), nullptr, buffer); - const auto r = static_cast(reinterpret_cast(hinst)); - - // anything <= 32 signals failure - if (r <= 32) { - log::warn( - "failed to find executable associated with file '{}', {}", - jarFile, shell::formatError(r)); - - return {}; - } - - DWORD binaryType = 0; - - if (!::GetBinaryTypeW(buffer, &binaryType)) { - const auto e = ::GetLastError(); - - log::warn( - "failed to determine binary type of '{}', {}", - QString::fromWCharArray(buffer), formatSystemMessage(e)); - - return {}; - } - - if (binaryType != SCS_32BIT_BINARY && binaryType != SCS_64BIT_BINARY) { - log::warn( - "unexpected binary type {} for file '{}'", - binaryType, QString::fromWCharArray(buffer)); - - return {}; - } - - return QString::fromWCharArray(buffer); -} - -QString getJavaHome() -{ - const QString key = "HKEY_LOCAL_MACHINE\\Software\\JavaSoft\\Java Runtime Environment"; - const QString value = "CurrentVersion"; - - QSettings reg(key, QSettings::NativeFormat); - - if (!reg.contains(value)) { - log::warn("key '{}\\{}' doesn't exist", key, value); - return {}; - } - - const QString currentVersion = reg.value("CurrentVersion").toString(); - const QString javaHome = QString("%1/JavaHome").arg(currentVersion); - - if (!reg.contains(javaHome)) { - log::warn( - "java version '{}' was found at '{}\\{}', but '{}\\{}' doesn't exist", - currentVersion, key, value, key, javaHome); - - return {}; - } - - const auto path = reg.value(javaHome).toString(); - return path + "\\bin\\javaw.exe"; -} - -QString findJavaInstallation(const QString& jarFile) -{ - // try to find java automatically based on the given jar file - if (!jarFile.isEmpty()) { - const auto s = getExecutableForJarFile(jarFile); - if (!s.isEmpty()) { - return s; - } - } - - // second attempt: look to the registry - const auto s = getJavaHome(); - if (!s.isEmpty()) { - return s; - } - - // not found - return {}; -} - -bool isBatchFile(const QFileInfo& target) -{ - const auto batchExtensions = {"cmd", "bat"}; - - const QString extension = target.suffix(); - for (auto&& e : batchExtensions) { - if (extension.compare(e, Qt::CaseInsensitive) == 0) { - return true; - } - } - - return false; -} - -bool isExeFile(const QFileInfo& target) -{ - return (target.suffix().compare("exe", Qt::CaseInsensitive) == 0); -} - -bool isJavaFile(const QFileInfo& target) -{ - return (target.suffix().compare("jar", Qt::CaseInsensitive) == 0); -} - -QFileInfo getCmdPath() -{ - const auto p = env::get("COMSPEC"); - if (!p.isEmpty()) { - return QFileInfo(p); - } - - QString systemDirectory; - - const std::size_t buffer_size = 1000; - wchar_t buffer[buffer_size + 1] = {}; - - const auto length = ::GetSystemDirectoryW(buffer, buffer_size); - if (length != 0) { - systemDirectory = QString::fromWCharArray(buffer, length); - - if (!systemDirectory.endsWith("\\")) { - systemDirectory += "\\"; - } - } else { - systemDirectory = "C:\\Windows\\System32\\"; - } - - return QFileInfo(systemDirectory + "cmd.exe"); -} - -FileExecutionTypes getFileExecutionType(const QFileInfo& target) -{ - if (isExeFile(target) || isBatchFile(target) || isJavaFile(target)) { - return FileExecutionTypes::Executable; - } - - return FileExecutionTypes::Other; -} - -FileExecutionContext getFileExecutionContext( - QWidget* parent, const QFileInfo& target) -{ - if (isExeFile(target)) { - return { - target, - "", - FileExecutionTypes::Executable - }; - } - - if (isBatchFile(target)) { - return { - getCmdPath(), - QString("/C \"%1\"").arg(QDir::toNativeSeparators(target.absoluteFilePath())), - FileExecutionTypes::Executable - }; - } - - if (isJavaFile(target)) { - auto java = findJavaInstallation(target.absoluteFilePath()); - - if (java.isEmpty()) { - java = QFileDialog::getOpenFileName( - parent, QObject::tr("Select binary"), - QString(), QObject::tr("Binary") + " (*.exe)"); - } - - if (!java.isEmpty()) { - return { - QFileInfo(java), - QString("-jar \"%1\"").arg(QDir::toNativeSeparators(target.absoluteFilePath())), - FileExecutionTypes::Executable - }; - } - } - - return {{}, {}, FileExecutionTypes::Other}; -} - -} // namespace - - - -namespace helper -{ - -bool helperExec( - QWidget* parent, - const std::wstring& moDirectory, const std::wstring& commandLine, BOOL async) -{ - const std::wstring fileName = moDirectory + L"\\helper.exe"; - - env::HandlePtr process; - - { - SHELLEXECUTEINFOW execInfo = {}; - - ULONG flags = SEE_MASK_FLAG_NO_UI; - if (!async) - flags |= SEE_MASK_NOCLOSEPROCESS; - - execInfo.cbSize = sizeof(SHELLEXECUTEINFOW); - execInfo.fMask = flags; - execInfo.hwnd = 0; - execInfo.lpVerb = L"runas"; - execInfo.lpFile = fileName.c_str(); - execInfo.lpParameters = commandLine.c_str(); - execInfo.lpDirectory = moDirectory.c_str(); - execInfo.nShow = SW_SHOW; - - if (!::ShellExecuteExW(&execInfo) && execInfo.hProcess == 0) { - const auto e = GetLastError(); - - spawn::dialogs::helperFailed( - parent, e, "ShellExecuteExW()", fileName, moDirectory, commandLine); - - return false; - } - - if (async) { - return true; - } - - process.reset(execInfo.hProcess); - } - - const auto r = ::WaitForSingleObject(process.get(), INFINITE); - - if (r != WAIT_OBJECT_0) { - // for WAIT_ABANDONED, the documentation doesn't mention that GetLastError() - // returns something meaningful, but code ERROR_ABANDONED_WAIT_0 exists, so - // use that instead - const auto code = (r == WAIT_ABANDONED ? - ERROR_ABANDONED_WAIT_0 : GetLastError()); - - spawn::dialogs::helperFailed( - parent, code, "WaitForSingleObject()", - fileName, moDirectory, commandLine); - - return false; - } - - DWORD exitCode = 0; - if (!GetExitCodeProcess(process.get(), &exitCode)) { - const auto e = GetLastError(); - - spawn::dialogs::helperFailed( - parent, e, "GetExitCodeProcess()", fileName, moDirectory, commandLine); - - return false; - } - - return (exitCode == 0); -} - -bool backdateBSAs( - QWidget* parent, const std::wstring &moPath, const std::wstring &dataPath) -{ - const std::wstring commandLine = fmt::format( - L"backdateBSA \"{}\"", dataPath); - - return helperExec(parent, moPath, commandLine, FALSE); -} - -bool adminLaunch( - QWidget* parent, const std::wstring &moPath, - const std::wstring &moFile, const std::wstring &workingDir) -{ - const std::wstring commandLine = fmt::format( - L"adminLaunch {} \"{}\" \"{}\"", - ::GetCurrentProcessId(), moFile, workingDir); - - return helperExec(parent, moPath, commandLine, true); -} - -} // namespace +/* +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 "spawn.h" + +#include "env.h" +#include "envmodule.h" +#include "envsecurity.h" +#include "envwindows.h" +#include "report.h" +#include "settings.h" +#include "settingsdialogworkarounds.h" +#include "shared/appconfig.h" +#include "shared/windows_error.h" +#include "utility.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace MOBase; +using namespace MOShared; + +namespace spawn::dialogs +{ + +std::wstring makeRightsDetails(const env::FileSecurity& fs) +{ + if (fs.rights.normalRights) { + return L"(normal rights)"; + } + + if (fs.rights.list.isEmpty()) { + return L"(none)"; + } + + std::wstring s = fs.rights.list.join("|").toStdWString(); + if (!fs.rights.hasExecute) { + s += L" (execute is missing)"; + } + + return s; +} + +QString makeDetails(const SpawnParameters& sp, DWORD code, const QString& more = {}) +{ + std::wstring owner, rights; + + if (sp.binary.isFile()) { + const auto fs = env::getFileSecurity(sp.binary.absoluteFilePath()); + + if (fs.error.isEmpty()) { + owner = fs.owner.toStdWString(); + rights = makeRightsDetails(fs); + } else { + owner = fs.error.toStdWString(); + rights = fs.error.toStdWString(); + } + } else { + owner = L"(file not found)"; + rights = L"(file not found)"; + } + + const bool cwdExists = + (sp.currentDirectory.isEmpty() ? true : sp.currentDirectory.exists()); + + const auto appDir = QCoreApplication::applicationDirPath(); + const auto sep = QDir::separator(); + + const std::wstring usvfs_x86_dll = + QFileInfo(appDir + sep + "usvfs_x86.dll").isFile() ? L"ok" : L"not found"; + + const std::wstring usvfs_x64_dll = + QFileInfo(appDir + sep + "usvfs_x64.dll").isFile() ? L"ok" : L"not found"; + + const std::wstring usvfs_x86_proxy = + QFileInfo(appDir + sep + "usvfs_proxy_x86.exe").isFile() ? L"ok" : L"not found"; + + const std::wstring usvfs_x64_proxy = + QFileInfo(appDir + sep + "usvfs_proxy_x64.exe").isFile() ? L"ok" : L"not found"; + + std::wstring elevated; + if (auto b = env::Environment().windowsInfo().isElevated()) { + elevated = (*b ? L"yes" : L"no"); + } else { + elevated = L"(not available)"; + } + + std::wstring f = L"Error {code} {codename}{more}: {error}\n" + L" . binary: '{bin}'\n" + L" . owner: {owner}\n" + L" . rights: {rights}\n" + L" . arguments: '{args}'\n" + L" . cwd: '{cwd}'{cwdexists}\n" + L" . stdout: {stdout}, stderr: {stderr}, hooked: {hooked}\n" + L" . MO elevated: {elevated}"; + + if (sp.hooked) { + f += L"\n . usvfs x86:{x86_dll} x64:{x64_dll} proxy_x86:{x86_proxy} " + L"proxy_x64:{x64_proxy}"; + } + + const std::wstring wmore = (more.isEmpty() ? L"" : (", " + more).toStdWString()); + + const auto s = fmt::format( + f, fmt::arg(L"code", code), fmt::arg(L"codename", errorCodeName(code)), + fmt::arg(L"more", wmore), + fmt::arg(L"bin", + QDir::toNativeSeparators(sp.binary.absoluteFilePath()).toStdWString()), + fmt::arg(L"owner", owner), fmt::arg(L"rights", rights), + fmt::arg(L"error", formatSystemMessage(code)), + fmt::arg(L"args", sp.arguments.toStdWString()), + fmt::arg( + L"cwd", + QDir::toNativeSeparators(sp.currentDirectory.absolutePath()).toStdWString()), + fmt::arg(L"cwdexists", (cwdExists ? L"" : L" (not found)")), + fmt::arg(L"stdout", (sp.stdOut == INVALID_HANDLE_VALUE ? L"no" : L"yes")), + fmt::arg(L"stderr", (sp.stdErr == INVALID_HANDLE_VALUE ? L"no" : L"yes")), + fmt::arg(L"hooked", (sp.hooked ? L"yes" : L"no")), + fmt::arg(L"x86_dll", usvfs_x86_dll), fmt::arg(L"x64_dll", usvfs_x64_dll), + fmt::arg(L"x86_proxy", usvfs_x86_proxy), fmt::arg(L"x64_proxy", usvfs_x64_proxy), + fmt::arg(L"elevated", elevated)); + + return QString::fromStdWString(s); +} + +QString makeContent(const SpawnParameters& sp, DWORD code) +{ + if (code == ERROR_INVALID_PARAMETER) { + return QObject::tr( + "This error typically happens because an antivirus has deleted critical " + "files from Mod Organizer's installation folder or has made them " + "generally inaccessible. Add an exclusion for Mod Organizer's " + "installation folder in your antivirus, reinstall Mod Organizer and try " + "again."); + } else if (code == ERROR_ACCESS_DENIED) { + return QObject::tr( + "This error typically happens because an antivirus is preventing Mod " + "Organizer from starting programs. Add an exclusion for Mod Organizer's " + "installation folder in your antivirus and try again."); + } else if (code == ERROR_FILE_NOT_FOUND) { + return QObject::tr("The file '%1' does not exist.") + .arg(QDir::toNativeSeparators(sp.binary.absoluteFilePath())); + } else if (code == ERROR_DIRECTORY) { + if (!sp.currentDirectory.exists()) { + return QObject::tr("The working directory '%1' does not exist.") + .arg(QDir::toNativeSeparators(sp.currentDirectory.absolutePath())); + } + } + + return QString::fromStdWString(formatSystemMessage(code)); +} + +QMessageBox::StandardButton badSteamReg(QWidget* parent, const QString& keyName, + const QString& valueName) +{ + const auto details = + QString("can't start steam, registry value at '%1' is empty or doesn't exist") + .arg(keyName + "\\" + valueName); + + log::error("{}", details); + + return MOBase::TaskDialog(parent, QObject::tr("Cannot start Steam")) + .main(QObject::tr("Cannot start Steam")) + .content( + QObject::tr("The path to the Steam executable cannot be found. You might try " + "reinstalling Steam.")) + .details(details) + .icon(QMessageBox::Critical) + .button({QObject::tr("Continue without starting Steam"), + QObject::tr("The program may fail to launch."), QMessageBox::Yes}) + .button({QObject::tr("Cancel"), QMessageBox::Cancel}) + .exec(); +} + +QMessageBox::StandardButton startSteamFailed(QWidget* parent, const QString& keyName, + const QString& valueName, + const QString& exe, + const SpawnParameters& sp, DWORD e) +{ + auto details = QString("a steam install was found in the registry at '%1': '%2'\n\n") + .arg(keyName + "\\" + valueName) + .arg(exe); + + details += makeDetails(sp, e); + + log::error("{}", details); + + return MOBase::TaskDialog(parent, QObject::tr("Cannot start Steam")) + .main(QObject::tr("Cannot start Steam")) + .content(makeContent(sp, e)) + .details(details) + .icon(QMessageBox::Critical) + .button({QObject::tr("Continue without starting Steam"), + QObject::tr("The program may fail to launch."), QMessageBox::Yes}) + .button({QObject::tr("Cancel"), QMessageBox::Cancel}) + .exec(); +} + +void spawnFailed(QWidget* parent, const SpawnParameters& sp, DWORD code) +{ + const auto details = makeDetails(sp, code); + log::error("{}", details); + + const auto title = QObject::tr("Cannot launch program"); + + const auto mainText = QObject::tr("Cannot start %1").arg(sp.binary.fileName()); + + MOBase::TaskDialog(parent, title) + .main(mainText) + .content(makeContent(sp, code)) + .details(details) + .icon(QMessageBox::Critical) + .exec(); +} + +void helperFailed(QWidget* parent, DWORD code, const QString& why, + const std::wstring& binary, const std::wstring& cwd, + const std::wstring& args) +{ + SpawnParameters sp; + sp.binary = QFileInfo(QString::fromStdWString(binary)); + sp.currentDirectory.setPath(QString::fromStdWString(cwd)); + sp.arguments = QString::fromStdWString(args); + + const auto details = makeDetails(sp, code, "in " + why); + log::error("{}", details); + + const auto title = QObject::tr("Cannot launch helper"); + + const auto mainText = QObject::tr("Cannot start %1").arg(sp.binary.fileName()); + + MOBase::TaskDialog(parent, title) + .main(mainText) + .content(makeContent(sp, code)) + .details(details) + .icon(QMessageBox::Critical) + .exec(); +} + +bool confirmRestartAsAdmin(QWidget* parent, const SpawnParameters& sp) +{ + const auto details = makeDetails(sp, ERROR_ELEVATION_REQUIRED); + + log::error("{}", details); + + const auto title = QObject::tr("Elevation required"); + + const auto mainText = QObject::tr("Cannot start %1").arg(sp.binary.fileName()); + + const auto content = QObject::tr( + "This program is requesting to run as administrator but Mod Organizer " + "itself is not running as administrator. Running programs as administrator " + "is typically unnecessary as long as the game and Mod Organizer have been " + "installed outside \"Program Files\".\r\n\r\n" + "You can restart Mod Organizer as administrator and try launching the " + "program again."); + + log::debug("asking user to restart MO as administrator"); + + const auto r = + MOBase::TaskDialog(parent, title) + .main(mainText) + .content(content) + .details(details) + .icon(QMessageBox::Question) + .button({QObject::tr("Restart Mod Organizer as administrator"), + QObject::tr( + "You must allow \"helper.exe\" to make changes to the system."), + QMessageBox::Yes}) + .button({QObject::tr("Cancel"), QMessageBox::Cancel}) + .exec(); + + return (r == QMessageBox::Yes); +} + +QMessageBox::StandardButton +confirmStartSteam(QWidget* parent, const SpawnParameters& sp, const QString& details) +{ + const auto title = QObject::tr("Launch Steam"); + const auto mainText = QObject::tr("This program requires Steam"); + const auto content = QObject::tr( + "Mod Organizer has detected that this program likely requires Steam to be " + "running to function properly."); + + return MOBase::TaskDialog(parent, title) + .main(mainText) + .content(content) + .details(details) + .icon(QMessageBox::Question) + .button({QObject::tr("Start Steam"), QMessageBox::Yes}) + .button({QObject::tr("Continue without starting Steam"), + QObject::tr("The program might fail to run."), QMessageBox::No}) + .button({QObject::tr("Cancel"), QMessageBox::Cancel}) + .remember("steamQuery", sp.binary.fileName()) + .exec(); +} + +QMessageBox::StandardButton confirmRestartAsAdminForSteam(QWidget* parent, + const SpawnParameters& sp) +{ + const auto title = QObject::tr("Elevation required"); + const auto mainText = QObject::tr("Steam is running as administrator"); + const auto content = QObject::tr( + "Running Steam as administrator is typically unnecessary and can cause " + "problems when Mod Organizer itself is not running as administrator." + "\r\n\r\n" + "You can restart Mod Organizer as administrator and try launching the " + "program again."); + + return MOBase::TaskDialog(parent, title) + .main(mainText) + .content(content) + .icon(QMessageBox::Question) + .button( + {QObject::tr("Restart Mod Organizer as administrator"), + QObject::tr("You must allow \"helper.exe\" to make changes to the system."), + QMessageBox::Yes}) + .button({QObject::tr("Continue"), QObject::tr("The program might fail to run."), + QMessageBox::No}) + .button({QObject::tr("Cancel"), QMessageBox::Cancel}) + .remember("steamAdminQuery", sp.binary.fileName()) + .exec(); +} + +bool eventLogNotRunning(QWidget* parent, const env::Service& s, + const SpawnParameters& sp) +{ + const auto title = QObject::tr("Event Log not running"); + const auto mainText = QObject::tr("The Event Log service is not running"); + const auto content = QObject::tr( + "The Windows Event Log service is not running. This can prevent USVFS from " + "running properly and your mods may not be recognized by the program being " + "launched."); + + const auto r = + MOBase::TaskDialog(parent, title) + .main(mainText) + .content(content) + .details(s.toString()) + .icon(QMessageBox::Question) + .remember("eventLogService", sp.binary.fileName()) + .button({QObject::tr("Continue"), QObject::tr("Your mods might not work."), + QMessageBox::Yes}) + .button({QObject::tr("Cancel"), QMessageBox::Cancel}) + .exec(); + + return (r == QMessageBox::Yes); +} + +QMessageBox::StandardButton +confirmBlacklisted(QWidget* parent, const SpawnParameters& sp, Settings& settings) +{ + const auto title = QObject::tr("Blacklisted program"); + const auto mainText = + QObject::tr("The program %1 is blacklisted").arg(sp.binary.fileName()); + const auto content = QObject::tr( + "The program you are attempting to launch is blacklisted in the virtual " + "filesystem. This will likely prevent it from seeing any mods, INI files " + "or any other virtualized files."); + + const auto details = "Executable: " + sp.binary.fileName() + + "\n" + "Current blacklist: " + + settings.executablesBlacklist(); + + auto r = MOBase::TaskDialog(parent, title) + .main(mainText) + .content(content) + .details(details) + .icon(QMessageBox::Question) + .remember("blacklistedExecutable", sp.binary.fileName()) + .button({QObject::tr("Continue"), + QObject::tr("Your mods might not work."), QMessageBox::Yes}) + .button({QObject::tr("Change the blacklist"), QMessageBox::Retry}) + .button({QObject::tr("Cancel"), QMessageBox::Cancel}) + .exec(); + + if (r == QMessageBox::Retry) { + if (!WorkaroundsSettingsTab::changeBlacklistNow(parent, settings)) { + r = QMessageBox::Cancel; + } + } + + return r; +} + +} // namespace spawn::dialogs + +namespace spawn +{ + +void logSpawning(const SpawnParameters& sp, const QString& realCmd) +{ + log::debug("spawning binary:\n" + " . exe: '{}'\n" + " . args: '{}'\n" + " . cwd: '{}'\n" + " . steam id: '{}'\n" + " . hooked: {}\n" + " . stdout: {}\n" + " . stderr: {}\n" + " . real cmd: '{}'", + sp.binary.absoluteFilePath(), sp.arguments, + sp.currentDirectory.absolutePath(), sp.steamAppID, sp.hooked, + (sp.stdOut == INVALID_HANDLE_VALUE ? "no" : "yes"), + (sp.stdErr == INVALID_HANDLE_VALUE ? "no" : "yes"), realCmd); +} + +DWORD spawn(const SpawnParameters& sp, HANDLE& processHandle) +{ + BOOL inheritHandles = FALSE; + + STARTUPINFO si = {}; + si.cb = sizeof(si); + + // inherit handles if we plan to use stdout or stderr reroute + if (sp.stdOut != INVALID_HANDLE_VALUE) { + si.hStdOutput = sp.stdOut; + inheritHandles = TRUE; + si.dwFlags |= STARTF_USESTDHANDLES; + } + + if (sp.stdErr != INVALID_HANDLE_VALUE) { + si.hStdError = sp.stdErr; + inheritHandles = TRUE; + si.dwFlags |= STARTF_USESTDHANDLES; + } + + const auto bin = QDir::toNativeSeparators(sp.binary.absoluteFilePath()); + const auto cwd = QDir::toNativeSeparators(sp.currentDirectory.absolutePath()); + + QString commandLine = "\"" + bin + "\""; + if (!sp.arguments.isEmpty()) { + commandLine += " " + sp.arguments; + } + + const QString moPath = QCoreApplication::applicationDirPath(); + const auto oldPath = env::appendToPath(QDir::toNativeSeparators(moPath)); + + PROCESS_INFORMATION pi = {}; + BOOL success = FALSE; + + logSpawning(sp, commandLine); + + const auto wcommandLine = commandLine.toStdWString(); + const auto wcwd = cwd.toStdWString(); + + const DWORD flags = CREATE_BREAKAWAY_FROM_JOB; + + if (sp.hooked) { + success = ::CreateProcessHooked(nullptr, const_cast(wcommandLine.c_str()), + nullptr, nullptr, inheritHandles, flags, nullptr, + wcwd.c_str(), &si, &pi); + } else { + success = ::CreateProcess(nullptr, const_cast(wcommandLine.c_str()), + nullptr, nullptr, inheritHandles, flags, nullptr, + wcwd.c_str(), &si, &pi); + } + + const auto e = GetLastError(); + env::setPath(oldPath); + + if (!success) { + return e; + } + + processHandle = pi.hProcess; + ::CloseHandle(pi.hThread); + + return ERROR_SUCCESS; +} + +bool restartAsAdmin(QWidget* parent) +{ + WCHAR cwd[MAX_PATH] = {}; + if (!GetCurrentDirectory(MAX_PATH, cwd)) { + cwd[0] = L'\0'; + } + + if (!helper::adminLaunch(parent, qApp->applicationDirPath().toStdWString(), + qApp->applicationFilePath().toStdWString(), + std::wstring(cwd))) { + log::error("admin launch failed"); + return false; + } + + log::debug("exiting MO"); + ExitModOrganizer(Exit::Force); + + return true; +} + +void startBinaryAdmin(QWidget* parent, const SpawnParameters& sp) +{ + if (!dialogs::confirmRestartAsAdmin(parent, sp)) { + log::debug("user declined"); + return; + } + + log::info("restarting MO as administrator"); + restartAsAdmin(parent); +} + +struct SteamStatus +{ + bool running = false; + bool accessible = false; +}; + +SteamStatus getSteamStatus() +{ + SteamStatus ss; + + const auto ps = env::Environment().runningProcesses(); + + for (const auto& p : ps) { + if ((p.name().compare("Steam.exe", Qt::CaseInsensitive) == 0) || + (p.name().compare("SteamService.exe", Qt::CaseInsensitive) == 0)) { + ss.running = true; + ss.accessible = p.canAccess(); + + log::debug("'{}' is running, accessible={}", p.name(), + (ss.accessible ? "yes" : "no")); + + break; + } + } + + return ss; +} + +QString makeSteamArguments(const QString& username, const QString& password) +{ + QString args; + + if (username != "") { + args += "-login " + username; + + if (password != "") { + args += " " + password; + } + } + + return args; +} + +bool startSteam(QWidget* parent) +{ + const QString keyName = "HKEY_CURRENT_USER\\Software\\Valve\\Steam"; + const QString valueName = "SteamExe"; + + const QSettings steamSettings(keyName, QSettings::NativeFormat); + const QString exe = steamSettings.value(valueName, "").toString(); + + if (exe.isEmpty()) { + return (dialogs::badSteamReg(parent, keyName, valueName) == QMessageBox::Yes); + } + + SpawnParameters sp; + sp.binary = QFileInfo(exe); + + // See if username and password supplied. If so, pass them into steam. + QString username, password; + if (Settings::instance().steam().login(username, password)) { + if (username.length() > 0) + MOBase::log::getDefault().addToBlacklist(username.toStdString(), + "STEAM_USERNAME"); + if (password.length() > 0) + MOBase::log::getDefault().addToBlacklist(password.toStdString(), + "STEAM_PASSWORD"); + sp.arguments = makeSteamArguments(username, password); + } + + log::debug("starting steam process:\n" + " . program: '{}'\n" + " . username={}, password={}", + sp.binary.filePath().toStdString(), (username.isEmpty() ? "no" : "yes"), + (password.isEmpty() ? "no" : "yes")); + + HANDLE ph = INVALID_HANDLE_VALUE; + const auto e = spawn(sp, ph); + ::CloseHandle(ph); + + if (e != ERROR_SUCCESS) { + // make sure username and passwords are not shown + sp.arguments = makeSteamArguments((username.isEmpty() ? "" : "USERNAME"), + (password.isEmpty() ? "" : "PASSWORD")); + + const auto r = dialogs::startSteamFailed(parent, keyName, valueName, exe, sp, e); + + return (r == QMessageBox::Yes); + } + + QMessageBox::information( + parent, QObject::tr("Waiting"), + QObject::tr("Please press OK once you're logged into steam.")); + + return true; +} + +bool checkSteam(QWidget* parent, const SpawnParameters& sp, const QDir& gameDirectory, + const QString& steamAppID, const Settings& settings) +{ + static const std::vector steamFiles = {"steam_api.dll", "steam_api64.dll"}; + + log::debug("checking steam"); + + if (!steamAppID.isEmpty()) { + env::set("SteamAPPId", steamAppID); + } else { + env::set("SteamAPPId", settings.steam().appID()); + } + + bool steamRequired = false; + QString details; + + for (const auto& file : steamFiles) { + const QFileInfo fi(gameDirectory.absoluteFilePath(file)); + if (fi.exists()) { + details = QString("managed game is located at '%1' and file '%2' exists") + .arg(gameDirectory.absolutePath()) + .arg(fi.absoluteFilePath()); + + log::debug("{}", details); + steamRequired = true; + + break; + } + } + + if (!steamRequired) { + log::debug("program doesn't seem to require steam"); + return true; + } + + auto ss = getSteamStatus(); + + if (!ss.running) { + log::debug("steam isn't running, asking to start steam"); + + const auto c = dialogs::confirmStartSteam(parent, sp, details); + + if (c == QDialogButtonBox::Yes) { + log::debug("user wants to start steam"); + + if (!startSteam(parent)) { + // cancel + return false; + } + + // double-check that Steam is started + ss = getSteamStatus(); + if (!ss.running) { + log::error("steam is still not running, hoping for the best"); + return true; + } + } else if (c == QDialogButtonBox::No) { + log::debug("user declined to start steam"); + return true; + } else { + log::debug("user cancelled"); + return false; + } + } + + if (ss.running && !ss.accessible) { + log::debug("steam is running but is not accessible, asking to restart MO"); + const auto c = dialogs::confirmRestartAsAdminForSteam(parent, sp); + + if (c == QDialogButtonBox::Yes) { + restartAsAdmin(parent); + return false; + } else if (c == QDialogButtonBox::No) { + log::debug("user declined to restart MO, continuing"); + return true; + } else { + log::debug("user cancelled"); + return false; + } + } + + return true; +} + +bool checkBlacklist(QWidget* parent, const SpawnParameters& sp, Settings& settings) +{ + for (;;) { + if (!settings.isExecutableBlacklisted(sp.binary.fileName())) { + return true; + } + + const auto r = dialogs::confirmBlacklisted(parent, sp, settings); + + if (r != QMessageBox::Retry) { + return (r == QMessageBox::Yes); + } + } +} + +HANDLE startBinary(QWidget* parent, const SpawnParameters& sp) +{ + HANDLE handle = INVALID_HANDLE_VALUE; + const auto e = spawn::spawn(sp, handle); + + switch (e) { + case ERROR_SUCCESS: { + return handle; + } + + case ERROR_ELEVATION_REQUIRED: { + startBinaryAdmin(parent, sp); + return INVALID_HANDLE_VALUE; + } + + default: { + dialogs::spawnFailed(parent, sp, e); + return INVALID_HANDLE_VALUE; + } + } +} + +QString getExecutableForJarFile(const QString& jarFile) +{ + const std::wstring jarFileW = jarFile.toStdWString(); + + WCHAR buffer[MAX_PATH]; + + const auto hinst = ::FindExecutableW(jarFileW.c_str(), nullptr, buffer); + const auto r = static_cast(reinterpret_cast(hinst)); + + // anything <= 32 signals failure + if (r <= 32) { + log::warn("failed to find executable associated with file '{}', {}", jarFile, + shell::formatError(r)); + + return {}; + } + + DWORD binaryType = 0; + + if (!::GetBinaryTypeW(buffer, &binaryType)) { + const auto e = ::GetLastError(); + + log::warn("failed to determine binary type of '{}', {}", + QString::fromWCharArray(buffer), formatSystemMessage(e)); + + return {}; + } + + if (binaryType != SCS_32BIT_BINARY && binaryType != SCS_64BIT_BINARY) { + log::warn("unexpected binary type {} for file '{}'", binaryType, + QString::fromWCharArray(buffer)); + + return {}; + } + + return QString::fromWCharArray(buffer); +} + +QString getJavaHome() +{ + const QString key = + "HKEY_LOCAL_MACHINE\\Software\\JavaSoft\\Java Runtime Environment"; + const QString value = "CurrentVersion"; + + QSettings reg(key, QSettings::NativeFormat); + + if (!reg.contains(value)) { + log::warn("key '{}\\{}' doesn't exist", key, value); + return {}; + } + + const QString currentVersion = reg.value("CurrentVersion").toString(); + const QString javaHome = QString("%1/JavaHome").arg(currentVersion); + + if (!reg.contains(javaHome)) { + log::warn("java version '{}' was found at '{}\\{}', but '{}\\{}' doesn't exist", + currentVersion, key, value, key, javaHome); + + return {}; + } + + const auto path = reg.value(javaHome).toString(); + return path + "\\bin\\javaw.exe"; +} + +QString findJavaInstallation(const QString& jarFile) +{ + // try to find java automatically based on the given jar file + if (!jarFile.isEmpty()) { + const auto s = getExecutableForJarFile(jarFile); + if (!s.isEmpty()) { + return s; + } + } + + // second attempt: look to the registry + const auto s = getJavaHome(); + if (!s.isEmpty()) { + return s; + } + + // not found + return {}; +} + +bool isBatchFile(const QFileInfo& target) +{ + const auto batchExtensions = {"cmd", "bat"}; + + const QString extension = target.suffix(); + for (auto&& e : batchExtensions) { + if (extension.compare(e, Qt::CaseInsensitive) == 0) { + return true; + } + } + + return false; +} + +bool isExeFile(const QFileInfo& target) +{ + return (target.suffix().compare("exe", Qt::CaseInsensitive) == 0); +} + +bool isJavaFile(const QFileInfo& target) +{ + return (target.suffix().compare("jar", Qt::CaseInsensitive) == 0); +} + +QFileInfo getCmdPath() +{ + const auto p = env::get("COMSPEC"); + if (!p.isEmpty()) { + return QFileInfo(p); + } + + QString systemDirectory; + + const std::size_t buffer_size = 1000; + wchar_t buffer[buffer_size + 1] = {}; + + const auto length = ::GetSystemDirectoryW(buffer, buffer_size); + if (length != 0) { + systemDirectory = QString::fromWCharArray(buffer, length); + + if (!systemDirectory.endsWith("\\")) { + systemDirectory += "\\"; + } + } else { + systemDirectory = "C:\\Windows\\System32\\"; + } + + return QFileInfo(systemDirectory + "cmd.exe"); +} + +FileExecutionTypes getFileExecutionType(const QFileInfo& target) +{ + if (isExeFile(target) || isBatchFile(target) || isJavaFile(target)) { + return FileExecutionTypes::Executable; + } + + return FileExecutionTypes::Other; +} + +FileExecutionContext getFileExecutionContext(QWidget* parent, const QFileInfo& target) +{ + if (isExeFile(target)) { + return {target, "", FileExecutionTypes::Executable}; + } + + if (isBatchFile(target)) { + return { + getCmdPath(), + QString("/C \"%1\"").arg(QDir::toNativeSeparators(target.absoluteFilePath())), + FileExecutionTypes::Executable}; + } + + if (isJavaFile(target)) { + auto java = findJavaInstallation(target.absoluteFilePath()); + + if (java.isEmpty()) { + java = + QFileDialog::getOpenFileName(parent, QObject::tr("Select binary"), QString(), + QObject::tr("Binary") + " (*.exe)"); + } + + if (!java.isEmpty()) { + return {QFileInfo(java), + QString("-jar \"%1\"") + .arg(QDir::toNativeSeparators(target.absoluteFilePath())), + FileExecutionTypes::Executable}; + } + } + + return {{}, {}, FileExecutionTypes::Other}; +} + +} // namespace spawn + +namespace helper +{ + +bool helperExec(QWidget* parent, const std::wstring& moDirectory, + const std::wstring& commandLine, BOOL async) +{ + const std::wstring fileName = moDirectory + L"\\helper.exe"; + + env::HandlePtr process; + + { + SHELLEXECUTEINFOW execInfo = {}; + + ULONG flags = SEE_MASK_FLAG_NO_UI; + if (!async) + flags |= SEE_MASK_NOCLOSEPROCESS; + + execInfo.cbSize = sizeof(SHELLEXECUTEINFOW); + execInfo.fMask = flags; + execInfo.hwnd = 0; + execInfo.lpVerb = L"runas"; + execInfo.lpFile = fileName.c_str(); + execInfo.lpParameters = commandLine.c_str(); + execInfo.lpDirectory = moDirectory.c_str(); + execInfo.nShow = SW_SHOW; + + if (!::ShellExecuteExW(&execInfo) && execInfo.hProcess == 0) { + const auto e = GetLastError(); + + spawn::dialogs::helperFailed(parent, e, "ShellExecuteExW()", fileName, + moDirectory, commandLine); + + return false; + } + + if (async) { + return true; + } + + process.reset(execInfo.hProcess); + } + + const auto r = ::WaitForSingleObject(process.get(), INFINITE); + + if (r != WAIT_OBJECT_0) { + // for WAIT_ABANDONED, the documentation doesn't mention that GetLastError() + // returns something meaningful, but code ERROR_ABANDONED_WAIT_0 exists, so + // use that instead + const auto code = (r == WAIT_ABANDONED ? ERROR_ABANDONED_WAIT_0 : GetLastError()); + + spawn::dialogs::helperFailed(parent, code, "WaitForSingleObject()", fileName, + moDirectory, commandLine); + + return false; + } + + DWORD exitCode = 0; + if (!GetExitCodeProcess(process.get(), &exitCode)) { + const auto e = GetLastError(); + + spawn::dialogs::helperFailed(parent, e, "GetExitCodeProcess()", fileName, + moDirectory, commandLine); + + return false; + } + + return (exitCode == 0); +} + +bool backdateBSAs(QWidget* parent, const std::wstring& moPath, + const std::wstring& dataPath) +{ + const std::wstring commandLine = fmt::format(L"backdateBSA \"{}\"", dataPath); + + return helperExec(parent, moPath, commandLine, FALSE); +} + +bool adminLaunch(QWidget* parent, const std::wstring& moPath, + const std::wstring& moFile, const std::wstring& workingDir) +{ + const std::wstring commandLine = fmt::format( + L"adminLaunch {} \"{}\" \"{}\"", ::GetCurrentProcessId(), moFile, workingDir); + + return helperExec(parent, moPath, commandLine, true); +} + +} // namespace helper diff --git a/src/spawn.h b/src/spawn.h index 0628781ef..7bca84c9e 100644 --- a/src/spawn.h +++ b/src/spawn.h @@ -1,121 +1,117 @@ -/* -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 SPAWN_H -#define SPAWN_H - -#define WIN32_LEAN_AND_MEAN -#include -#include -#include -#include - -class Settings; - -namespace spawn -{ - -/* - * @param binary the binary to spawn - * @param arguments arguments to pass to the binary - * @param profileName name of the active profile - * @param currentDirectory the directory to use as the working directory to run in - * @param logLevel log level to be used by the hook library. Ignored if hooked is false - * @param hooked if set, the binary is started with mo injected - * @param stdout if not equal to INVALID_HANDLE_VALUE, this is used as stdout for the process - * @param stderr if not equal to INVALID_HANDLE_VALUE, this is used as stderr for the process -*/ -struct SpawnParameters -{ - QFileInfo binary; - QString arguments; - QDir currentDirectory; - QString steamAppID; - bool hooked = false; - HANDLE stdOut = INVALID_HANDLE_VALUE; - HANDLE stdErr = INVALID_HANDLE_VALUE; -}; - - -bool checkSteam( - QWidget* parent, const SpawnParameters& sp, - const QDir& gameDirectory, const QString &steamAppID, const Settings& settings); - -bool checkBlacklist( - QWidget* parent, const SpawnParameters& sp, Settings& settings); - -/** - * @brief spawn a binary with Mod Organizer injected - * @return the process handle - **/ -HANDLE startBinary(QWidget* parent, const SpawnParameters& sp); - - -enum class FileExecutionTypes -{ - Executable = 1, - Other -}; - -struct FileExecutionContext -{ - QFileInfo binary; - QString arguments; - FileExecutionTypes type; -}; - -QString findJavaInstallation(const QString& jarFile); - -FileExecutionContext getFileExecutionContext( - QWidget* parent, const QFileInfo& target); - -FileExecutionTypes getFileExecutionType(const QFileInfo& target); - -} // namespace - - -// convenience functions to work with the external helper program, which is used -// to make changes on the system that require administrative rights, so that -// ModOrganizer itself can run without special privileges -// -namespace helper -{ - -/** -* @brief sets the last modified time for all .bsa-files in the target directory well into the past -* @param moPath absolute path to the modOrganizer base directory -* @param dataPath the path taht contains the .bsa-files, usually the data directory of the game -**/ -bool backdateBSAs( - QWidget* parent, const std::wstring &moPath, const std::wstring &dataPath); - -/** -* @brief waits for the current process to exit and restarts it as an administrator -* @param moPath absolute path to the modOrganizer base directory -* @param moFile file name of modOrganizer -* @param workingDir current working directory -**/ -bool adminLaunch( - QWidget* parent, const std::wstring &moPath, - const std::wstring &moFile, const std::wstring &workingDir); - -} // namespace - -#endif // SPAWN_H - +/* +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 SPAWN_H +#define SPAWN_H + +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#include + +class Settings; + +namespace spawn +{ + +/* + * @param binary the binary to spawn + * @param arguments arguments to pass to the binary + * @param profileName name of the active profile + * @param currentDirectory the directory to use as the working directory to run in + * @param logLevel log level to be used by the hook library. Ignored if hooked is false + * @param hooked if set, the binary is started with mo injected + * @param stdout if not equal to INVALID_HANDLE_VALUE, this is used as stdout for the + * process + * @param stderr if not equal to INVALID_HANDLE_VALUE, this is used as stderr for the + * process + */ +struct SpawnParameters +{ + QFileInfo binary; + QString arguments; + QDir currentDirectory; + QString steamAppID; + bool hooked = false; + HANDLE stdOut = INVALID_HANDLE_VALUE; + HANDLE stdErr = INVALID_HANDLE_VALUE; +}; + +bool checkSteam(QWidget* parent, const SpawnParameters& sp, const QDir& gameDirectory, + const QString& steamAppID, const Settings& settings); + +bool checkBlacklist(QWidget* parent, const SpawnParameters& sp, Settings& settings); + +/** + * @brief spawn a binary with Mod Organizer injected + * @return the process handle + **/ +HANDLE startBinary(QWidget* parent, const SpawnParameters& sp); + +enum class FileExecutionTypes +{ + Executable = 1, + Other +}; + +struct FileExecutionContext +{ + QFileInfo binary; + QString arguments; + FileExecutionTypes type; +}; + +QString findJavaInstallation(const QString& jarFile); + +FileExecutionContext getFileExecutionContext(QWidget* parent, const QFileInfo& target); + +FileExecutionTypes getFileExecutionType(const QFileInfo& target); + +} // namespace spawn + +// convenience functions to work with the external helper program, which is used +// to make changes on the system that require administrative rights, so that +// ModOrganizer itself can run without special privileges +// +namespace helper +{ + +/** + * @brief sets the last modified time for all .bsa-files in the target directory well + *into the past + * @param moPath absolute path to the modOrganizer base directory + * @param dataPath the path taht contains the .bsa-files, usually the data directory of + *the game + **/ +bool backdateBSAs(QWidget* parent, const std::wstring& moPath, + const std::wstring& dataPath); + +/** + * @brief waits for the current process to exit and restarts it as an administrator + * @param moPath absolute path to the modOrganizer base directory + * @param moFile file name of modOrganizer + * @param workingDir current working directory + **/ +bool adminLaunch(QWidget* parent, const std::wstring& moPath, + const std::wstring& moFile, const std::wstring& workingDir); + +} // namespace helper + +#endif // SPAWN_H diff --git a/src/statusbar.cpp b/src/statusbar.cpp index 094aa743a..3b26dd91f 100644 --- a/src/statusbar.cpp +++ b/src/statusbar.cpp @@ -1,23 +1,22 @@ #include "statusbar.h" +#include "instancemanager.h" #include "nexusinterface.h" -#include "settings.h" #include "organizercore.h" -#include "instancemanager.h" +#include "settings.h" #include "ui_mainwindow.h" -StatusBar::StatusBar(QWidget* parent) : - QStatusBar(parent), ui(nullptr), m_normal(new QLabel), - m_progress(new QProgressBar), m_progressSpacer1(new QWidget), - m_progressSpacer2(new QWidget), m_notifications(nullptr), m_update(nullptr), - m_api(new QLabel) -{ -} +StatusBar::StatusBar(QWidget* parent) + : QStatusBar(parent), ui(nullptr), m_normal(new QLabel), + m_progress(new QProgressBar), m_progressSpacer1(new QWidget), + m_progressSpacer2(new QWidget), m_notifications(nullptr), m_update(nullptr), + m_api(new QLabel) +{} void StatusBar::setup(Ui::MainWindow* mainWindowUI, const Settings& settings) { - ui = mainWindowUI; + ui = mainWindowUI; m_notifications = new StatusBarAction(ui->actionNotifications); - m_update = new StatusBarAction(ui->actionUpdate); + m_update = new StatusBarAction(ui->actionUpdate); addWidget(m_normal); @@ -26,13 +25,12 @@ void StatusBar::setup(Ui::MainWindow* mainWindowUI, const Settings& settings) addPermanentWidget(m_progress); m_progressSpacer2->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - addPermanentWidget(m_progressSpacer2,0); + addPermanentWidget(m_progressSpacer2, 0); addPermanentWidget(m_notifications); addPermanentWidget(m_update); addPermanentWidget(m_api); - m_progress->setTextVisible(true); m_progress->setRange(0, 100); m_progress->setMaximumWidth(300); @@ -43,12 +41,12 @@ void StatusBar::setup(Ui::MainWindow* mainWindowUI, const Settings& settings) m_api->setObjectName("apistats"); m_api->setToolTip(QObject::tr( - "This tracks the number of queued Nexus API requests, as well as the " - "remaining daily and hourly requests. The Nexus API limits you to a pool " - "of requests per day and requests per hour. It is dynamically updated " - "every time a request is completed. If you run out of requests, you will " - "be unable to queue downloads, check updates, parse mod info, or even log " - "in. Both pools must be consumed before this happens.")); + "This tracks the number of queued Nexus API requests, as well as the " + "remaining daily and hourly requests. The Nexus API limits you to a pool " + "of requests per day and requests per hour. It is dynamically updated " + "every time a request is completed. If you run out of requests, you will " + "be unable to queue downloads, check updates, parse mod info, or even log " + "in. Both pools must be consumed before this happens.")); clearMessage(); setProgress(-1); @@ -59,7 +57,7 @@ void StatusBar::setup(Ui::MainWindow* mainWindowUI, const Settings& settings) void StatusBar::setProgress(int percent) { - bool visible =true; + bool visible = true; if (percent < 0 || percent >= 100) { clearMessage(); @@ -88,23 +86,23 @@ void StatusBar::setAPI(const APIStats& stats, const APIUserAccount& user) QString backgroundColor; if (user.type() == APIUserAccountTypes::None) { - text = "API: not logged in"; - textColor = ""; + text = "API: not logged in"; + textColor = ""; backgroundColor = ""; } else { text = QString("API: Queued: %1 | Daily: %2 | Hourly: %3") - .arg(stats.requestsQueued) - .arg(user.limits().remainingDailyRequests) - .arg(user.limits().remainingHourlyRequests); + .arg(stats.requestsQueued) + .arg(user.limits().remainingDailyRequests) + .arg(user.limits().remainingHourlyRequests); if (user.remainingRequests() > 500) { - textColor = "white"; + textColor = "white"; backgroundColor = "darkgreen"; } else if (user.remainingRequests() > 200) { - textColor = "black"; + textColor = "black"; backgroundColor = "rgb(226, 192, 0)"; // yellow } else { - textColor = "white"; + textColor = "white"; backgroundColor = "darkred"; } } @@ -154,15 +152,12 @@ void StatusBar::updateNormalMessage(OrganizerCore& core) } QString instance = "?"; - if (auto i=InstanceManager::singleton().currentInstance()) + if (auto i = InstanceManager::singleton().currentInstance()) instance = i->displayName(); QString profile = core.profileName(); - const auto s = QString("%1 - %2 - %3") - .arg(game) - .arg(instance) - .arg(profile); + const auto s = QString("%1 - %2 - %3").arg(game).arg(instance).arg(profile); m_normal->setText(s); } @@ -198,9 +193,8 @@ void StatusBar::visibilityChanged(bool visible) ui->centralWidget->layout()->setContentsMargins(m); } - StatusBarAction::StatusBarAction(QAction* action) - : m_action(action), m_icon(new QLabel), m_text(new QLabel) + : m_action(action), m_icon(new QLabel), m_text(new QLabel) { setLayout(new QHBoxLayout); layout()->setContentsMargins(0, 0, 0, 0); @@ -230,7 +224,7 @@ QString StatusBarAction::cleanupActionText(const QString& original) const QString s = original; s.replace(QRegularExpression("\\&([^&])"), "\\1"); // &Item -> Item - s.replace("&&", "&"); // &&Item -> &Item + s.replace("&&", "&"); // &&Item -> &Item if (s.endsWith("...")) { s = s.left(s.size() - 3); diff --git a/src/statusbar.h b/src/statusbar.h index 6e033cbd1..9e3bf3b02 100644 --- a/src/statusbar.h +++ b/src/statusbar.h @@ -1,16 +1,18 @@ #ifndef MO_STATUSBAR_H #define MO_STATUSBAR_H -#include #include +#include struct APIStats; class APIUserAccount; class Settings; class OrganizerCore; -namespace Ui { class MainWindow; } - +namespace Ui +{ +class MainWindow; +} class StatusBarAction : public QWidget { @@ -30,13 +32,12 @@ class StatusBarAction : public QWidget QString cleanupActionText(const QString& s) const; }; - class StatusBar : public QStatusBar { Q_OBJECT; public: - StatusBar(QWidget* parent=nullptr); + StatusBar(QWidget* parent = nullptr); void setup(Ui::MainWindow* ui, const Settings& settings); @@ -64,4 +65,4 @@ class StatusBar : public QStatusBar void visibilityChanged(bool visible); }; -#endif // MO_STATUSBAR_H +#endif // MO_STATUSBAR_H diff --git a/src/syncoverwritedialog.cpp b/src/syncoverwritedialog.cpp index 41c2b202d..d3e2905c1 100644 --- a/src/syncoverwritedialog.cpp +++ b/src/syncoverwritedialog.cpp @@ -1,171 +1,176 @@ -/* -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 "syncoverwritedialog.h" -#include "shared/directoryentry.h" -#include "shared/fileentry.h" -#include "shared/filesorigin.h" -#include "ui_syncoverwritedialog.h" - -#include -#include -#include - -#include -#include -#include -#include - - -using namespace MOBase; -using namespace MOShared; - - -SyncOverwriteDialog::SyncOverwriteDialog(const QString &path, DirectoryEntry *directoryStructure, QWidget *parent) - : TutorableDialog("SyncOverwrite", parent), - ui(new Ui::SyncOverwriteDialog), m_SourcePath(path), m_DirectoryStructure(directoryStructure) -{ - ui->setupUi(this); - refresh(path); - - QHeaderView *headerView = ui->syncTree->header(); -#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) - headerView->setSectionResizeMode(0, QHeaderView::Stretch); - headerView->setSectionResizeMode(1, QHeaderView::Interactive); -#else - headerView->setResizeMode(0, QHeaderView::Stretch); - headerView->setResizeMode(1, QHeaderView::Interactive); -#endif -} - - -SyncOverwriteDialog::~SyncOverwriteDialog() -{ - delete ui; -} - - -static void addToComboBox(QComboBox *box, const QString &name, const QVariant &userData) -{ - if (QString::compare(name, "overwrite", Qt::CaseInsensitive) != 0) { - box->addItem(name, userData); - } -} - - -void SyncOverwriteDialog::readTree(const QString &path, DirectoryEntry *directoryStructure, QTreeWidgetItem *subTree) -{ - QDir overwrite(path); - overwrite.setFilter(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot); - QDirIterator dirIter(overwrite); - while (dirIter.hasNext()) { - dirIter.next(); - QFileInfo fileInfo = dirIter.fileInfo(); - - QString file = fileInfo.fileName(); - if (file == "meta.ini") { - continue; - } - - QTreeWidgetItem *newItem = new QTreeWidgetItem(subTree, QStringList(file)); - - if (fileInfo.isDir()) { - DirectoryEntry *subDir = directoryStructure->findSubDirectory(ToWString(file)); - if (subDir != nullptr) { - readTree(fileInfo.absoluteFilePath(), subDir, newItem); - } else { - log::error("no directory structure for {}?", file); - delete newItem; - newItem = nullptr; - } - } else { - const FileEntryPtr entry = directoryStructure->findFile(ToWString(file)); - QComboBox* combo = new QComboBox(ui->syncTree); - combo->addItem(tr(""), -1); - if (entry.get() != nullptr) { - bool ignore; - int origin = entry->getOrigin(ignore); - addToComboBox(combo, ToQString(m_DirectoryStructure->getOriginByID(origin).getName()), origin); - const auto &alternatives = entry->getAlternatives(); - for (const auto& alt : alternatives) { - addToComboBox(combo, ToQString(m_DirectoryStructure->getOriginByID(alt.originID()).getName()), alt.originID()); - } - combo->setCurrentIndex(combo->count() - 1); - } else { - combo->setCurrentIndex(0); - } - ui->syncTree->setItemWidget(newItem, 1, combo); - } - if (newItem != nullptr) { - subTree->addChild(newItem); - } - } -} - - -void SyncOverwriteDialog::refresh(const QString &path) -{ - QTreeWidgetItem *rootItem = new QTreeWidgetItem(ui->syncTree, QStringList("")); - readTree(path, m_DirectoryStructure, rootItem); - ui->syncTree->addTopLevelItem(rootItem); - ui->syncTree->expandAll(); -} - - -void SyncOverwriteDialog::applyTo(QTreeWidgetItem *item, const QString &path, const QString &modDirectory) -{ - for (int i = 0; i < item->childCount(); ++i) { - QTreeWidgetItem *child = item->child(i); - QString filePath; - if (path.length() != 0) { - filePath = path + "/" + child->text(0); - } else { - filePath = child->text(0); - } - if (child->childCount() != 0) { - applyTo(child, filePath, modDirectory); - } else { - QComboBox *comboBox = qobject_cast(ui->syncTree->itemWidget(child, 1)); - if (comboBox != nullptr) { - int originID = comboBox->itemData(comboBox->currentIndex(), Qt::UserRole).toInt(); - if (originID != -1) { - FilesOrigin &origin = m_DirectoryStructure->getOriginByID(originID); - QString source = m_SourcePath + "/" + filePath; - QString destination = modDirectory + "/" + ToQString(origin.getName()) + "/" + filePath; - if (!QFile::remove(destination)) { - reportError(tr("failed to remove %1").arg(destination)); - } else if (!QFile::rename(source, destination)) { - reportError(tr("failed to move %1 to %2").arg(source).arg(destination)); - } - } - } - } - } - - QDir dir(m_SourcePath + "/" + path); - if ((path.length() > 0) && (dir.count() == 2)) { - dir.rmpath("."); - } -} - - -void SyncOverwriteDialog::apply(const QString &modDirectory) -{ - applyTo(ui->syncTree->topLevelItem(0), "", modDirectory); -} +/* +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 "syncoverwritedialog.h" +#include "shared/directoryentry.h" +#include "shared/fileentry.h" +#include "shared/filesorigin.h" +#include "ui_syncoverwritedialog.h" + +#include +#include +#include + +#include +#include +#include +#include + +using namespace MOBase; +using namespace MOShared; + +SyncOverwriteDialog::SyncOverwriteDialog(const QString& path, + DirectoryEntry* directoryStructure, + QWidget* parent) + : TutorableDialog("SyncOverwrite", parent), ui(new Ui::SyncOverwriteDialog), + m_SourcePath(path), m_DirectoryStructure(directoryStructure) +{ + ui->setupUi(this); + refresh(path); + + QHeaderView* headerView = ui->syncTree->header(); +#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) + headerView->setSectionResizeMode(0, QHeaderView::Stretch); + headerView->setSectionResizeMode(1, QHeaderView::Interactive); +#else + headerView->setResizeMode(0, QHeaderView::Stretch); + headerView->setResizeMode(1, QHeaderView::Interactive); +#endif +} + +SyncOverwriteDialog::~SyncOverwriteDialog() +{ + delete ui; +} + +static void addToComboBox(QComboBox* box, const QString& name, const QVariant& userData) +{ + if (QString::compare(name, "overwrite", Qt::CaseInsensitive) != 0) { + box->addItem(name, userData); + } +} + +void SyncOverwriteDialog::readTree(const QString& path, + DirectoryEntry* directoryStructure, + QTreeWidgetItem* subTree) +{ + QDir overwrite(path); + overwrite.setFilter(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot); + QDirIterator dirIter(overwrite); + while (dirIter.hasNext()) { + dirIter.next(); + QFileInfo fileInfo = dirIter.fileInfo(); + + QString file = fileInfo.fileName(); + if (file == "meta.ini") { + continue; + } + + QTreeWidgetItem* newItem = new QTreeWidgetItem(subTree, QStringList(file)); + + if (fileInfo.isDir()) { + DirectoryEntry* subDir = directoryStructure->findSubDirectory(ToWString(file)); + if (subDir != nullptr) { + readTree(fileInfo.absoluteFilePath(), subDir, newItem); + } else { + log::error("no directory structure for {}?", file); + delete newItem; + newItem = nullptr; + } + } else { + const FileEntryPtr entry = directoryStructure->findFile(ToWString(file)); + QComboBox* combo = new QComboBox(ui->syncTree); + combo->addItem(tr(""), -1); + if (entry.get() != nullptr) { + bool ignore; + int origin = entry->getOrigin(ignore); + addToComboBox(combo, + ToQString(m_DirectoryStructure->getOriginByID(origin).getName()), + origin); + const auto& alternatives = entry->getAlternatives(); + for (const auto& alt : alternatives) { + addToComboBox( + combo, + ToQString(m_DirectoryStructure->getOriginByID(alt.originID()).getName()), + alt.originID()); + } + combo->setCurrentIndex(combo->count() - 1); + } else { + combo->setCurrentIndex(0); + } + ui->syncTree->setItemWidget(newItem, 1, combo); + } + if (newItem != nullptr) { + subTree->addChild(newItem); + } + } +} + +void SyncOverwriteDialog::refresh(const QString& path) +{ + QTreeWidgetItem* rootItem = new QTreeWidgetItem(ui->syncTree, QStringList("")); + readTree(path, m_DirectoryStructure, rootItem); + ui->syncTree->addTopLevelItem(rootItem); + ui->syncTree->expandAll(); +} + +void SyncOverwriteDialog::applyTo(QTreeWidgetItem* item, const QString& path, + const QString& modDirectory) +{ + for (int i = 0; i < item->childCount(); ++i) { + QTreeWidgetItem* child = item->child(i); + QString filePath; + if (path.length() != 0) { + filePath = path + "/" + child->text(0); + } else { + filePath = child->text(0); + } + if (child->childCount() != 0) { + applyTo(child, filePath, modDirectory); + } else { + QComboBox* comboBox = + qobject_cast(ui->syncTree->itemWidget(child, 1)); + if (comboBox != nullptr) { + int originID = + comboBox->itemData(comboBox->currentIndex(), Qt::UserRole).toInt(); + if (originID != -1) { + FilesOrigin& origin = m_DirectoryStructure->getOriginByID(originID); + QString source = m_SourcePath + "/" + filePath; + QString destination = + modDirectory + "/" + ToQString(origin.getName()) + "/" + filePath; + if (!QFile::remove(destination)) { + reportError(tr("failed to remove %1").arg(destination)); + } else if (!QFile::rename(source, destination)) { + reportError(tr("failed to move %1 to %2").arg(source).arg(destination)); + } + } + } + } + } + + QDir dir(m_SourcePath + "/" + path); + if ((path.length() > 0) && (dir.count() == 2)) { + dir.rmpath("."); + } +} + +void SyncOverwriteDialog::apply(const QString& modDirectory) +{ + applyTo(ui->syncTree->topLevelItem(0), "", modDirectory); +} diff --git a/src/syncoverwritedialog.h b/src/syncoverwritedialog.h index cb4c92fb1..f3d1ae8e3 100644 --- a/src/syncoverwritedialog.h +++ b/src/syncoverwritedialog.h @@ -1,56 +1,57 @@ -/* -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 SYNCOVERWRITEDIALOG_H -#define SYNCOVERWRITEDIALOG_H - -#include "tutorabledialog.h" -#include "shared/fileregisterfwd.h" -#include - -namespace Ui { -class SyncOverwriteDialog; -} - -class SyncOverwriteDialog : public MOBase::TutorableDialog -{ - Q_OBJECT - -public: - explicit SyncOverwriteDialog( - const QString &path, MOShared::DirectoryEntry *directoryStructure, - QWidget *parent = 0); - - ~SyncOverwriteDialog(); - - void apply(const QString &modDirectory); -private: - void refresh(const QString &path); - void readTree(const QString &path, MOShared::DirectoryEntry *directoryStructure, QTreeWidgetItem *subTree); - void applyTo(QTreeWidgetItem *item, const QString &path, const QString &modDirectory); - -private: - - Ui::SyncOverwriteDialog *ui; - QString m_SourcePath; - MOShared::DirectoryEntry *m_DirectoryStructure; - -}; - -#endif // SYNCOVERWRITEDIALOG_H +/* +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 SYNCOVERWRITEDIALOG_H +#define SYNCOVERWRITEDIALOG_H + +#include "shared/fileregisterfwd.h" +#include "tutorabledialog.h" +#include + +namespace Ui +{ +class SyncOverwriteDialog; +} + +class SyncOverwriteDialog : public MOBase::TutorableDialog +{ + Q_OBJECT + +public: + explicit SyncOverwriteDialog(const QString& path, + MOShared::DirectoryEntry* directoryStructure, + QWidget* parent = 0); + + ~SyncOverwriteDialog(); + + void apply(const QString& modDirectory); + +private: + void refresh(const QString& path); + void readTree(const QString& path, MOShared::DirectoryEntry* directoryStructure, + QTreeWidgetItem* subTree); + void applyTo(QTreeWidgetItem* item, const QString& path, const QString& modDirectory); + +private: + Ui::SyncOverwriteDialog* ui; + QString m_SourcePath; + MOShared::DirectoryEntry* m_DirectoryStructure; +}; + +#endif // SYNCOVERWRITEDIALOG_H diff --git a/src/texteditor.cpp b/src/texteditor.cpp index 7f62a6ef0..281a545d7 100644 --- a/src/texteditor.cpp +++ b/src/texteditor.cpp @@ -1,16 +1,15 @@ #include "texteditor.h" #include "utility.h" -#include #include +#include using namespace MOBase; -TextEditor::TextEditor(QWidget* parent) : - QPlainTextEdit(parent), - m_toolbar(nullptr), m_lineNumbers(nullptr), m_highlighter(nullptr), - m_dirty(false), m_loading(false) +TextEditor::TextEditor(QWidget* parent) + : QPlainTextEdit(parent), m_toolbar(nullptr), m_lineNumbers(nullptr), + m_highlighter(nullptr), m_dirty(false), m_loading(false) { - m_toolbar = new TextEditorToolbar(*this); + m_toolbar = new TextEditorToolbar(*this); m_lineNumbers = new TextEditorLineNumbers(*this); m_highlighter = new TextEditorHighlighter(document()); @@ -19,13 +18,13 @@ TextEditor::TextEditor(QWidget* parent) : emit modified(false); - connect( - document(), &QTextDocument::modificationChanged, - [&](bool b){ onModified(b); }); + connect(document(), &QTextDocument::modificationChanged, [&](bool b) { + onModified(b); + }); - connect( - this, &QPlainTextEdit::cursorPositionChanged, - [&]{ highlightCurrentLine(); }); + connect(this, &QPlainTextEdit::cursorPositionChanged, [&] { + highlightCurrentLine(); + }); } void TextEditor::setDefaultStyle() @@ -42,12 +41,12 @@ void TextEditor::setDefaultStyle() { auto w = std::make_unique(); - if (auto* s=style()) { + if (auto* s = style()) { s->polish(w.get()); } - textColor = w->palette().color(QPalette::WindowText); - altTextColor = w->palette().color(QPalette::Disabled, QPalette::WindowText); + textColor = w->palette().color(QPalette::WindowText); + altTextColor = w->palette().color(QPalette::Disabled, QPalette::WindowText); backgroundColor = w->palette().color(QPalette::Window); } @@ -170,10 +169,10 @@ void TextEditor::setBackgroundColor(const QColor& c) m_highlighter->setBackgroundColor(c); setStyleSheet(QString("QPlainTextEdit{ background-color: rgba(%1, %2, %3, %4); }") - .arg(c.redF() * 255) - .arg(c.greenF() * 255) - .arg(c.blueF() * 255) - .arg(c.alphaF())); + .arg(c.redF() * 255) + .arg(c.greenF() * 255) + .arg(c.blueF() * 255) + .arg(c.alphaF())); } QColor TextEditor::textColor() const @@ -245,20 +244,19 @@ QWidget* TextEditor::wrapEditWidget() // wrapping the QPlainTextEdit into a new widget so the toolbar can be // displayed above it - if (auto* parentLayout=parentWidget()->layout()) { + if (auto* parentLayout = parentWidget()->layout()) { // the edit's parent has a regular layout, replace the edit by the new // widget and delete the QLayoutItem that's returned as it's not needed delete parentLayout->replaceWidget(this, widget.get()); - } else if (auto* splitter=qobject_cast(parentWidget())) { + } else if (auto* splitter = qobject_cast(parentWidget())) { // the edit's parent is a QSplitter, which doesn't have a layout; replace // the edit by using its index in the splitter auto index = splitter->indexOf(this); if (index == -1) { - log::error( - "TextEditor: cannot wrap edit widget to display a toolbar, " - "parent is a splitter, but widget isn't in it"); + log::error("TextEditor: cannot wrap edit widget to display a toolbar, " + "parent is a splitter, but widget isn't in it"); return nullptr; } @@ -267,9 +265,8 @@ QWidget* TextEditor::wrapEditWidget() } else { // unknown parent - log::error( - "TextEditor: cannot wrap edit widget to display a toolbar, " - "no parent or parent has no layout"); + log::error("TextEditor: cannot wrap edit widget to display a toolbar, " + "no parent or parent has no layout"); return nullptr; } @@ -282,7 +279,8 @@ void TextEditor::resizeEvent(QResizeEvent* e) QPlainTextEdit::resizeEvent(e); QRect cr = contentsRect(); - m_lineNumbers->setGeometry(QRect(cr.left(), cr.top(), m_lineNumbers->areaWidth(), cr.height())); + m_lineNumbers->setGeometry( + QRect(cr.left(), cr.top(), m_lineNumbers->areaWidth(), cr.height())); } void TextEditor::paintLineNumbers(QPaintEvent* e, const QColor& textColor) @@ -293,23 +291,22 @@ void TextEditor::paintLineNumbers(QPaintEvent* e, const QColor& textColor) QPainter painter(m_lineNumbers); QTextBlock block = firstVisibleBlock(); - int blockNumber = block.blockNumber(); - int top = (int) blockBoundingGeometry(block).translated(contentOffset()).top(); - int bottom = top + (int) blockBoundingRect(block).height(); + int blockNumber = block.blockNumber(); + int top = (int)blockBoundingGeometry(block).translated(contentOffset()).top(); + int bottom = top + (int)blockBoundingRect(block).height(); while (block.isValid() && top <= e->rect().bottom()) { if (block.isVisible() && bottom >= e->rect().top()) { QString number = QString::number(blockNumber + 1); painter.setPen(textColor); - painter.drawText( - 0, top, m_lineNumbers->width() - 3, fontMetrics().height(), - Qt::AlignRight, number); + painter.drawText(0, top, m_lineNumbers->width() - 3, fontMetrics().height(), + Qt::AlignRight, number); } - block = block.next(); - top = bottom; - bottom = top + (int) blockBoundingRect(block).height(); + block = block.next(); + top = bottom; + bottom = top + (int)blockBoundingRect(block).height(); ++blockNumber; } } @@ -333,13 +330,10 @@ void TextEditor::highlightCurrentLine() setExtraSelections(extraSelections); } - -TextEditorHighlighter::TextEditorHighlighter(QTextDocument* doc) : - QSyntaxHighlighter(doc), - m_background(QColor("transparent")), - m_text(QColor("black")) -{ -} +TextEditorHighlighter::TextEditorHighlighter(QTextDocument* doc) + : QSyntaxHighlighter(doc), m_background(QColor("transparent")), + m_text(QColor("black")) +{} QColor TextEditorHighlighter::backgroundColor() const { @@ -377,14 +371,17 @@ void TextEditorHighlighter::changed() rehighlight(); } - TextEditorLineNumbers::TextEditorLineNumbers(TextEditor& editor) - : QFrame(&editor), m_editor(editor) + : QFrame(&editor), m_editor(editor) { setFont(editor.font()); - connect(&m_editor, &QPlainTextEdit::blockCountChanged, [&]{ updateAreaWidth(); }); - connect(&m_editor, &QPlainTextEdit::updateRequest, [&](auto&& rect, int dy){ updateArea(rect, dy); }); + connect(&m_editor, &QPlainTextEdit::blockCountChanged, [&] { + updateAreaWidth(); + }); + connect(&m_editor, &QPlainTextEdit::updateRequest, [&](auto&& rect, int dy) { + updateArea(rect, dy); + }); updateAreaWidth(); } @@ -397,7 +394,7 @@ QSize TextEditorLineNumbers::sizeHint() const int TextEditorLineNumbers::areaWidth() const { int digits = 1; - int max = std::max(1, m_editor.blockCount()); + int max = std::max(1, m_editor.blockCount()); while (max >= 10) { max /= 10; @@ -447,7 +444,7 @@ void TextEditorLineNumbers::updateAreaWidth() m_editor.setViewportMargins(areaWidth(), 0, 0, 0); } -void TextEditorLineNumbers::updateArea(const QRect &rect, int dy) +void TextEditorLineNumbers::updateArea(const QRect& rect, int dy) { if (dy) { scroll(0, dy); @@ -460,32 +457,35 @@ void TextEditorLineNumbers::updateArea(const QRect &rect, int dy) } } - -TextEditorToolbar::TextEditorToolbar(TextEditor& editor) : - m_editor(editor), m_save(nullptr), m_wordWrap(nullptr), m_explore(nullptr), - m_path(nullptr) +TextEditorToolbar::TextEditorToolbar(TextEditor& editor) + : m_editor(editor), m_save(nullptr), m_wordWrap(nullptr), m_explore(nullptr), + m_path(nullptr) { - m_save = new QAction( - QIcon(":/MO/gui/save"), QObject::tr("&Save"), &editor); + m_save = new QAction(QIcon(":/MO/gui/save"), QObject::tr("&Save"), &editor); m_save->setShortcutContext(Qt::WidgetWithChildrenShortcut); m_save->setShortcut(Qt::CTRL + Qt::Key_S); m_editor.addAction(m_save); - m_wordWrap = new QAction( - QIcon(":/MO/gui/word-wrap"), QObject::tr("&Word wrap"), &editor); + m_wordWrap = + new QAction(QIcon(":/MO/gui/word-wrap"), QObject::tr("&Word wrap"), &editor); m_wordWrap->setCheckable(true); - m_explore = new QAction( - QObject::tr("&Open in Explorer"), &editor); + m_explore = new QAction(QObject::tr("&Open in Explorer"), &editor); m_path = new QLineEdit; m_path->setReadOnly(true); - QObject::connect(m_save, &QAction::triggered, [&]{ m_editor.save(); }); - QObject::connect(m_wordWrap, &QAction::triggered, [&]{ m_editor.toggleWordWrap(); }); - QObject::connect(m_explore, &QAction::triggered, [&]{ m_editor.explore(); }); + QObject::connect(m_save, &QAction::triggered, [&] { + m_editor.save(); + }); + QObject::connect(m_wordWrap, &QAction::triggered, [&] { + m_editor.toggleWordWrap(); + }); + QObject::connect(m_explore, &QAction::triggered, [&] { + m_editor.explore(); + }); auto* layout = new QHBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); @@ -505,9 +505,15 @@ TextEditorToolbar::TextEditorToolbar(TextEditor& editor) : layout->addWidget(m_path); - QObject::connect(&m_editor, &TextEditor::modified, [&](bool b){ onTextModified(b); }); - QObject::connect(&m_editor, &TextEditor::wordWrapChanged, [&](bool b){ onWordWrap(b); }); - QObject::connect(&m_editor, &TextEditor::loaded, [&](QString f){ onLoaded(f); }); + QObject::connect(&m_editor, &TextEditor::modified, [&](bool b) { + onTextModified(b); + }); + QObject::connect(&m_editor, &TextEditor::wordWrapChanged, [&](bool b) { + onWordWrap(b); + }); + QObject::connect(&m_editor, &TextEditor::loaded, [&](QString f) { + onLoaded(f); + }); } void TextEditorToolbar::onTextModified(bool b) diff --git a/src/texteditor.h b/src/texteditor.h index dc53f1c46..7f6adcdb0 100644 --- a/src/texteditor.h +++ b/src/texteditor.h @@ -24,7 +24,6 @@ class TextEditorToolbar : public QFrame void onLoaded(const QString& s); }; - // mostly from https://doc.qt.io/qt-5/qtwidgets-widgets-codeeditor-example.html // class TextEditorLineNumbers : public QFrame @@ -46,17 +45,16 @@ class TextEditorLineNumbers : public QFrame void setBackgroundColor(const QColor& c); protected: - void paintEvent(QPaintEvent *event) override; + void paintEvent(QPaintEvent* event) override; private: TextEditor& m_editor; QColor m_background, m_text; void updateAreaWidth(); - void updateArea(const QRect &rect, int dy); + void updateArea(const QRect& rect, int dy); }; - class TextEditorHighlighter : public QSyntaxHighlighter { Q_OBJECT; @@ -79,18 +77,18 @@ class TextEditorHighlighter : public QSyntaxHighlighter void changed(); }; - class TextEditor : public QPlainTextEdit { Q_OBJECT; Q_PROPERTY(QColor textColor READ textColor WRITE setTextColor); Q_PROPERTY(QColor backgroundColor READ backgroundColor WRITE setBackgroundColor); - Q_PROPERTY(QColor highlightBackgroundColor READ highlightBackgroundColor WRITE setHighlightBackgroundColor); + Q_PROPERTY(QColor highlightBackgroundColor READ highlightBackgroundColor WRITE + setHighlightBackgroundColor); friend class TextEditorLineNumbers; public: - TextEditor(QWidget* parent=nullptr); + TextEditor(QWidget* parent = nullptr); void setupToolbar(); @@ -145,7 +143,6 @@ class TextEditor : public QPlainTextEdit void paintLineNumbers(QPaintEvent* e, const QColor& textColor); }; - class HTMLEditor : public QTextEdit { Q_OBJECT; @@ -162,4 +159,4 @@ class HTMLEditor : public QTextEdit private: }; -#endif // MO_TEXTEDITOR_H +#endif // MO_TEXTEDITOR_H diff --git a/src/thread_utils.h b/src/thread_utils.h index e816a4e2a..1eec83a6b 100644 --- a/src/thread_utils.h +++ b/src/thread_utils.h @@ -1,16 +1,16 @@ #ifndef MO2_THREAD_UTILS_H #define MO2_THREAD_UTILS_H -#include #include +#include #include #include // in main.cpp void setExceptionHandlers(); - -namespace MOShared { +namespace MOShared +{ // starts an std::thread with an unhandled exception handler for core dumps // and a top-level catch @@ -18,7 +18,7 @@ namespace MOShared { template std::thread startSafeThread(F&& f) { - return std::thread([f=std::forward(f)] { + return std::thread([f = std::forward(f)] { setExceptionHandlers(); f(); }); @@ -47,7 +47,7 @@ void parallelMap(It begin, It end, Callable callable, std::size_t nThreads) // Create the thread: // - The mutex is only used to fetch/increment the iterator. // - The callable is copied in each thread to avoid conflicts. - for (auto &thread: threads) { + for (auto& thread : threads) { thread = startSafeThread([&m, &begin, end, callable]() { while (true) { decltype(begin) it; @@ -71,6 +71,6 @@ void parallelMap(It begin, It end, Callable callable, std::size_t nThreads) } } -} +} // namespace MOShared #endif diff --git a/src/transfersavesdialog.cpp b/src/transfersavesdialog.cpp index 5288d4082..2761ed1de 100644 --- a/src/transfersavesdialog.cpp +++ b/src/transfersavesdialog.cpp @@ -1,283 +1,278 @@ -/* -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 "transfersavesdialog.h" - -#include "ui_transfersavesdialog.h" -#include "iplugingame.h" -#include "isavegame.h" -#include "savegameinfo.h" -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace MOBase; -using namespace MOShared; - -TransferSavesDialog::TransferSavesDialog(const Profile &profile, IPluginGame const *gamePlugin, QWidget *parent) - : TutorableDialog("TransferSaves", parent) - , ui(new Ui::TransferSavesDialog) - , m_Profile(profile) - , m_GamePlugin(gamePlugin) -{ - ui->setupUi(this); - ui->label_2->setText(tr("Characters for profile %1").arg(m_Profile.name())); - refreshGlobalSaves(); - refreshLocalSaves(); - refreshGlobalCharacters(); - refreshLocalCharacters(); -} - -TransferSavesDialog::~TransferSavesDialog() -{ - delete ui; -} - -void TransferSavesDialog::refreshGlobalSaves() -{ - refreshSaves(m_GlobalSaves, m_GamePlugin->savesDirectory().absolutePath()); -} - - -void TransferSavesDialog::refreshLocalSaves() -{ - refreshSaves(m_LocalSaves, m_Profile.savePath()); -} - - -void TransferSavesDialog::refreshGlobalCharacters() -{ - refreshCharacters(m_GlobalSaves, ui->globalCharacterList, ui->copyToLocalBtn, ui->moveToLocalBtn); -} - - -void TransferSavesDialog::refreshLocalCharacters() -{ - refreshCharacters(m_LocalSaves, ui->localCharacterList, ui->copyToGlobalBtn, ui->moveToGlobalBtn); -} - - -bool TransferSavesDialog::testOverwrite(OverwriteMode &overwriteMode, const QString &destinationFile) -{ - QMessageBox::StandardButton res = overwriteMode == OVERWRITE_YES ? QMessageBox::Yes : QMessageBox::No; - if (overwriteMode == OVERWRITE_ASK) { - res = QMessageBox::question(this, tr("Overwrite"), - tr("Overwrite the file \"%1\"").arg(destinationFile), - QMessageBox::Yes | QMessageBox::No | QMessageBox::YesToAll | QMessageBox::NoToAll); - if (res == QMessageBox::YesToAll) { - overwriteMode = OVERWRITE_YES; - res = QMessageBox::Yes; - } else if (res == QMessageBox::NoToAll) { - overwriteMode = OVERWRITE_NO; - res = QMessageBox::No; - } - } - return res == QMessageBox::Yes; -} - - -#define MOVE_SAVES "Move all save games of character \"%1\"" -#define COPY_SAVES "Copy all save games of character \"%1\"" - -#define TO_PROFILE "to the profile?" -#define TO_GLOBAL "to the global location? Please be aware that this will mess up the running number of save games." - -void TransferSavesDialog::on_moveToLocalBtn_clicked() -{ - QString character = ui->globalCharacterList->currentItem()->text(); - if (transferCharacters( - character, MOVE_SAVES TO_PROFILE, - m_GamePlugin->savesDirectory(), - m_GlobalSaves[character], - m_Profile.savePath(), - [this](const QString &source, const QString &destination) -> bool { - return shellMove(source, destination, this); - }, - "Failed to move {} to {}")) { - refreshGlobalSaves(); - refreshGlobalCharacters(); - refreshLocalSaves(); - refreshLocalCharacters(); - } -} - -void TransferSavesDialog::on_copyToLocalBtn_clicked() -{ - QString character = ui->globalCharacterList->currentItem()->text(); - if (transferCharacters( - character, COPY_SAVES TO_PROFILE, - m_GamePlugin->savesDirectory(), - m_GlobalSaves[character], - m_Profile.savePath(), - [this](const QString &source, const QString &destination) -> bool { - return shellCopy(source, destination, this); - }, - "Failed to copy {} to {}")) { - refreshLocalSaves(); - refreshLocalCharacters(); - } -} - -void TransferSavesDialog::on_moveToGlobalBtn_clicked() -{ - QString character = ui->localCharacterList->currentItem()->text(); - if (transferCharacters( - character, MOVE_SAVES TO_GLOBAL, - m_Profile.savePath(), - m_LocalSaves[character], - m_GamePlugin->savesDirectory().absolutePath(), - [this](const QString &source, const QString &destination) -> bool { - return shellMove(source, destination, this); - }, - "Failed to move {} to {}")) { - refreshGlobalSaves(); - refreshGlobalCharacters(); - refreshLocalSaves(); - refreshLocalCharacters(); - } -} - -void TransferSavesDialog::on_copyToGlobalBtn_clicked() -{ - QString character = ui->localCharacterList->currentItem()->text(); - if (transferCharacters( - character, COPY_SAVES TO_GLOBAL, - m_Profile.savePath(), - m_LocalSaves[character], - m_GamePlugin->savesDirectory().absolutePath(), - [this](const QString &source, const QString &destination) -> bool { - return shellCopy(source, destination, this); - }, - "Failed to copy {} to {}")) { - refreshGlobalSaves(); - refreshGlobalCharacters(); - } -} - -void TransferSavesDialog::on_doneButton_clicked() -{ - close(); -} - -void TransferSavesDialog::on_globalCharacterList_currentTextChanged(const QString ¤tText) -{ - ui->globalSavesList->clear(); - //sadly this can get called while we're resetting the list, with an invalid - //name, so we have to check. - SaveCollection::const_iterator saveList = m_GlobalSaves.find(currentText); - if (saveList != m_GlobalSaves.end()) { - for (SaveListItem const &save : saveList->second) { - ui->globalSavesList->addItem(QFileInfo(save->getFilepath()).fileName()); - } - } -} - -void TransferSavesDialog::on_localCharacterList_currentTextChanged(const QString ¤tText) -{ - ui->localSavesList->clear(); - //sadly this can get called while we're resetting the list, with an invalid - //name, so we have to check. - SaveCollection::const_iterator saveList = m_LocalSaves.find(currentText); - if (saveList != m_LocalSaves.end()) { - for (SaveListItem const &save : saveList->second) { - ui->localSavesList->addItem(QFileInfo(save->getFilepath()).fileName()); - } - } -} - -void TransferSavesDialog::refreshSaves(SaveCollection &saveCollection, QString const &savedir) -{ - saveCollection.clear(); - - auto saves = m_GamePlugin->listSaves(savedir); - std::sort(saves.begin(), saves.end(), [](auto const& lhs, auto const& rhs) { - return lhs->getCreationTime() > rhs->getCreationTime(); - }); - - for (auto& save: saves) { - saveCollection[save->getSaveGroupIdentifier()].push_back(save); - } -} - -void TransferSavesDialog::refreshCharacters(const SaveCollection &saveCollection, - QListWidget *charList, QPushButton *copy, QPushButton *move) -{ - charList->clear(); - for (SaveCollection::value_type const &val : saveCollection) { - charList->addItem(val.first); - } - if (charList->count() > 0) { - charList->setCurrentRow(0); - copy->setEnabled(true); - move->setEnabled(true); - } else { - copy->setEnabled(false); - move->setEnabled(false); - } -} - -bool TransferSavesDialog::transferCharacters( - QString const &character, char const *message, - QDir const& sourceDirectory, - SaveList &saves, - QDir const& destination, - const std::function &method, - char const *errmsg) -{ - if (QMessageBox::question(this, tr("Confirm"), - tr(message).arg(character), - QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { - return false; - } - - OverwriteMode overwriteMode = OVERWRITE_ASK; - - for (SaveListItem const &save : saves) { - for (QString source : save->allFiles()) { - QFileInfo sourceFile(source); - QString destinationFile(destination.absoluteFilePath(sourceDirectory.relativeFilePath(source))); - - //If the file is already there, let them skip (or not). - if (QFile::exists(destinationFile)) { - if (! testOverwrite(overwriteMode, destinationFile)) { - continue; - } - //OK, they want to remove it. - QFile::remove(destinationFile); - } - - if (!method(sourceFile.absoluteFilePath(), destinationFile)) { - log::error(errmsg, sourceFile.absoluteFilePath(), destinationFile); - } - } - } - return true; -} +/* +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 "transfersavesdialog.h" + +#include "iplugingame.h" +#include "isavegame.h" +#include "savegameinfo.h" +#include "ui_transfersavesdialog.h" +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace MOBase; +using namespace MOShared; + +TransferSavesDialog::TransferSavesDialog(const Profile& profile, + IPluginGame const* gamePlugin, QWidget* parent) + : TutorableDialog("TransferSaves", parent), ui(new Ui::TransferSavesDialog), + m_Profile(profile), m_GamePlugin(gamePlugin) +{ + ui->setupUi(this); + ui->label_2->setText(tr("Characters for profile %1").arg(m_Profile.name())); + refreshGlobalSaves(); + refreshLocalSaves(); + refreshGlobalCharacters(); + refreshLocalCharacters(); +} + +TransferSavesDialog::~TransferSavesDialog() +{ + delete ui; +} + +void TransferSavesDialog::refreshGlobalSaves() +{ + refreshSaves(m_GlobalSaves, m_GamePlugin->savesDirectory().absolutePath()); +} + +void TransferSavesDialog::refreshLocalSaves() +{ + refreshSaves(m_LocalSaves, m_Profile.savePath()); +} + +void TransferSavesDialog::refreshGlobalCharacters() +{ + refreshCharacters(m_GlobalSaves, ui->globalCharacterList, ui->copyToLocalBtn, + ui->moveToLocalBtn); +} + +void TransferSavesDialog::refreshLocalCharacters() +{ + refreshCharacters(m_LocalSaves, ui->localCharacterList, ui->copyToGlobalBtn, + ui->moveToGlobalBtn); +} + +bool TransferSavesDialog::testOverwrite(OverwriteMode& overwriteMode, + const QString& destinationFile) +{ + QMessageBox::StandardButton res = + overwriteMode == OVERWRITE_YES ? QMessageBox::Yes : QMessageBox::No; + if (overwriteMode == OVERWRITE_ASK) { + res = QMessageBox::question(this, tr("Overwrite"), + tr("Overwrite the file \"%1\"").arg(destinationFile), + QMessageBox::Yes | QMessageBox::No | + QMessageBox::YesToAll | QMessageBox::NoToAll); + if (res == QMessageBox::YesToAll) { + overwriteMode = OVERWRITE_YES; + res = QMessageBox::Yes; + } else if (res == QMessageBox::NoToAll) { + overwriteMode = OVERWRITE_NO; + res = QMessageBox::No; + } + } + return res == QMessageBox::Yes; +} + +#define MOVE_SAVES "Move all save games of character \"%1\"" +#define COPY_SAVES "Copy all save games of character \"%1\"" + +#define TO_PROFILE "to the profile?" +#define TO_GLOBAL \ + "to the global location? Please be aware that this will mess up the running number " \ + "of save games." + +void TransferSavesDialog::on_moveToLocalBtn_clicked() +{ + QString character = ui->globalCharacterList->currentItem()->text(); + if (transferCharacters( + character, MOVE_SAVES TO_PROFILE, m_GamePlugin->savesDirectory(), + m_GlobalSaves[character], m_Profile.savePath(), + [this](const QString& source, const QString& destination) -> bool { + return shellMove(source, destination, this); + }, + "Failed to move {} to {}")) { + refreshGlobalSaves(); + refreshGlobalCharacters(); + refreshLocalSaves(); + refreshLocalCharacters(); + } +} + +void TransferSavesDialog::on_copyToLocalBtn_clicked() +{ + QString character = ui->globalCharacterList->currentItem()->text(); + if (transferCharacters( + character, COPY_SAVES TO_PROFILE, m_GamePlugin->savesDirectory(), + m_GlobalSaves[character], m_Profile.savePath(), + [this](const QString& source, const QString& destination) -> bool { + return shellCopy(source, destination, this); + }, + "Failed to copy {} to {}")) { + refreshLocalSaves(); + refreshLocalCharacters(); + } +} + +void TransferSavesDialog::on_moveToGlobalBtn_clicked() +{ + QString character = ui->localCharacterList->currentItem()->text(); + if (transferCharacters( + character, MOVE_SAVES TO_GLOBAL, m_Profile.savePath(), + m_LocalSaves[character], m_GamePlugin->savesDirectory().absolutePath(), + [this](const QString& source, const QString& destination) -> bool { + return shellMove(source, destination, this); + }, + "Failed to move {} to {}")) { + refreshGlobalSaves(); + refreshGlobalCharacters(); + refreshLocalSaves(); + refreshLocalCharacters(); + } +} + +void TransferSavesDialog::on_copyToGlobalBtn_clicked() +{ + QString character = ui->localCharacterList->currentItem()->text(); + if (transferCharacters( + character, COPY_SAVES TO_GLOBAL, m_Profile.savePath(), + m_LocalSaves[character], m_GamePlugin->savesDirectory().absolutePath(), + [this](const QString& source, const QString& destination) -> bool { + return shellCopy(source, destination, this); + }, + "Failed to copy {} to {}")) { + refreshGlobalSaves(); + refreshGlobalCharacters(); + } +} + +void TransferSavesDialog::on_doneButton_clicked() +{ + close(); +} + +void TransferSavesDialog::on_globalCharacterList_currentTextChanged( + const QString& currentText) +{ + ui->globalSavesList->clear(); + // sadly this can get called while we're resetting the list, with an invalid + // name, so we have to check. + SaveCollection::const_iterator saveList = m_GlobalSaves.find(currentText); + if (saveList != m_GlobalSaves.end()) { + for (SaveListItem const& save : saveList->second) { + ui->globalSavesList->addItem(QFileInfo(save->getFilepath()).fileName()); + } + } +} + +void TransferSavesDialog::on_localCharacterList_currentTextChanged( + const QString& currentText) +{ + ui->localSavesList->clear(); + // sadly this can get called while we're resetting the list, with an invalid + // name, so we have to check. + SaveCollection::const_iterator saveList = m_LocalSaves.find(currentText); + if (saveList != m_LocalSaves.end()) { + for (SaveListItem const& save : saveList->second) { + ui->localSavesList->addItem(QFileInfo(save->getFilepath()).fileName()); + } + } +} + +void TransferSavesDialog::refreshSaves(SaveCollection& saveCollection, + QString const& savedir) +{ + saveCollection.clear(); + + auto saves = m_GamePlugin->listSaves(savedir); + std::sort(saves.begin(), saves.end(), [](auto const& lhs, auto const& rhs) { + return lhs->getCreationTime() > rhs->getCreationTime(); + }); + + for (auto& save : saves) { + saveCollection[save->getSaveGroupIdentifier()].push_back(save); + } +} + +void TransferSavesDialog::refreshCharacters(const SaveCollection& saveCollection, + QListWidget* charList, QPushButton* copy, + QPushButton* move) +{ + charList->clear(); + for (SaveCollection::value_type const& val : saveCollection) { + charList->addItem(val.first); + } + if (charList->count() > 0) { + charList->setCurrentRow(0); + copy->setEnabled(true); + move->setEnabled(true); + } else { + copy->setEnabled(false); + move->setEnabled(false); + } +} + +bool TransferSavesDialog::transferCharacters( + QString const& character, char const* message, QDir const& sourceDirectory, + SaveList& saves, QDir const& destination, + const std::function& method, + char const* errmsg) +{ + if (QMessageBox::question(this, tr("Confirm"), tr(message).arg(character), + QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { + return false; + } + + OverwriteMode overwriteMode = OVERWRITE_ASK; + + for (SaveListItem const& save : saves) { + for (QString source : save->allFiles()) { + QFileInfo sourceFile(source); + QString destinationFile( + destination.absoluteFilePath(sourceDirectory.relativeFilePath(source))); + + // If the file is already there, let them skip (or not). + if (QFile::exists(destinationFile)) { + if (!testOverwrite(overwriteMode, destinationFile)) { + continue; + } + // OK, they want to remove it. + QFile::remove(destinationFile); + } + + if (!method(sourceFile.absoluteFilePath(), destinationFile)) { + log::error(errmsg, sourceFile.absoluteFilePath(), destinationFile); + } + } + } + return true; +} diff --git a/src/transfersavesdialog.h b/src/transfersavesdialog.h index fe78aa83b..5559b1d64 100644 --- a/src/transfersavesdialog.h +++ b/src/transfersavesdialog.h @@ -1,103 +1,115 @@ -/* -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 TRANSFERSAVESDIALOG_H -#define TRANSFERSAVESDIALOG_H - -#include "tutorabledialog.h" -#include "profile.h" - -class QListWidget; -#include -class QPushButton; -#include -class QWidget; - -#include -#include -#include - -namespace Ui { class TransferSavesDialog; } -namespace MOBase { class IPluginGame; } -namespace MOBase { class ISaveGame; } - -class TransferSavesDialog : public MOBase::TutorableDialog { - Q_OBJECT - -public: - explicit TransferSavesDialog(const Profile &profile, - MOBase::IPluginGame const *gamePlugin, - QWidget *parent = 0); - ~TransferSavesDialog(); - -private slots: - - void on_moveToLocalBtn_clicked(); - - void on_doneButton_clicked(); - - void on_globalCharacterList_currentTextChanged(const QString ¤tText); - - void on_localCharacterList_currentTextChanged(const QString ¤tText); - - void on_copyToLocalBtn_clicked(); - - void on_moveToGlobalBtn_clicked(); - - void on_copyToGlobalBtn_clicked(); - -private: - enum OverwriteMode { OVERWRITE_ASK, OVERWRITE_YES, OVERWRITE_NO }; - -private: - void refreshGlobalCharacters(); - void refreshLocalCharacters(); - void refreshGlobalSaves(); - void refreshLocalSaves(); - bool testOverwrite(OverwriteMode &overwriteMode, - const QString &destinationFile); - -private: - Ui::TransferSavesDialog *ui; - - Profile m_Profile; - - MOBase::IPluginGame const *m_GamePlugin; - - using SaveListItem = std::shared_ptr; - using SaveList = std::vector; - using SaveCollection = std::map; - - SaveCollection m_GlobalSaves; - SaveCollection m_LocalSaves; - - void refreshSaves(SaveCollection &saveCollection, const QString &savedir); - void refreshCharacters(SaveCollection const &saveCollection, - QListWidget *charList, QPushButton *copy, - QPushButton *move); - - bool transferCharacters( - QString const &character, char const *message, - QDir const& sourceDirectory, SaveList &saves, - QDir const& dest, - const std::function &method, - char const *errmsg); -}; - -#endif // TRANSFERSAVESDIALOG_H +/* +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 TRANSFERSAVESDIALOG_H +#define TRANSFERSAVESDIALOG_H + +#include "profile.h" +#include "tutorabledialog.h" + +class QListWidget; +#include +class QPushButton; +#include +class QWidget; + +#include +#include +#include + +namespace Ui +{ +class TransferSavesDialog; +} +namespace MOBase +{ +class IPluginGame; +} +namespace MOBase +{ +class ISaveGame; +} + +class TransferSavesDialog : public MOBase::TutorableDialog +{ + Q_OBJECT + +public: + explicit TransferSavesDialog(const Profile& profile, + MOBase::IPluginGame const* gamePlugin, + QWidget* parent = 0); + ~TransferSavesDialog(); + +private slots: + + void on_moveToLocalBtn_clicked(); + + void on_doneButton_clicked(); + + void on_globalCharacterList_currentTextChanged(const QString& currentText); + + void on_localCharacterList_currentTextChanged(const QString& currentText); + + void on_copyToLocalBtn_clicked(); + + void on_moveToGlobalBtn_clicked(); + + void on_copyToGlobalBtn_clicked(); + +private: + enum OverwriteMode + { + OVERWRITE_ASK, + OVERWRITE_YES, + OVERWRITE_NO + }; + +private: + void refreshGlobalCharacters(); + void refreshLocalCharacters(); + void refreshGlobalSaves(); + void refreshLocalSaves(); + bool testOverwrite(OverwriteMode& overwriteMode, const QString& destinationFile); + +private: + Ui::TransferSavesDialog* ui; + + Profile m_Profile; + + MOBase::IPluginGame const* m_GamePlugin; + + using SaveListItem = std::shared_ptr; + using SaveList = std::vector; + using SaveCollection = std::map; + + SaveCollection m_GlobalSaves; + SaveCollection m_LocalSaves; + + void refreshSaves(SaveCollection& saveCollection, const QString& savedir); + void refreshCharacters(SaveCollection const& saveCollection, QListWidget* charList, + QPushButton* copy, QPushButton* move); + + bool + transferCharacters(QString const& character, char const* message, + QDir const& sourceDirectory, SaveList& saves, QDir const& dest, + const std::function& method, + char const* errmsg); +}; + +#endif // TRANSFERSAVESDIALOG_H diff --git a/src/uilocker.cpp b/src/uilocker.cpp index 3dba6738f..eb3033f1a 100644 --- a/src/uilocker.cpp +++ b/src/uilocker.cpp @@ -7,12 +7,14 @@ class UILockerInterface { public: - UILockerInterface(QWidget* mainUI) : - m_mainUI(mainUI), m_target(nullptr), m_message(nullptr), m_info(nullptr), - m_buttons(nullptr), m_reason(UILocker::NoReason) + UILockerInterface(QWidget* mainUI) + : m_mainUI(mainUI), m_target(nullptr), m_message(nullptr), m_info(nullptr), + m_buttons(nullptr), m_reason(UILocker::NoReason) { m_timer.reset(new QTimer); - QObject::connect(m_timer.get(), &QTimer::timeout, [&]{ checkTarget(); }); + QObject::connect(m_timer.get(), &QTimer::timeout, [&] { + checkTarget(); + }); m_timer->start(200); set(); @@ -93,17 +95,14 @@ class UILockerInterface m_info->setText(s); } - QWidget* topLevel() - { - return m_topLevel.data(); - } + QWidget* topLevel() { return m_topLevel.data(); } private: class Filter : public QObject { public: - std::function resized; - std::function closed; + std::function resized; + std::function closed; protected: bool eventFilter(QObject* o, QEvent* e) override @@ -122,7 +121,6 @@ class UILockerInterface } }; - std::unique_ptr m_timer; QWidget* m_mainUI; QWidget* m_target; @@ -134,11 +132,7 @@ class UILockerInterface std::unique_ptr m_filter; UILocker::Reasons m_reason; - - bool hasMainUI() const - { - return (m_target != nullptr); - } + bool hasMainUI() const { return (m_target != nullptr); } QWidget* findTarget() { @@ -161,7 +155,6 @@ class UILockerInterface return true; }; - // find a modal dialog QWidget* w = QApplication::activeModalWidget(); @@ -189,7 +182,7 @@ class UILockerInterface return m_mainUI; } - QWidget* createTransparentWidget(QWidget* parent=nullptr) + QWidget* createTransparentWidget(QWidget* parent = nullptr) { auto* w = new QWidget(parent); @@ -212,8 +205,12 @@ class UILockerInterface m_topLevel->setGeometry(mainUI->rect()); m_filter.reset(new Filter); - m_filter->resized = [=]{ m_topLevel->setGeometry(mainUI->rect()); }; - m_filter->closed = [=]{ checkTarget(); }; + m_filter->resized = [=] { + m_topLevel->setGeometry(mainUI->rect()); + }; + m_filter->closed = [=] { + checkTarget(); + }; mainUI->installEventFilter(m_filter.get()); @@ -235,7 +232,7 @@ class UILockerInterface QFrame* createFrame() { auto* frame = new QFrame; - auto* ly = new QVBoxLayout(frame); + auto* ly = new QVBoxLayout(frame); if (hasMainUI()) { frame->setFrameStyle(QFrame::StyledPanel); @@ -288,43 +285,37 @@ class UILockerInterface m_buttons->setLayout(new QHBoxLayout); } - void updateMessage(UILocker::Reasons reason) { - switch (reason) - { - case UILocker::LockUI: - { - QString s; - - if (hasMainUI()) { - s = QObject::tr( - "Mod Organizer is locked while the application is running."); - } else { - s = QObject::tr("Mod Organizer is currently running an application."); - } + switch (reason) { + case UILocker::LockUI: { + QString s; + + if (hasMainUI()) { + s = QObject::tr("Mod Organizer is locked while the application is running."); + } else { + s = QObject::tr("Mod Organizer is currently running an application."); + } - m_message->setText(s); + m_message->setText(s); - break; - } + break; + } - case UILocker::OutputRequired: - { - m_message->setText(QObject::tr( - "The application must run to completion because its output is " - "required.")); + case UILocker::OutputRequired: { + m_message->setText( + QObject::tr("The application must run to completion because its output is " + "required.")); - break; - } + break; + } - case UILocker::PreventExit: - { - m_message->setText(QObject::tr( + case UILocker::PreventExit: { + m_message->setText(QObject::tr( "Mod Organizer is waiting on an application to close before exiting.")); - break; - } + break; + } } } @@ -333,40 +324,37 @@ class UILockerInterface MOBase::deleteChildWidgets(m_buttons); auto* ly = m_buttons->layout(); - switch (reason) - { - case UILocker::LockUI: // fall-through - case UILocker::OutputRequired: - { - auto* unlock = new QPushButton(QObject::tr("Unlock")); + switch (reason) { + case UILocker::LockUI: // fall-through + case UILocker::OutputRequired: { + auto* unlock = new QPushButton(QObject::tr("Unlock")); - QObject::connect(unlock, &QPushButton::clicked, [&]{ - UILocker::instance().onForceUnlock(); - }); + QObject::connect(unlock, &QPushButton::clicked, [&] { + UILocker::instance().onForceUnlock(); + }); - ly->addWidget(unlock); + ly->addWidget(unlock); - break; - } + break; + } - case UILocker::PreventExit: - { - auto* exit = new QPushButton(QObject::tr("Exit Now")); - QObject::connect(exit, &QPushButton::clicked, [&]{ - UILocker::instance().onForceUnlock(); - }); + case UILocker::PreventExit: { + auto* exit = new QPushButton(QObject::tr("Exit Now")); + QObject::connect(exit, &QPushButton::clicked, [&] { + UILocker::instance().onForceUnlock(); + }); - ly->addWidget(exit); + ly->addWidget(exit); - auto* cancel = new QPushButton(QObject::tr("Cancel")); - QObject::connect(cancel, &QPushButton::clicked, [&]{ - UILocker::instance().onCancel(); - }); + auto* cancel = new QPushButton(QObject::tr("Cancel")); + QObject::connect(cancel, &QPushButton::clicked, [&] { + UILocker::instance().onCancel(); + }); - ly->addWidget(cancel); + ly->addWidget(cancel); - break; - } + break; + } } } }; @@ -378,7 +366,7 @@ UILocker::Session::~Session() void UILocker::Session::unlock() { - QMetaObject::invokeMethod(qApp, [this]{ + QMetaObject::invokeMethod(qApp, [this] { UILocker::instance().unlock(this); }); } @@ -387,11 +375,11 @@ void UILocker::Session::setInfo(DWORD pid, const QString& name) { { std::scoped_lock lock(m_mutex); - m_pid = pid; + m_pid = pid; m_name = name; } - QMetaObject::invokeMethod(qApp, [this]{ + QMetaObject::invokeMethod(qApp, [this] { UILocker::instance().updateLabel(); }); } @@ -413,12 +401,9 @@ UILocker::Results UILocker::Session::result() const return UILocker::instance().result(); } - static UILocker* g_instance = nullptr; - -UILocker::UILocker() - : m_parent(nullptr), m_result(NoResult) +UILocker::UILocker() : m_parent(nullptr), m_result(NoResult) { Q_ASSERT(!g_instance); g_instance = this; @@ -429,7 +414,7 @@ UILocker::~UILocker() const auto v = m_sessions; for (auto& wp : v) { - if (auto s=wp.lock()) { + if (auto s = wp.lock()) { unlock(s.get()); } } @@ -474,7 +459,7 @@ void UILocker::unlock(Session* s) break; } - if (auto ss=itor->lock()) { + if (auto ss = itor->lock()) { if (ss.get() == s) { itor = m_sessions.erase(itor); continue; @@ -520,8 +505,8 @@ void UILocker::updateLabel() QStringList labels; - for (auto itor=m_sessions.rbegin(); itor!=m_sessions.rend(); ++itor) { - if (auto ss=itor->lock()) { + for (auto itor = m_sessions.rbegin(); itor != m_sessions.rend(); ++itor) { + if (auto ss = itor->lock()) { labels.push_back(QString("%1 (%2)").arg(ss->name()).arg(ss->pid())); } } @@ -573,7 +558,7 @@ void UILocker::disableAll() // top level widgets include the main window and dialogs for (auto* w : QApplication::topLevelWidgets()) { - if (auto* mw=dynamic_cast(w)) { + if (auto* mw = dynamic_cast(w)) { // this is the main window, disable the central widgets and the stuff // around it @@ -592,8 +577,7 @@ void UILocker::disableAll() } } - - if (auto* d=dynamic_cast(w)) { + if (auto* d = dynamic_cast(w)) { // this is a dialog if (d == m_ui->topLevel()) { diff --git a/src/uilocker.h b/src/uilocker.h index 44d9d8a2d..ec188f1ad 100644 --- a/src/uilocker.h +++ b/src/uilocker.h @@ -43,7 +43,6 @@ class UILocker Cancelled }; - class Session { public: @@ -62,7 +61,6 @@ class UILocker QString m_name; }; - UILocker(); ~UILocker(); diff --git a/src/updatedialog.cpp b/src/updatedialog.cpp index d3336e933..41e0801b2 100644 --- a/src/updatedialog.cpp +++ b/src/updatedialog.cpp @@ -1,21 +1,26 @@ #include "updatedialog.h" #include "ui_updatedialog.h" -#include "lootdialog.h" // for MarkdownPage +#include "lootdialog.h" // for MarkdownPage #include using namespace MOBase; -UpdateDialog::UpdateDialog(QWidget* parent) : - QDialog(parent, Qt::WindowMaximizeButtonHint | Qt::WindowCloseButtonHint), ui(new Ui::UpdateDialog) +UpdateDialog::UpdateDialog(QWidget* parent) + : QDialog(parent, Qt::WindowMaximizeButtonHint | Qt::WindowCloseButtonHint), + ui(new Ui::UpdateDialog) { // Basic UI stuff ui->setupUi(this); - connect(ui->installButton, &QPushButton::pressed, this, [&]{ done(QDialog::Accepted); }); - connect(ui->cancelButton, &QPushButton::pressed, this, [&]{ done(QDialog::Rejected); }); + connect(ui->installButton, &QPushButton::pressed, this, [&] { + done(QDialog::Accepted); + }); + connect(ui->cancelButton, &QPushButton::pressed, this, [&] { + done(QDialog::Rejected); + }); // Replace a label with an icon - QIcon icon = style()->standardIcon(QStyle::SP_MessageBoxQuestion); + QIcon icon = style()->standardIcon(QStyle::SP_MessageBoxQuestion); QPixmap pixmap = icon.pixmap(QSize(32, 32)); ui->iconLabel->setPixmap(pixmap); ui->iconLabel->setScaledContents(true); @@ -44,7 +49,9 @@ UpdateDialog::UpdateDialog(QWidget* parent) : // Setting up the expander m_expander.set(ui->detailsButton, ui->detailsWidget); - connect(&m_expander, &ExpanderWidget::toggled, this, [&]{ adjustSize(); }); + connect(&m_expander, &ExpanderWidget::toggled, this, [&] { + adjustSize(); + }); // Adjust sizes after the expander hides stuff adjustSize(); @@ -59,9 +66,8 @@ void UpdateDialog::setChangeLogs(const QString& text) void UpdateDialog::setVersions(const QString& oldVersion, const QString& newVersion) { - ui->updateLabel->setText( - tr("Mod Organizer %1 is available. The current version is %2. Updating will not affect your mods or profiles.") - .arg(newVersion) - .arg(oldVersion) - ); + ui->updateLabel->setText(tr("Mod Organizer %1 is available. The current version is " + "%2. Updating will not affect your mods or profiles.") + .arg(newVersion) + .arg(oldVersion)); } diff --git a/src/updatedialog.h b/src/updatedialog.h index 1dd5e0b19..798fbee28 100644 --- a/src/updatedialog.h +++ b/src/updatedialog.h @@ -3,10 +3,13 @@ #include +#include "lootdialog.h" // for MarkdownDocument #include -#include "lootdialog.h" // for MarkdownDocument -namespace Ui { class UpdateDialog; } +namespace Ui +{ +class UpdateDialog; +} class UpdateDialog : public QDialog { @@ -25,4 +28,4 @@ class UpdateDialog : public QDialog MarkdownDocument m_changeLogs; }; -#endif // MODORGANIZER_UPDATEDIALOG_H +#endif // MODORGANIZER_UPDATEDIALOG_H diff --git a/src/usvfsconnector.cpp b/src/usvfsconnector.cpp index 611b905ba..940d0942d 100644 --- a/src/usvfsconnector.cpp +++ b/src/usvfsconnector.cpp @@ -18,26 +18,26 @@ along with Mod Organizer. If not, see . */ #include "usvfsconnector.h" -#include "settings.h" -#include "organizercore.h" #include "envmodule.h" +#include "organizercore.h" +#include "settings.h" #include "shared/util.h" +#include +#include +#include +#include +#include #include +#include #include -#include #include -#include -#include -#include -#include -#include static const char SHMID[] = "mod_organizer_instance"; using namespace MOBase; -std::string to_hex(void *bufferIn, size_t bufferSize) +std::string to_hex(void* bufferIn, size_t bufferSize) { - unsigned char *buffer = static_cast(bufferIn); + unsigned char* buffer = static_cast(bufferIn); std::ostringstream temp; temp << std::hex; for (size_t i = 0; i < bufferSize; ++i) { @@ -51,22 +51,18 @@ std::string to_hex(void *bufferIn, size_t bufferSize) return temp.str(); } - LogWorker::LogWorker() - : m_Buffer(1024, '\0') - , m_QuitRequested(false) - , m_LogFile(qApp->property("dataPath").toString() - + QString("/logs/usvfs-%1.log") - .arg(QDateTime::currentDateTimeUtc().toString( - "yyyy-MM-dd_hh-mm-ss"))) + : m_Buffer(1024, '\0'), m_QuitRequested(false), + m_LogFile( + qApp->property("dataPath").toString() + + QString("/logs/usvfs-%1.log") + .arg(QDateTime::currentDateTimeUtc().toString("yyyy-MM-dd_hh-mm-ss"))) { m_LogFile.open(QIODevice::WriteOnly); log::debug("usvfs log messages are written to {}", m_LogFile.fileName()); } -LogWorker::~LogWorker() -{ -} +LogWorker::~LogWorker() {} void LogWorker::process() { @@ -95,34 +91,33 @@ void LogWorker::exit() LogLevel toUsvfsLogLevel(log::Levels level) { switch (level) { - case log::Info: - return LogLevel::Info; - case log::Warning: - return LogLevel::Warning; - case log::Error: - return LogLevel::Error; - case log::Debug: // fall-through - default: - return LogLevel::Debug; + case log::Info: + return LogLevel::Info; + case log::Warning: + return LogLevel::Warning; + case log::Error: + return LogLevel::Error; + case log::Debug: // fall-through + default: + return LogLevel::Debug; } } CrashDumpsType toUsvfsCrashDumpsType(env::CoreDumpTypes type) { - switch (type) - { - case env::CoreDumpTypes::None: - return CrashDumpsType::None; + switch (type) { + case env::CoreDumpTypes::None: + return CrashDumpsType::None; - case env::CoreDumpTypes::Data: - return CrashDumpsType::Data; + case env::CoreDumpTypes::Data: + return CrashDumpsType::Data; - case env::CoreDumpTypes::Full: - return CrashDumpsType::Full; + case env::CoreDumpTypes::Full: + return CrashDumpsType::Full; - case env::CoreDumpTypes::Mini: - default: - return CrashDumpsType::Mini; + case env::CoreDumpTypes::Mini: + default: + return CrashDumpsType::Mini; } } @@ -133,9 +128,10 @@ UsvfsConnector::UsvfsConnector() const auto& s = Settings::instance(); const LogLevel logLevel = toUsvfsLogLevel(s.diagnostics().logLevel()); - const auto dumpType = toUsvfsCrashDumpsType(s.diagnostics().coreDumpType()); - const auto delay = duration_cast(s.diagnostics().spawnDelay()); - std::string dumpPath = MOShared::ToString(OrganizerCore::getGlobalCoreDumpPath(), true); + const auto dumpType = toUsvfsCrashDumpsType(s.diagnostics().coreDumpType()); + const auto delay = duration_cast(s.diagnostics().spawnDelay()); + std::string dumpPath = + MOShared::ToString(OrganizerCore::getGlobalCoreDumpPath(), true); usvfsParameters* params = usvfsCreateParameters(); @@ -148,15 +144,12 @@ UsvfsConnector::UsvfsConnector() InitLogging(false); - log::debug( - "initializing usvfs:\n" - " . instance: {}\n" - " . log: {}\n" - " . dump: {} ({})", - SHMID, - usvfsLogLevelToString(logLevel), - dumpPath.c_str(), - usvfsCrashDumpTypeToString(dumpType)); + log::debug("initializing usvfs:\n" + " . instance: {}\n" + " . log: {}\n" + " . dump: {} ({})", + SHMID, usvfsLogLevelToString(logLevel), dumpPath.c_str(), + usvfsCrashDumpTypeToString(dumpType)); usvfsCreateVFS(params); usvfsFreeParameters(params); @@ -185,8 +178,7 @@ UsvfsConnector::~UsvfsConnector() m_WorkerThread.wait(); } - -void UsvfsConnector::updateMapping(const MappingType &mapping) +void UsvfsConnector::updateMapping(const MappingType& mapping) { const auto start = std::chrono::high_resolution_clock::now(); @@ -197,7 +189,7 @@ void UsvfsConnector::updateMapping(const MappingType &mapping) int value = 0; int files = 0; - int dirs = 0; + int dirs = 0; log::debug("Updating VFS mappings..."); @@ -214,11 +206,9 @@ void UsvfsConnector::updateMapping(const MappingType &mapping) } if (map.isDirectory) { - VirtualLinkDirectoryStatic(map.source.toStdWString().c_str(), - map.destination.toStdWString().c_str(), - (map.createTarget ? LINKFLAG_CREATETARGET : 0) - | LINKFLAG_RECURSIVE - ); + VirtualLinkDirectoryStatic( + map.source.toStdWString().c_str(), map.destination.toStdWString().c_str(), + (map.createTarget ? LINKFLAG_CREATETARGET : 0) | LINKFLAG_RECURSIVE); ++dirs; } else { VirtualLinkFile(map.source.toStdWString().c_str(), @@ -227,18 +217,18 @@ void UsvfsConnector::updateMapping(const MappingType &mapping) } } - const auto end = std::chrono::high_resolution_clock::now(); + const auto end = std::chrono::high_resolution_clock::now(); const auto time = std::chrono::duration_cast(end - start); - log::debug( - "VFS mappings updated, linked {} dirs and {} files in {}ms", - dirs, files, time.count()); + log::debug("VFS mappings updated, linked {} dirs and {} files in {}ms", dirs, files, + time.count()); } -void UsvfsConnector::updateParams( - MOBase::log::Levels logLevel, env::CoreDumpTypes coreDumpType, - const QString& crashDumpsPath, std::chrono::seconds spawnDelay, - QString executableBlacklist) +void UsvfsConnector::updateParams(MOBase::log::Levels logLevel, + env::CoreDumpTypes coreDumpType, + const QString& crashDumpsPath, + std::chrono::seconds spawnDelay, + QString executableBlacklist) { using namespace std::chrono; @@ -260,26 +250,24 @@ void UsvfsConnector::updateParams( } } -void UsvfsConnector::updateForcedLibraries(const QList &forcedLibraries) +void UsvfsConnector::updateForcedLibraries( + const QList& forcedLibraries) { ClearLibraryForceLoads(); for (auto setting : forcedLibraries) { if (setting.enabled()) { - ForceLoadLibrary( - setting.process().toStdWString().data(), - setting.library().toStdWString().data() - ); + ForceLoadLibrary(setting.process().toStdWString().data(), + setting.library().toStdWString().data()); } } } - std::vector getRunningUSVFSProcesses() { std::vector pids; { - size_t count = 0; + size_t count = 0; DWORD* buffer = nullptr; if (!::GetVFSProcessList2(&count, &buffer)) { log::error("failed to get usvfs process list"); @@ -296,13 +284,13 @@ std::vector getRunningUSVFSProcesses() std::vector v; const auto rights = - PROCESS_QUERY_LIMITED_INFORMATION | // exit code, image name, etc. - SYNCHRONIZE | // wait functions - PROCESS_SET_QUOTA | PROCESS_TERMINATE; // add to job + PROCESS_QUERY_LIMITED_INFORMATION | // exit code, image name, etc. + SYNCHRONIZE | // wait functions + PROCESS_SET_QUOTA | PROCESS_TERMINATE; // add to job for (auto&& pid : pids) { if (pid == thisPid) { - continue; // obviously don't wait for MO process + continue; // obviously don't wait for MO process } HANDLE handle = ::OpenProcess(rights, FALSE, pid); @@ -310,9 +298,7 @@ std::vector getRunningUSVFSProcesses() if (handle == INVALID_HANDLE_VALUE) { const auto e = GetLastError(); - log::warn( - "failed to open usvfs process {}: {}", - pid, formatSystemMessage(e)); + log::warn("failed to open usvfs process {}: {}", pid, formatSystemMessage(e)); continue; } diff --git a/src/usvfsconnector.h b/src/usvfsconnector.h index bf5d49cec..798d59837 100644 --- a/src/usvfsconnector.h +++ b/src/usvfsconnector.h @@ -17,29 +17,27 @@ You should have received a copy of the GNU General Public License along with Mod Organizer. If not, see . */ - #ifndef USVFSCONNECTOR_H #define USVFSCONNECTOR_H -#include -#include -#include -#include -#include +#include "envdump.h" +#include "executableinfo.h" #include +#include #include -#include +#include +#include +#include +#include #include -#include "executableinfo.h" -#include "envdump.h" - +#include -class LogWorker : public QThread { +class LogWorker : public QThread +{ Q_OBJECT public: - LogWorker(); ~LogWorker(); @@ -50,60 +48,54 @@ public slots: signals: - void outputLog(const QString &message); + void outputLog(const QString& message); void finished(); private: - std::string m_Buffer; bool m_QuitRequested; QFile m_LogFile; - }; - -class UsvfsConnectorException : public std::exception { +class UsvfsConnectorException : public std::exception +{ public: - UsvfsConnectorException(const QString &text) - : std::exception(), m_Message(text.toLocal8Bit()) {} + UsvfsConnectorException(const QString& text) + : std::exception(), m_Message(text.toLocal8Bit()) + {} + + virtual const char* what() const throw() { return m_Message.constData(); } - virtual const char* what() const throw() - { return m_Message.constData(); } private: QByteArray m_Message; - }; - -class UsvfsConnector : public QObject { +class UsvfsConnector : public QObject +{ Q_OBJECT public: - UsvfsConnector(); ~UsvfsConnector(); - void updateMapping(const MappingType &mapping); + void updateMapping(const MappingType& mapping); - void updateParams( - MOBase::log::Levels logLevel, env::CoreDumpTypes coreDumpType, - const QString& crashDumpsPath, std::chrono::seconds spawnDelay, - QString executableBlacklist); + void updateParams(MOBase::log::Levels logLevel, env::CoreDumpTypes coreDumpType, + const QString& crashDumpsPath, std::chrono::seconds spawnDelay, + QString executableBlacklist); void updateForcedLibraries( - const QList &forcedLibraries); + const QList& forcedLibraries); private: - LogWorker m_LogWorker; QThread m_WorkerThread; - }; CrashDumpsType crashDumpsType(int type); std::vector getRunningUSVFSProcesses(); -#endif // USVFSCONNECTOR_H +#endif // USVFSCONNECTOR_H diff --git a/src/viewmarkingscrollbar.cpp b/src/viewmarkingscrollbar.cpp index 56754237e..7fe5c1c3c 100644 --- a/src/viewmarkingscrollbar.cpp +++ b/src/viewmarkingscrollbar.cpp @@ -1,55 +1,56 @@ -#include "viewmarkingscrollbar.h" -#include "modelutils.h" -#include -#include -#include - -using namespace MOShared; - -ViewMarkingScrollBar::ViewMarkingScrollBar(QTreeView* view, int role) - : QScrollBar(view) - , m_view(view) - , m_role(role) -{ - // not implemented for horizontal sliders - Q_ASSERT(this->orientation() == Qt::Vertical); -} - -QColor ViewMarkingScrollBar::color(const QModelIndex& index) const -{ - auto data = index.data(m_role); - if (data.canConvert()) { - return data.value(); - } - return QColor(); -} - -void ViewMarkingScrollBar::paintEvent(QPaintEvent* event) -{ - if (m_view->model() == nullptr) { - return; - } - QScrollBar::paintEvent(event); - - QStyleOptionSlider styleOption; - initStyleOption(&styleOption); - - QPainter painter(this); - - QRect handleRect = style()->subControlRect(QStyle::CC_ScrollBar, &styleOption, QStyle::SC_ScrollBarSlider, this); - QRect innerRect = style()->subControlRect(QStyle::CC_ScrollBar, &styleOption, QStyle::SC_ScrollBarGroove, this); - - auto indices = visibleIndex(m_view, 0); - - painter.translate(innerRect.topLeft() + QPoint(0, 3)); - qreal scale = static_cast(innerRect.height() - 3) / static_cast(indices.size()); - - for (int i = 0; i < indices.size(); ++i) { - QColor color = this->color(indices[i]); - if (color.isValid()) { - painter.setPen(color); - painter.setBrush(color); - painter.drawRect(QRect(2, i * scale - 2, handleRect.width() - 5, 3)); - } - } -} +#include "viewmarkingscrollbar.h" +#include "modelutils.h" +#include +#include +#include + +using namespace MOShared; + +ViewMarkingScrollBar::ViewMarkingScrollBar(QTreeView* view, int role) + : QScrollBar(view), m_view(view), m_role(role) +{ + // not implemented for horizontal sliders + Q_ASSERT(this->orientation() == Qt::Vertical); +} + +QColor ViewMarkingScrollBar::color(const QModelIndex& index) const +{ + auto data = index.data(m_role); + if (data.canConvert()) { + return data.value(); + } + return QColor(); +} + +void ViewMarkingScrollBar::paintEvent(QPaintEvent* event) +{ + if (m_view->model() == nullptr) { + return; + } + QScrollBar::paintEvent(event); + + QStyleOptionSlider styleOption; + initStyleOption(&styleOption); + + QPainter painter(this); + + QRect handleRect = style()->subControlRect(QStyle::CC_ScrollBar, &styleOption, + QStyle::SC_ScrollBarSlider, this); + QRect innerRect = style()->subControlRect(QStyle::CC_ScrollBar, &styleOption, + QStyle::SC_ScrollBarGroove, this); + + auto indices = visibleIndex(m_view, 0); + + painter.translate(innerRect.topLeft() + QPoint(0, 3)); + qreal scale = + static_cast(innerRect.height() - 3) / static_cast(indices.size()); + + for (int i = 0; i < indices.size(); ++i) { + QColor color = this->color(indices[i]); + if (color.isValid()) { + painter.setPen(color); + painter.setBrush(color); + painter.drawRect(QRect(2, i * scale - 2, handleRect.width() - 5, 3)); + } + } +} diff --git a/src/viewmarkingscrollbar.h b/src/viewmarkingscrollbar.h index 01b5a8c08..8fff05e91 100644 --- a/src/viewmarkingscrollbar.h +++ b/src/viewmarkingscrollbar.h @@ -1,26 +1,24 @@ -#ifndef VIEWMARKINGSCROLLBAR_H -#define VIEWMARKINGSCROLLBAR_H - -#include -#include - - -class ViewMarkingScrollBar : public QScrollBar -{ -public: - ViewMarkingScrollBar(QTreeView* view, int role); - -protected: - void paintEvent(QPaintEvent *event) override; - - // retrieve the color of the marker for the given index - // - virtual QColor color(const QModelIndex& index) const; - -private: - QTreeView* m_view; - int m_role; -}; - - -#endif // VIEWMARKINGSCROLLBAR_H +#ifndef VIEWMARKINGSCROLLBAR_H +#define VIEWMARKINGSCROLLBAR_H + +#include +#include + +class ViewMarkingScrollBar : public QScrollBar +{ +public: + ViewMarkingScrollBar(QTreeView* view, int role); + +protected: + void paintEvent(QPaintEvent* event) override; + + // retrieve the color of the marker for the given index + // + virtual QColor color(const QModelIndex& index) const; + +private: + QTreeView* m_view; + int m_role; +}; + +#endif // VIEWMARKINGSCROLLBAR_H diff --git a/src/virtualfiletree.cpp b/src/virtualfiletree.cpp index 983523e61..dcc129af9 100644 --- a/src/virtualfiletree.cpp +++ b/src/virtualfiletree.cpp @@ -1,49 +1,70 @@ #include "virtualfiletree.h" #include "shared/directoryentry.h" -#include "shared/filesorigin.h" #include "shared/fileentry.h" +#include "shared/filesorigin.h" // #include "shared/util.h" using namespace MOBase; using namespace MOShared; -class VirtualFileTreeImpl : public VirtualFileTree { +class VirtualFileTreeImpl : public VirtualFileTree +{ public: - - /** * */ - VirtualFileTreeImpl(std::shared_ptr parent, const DirectoryEntry* dir) : - FileTreeEntry(parent, parent ? QString::fromStdWString(dir->getName()) : ""), - VirtualFileTree(), - m_dirEntry(dir) { } + VirtualFileTreeImpl(std::shared_ptr parent, + const DirectoryEntry* dir) + : FileTreeEntry(parent, parent ? QString::fromStdWString(dir->getName()) : ""), + VirtualFileTree(), m_dirEntry(dir) + {} protected: - /** * No mutable operations allowed. */ - bool beforeReplace(IFileTree const* dstTree, FileTreeEntry const* destination, FileTreeEntry const* source) override { return false; } - bool beforeInsert(IFileTree const* entry, FileTreeEntry const* name) override { return false; } - bool beforeRemove(IFileTree const* entry, FileTreeEntry const* name) override { return false; } - std::shared_ptr makeFile(std::shared_ptr parent, QString name) const override { return nullptr; } - std::shared_ptr makeDirectory(std::shared_ptr parent, QString name) const override { return nullptr; } + bool beforeReplace(IFileTree const* dstTree, FileTreeEntry const* destination, + FileTreeEntry const* source) override + { + return false; + } + bool beforeInsert(IFileTree const* entry, FileTreeEntry const* name) override + { + return false; + } + bool beforeRemove(IFileTree const* entry, FileTreeEntry const* name) override + { + return false; + } + std::shared_ptr makeFile(std::shared_ptr parent, + QString name) const override + { + return nullptr; + } + std::shared_ptr makeDirectory(std::shared_ptr parent, + QString name) const override + { + return nullptr; + } - bool doPopulate(std::shared_ptr parent, std::vector>& entries) const override { + bool doPopulate(std::shared_ptr parent, + std::vector>& entries) const override + { for (auto* subdirEntry : m_dirEntry->getSubDirectories()) { entries.push_back(std::make_shared(parent, subdirEntry)); } for (auto& file : m_dirEntry->getFiles()) { - entries.push_back(createFileEntry(parent, QString::fromStdWString(file->getName()))); + entries.push_back( + createFileEntry(parent, QString::fromStdWString(file->getName()))); } // Vector is already sorted: return true; } - std::shared_ptr doClone() const { + std::shared_ptr doClone() const + { return std::make_shared(nullptr, m_dirEntry); } @@ -54,6 +75,8 @@ class VirtualFileTreeImpl : public VirtualFileTree { /** * */ -std::shared_ptr VirtualFileTree::makeTree(const DirectoryEntry* rootEntry) { +std::shared_ptr +VirtualFileTree::makeTree(const DirectoryEntry* rootEntry) +{ return std::make_shared(nullptr, rootEntry); } diff --git a/src/virtualfiletree.h b/src/virtualfiletree.h index ebce54865..de3e6f718 100644 --- a/src/virtualfiletree.h +++ b/src/virtualfiletree.h @@ -26,7 +26,7 @@ along with Mod Organizer. If not, see . namespace MOShared { - class DirectoryEntry; +class DirectoryEntry; } /** @@ -38,9 +38,9 @@ namespace MOShared * This class does not expose mutable operations, so any mutable operations will * fail. */ -class VirtualFileTree : public MOBase::IFileTree { +class VirtualFileTree : public MOBase::IFileTree +{ public: - /** * @brief Create a new file tree representing the given VFS directory. * @@ -48,13 +48,15 @@ class VirtualFileTree : public MOBase::IFileTree { * * @return a file tree representing the VFS directory. */ - static std::shared_ptr makeTree(const MOShared::DirectoryEntry* root); + static std::shared_ptr + makeTree(const MOShared::DirectoryEntry* root); protected: - using IFileTree::IFileTree; - virtual bool doPopulate(std::shared_ptr parent, std::vector>& entries) const = 0; + virtual bool + doPopulate(std::shared_ptr parent, + std::vector>& entries) const = 0; }; #endif